docs: rename all files from .md to .rst
This commit is contained in:
parent
ae928d0de9
commit
085d0cb44e
|
@ -0,0 +1,365 @@
|
|||
.. raw:: html
|
||||
|
||||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinBasics() {
|
||||
|
||||
#shelldown[1][3] >/dev/null
|
||||
#shelldown[1][4] >/dev/null
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][6])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][7])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
|
||||
#shelldown[3][-1]
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
|
||||
RES=$((echo y) | #shelldown[5][-1] $1)
|
||||
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
|
||||
|
||||
#shelldown[6][0]
|
||||
#shelldown[6][1]
|
||||
RES=$(#shelldown[6][2] | jq '.data.coins[0].denom' | tr -d '"')
|
||||
assertTrue "Line $LINENO: Expected to have mycoins, got $RES" '[[ $RES == mycoin ]]'
|
||||
RES="$(#shelldown[6][3] 2>&1)"
|
||||
assertTrue "Line $LINENO: Expected to contain ERROR, got $RES" '[[ $RES == *ERROR* ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[7][-1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$(#shelldown[8][-1] | jq '.data.coins[0].amount')
|
||||
assertTrue "Line $LINENO: Expected to contain 1000 mycoin, got $RES" '[[ $RES == 1000 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[9][-1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[10][-1])
|
||||
assertTrue "Line $LINENO: Expected to contain insufficient funds error, got $RES" \
|
||||
'[[ $RES == *"Insufficient Funds"* ]]'
|
||||
|
||||
#perform a substitution within the final tests
|
||||
HASH=$((echo $KEYPASS) | #shelldown[11][-1] | jq '.hash' | tr -d '"')
|
||||
PRESUB="#shelldown[12][-1]"
|
||||
RES=$(eval ${PRESUB/<HASH>/$HASH})
|
||||
assertTrue "Line $LINENO: Expected to not contain Error, got $RES" '[[ $RES != *Error* ]]'
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
Basecoin Basics
|
||||
===============
|
||||
|
||||
Here we explain how to get started with a basic Basecoin blockchain, how
|
||||
to send transactions between accounts using the ``basecoin`` tool, and
|
||||
what is happening under the hood.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
With go, it's one command:
|
||||
|
||||
.. code:: shelldown[0]
|
||||
|
||||
go get -u github.com/tendermint/basecoin/cmd/...
|
||||
|
||||
If you have trouble, see the `installation guide <install.md>`__.
|
||||
|
||||
Note the above command installs two binaries: ``basecoin`` and
|
||||
``basecli``. The former is the running node. The latter is a
|
||||
command-line light-client. This tutorial assumes you have a 'fresh'
|
||||
working environment. See `how to clean up, below <#clean-up>`__.
|
||||
|
||||
Generate some keys
|
||||
------------------
|
||||
|
||||
Let's generate two keys, one to receive an initial allocation of coins,
|
||||
and one to send some coins to later:
|
||||
|
||||
.. code:: shelldown[1]
|
||||
|
||||
basecli keys new cool
|
||||
basecli keys new friend
|
||||
|
||||
You'll need to enter passwords. You can view your key names and
|
||||
addresses with ``basecli keys list``, or see a particular key's address
|
||||
with ``basecli keys get <NAME>``.
|
||||
|
||||
Initialize Basecoin
|
||||
-------------------
|
||||
|
||||
To initialize a new Basecoin blockchain, run:
|
||||
|
||||
.. code:: shelldown[2]
|
||||
|
||||
basecoin init <ADDRESS>
|
||||
|
||||
If you prefer not to copy-paste, you can provide the address
|
||||
programatically:
|
||||
|
||||
.. code:: shelldown[3]
|
||||
|
||||
basecoin init $(basecli keys get cool | awk '{print $2}')
|
||||
|
||||
This will create the necessary files for a Basecoin blockchain with one
|
||||
validator and one account (corresponding to your key) in
|
||||
``~/.basecoin``. For more options on setup, see the `guide to using the
|
||||
Basecoin tool </docs/guide/basecoin-tool.md>`__.
|
||||
|
||||
If you like, you can manually add some more accounts to the blockchain
|
||||
by generating keys and editing the ``~/.basecoin/genesis.json``.
|
||||
|
||||
Start
|
||||
-----
|
||||
|
||||
Now we can start Basecoin:
|
||||
|
||||
.. code:: shelldown[4]
|
||||
|
||||
basecoin start
|
||||
|
||||
You should see blocks start streaming in!
|
||||
|
||||
Initialize Light-Client
|
||||
-----------------------
|
||||
|
||||
Now that Basecoin is running we can initialize ``basecli``, the
|
||||
light-client utility. Basecli is used for sending transactions and
|
||||
querying the state. Leave Basecoin running and open a new terminal
|
||||
window. Here run:
|
||||
|
||||
.. code:: shelldown[5]
|
||||
|
||||
basecli init --node=tcp://localhost:46657 --genesis=$HOME/.basecoin/genesis.json
|
||||
|
||||
If you provide the genesis file to basecli, it can calculate the proper
|
||||
chainID and validator hash. Basecli needs to get this information from
|
||||
some trusted source, so all queries done with ``basecli`` can be
|
||||
cryptographically proven to be correct according to a known validator
|
||||
set.
|
||||
|
||||
Note: that --genesis only works if there have been no validator set
|
||||
changes since genesis. If there are validator set changes, you need to
|
||||
find the current set through some other method.
|
||||
|
||||
Send transactions
|
||||
-----------------
|
||||
|
||||
Now we are ready to send some transactions. First Let's check the
|
||||
balance of the two accounts we setup earlier:
|
||||
|
||||
.. code:: shelldown[6]
|
||||
|
||||
ME=$(basecli keys get cool | awk '{print $2}')
|
||||
YOU=$(basecli keys get friend | awk '{print $2}')
|
||||
basecli query account $ME
|
||||
basecli query account $YOU
|
||||
|
||||
The first account is flush with cash, while the second account doesn't
|
||||
exist. Let's send funds from the first account to the second:
|
||||
|
||||
.. code:: shelldown[7]
|
||||
|
||||
basecli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
|
||||
Now if we check the second account, it should have ``1000`` 'mycoin'
|
||||
coins!
|
||||
|
||||
.. code:: shelldown[8]
|
||||
|
||||
basecli query account $YOU
|
||||
|
||||
We can send some of these coins back like so:
|
||||
|
||||
.. code:: shelldown[9]
|
||||
|
||||
basecli tx send --name=friend --amount=500mycoin --to=$ME --sequence=1
|
||||
|
||||
Note how we use the ``--name`` flag to select a different account to
|
||||
send from.
|
||||
|
||||
If we try to send too much, we'll get an error:
|
||||
|
||||
.. code:: shelldown[10]
|
||||
|
||||
basecli tx send --name=friend --amount=500000mycoin --to=$ME --sequence=2
|
||||
|
||||
Let's send another transaction:
|
||||
|
||||
.. code:: shelldown[11]
|
||||
|
||||
basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2
|
||||
|
||||
Note the ``hash`` value in the response - this is the hash of the
|
||||
transaction. We can query for the transaction by this hash:
|
||||
|
||||
.. code:: shelldown[12]
|
||||
|
||||
basecli query tx <HASH>
|
||||
|
||||
See ``basecli tx send --help`` for additional details.
|
||||
|
||||
Proof
|
||||
-----
|
||||
|
||||
Even if you don't see it in the UI, the result of every query comes with
|
||||
a proof. This is a Merkle proof that the result of the query is actually
|
||||
contained in the state. And the state's Merkle root is contained in a
|
||||
recent block header. Behind the scenes, ``countercli`` will not only
|
||||
verify that this state matches the header, but also that the header is
|
||||
properly signed by the known validator set. It will even update the
|
||||
validator set as needed, so long as there have not been major changes
|
||||
and it is secure to do so. So, if you wonder why the query may take a
|
||||
second... there is a lot of work going on in the background to make sure
|
||||
even a lying full node can't trick your client.
|
||||
|
||||
In a latter `guide on InterBlockchain Communication <ibc.md>`__, we'll
|
||||
use these proofs to post transactions to other chains.
|
||||
|
||||
Accounts and Transactions
|
||||
-------------------------
|
||||
|
||||
For a better understanding of how to further use the tools, it helps to
|
||||
understand the underlying data structures.
|
||||
|
||||
Accounts
|
||||
~~~~~~~~
|
||||
|
||||
The Basecoin state consists entirely of a set of accounts. Each account
|
||||
contains a public key, a balance in many different coin denominations,
|
||||
and a strictly increasing sequence number for replay protection. This
|
||||
type of account was directly inspired by accounts in Ethereum, and is
|
||||
unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). Note
|
||||
Basecoin is a multi-asset cryptocurrency, so each account can have many
|
||||
different kinds of tokens.
|
||||
|
||||
.. code:: 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"`
|
||||
}
|
||||
|
||||
If you want to add more coins to a blockchain, you can do so manually in
|
||||
the ``~/.basecoin/genesis.json`` before you start the blockchain for the
|
||||
first time.
|
||||
|
||||
Accounts are serialized and stored in a Merkle tree under the key
|
||||
``base/a/<address>``, where ``<address>`` 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 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:
|
||||
|
||||
.. code:: 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 Basecoin, the ``Fee`` is meant to be used by the validators to inform
|
||||
the ordering of transactions, like in Bitcoin. And the ``Gas`` is meant
|
||||
to be used by the application plugin to control its execution. There is
|
||||
currently no means to pass ``Fee`` information to the Tendermint
|
||||
validators, but it will come soon...
|
||||
|
||||
Note also that the ``PubKey`` only needs to be sent for
|
||||
``Sequence == 0``. After that, it is stored under the account in the
|
||||
Merkle tree and subsequent transactions can exclude it, using only the
|
||||
``Address`` to refer to the sender. Ethereum does not require public
|
||||
keys to be sent in transactions as it uses a different elliptic curve
|
||||
scheme which enables the public key to be derived from the signature
|
||||
itself.
|
||||
|
||||
Finally, note that the use of multiple inputs and multiple outputs
|
||||
allows us to send many different types of tokens between many different
|
||||
accounts at once in an atomic transaction. Thus, the ``SendTx`` can
|
||||
serve as a basic unit of decentralized exchange. When using multiple
|
||||
inputs and outputs, you must make sure that the sum of coins of the
|
||||
inputs equals the sum of coins of the outputs (no creating money), and
|
||||
that all accounts that provide inputs have signed the transaction.
|
||||
|
||||
Clean Up
|
||||
--------
|
||||
|
||||
**WARNING:** Running these commands will wipe out any existing
|
||||
information in both the ``~/.basecli`` and ``~/.basecoin`` directories,
|
||||
including private keys.
|
||||
|
||||
To remove all the files created and refresh your environment (e.g., if
|
||||
starting this tutorial again or trying something new), the following
|
||||
commands are run:
|
||||
|
||||
.. code:: shelldown[end-of-tutorials]
|
||||
|
||||
basecli reset_all
|
||||
rm -rf ~/.basecoin
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In this guide, we introduced the ``basecoin`` and ``basecli`` tools,
|
||||
demonstrated how to start a new basecoin blockchain and how to send
|
||||
tokens between accounts, and discussed the underlying data types for
|
||||
accounts and transactions, specifically the ``Account`` and the
|
||||
``SendTx``. In the `next guide <basecoin-plugins.md>`__, we introduce
|
||||
the Basecoin plugin system, which uses a new transaction type, the
|
||||
``AppTx``, to extend the functionality of the Basecoin system with
|
||||
arbitrary logic.
|
|
@ -0,0 +1,276 @@
|
|||
.. raw:: html
|
||||
|
||||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinPlugins() {
|
||||
|
||||
#Initialization
|
||||
#shelldown[0][1]
|
||||
#shelldown[0][2]
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
#Making Keys
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][4])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][5])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
|
||||
#shelldown[0][7] >/dev/null
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[0][9] >>/dev/null 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
|
||||
RES=$((echo y) | #shelldown[1][0] $1)
|
||||
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
|
||||
|
||||
#shelldown[1][2]
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
RES=$((echo $KEYPASS) | #shelldown[1][3] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[2][0])
|
||||
assertTrue "Line $LINENO: Expected to contain Valid error, got $RES" \
|
||||
'[[ $RES == *"Counter Tx marked invalid"* ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[2][1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$(#shelldown[3][-1] | jq '.data.counter')
|
||||
assertTrue "Line $LINENO: Expected Counter of 1, got $RES" '[[ $RES == 1 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[4][0] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
RES=$(#shelldown[4][1])
|
||||
RESCOUNT=$(printf "$RES" | jq '.data.counter')
|
||||
RESFEE=$(printf "$RES" | jq '.data.total_fees[0].amount')
|
||||
assertTrue "Line $LINENO: Expected Counter of 2, got $RES" '[[ $RESCOUNT == 2 ]]'
|
||||
assertTrue "Line $LINENO: Expected TotalFees of 2, got $RES" '[[ $RESFEE == 2 ]]'
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
Basecoin Plugins
|
||||
================
|
||||
|
||||
In the `previous guide <basecoin-basics.md>`__, we saw how to use the
|
||||
``basecoin`` tool to start a blockchain and the ``basecli`` tools to
|
||||
send transactions. We also learned about ``Account`` and ``SendTx``, the
|
||||
basic data types giving us a multi-asset cryptocurrency. Here, we will
|
||||
demonstrate how to extend the tools to use another transaction type, the
|
||||
``AppTx``, so we can send data to a custom plugin. In this example we
|
||||
explore a simple plugin named ``counter``.
|
||||
|
||||
Example Plugin
|
||||
--------------
|
||||
|
||||
The design of the ``basecoin`` tool makes it easy to extend for custom
|
||||
functionality. The Counter plugin is bundled with basecoin, so if you
|
||||
have already `installed basecoin <install.md>`__ and run
|
||||
``make install`` then you should be able to run a full node with
|
||||
``counter`` and the a light-client ``countercli`` from terminal. The
|
||||
Counter plugin is just like the ``basecoin`` tool. They both use the
|
||||
same library of commands, including one for signing and broadcasting
|
||||
``SendTx``.
|
||||
|
||||
Counter transactions take two custom inputs, a boolean argument named
|
||||
``valid``, and a coin amount named ``countfee``. The transaction is only
|
||||
accepted if both ``valid`` is set to true and the transaction input
|
||||
coins is greater than ``countfee`` that the user provides.
|
||||
|
||||
A new blockchain can be initialized and started just like in the
|
||||
`previous guide <basecoin-basics.md>`__:
|
||||
|
||||
.. code:: shelldown[0]
|
||||
|
||||
# WARNING: this wipes out data - but counter is only for demos...
|
||||
rm -rf ~/.counter
|
||||
countercli reset_all
|
||||
|
||||
countercli keys new cool
|
||||
countercli keys new friend
|
||||
|
||||
counter init $(countercli keys get cool | awk '{print $2}')
|
||||
|
||||
counter start
|
||||
|
||||
The default files are stored in ``~/.counter``. In another window we can
|
||||
initialize the light-client and send a transaction:
|
||||
|
||||
.. code:: shelldown[1]
|
||||
|
||||
countercli init --node=tcp://localhost:46657 --genesis=$HOME/.counter/genesis.json
|
||||
|
||||
YOU=$(countercli keys get friend | awk '{print $2}')
|
||||
countercli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
|
||||
But the Counter has an additional command, ``countercli tx counter``,
|
||||
which crafts an ``AppTx`` specifically for this plugin:
|
||||
|
||||
.. code:: shelldown[2]
|
||||
|
||||
countercli tx counter --name cool
|
||||
countercli tx counter --name cool --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:
|
||||
|
||||
.. code:: shelldown[3]
|
||||
|
||||
countercli query counter
|
||||
|
||||
Tada! We can now see that our custom counter plugin transactions went
|
||||
through. You should see a Counter value of 1 representing the number of
|
||||
valid transactions. If we send another transaction, and then query
|
||||
again, we will see the value increment. Note that we need the sequence
|
||||
number here to send the coins (it didn't increment when we just pinged
|
||||
the counter)
|
||||
|
||||
.. code:: shelldown[4]
|
||||
|
||||
countercli tx counter --name cool --countfee=2mycoin --sequence=2 --valid
|
||||
countercli query counter
|
||||
|
||||
The Counter value should be 2, because we sent a second valid
|
||||
transaction. And this time, since we sent a countfee (which must be less
|
||||
than or equal to the total amount sent with the tx), it stores the
|
||||
``TotalFees`` on the counter as well.
|
||||
|
||||
Keep it mind that, just like with ``basecli``, the ``countercli``
|
||||
verifies a proof that the query response is correct and up-to-date.
|
||||
|
||||
Now, before we implement our own plugin and tooling, it helps to
|
||||
understand the ``AppTx`` and the design of the plugin system.
|
||||
|
||||
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.
|
||||
|
||||
.. code:: 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:
|
||||
|
||||
.. code:: 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/counter``, and modify the code accordingly. Here, we will
|
||||
briefly describe the design and the changes to be made, but see the code
|
||||
for more details.
|
||||
|
||||
First is the ``cmd/counter/main.go``, which drives the program. It can
|
||||
be left alone, but you should change any occurrences of ``counter`` to
|
||||
whatever your plugin tool is going to be called. You must also register
|
||||
your plugin(s) with the basecoin app with ``RegisterStartPlugin``.
|
||||
|
||||
The light-client is located in ``cmd/countercli/main.go`` and allows for
|
||||
transaction and query commands. This file can also be left mostly alone
|
||||
besides replacing the application name and adding references to new
|
||||
plugin commands.
|
||||
|
||||
Next is the custom commands in ``cmd/countercli/commands/``. These files
|
||||
are where we extend the tool with any new commands and flags we need to
|
||||
send transactions or queries to our plugin. You define custom ``tx`` and
|
||||
``query`` subcommands, which are registered in ``main.go`` (avoiding
|
||||
``init()`` auto-registration, for less magic and more control in the
|
||||
main executable).
|
||||
|
||||
Finally is ``plugins/counter/counter.go``, where we provide an
|
||||
implementation of the ``Plugin`` interface. The most important part of
|
||||
the implementation is the ``RunTx`` method, which determines the meaning
|
||||
of the data sent along in the ``AppTx``. In our example, we define a new
|
||||
transaction type, the ``CounterTx``, which we expect to be encoded in
|
||||
the ``AppTx.Data``, and thus to be decoded in the ``RunTx`` method, and
|
||||
used to update the plugin state.
|
||||
|
||||
For more examples and inspiration, see our `repository of example
|
||||
plugins <https://github.com/tendermint/basecoin-examples>`__.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In this guide, we demonstrated how to create a new plugin and how to
|
||||
extend the ``basecoin`` tool to start a blockchain with the plugin
|
||||
enabled and send transactions to it. In the next guide, we introduce a
|
||||
`plugin for Inter Blockchain Communication <ibc.md>`__, which allows us
|
||||
to publish proofs of the state of one blockchain to another, and thus to
|
||||
transfer tokens and data between them.
|
|
@ -0,0 +1,260 @@
|
|||
.. raw:: html
|
||||
|
||||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinTool() {
|
||||
|
||||
rm -rf ~/.basecoin
|
||||
rm -rf ~/.basecli
|
||||
rm -rf example-data
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
(echo $KEYPASS; echo $KEYPASS) | #shelldown[0][0] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[0][1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[1][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[1][1] ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[1][2] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[2][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[2][1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[3][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[5][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER2=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER $PID_SERVER2 >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[6][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[6][1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER2=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER $PID_SERVER2 >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[7][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[8][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
(echo $KEYPASS; echo $KEYPASS) | #shelldown[9][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[10][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[11][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#cleanup
|
||||
rm -rf example-data
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
The Basecoin Tool
|
||||
=================
|
||||
|
||||
In previous tutorials we learned the `basics of the Basecoin
|
||||
CLI </docs/guide/basecoin-basics.md>`__ and `how to implement a
|
||||
plugin </docs/guide/basecoin-plugins.md>`__. In this tutorial, we
|
||||
provide more details on using the Basecoin tool.
|
||||
|
||||
Generate a Key
|
||||
==============
|
||||
|
||||
Generate a key using the ``basecli`` tool:
|
||||
|
||||
.. code:: shelldown[0]
|
||||
|
||||
basecli keys new mykey
|
||||
ME=$(basecli keys get mykey | awk '{print $2}')
|
||||
|
||||
Data Directory
|
||||
==============
|
||||
|
||||
By default, ``basecoin`` works out of ``~/.basecoin``. To change this,
|
||||
set the ``BCHOME`` environment variable:
|
||||
|
||||
.. code:: shelldown[1]
|
||||
|
||||
export BCHOME=~/.my_basecoin_data
|
||||
basecoin init $ME
|
||||
basecoin start
|
||||
|
||||
or
|
||||
|
||||
.. code:: shelldown[2]
|
||||
|
||||
BCHOME=~/.my_basecoin_data basecoin init $ME
|
||||
BCHOME=~/.my_basecoin_data basecoin start
|
||||
|
||||
ABCI Server
|
||||
===========
|
||||
|
||||
So far we have run Basecoin and Tendermint in a single process. However,
|
||||
since we use ABCI, we can actually run them in different processes.
|
||||
First, initialize them:
|
||||
|
||||
.. code:: shelldown[3]
|
||||
|
||||
basecoin init $ME
|
||||
|
||||
This will create a single ``genesis.json`` file in ``~/.basecoin`` with
|
||||
the information for both Basecoin and Tendermint.
|
||||
|
||||
Now, In one window, run
|
||||
|
||||
.. code:: shelldown[4]
|
||||
|
||||
basecoin start --without-tendermint
|
||||
|
||||
and in another,
|
||||
|
||||
.. code:: shelldown[5]
|
||||
|
||||
TMROOT=~/.basecoin tendermint node
|
||||
|
||||
You should see Tendermint start making blocks!
|
||||
|
||||
Alternatively, you could ignore the Tendermint details in
|
||||
``~/.basecoin/genesis.json`` and use a separate directory by running:
|
||||
|
||||
.. code:: shelldown[6]
|
||||
|
||||
tendermint init
|
||||
tendermint node
|
||||
|
||||
For more details on using ``tendermint``, see `the
|
||||
guide <https://tendermint.com/docs/guides/using-tendermint>`__.
|
||||
|
||||
Keys and Genesis
|
||||
================
|
||||
|
||||
In previous tutorials we used ``basecoin init`` to initialize
|
||||
``~/.basecoin`` with the default configuration. This command creates
|
||||
files both for Tendermint and for Basecoin, and a single
|
||||
``genesis.json`` file for both of them. For more information on these
|
||||
files, see the `guide to using
|
||||
Tendermint <https://tendermint.com/docs/guides/using-tendermint>`__.
|
||||
|
||||
Now let's make our own custom Basecoin data.
|
||||
|
||||
First, create a new directory:
|
||||
|
||||
.. code:: shelldown[7]
|
||||
|
||||
mkdir example-data
|
||||
|
||||
We can tell ``basecoin`` to use this directory by exporting the
|
||||
``BCHOME`` environment variable:
|
||||
|
||||
.. code:: shelldown[8]
|
||||
|
||||
export BCHOME=$(pwd)/example-data
|
||||
|
||||
If you're going to be using multiple terminal windows, make sure to add
|
||||
this variable to your shell startup scripts (eg. ``~/.bashrc``).
|
||||
|
||||
Now, let's create a new key:
|
||||
|
||||
.. code:: shelldown[9]
|
||||
|
||||
basecli keys new foobar
|
||||
|
||||
The key's info can be retrieved with
|
||||
|
||||
.. code:: shelldown[10]
|
||||
|
||||
basecli keys get foobar -o=json
|
||||
|
||||
You should get output which looks similar to the following:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"name": "foobar",
|
||||
"address": "404C5003A703C7DA888C96A2E901FCE65A6869D9",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
|
||||
}
|
||||
}
|
||||
|
||||
Yours will look different - each key is randomly derived. Now we can
|
||||
make a ``genesis.json`` file and add an account with our public key:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"app_hash": "",
|
||||
"chain_id": "example-chain",
|
||||
"genesis_time": "0001-01-01T00:00:00.000Z",
|
||||
"validators": [
|
||||
{
|
||||
"amount": 10,
|
||||
"name": "",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
|
||||
}
|
||||
}
|
||||
],
|
||||
"app_options": {
|
||||
"accounts": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "gold",
|
||||
"amount": 1000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Here we've granted ourselves ``1000000000`` units of the ``gold`` token.
|
||||
Note that we've also set the ``chain-id`` to be ``example-chain``. All
|
||||
transactions must therefore include the ``--chain-id example-chain`` in
|
||||
order to make sure they are valid for this chain. Previously, we didn't
|
||||
need this flag because we were using the default chain ID
|
||||
("test\_chain\_id"). Now that we're using a custom chain, we need to
|
||||
specify the chain explicitly on the command line.
|
||||
|
||||
Note we have also left out the details of the Tendermint genesis. These
|
||||
are documented in the `Tendermint
|
||||
guide <https://tendermint.com/docs/guides/using-tendermint>`__.
|
||||
|
||||
Reset
|
||||
=====
|
||||
|
||||
You can reset all blockchain data by running:
|
||||
|
||||
.. code:: shelldown[11]
|
||||
|
||||
basecoin unsafe_reset_all
|
||||
|
||||
Similarly, you can reset client data by running:
|
||||
|
||||
.. code:: shelldown[12]
|
||||
|
||||
basecli reset_all
|
||||
|
||||
Genesis
|
||||
=======
|
||||
|
||||
Any required plugin initialization should be constructed using
|
||||
``SetOption`` on genesis. When starting a new chain for the first time,
|
||||
``SetOption`` will be called for each item the genesis file. Within
|
||||
genesis.json file entries are made in the format:
|
||||
``"<plugin>/<key>", "<value>"``, where ``<plugin>`` is the plugin 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.
|
296
docs/glossary.md
296
docs/glossary.md
|
@ -1,296 +0,0 @@
|
|||
# Glossary
|
||||
|
||||
This glossary defines many terms used throughout documentation of Quark. If
|
||||
there is every a concept that seems unclear, check here. This is mainly to
|
||||
provide a background and general understanding of the different words and
|
||||
concepts that are used. Other documents will explain in more detail how to
|
||||
combine these concepts to build a particular application.
|
||||
|
||||
## Transaction
|
||||
|
||||
A transaction is a packet of binary data that contains all information to
|
||||
validate and perform an action on the blockchain. The only other data that it
|
||||
interacts with is the current state of the chain (key-value store), and
|
||||
it must have a deterministic action. The transaction is the main piece of one
|
||||
request.
|
||||
|
||||
We currently make heavy use of [go-wire](https://github.com/tendermint/go-wire)
|
||||
and [data](https://github.com/tendermint/go-wire/tree/master/data) to provide
|
||||
binary and json encodings and decodings for `struct` or interface` objects.
|
||||
Here, encoding and decoding operations are designed to operate with interfaces
|
||||
nested any amount times (like an onion!). There is one public `TxMapper`
|
||||
in the basecoin root package, and all modules can register their own transaction
|
||||
types there. This allows us to deserialize the entire transaction in one location
|
||||
(even with types defined in other repos), to easily embed an arbitrary transaction
|
||||
inside another without specifying the type, and provide an automatic json
|
||||
representation allowing for users (or apps) to inspect the chain.
|
||||
|
||||
Note how we can wrap any other transaction, add a fee level, and not worry
|
||||
about the encoding in our module any more?
|
||||
|
||||
```golang
|
||||
type Fee struct {
|
||||
Fee coin.Coin `json:"fee"`
|
||||
Payer basecoin.Actor `json:"payer"` // the address who pays the fee
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
```
|
||||
|
||||
## Context (ctx)
|
||||
|
||||
As a request passes through the system, it may pick up information such as the
|
||||
authorization it has received from another middleware, or the block height the
|
||||
request runs at. In order to carry this information between modules it is
|
||||
saved to the context. Further, all information must be deterministic from
|
||||
the context in which the request runs (based on the transaction and the block
|
||||
it was included in) and can be used to validate the transaction.
|
||||
|
||||
## Data Store
|
||||
|
||||
In order to provide proofs to Tendermint, we keep all data in one key-value
|
||||
(kv) store which is indexed with a merkle tree. This allows for the easy
|
||||
generation of a root hash and proofs for queries without requiring complex
|
||||
logic inside each module. Standardization of this process also allows powerful
|
||||
light-client tooling as any store data may be verified on the fly.
|
||||
|
||||
The largest limitation of the current implemenation of the kv-store is that
|
||||
interface that the application must use can only `Get` and `Set` single data
|
||||
points. That said, there are some data structures like queues and range
|
||||
queries that are available in `state` package. These provide higher-level
|
||||
functionality in a standard format, but have not yet been integrated into the
|
||||
kv-store interface.
|
||||
|
||||
## Isolation
|
||||
|
||||
One of the main arguments for blockchain is security. So while we encourage
|
||||
the use of third-party modules, all developers must be vigilant against
|
||||
security holes. If you use the
|
||||
[stack](https://github.com/cosmos/cosmos-sdk/tree/master/stack)
|
||||
package, it will provide two different types of compartmentalization security.
|
||||
|
||||
The first is to limit the working kv-store space of each module. When
|
||||
`DeliverTx` is called for a module, it is never given the entire data store,
|
||||
but rather only its own prefixed subset of the store. This is achieved by
|
||||
prefixing all keys transparently with `<module name> + 0x0`, using the null
|
||||
byte as a separator. Since the module name must be a string, no malicious
|
||||
naming scheme can ever lead to a collision. Inside a module, we can
|
||||
write using any key value we desire without the possibility that we
|
||||
have modified data belonging to separate module.
|
||||
|
||||
The second is to add permissions to the transaction context. The transaction
|
||||
context can specify that the tx has been signed by one or multiple specific
|
||||
[actors](https://github.com/tendermint/basecoin/blob/unstable/context.go#L18).
|
||||
A transactions will only be executed if the permission requirements have been
|
||||
fulfilled. For example the sender of funds must have signed, or 2 out of 3
|
||||
multi-signature actors must have signed a joint account. To prevent the
|
||||
forgery of account signatures from unintended modules each permission
|
||||
is associated with the module that granted it (in this case
|
||||
[auth](https://github.com/cosmos/cosmos-sdk/tree/master/modules/auth)),
|
||||
and if a module tries to add a permission for another module, it will
|
||||
panic. There is also protection if a module creates a brand new fake
|
||||
context to trick the downstream modules. Each context enforces
|
||||
the rules on how to make child contexts, and the stack middleware builder
|
||||
enforces that the context passed from one level to the next is a valid
|
||||
child of the original one.
|
||||
|
||||
These security measures ensure that modules can confidently write to their
|
||||
local section of the database and trust the permissions associated with the
|
||||
context, without concern of interference from other modules. (Okay,
|
||||
if you see a bunch of C-code in the module traversing through all the
|
||||
memory space of the application, then get worried....)
|
||||
|
||||
## Handler
|
||||
|
||||
The ABCI interface is handled by `app`, which translates these data structures
|
||||
into an internal format that is more convenient, but unable to travel over the
|
||||
wire. The basic interface for any code that modifies state is the `Handler`
|
||||
interface, which provides four methods:
|
||||
|
||||
```golang
|
||||
Name() string
|
||||
CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error)
|
||||
```
|
||||
|
||||
Note the `Context`, `KVStore`, and `Tx` as principal carriers of information.
|
||||
And that Result is always success, and we have a second error return
|
||||
for errors (which is much more standard golang that `res.IsErr()`)
|
||||
|
||||
The `Handler` interface is designed to be the basis for all modules that
|
||||
execute transactions, and this can provide a large degree of code
|
||||
interoperability, much like `http.Handler` does in golang web development.
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware is a series of processing steps that any request must travel through
|
||||
before (and after) executing the registered `Handler`. Some examples are a
|
||||
logger (that records the time before executing the transaction, then outputs
|
||||
info - including duration - after the execution), of a signature checker (which
|
||||
unwraps the transaction by one layer, verifies signatures, and adds the
|
||||
permissions to the Context before passing the request along).
|
||||
|
||||
In keeping with the standardization of `http.Handler` and inspired by the
|
||||
super minimal [negroni](https://github.com/urfave/negroni/blob/master/README.md)
|
||||
package, we just provide one more `Middleware` interface, which has an extra
|
||||
`next` parameter, and a `Stack` that can wire all the levels together (which
|
||||
also gives us a place to perform isolation of each step).
|
||||
|
||||
```golang
|
||||
Name() string
|
||||
CheckTx(ctx Context, store state.KVStore, tx Tx, next Checker) (Result, error)
|
||||
DeliverTx(ctx Context, store state.KVStore, tx Tx, next Deliver) (Result, error)
|
||||
SetOption(l log.Logger, store state.KVStore, module, key, value string, next Optioner) (string, error)
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
A module is a set of functionality which should be typically designed as
|
||||
self-sufficient. Common elements of a module are:
|
||||
|
||||
* transaction types (either end transactions, or transaction wrappers)
|
||||
* custom error codes
|
||||
* data models (to persist in the kv-store)
|
||||
* handler (to handle any end transactions)
|
||||
* middleware (to handler any wrapper transactions)
|
||||
|
||||
To enable a module, you must add the appropriate middleware (if any) to the
|
||||
stack in `main.go` for the client application (default:
|
||||
`basecli/main.go`), as well as adding the handler (if any) to the dispatcher
|
||||
(default: `app/app.go`). Once the stack is compiled into a `Handler`,
|
||||
then each transaction is handled by the appropriate module.
|
||||
|
||||
## Dispatcher
|
||||
|
||||
We usually will want to have multiple modules working together, and need to
|
||||
make sure the correct transactions get to the correct module. So we have
|
||||
`coin` sending money, `roles` to create multi-sig accounts, and `ibc` for
|
||||
following other chains all working together without interference.
|
||||
|
||||
After the chain of middleware, we can register a `Dispatcher`, which also
|
||||
implements the `Handler` interface. We then register a list of modules with
|
||||
the dispatcher. Every module has a unique `Name()`, which is used for
|
||||
isolating its state space. We use this same name for routing transactions.
|
||||
Each transaction implementation must be registed with go-wire via `TxMapper`,
|
||||
so we just look at the registered name of this transaction, which should be
|
||||
of the form `<module name>/xxx`. The dispatcher grabs the appropriate module
|
||||
name from the tx name and routes it if the module is present.
|
||||
|
||||
This all seems like a bit of magic, but really we're just making use of go-wire
|
||||
magic that we are already using, rather than add another layer. For all the
|
||||
transactions to be properly routed, the only thing you need to remember is to
|
||||
use the following pattern:
|
||||
|
||||
```golang
|
||||
const (
|
||||
NameCoin = "coin"
|
||||
TypeSend = NameCoin + "/send"
|
||||
)
|
||||
```
|
||||
|
||||
## Inter-Plugin Communication (IPC)
|
||||
|
||||
But wait, there's more... since we have isolated all the modules from each
|
||||
other, we need to allow some way for them to interact in a controlled fashion.
|
||||
One example is the `fee` middleware, which wants to deduct coins from the
|
||||
calling account and can be accomplished most easily with the `coin` module.
|
||||
|
||||
To make a call from the middleware, we the `next` Handler, which will execute
|
||||
the rest of the stack. It can create a new SendTx and pass it down the
|
||||
stack. If it returns success, do the rest of the processing (and send the
|
||||
original transaction down the stack), otherwise abort.
|
||||
|
||||
However, if one `Handler` inside the `Dispatcher` wants to do this, it becomes
|
||||
more complex. The solution is that the `Dispatcher` accepts not a `Handler`,
|
||||
but a `Dispatchable`, which looks like a middleware, except that the `next`
|
||||
argument is a callback to the dispatcher to execute a sub-transaction. If a
|
||||
module doesn't want to use this functionality, it can just implement `Handler`
|
||||
and call `stack.WrapHandler(h)` to convert it to a `Dispatchable` that never
|
||||
uses the callback.
|
||||
|
||||
One example of this is the counter app, which can optionally accept a payment.
|
||||
If the transaction contains a payment, it must create a SendTx and pass this
|
||||
to the dispatcher to deduct the amount from the proper account. Take a look at
|
||||
[counter plugin](https://github.com/cosmos/cosmos-sdk/blob/master/docs/guide/counter/plugins/counter/counter.go)for a better idea.
|
||||
|
||||
## Permissions
|
||||
|
||||
IPC requires a more complex permissioning system to allow the modules to have
|
||||
limited access to each other and also to allow more types of permissions than
|
||||
simple public key signatures. Rather than just use an address to identify
|
||||
who is performing an action, we can use a more complex structure:
|
||||
|
||||
```golang
|
||||
type Actor struct {
|
||||
ChainID string `json:"chain"` // this is empty unless it comes from a different chain
|
||||
App string `json:"app"` // the app that the actor belongs to
|
||||
Address data.Bytes `json:"addr"` // arbitrary app-specific unique id
|
||||
}
|
||||
```
|
||||
|
||||
Here, the `Actor` abstracts any address that can authorize actions, hold funds,
|
||||
or initiate any sort of transaction. It doesn't just have to be a pubkey on
|
||||
this chain, it could stem from another app (such as multi-sig account), or even
|
||||
another chain (via IBC)
|
||||
|
||||
`ChainID` is for IBC, discussed below. Let's focus on `App` and `Address`.
|
||||
For a signature, the App is `auth`, and any modules can check to see if a
|
||||
specific public key address signed like this `ctx.HasPermission(auth.SigPerm(addr))`.
|
||||
However, we can also authorize a tx with `roles`, which handles multi-sig accounts,
|
||||
it checks if there were enough signatures by checking as above, then it can add
|
||||
the role permission like `ctx= ctx.WithPermissions(NewPerm(assume.Role))`
|
||||
|
||||
In addition to the permissions schema, the Actors are addresses just like public key
|
||||
addresses. So one can create a mulit-sig role, then send coin there, which can
|
||||
only be moved upon meeting the authorization requirements from that module.
|
||||
`coin` doesn't even know the existence of `roles` and one could build any other
|
||||
sort of module to provide permissions (like bind the outcome of an election to
|
||||
move coins or to modify the accounts on a role).
|
||||
|
||||
One idea - not yet implemented - is to provide scopes on the permissions.
|
||||
Currently, if I sign a transaction to one module, it can pass it on to any other
|
||||
module over IPC with the same permissions. It could move coins, vote in an election,
|
||||
or anything else. Ideally, when signing, one could also specify the scope(s) that
|
||||
this signature authorizes. The [oauth protocol](https://api.slack.com/docs/oauth-scopes)
|
||||
also has to deal with a similar problem, and maybe could provide some inspiration.
|
||||
|
||||
|
||||
## Replay Protection
|
||||
|
||||
In order to prevent [replay
|
||||
attacks](https://en.wikipedia.org/wiki/Replay_attack) a multi account nonce system
|
||||
has been constructed as a module, which can be found in
|
||||
`modules/nonce`. By adding the nonce module to the stack, each
|
||||
transaction is verified for authenticity against replay attacks. This is
|
||||
achieved by requiring that a new signed copy of the sequence number which must
|
||||
be exactly 1 greater than the sequence number of the previous transaction. A
|
||||
distinct sequence number is assigned per chain-id, application, and group of
|
||||
signers. Each sequence number is tracked as a nonce-store entry where the key
|
||||
is the marshaled list of actors after having been sorted by chain, app, and
|
||||
address.
|
||||
|
||||
```golang
|
||||
// Tx - Nonce transaction structure, contains list of signers and current sequence number
|
||||
type Tx struct {
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Signers []basecoin.Actor `json:"signers"`
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
```
|
||||
|
||||
By distinguishing sequence numbers across groups of Signers, multi-signature
|
||||
Actors need not lock up use of their Address while waiting for all the members
|
||||
of a multi-sig transaction to occur. Instead only the multi-sig account will
|
||||
be locked, while other accounts belonging to that signer can be used and signed
|
||||
with other sequence numbers.
|
||||
|
||||
By abstracting out the nonce module in the stack, entire series of transactions
|
||||
can occur without needing to verify the nonce for each member of the series. An
|
||||
common example is a stack which will send coins and charge a fee. Within the SDK
|
||||
this can be achieved using separate modules in a stack, one to send the coins
|
||||
and the other to charge the fee, however both modules do not need to check the
|
||||
nonce. This can occur as a separate module earlier in the stack.
|
||||
|
||||
## IBC (Inter-Blockchain Communication)
|
||||
|
||||
Stay tuned!
|
|
@ -0,0 +1,334 @@
|
|||
Glossary
|
||||
========
|
||||
|
||||
This glossary defines many terms used throughout documentation of Quark.
|
||||
If there is every a concept that seems unclear, check here. This is
|
||||
mainly to provide a background and general understanding of the
|
||||
different words and concepts that are used. Other documents will explain
|
||||
in more detail how to combine these concepts to build a particular
|
||||
application.
|
||||
|
||||
Transaction
|
||||
-----------
|
||||
|
||||
A transaction is a packet of binary data that contains all information
|
||||
to validate and perform an action on the blockchain. The only other data
|
||||
that it interacts with is the current state of the chain (key-value
|
||||
store), and it must have a deterministic action. The transaction is the
|
||||
main piece of one request.
|
||||
|
||||
We currently make heavy use of
|
||||
`go-wire <https://github.com/tendermint/go-wire>`__ and
|
||||
`data <https://github.com/tendermint/go-wire/tree/master/data>`__ to
|
||||
provide binary and json encodings and decodings for ``struct`` or
|
||||
interface\ ``objects. Here, encoding and decoding operations are designed to operate with interfaces nested any amount times (like an onion!). There is one public``\ TxMapper\`
|
||||
in the basecoin root package, and all modules can register their own
|
||||
transaction types there. This allows us to deserialize the entire
|
||||
transaction in one location (even with types defined in other repos), to
|
||||
easily embed an arbitrary transaction inside another without specifying
|
||||
the type, and provide an automatic json representation allowing for
|
||||
users (or apps) to inspect the chain.
|
||||
|
||||
Note how we can wrap any other transaction, add a fee level, and not
|
||||
worry about the encoding in our module any more?
|
||||
|
||||
.. code:: golang
|
||||
|
||||
type Fee struct {
|
||||
Fee coin.Coin `json:"fee"`
|
||||
Payer basecoin.Actor `json:"payer"` // the address who pays the fee
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
Context (ctx)
|
||||
-------------
|
||||
|
||||
As a request passes through the system, it may pick up information such
|
||||
as the authorization it has received from another middleware, or the
|
||||
block height the request runs at. In order to carry this information
|
||||
between modules it is saved to the context. Further, all information
|
||||
must be deterministic from the context in which the request runs (based
|
||||
on the transaction and the block it was included in) and can be used to
|
||||
validate the transaction.
|
||||
|
||||
Data Store
|
||||
----------
|
||||
|
||||
In order to provide proofs to Tendermint, we keep all data in one
|
||||
key-value (kv) store which is indexed with a merkle tree. This allows
|
||||
for the easy generation of a root hash and proofs for queries without
|
||||
requiring complex logic inside each module. Standardization of this
|
||||
process also allows powerful light-client tooling as any store data may
|
||||
be verified on the fly.
|
||||
|
||||
The largest limitation of the current implemenation of the kv-store is
|
||||
that interface that the application must use can only ``Get`` and
|
||||
``Set`` single data points. That said, there are some data structures
|
||||
like queues and range queries that are available in ``state`` package.
|
||||
These provide higher-level functionality in a standard format, but have
|
||||
not yet been integrated into the kv-store interface.
|
||||
|
||||
Isolation
|
||||
---------
|
||||
|
||||
One of the main arguments for blockchain is security. So while we
|
||||
encourage the use of third-party modules, all developers must be
|
||||
vigilant against security holes. If you use the
|
||||
`stack <https://github.com/cosmos/cosmos-sdk/tree/master/stack>`__
|
||||
package, it will provide two different types of compartmentalization
|
||||
security.
|
||||
|
||||
The first is to limit the working kv-store space of each module. When
|
||||
``DeliverTx`` is called for a module, it is never given the entire data
|
||||
store, but rather only its own prefixed subset of the store. This is
|
||||
achieved by prefixing all keys transparently with
|
||||
``<module name> + 0x0``, using the null byte as a separator. Since the
|
||||
module name must be a string, no malicious naming scheme can ever lead
|
||||
to a collision. Inside a module, we can write using any key value we
|
||||
desire without the possibility that we have modified data belonging to
|
||||
separate module.
|
||||
|
||||
The second is to add permissions to the transaction context. The
|
||||
transaction context can specify that the tx has been signed by one or
|
||||
multiple specific
|
||||
`actors <https://github.com/tendermint/basecoin/blob/unstable/context.go#L18>`__.
|
||||
A transactions will only be executed if the permission requirements have
|
||||
been fulfilled. For example the sender of funds must have signed, or 2
|
||||
out of 3 multi-signature actors must have signed a joint account. To
|
||||
prevent the forgery of account signatures from unintended modules each
|
||||
permission is associated with the module that granted it (in this case
|
||||
`auth <https://github.com/cosmos/cosmos-sdk/tree/master/modules/auth>`__),
|
||||
and if a module tries to add a permission for another module, it will
|
||||
panic. There is also protection if a module creates a brand new fake
|
||||
context to trick the downstream modules. Each context enforces the rules
|
||||
on how to make child contexts, and the stack middleware builder enforces
|
||||
that the context passed from one level to the next is a valid child of
|
||||
the original one.
|
||||
|
||||
These security measures ensure that modules can confidently write to
|
||||
their local section of the database and trust the permissions associated
|
||||
with the context, without concern of interference from other modules.
|
||||
(Okay, if you see a bunch of C-code in the module traversing through all
|
||||
the memory space of the application, then get worried....)
|
||||
|
||||
Handler
|
||||
-------
|
||||
|
||||
The ABCI interface is handled by ``app``, which translates these data
|
||||
structures into an internal format that is more convenient, but unable
|
||||
to travel over the wire. The basic interface for any code that modifies
|
||||
state is the ``Handler`` interface, which provides four methods:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
Name() string
|
||||
CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error)
|
||||
|
||||
Note the ``Context``, ``KVStore``, and ``Tx`` as principal carriers of
|
||||
information. And that Result is always success, and we have a second
|
||||
error return for errors (which is much more standard golang that
|
||||
``res.IsErr()``)
|
||||
|
||||
The ``Handler`` interface is designed to be the basis for all modules
|
||||
that execute transactions, and this can provide a large degree of code
|
||||
interoperability, much like ``http.Handler`` does in golang web
|
||||
development.
|
||||
|
||||
Middleware
|
||||
----------
|
||||
|
||||
Middleware is a series of processing steps that any request must travel
|
||||
through before (and after) executing the registered ``Handler``. Some
|
||||
examples are a logger (that records the time before executing the
|
||||
transaction, then outputs info - including duration - after the
|
||||
execution), of a signature checker (which unwraps the transaction by one
|
||||
layer, verifies signatures, and adds the permissions to the Context
|
||||
before passing the request along).
|
||||
|
||||
In keeping with the standardization of ``http.Handler`` and inspired by
|
||||
the super minimal
|
||||
`negroni <https://github.com/urfave/negroni/blob/master/README.md>`__
|
||||
package, we just provide one more ``Middleware`` interface, which has an
|
||||
extra ``next`` parameter, and a ``Stack`` that can wire all the levels
|
||||
together (which also gives us a place to perform isolation of each
|
||||
step).
|
||||
|
||||
.. code:: golang
|
||||
|
||||
Name() string
|
||||
CheckTx(ctx Context, store state.KVStore, tx Tx, next Checker) (Result, error)
|
||||
DeliverTx(ctx Context, store state.KVStore, tx Tx, next Deliver) (Result, error)
|
||||
SetOption(l log.Logger, store state.KVStore, module, key, value string, next Optioner) (string, error)
|
||||
|
||||
Modules
|
||||
-------
|
||||
|
||||
A module is a set of functionality which should be typically designed as
|
||||
self-sufficient. Common elements of a module are:
|
||||
|
||||
- transaction types (either end transactions, or transaction wrappers)
|
||||
- custom error codes
|
||||
- data models (to persist in the kv-store)
|
||||
- handler (to handle any end transactions)
|
||||
- middleware (to handler any wrapper transactions)
|
||||
|
||||
To enable a module, you must add the appropriate middleware (if any) to
|
||||
the stack in ``main.go`` for the client application (default:
|
||||
``basecli/main.go``), as well as adding the handler (if any) to the
|
||||
dispatcher (default: ``app/app.go``). Once the stack is compiled into a
|
||||
``Handler``, then each transaction is handled by the appropriate module.
|
||||
|
||||
Dispatcher
|
||||
----------
|
||||
|
||||
We usually will want to have multiple modules working together, and need
|
||||
to make sure the correct transactions get to the correct module. So we
|
||||
have ``coin`` sending money, ``roles`` to create multi-sig accounts, and
|
||||
``ibc`` for following other chains all working together without
|
||||
interference.
|
||||
|
||||
After the chain of middleware, we can register a ``Dispatcher``, which
|
||||
also implements the ``Handler`` interface. We then register a list of
|
||||
modules with the dispatcher. Every module has a unique ``Name()``, which
|
||||
is used for isolating its state space. We use this same name for routing
|
||||
transactions. Each transaction implementation must be registed with
|
||||
go-wire via ``TxMapper``, so we just look at the registered name of this
|
||||
transaction, which should be of the form ``<module name>/xxx``. The
|
||||
dispatcher grabs the appropriate module name from the tx name and routes
|
||||
it if the module is present.
|
||||
|
||||
This all seems like a bit of magic, but really we're just making use of
|
||||
go-wire magic that we are already using, rather than add another layer.
|
||||
For all the transactions to be properly routed, the only thing you need
|
||||
to remember is to use the following pattern:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
const (
|
||||
NameCoin = "coin"
|
||||
TypeSend = NameCoin + "/send"
|
||||
)
|
||||
|
||||
Inter-Plugin Communication (IPC)
|
||||
--------------------------------
|
||||
|
||||
But wait, there's more... since we have isolated all the modules from
|
||||
each other, we need to allow some way for them to interact in a
|
||||
controlled fashion. One example is the ``fee`` middleware, which wants
|
||||
to deduct coins from the calling account and can be accomplished most
|
||||
easily with the ``coin`` module.
|
||||
|
||||
To make a call from the middleware, we the ``next`` Handler, which will
|
||||
execute the rest of the stack. It can create a new SendTx and pass it
|
||||
down the stack. If it returns success, do the rest of the processing
|
||||
(and send the original transaction down the stack), otherwise abort.
|
||||
|
||||
However, if one ``Handler`` inside the ``Dispatcher`` wants to do this,
|
||||
it becomes more complex. The solution is that the ``Dispatcher`` accepts
|
||||
not a ``Handler``, but a ``Dispatchable``, which looks like a
|
||||
middleware, except that the ``next`` argument is a callback to the
|
||||
dispatcher to execute a sub-transaction. If a module doesn't want to use
|
||||
this functionality, it can just implement ``Handler`` and call
|
||||
``stack.WrapHandler(h)`` to convert it to a ``Dispatchable`` that never
|
||||
uses the callback.
|
||||
|
||||
One example of this is the counter app, which can optionally accept a
|
||||
payment. If the transaction contains a payment, it must create a SendTx
|
||||
and pass this to the dispatcher to deduct the amount from the proper
|
||||
account. Take a look at `counter
|
||||
plugin <https://github.com/cosmos/cosmos-sdk/blob/master/docs/guide/counter/plugins/counter/counter.go>`__\ for
|
||||
a better idea.
|
||||
|
||||
Permissions
|
||||
-----------
|
||||
|
||||
IPC requires a more complex permissioning system to allow the modules to
|
||||
have limited access to each other and also to allow more types of
|
||||
permissions than simple public key signatures. Rather than just use an
|
||||
address to identify who is performing an action, we can use a more
|
||||
complex structure:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
type Actor struct {
|
||||
ChainID string `json:"chain"` // this is empty unless it comes from a different chain
|
||||
App string `json:"app"` // the app that the actor belongs to
|
||||
Address data.Bytes `json:"addr"` // arbitrary app-specific unique id
|
||||
}
|
||||
|
||||
Here, the ``Actor`` abstracts any address that can authorize actions,
|
||||
hold funds, or initiate any sort of transaction. It doesn't just have to
|
||||
be a pubkey on this chain, it could stem from another app (such as
|
||||
multi-sig account), or even another chain (via IBC)
|
||||
|
||||
``ChainID`` is for IBC, discussed below. Let's focus on ``App`` and
|
||||
``Address``. For a signature, the App is ``auth``, and any modules can
|
||||
check to see if a specific public key address signed like this
|
||||
``ctx.HasPermission(auth.SigPerm(addr))``. However, we can also
|
||||
authorize a tx with ``roles``, which handles multi-sig accounts, it
|
||||
checks if there were enough signatures by checking as above, then it can
|
||||
add the role permission like
|
||||
``ctx= ctx.WithPermissions(NewPerm(assume.Role))``
|
||||
|
||||
In addition to the permissions schema, the Actors are addresses just
|
||||
like public key addresses. So one can create a mulit-sig role, then send
|
||||
coin there, which can only be moved upon meeting the authorization
|
||||
requirements from that module. ``coin`` doesn't even know the existence
|
||||
of ``roles`` and one could build any other sort of module to provide
|
||||
permissions (like bind the outcome of an election to move coins or to
|
||||
modify the accounts on a role).
|
||||
|
||||
One idea - not yet implemented - is to provide scopes on the
|
||||
permissions. Currently, if I sign a transaction to one module, it can
|
||||
pass it on to any other module over IPC with the same permissions. It
|
||||
could move coins, vote in an election, or anything else. Ideally, when
|
||||
signing, one could also specify the scope(s) that this signature
|
||||
authorizes. The `oauth
|
||||
protocol <https://api.slack.com/docs/oauth-scopes>`__ also has to deal
|
||||
with a similar problem, and maybe could provide some inspiration.
|
||||
|
||||
Replay Protection
|
||||
-----------------
|
||||
|
||||
In order to prevent `replay
|
||||
attacks <https://en.wikipedia.org/wiki/Replay_attack>`__ a multi account
|
||||
nonce system has been constructed as a module, which can be found in
|
||||
``modules/nonce``. By adding the nonce module to the stack, each
|
||||
transaction is verified for authenticity against replay attacks. This is
|
||||
achieved by requiring that a new signed copy of the sequence number
|
||||
which must be exactly 1 greater than the sequence number of the previous
|
||||
transaction. A distinct sequence number is assigned per chain-id,
|
||||
application, and group of signers. Each sequence number is tracked as a
|
||||
nonce-store entry where the key is the marshaled list of actors after
|
||||
having been sorted by chain, app, and address.
|
||||
|
||||
.. code:: golang
|
||||
|
||||
// Tx - Nonce transaction structure, contains list of signers and current sequence number
|
||||
type Tx struct {
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Signers []basecoin.Actor `json:"signers"`
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
By distinguishing sequence numbers across groups of Signers,
|
||||
multi-signature Actors need not lock up use of their Address while
|
||||
waiting for all the members of a multi-sig transaction to occur. Instead
|
||||
only the multi-sig account will be locked, while other accounts
|
||||
belonging to that signer can be used and signed with other sequence
|
||||
numbers.
|
||||
|
||||
By abstracting out the nonce module in the stack, entire series of
|
||||
transactions can occur without needing to verify the nonce for each
|
||||
member of the series. An common example is a stack which will send coins
|
||||
and charge a fee. Within the SDK this can be achieved using separate
|
||||
modules in a stack, one to send the coins and the other to charge the
|
||||
fee, however both modules do not need to check the nonce. This can occur
|
||||
as a separate module earlier in the stack.
|
||||
|
||||
IBC (Inter-Blockchain Communication)
|
||||
------------------------------------
|
||||
|
||||
Stay tuned!
|
|
@ -1,333 +0,0 @@
|
|||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinBasics() {
|
||||
|
||||
#shelldown[1][3] >/dev/null
|
||||
#shelldown[1][4] >/dev/null
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][6])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][7])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
|
||||
#shelldown[3][-1]
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
|
||||
RES=$((echo y) | #shelldown[5][-1] $1)
|
||||
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
|
||||
|
||||
#shelldown[6][0]
|
||||
#shelldown[6][1]
|
||||
RES=$(#shelldown[6][2] | jq '.data.coins[0].denom' | tr -d '"')
|
||||
assertTrue "Line $LINENO: Expected to have mycoins, got $RES" '[[ $RES == mycoin ]]'
|
||||
RES="$(#shelldown[6][3] 2>&1)"
|
||||
assertTrue "Line $LINENO: Expected to contain ERROR, got $RES" '[[ $RES == *ERROR* ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[7][-1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$(#shelldown[8][-1] | jq '.data.coins[0].amount')
|
||||
assertTrue "Line $LINENO: Expected to contain 1000 mycoin, got $RES" '[[ $RES == 1000 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[9][-1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[10][-1])
|
||||
assertTrue "Line $LINENO: Expected to contain insufficient funds error, got $RES" \
|
||||
'[[ $RES == *"Insufficient Funds"* ]]'
|
||||
|
||||
#perform a substitution within the final tests
|
||||
HASH=$((echo $KEYPASS) | #shelldown[11][-1] | jq '.hash' | tr -d '"')
|
||||
PRESUB="#shelldown[12][-1]"
|
||||
RES=$(eval ${PRESUB/<HASH>/$HASH})
|
||||
assertTrue "Line $LINENO: Expected to not contain Error, got $RES" '[[ $RES != *Error* ]]'
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
# Basecoin Basics
|
||||
|
||||
Here we explain how to get started with a basic Basecoin blockchain,
|
||||
how to send transactions between accounts using the `basecoin` tool,
|
||||
and what is happening under the hood.
|
||||
|
||||
## Install
|
||||
|
||||
With go, it's one command:
|
||||
|
||||
```shelldown[0]
|
||||
go get -u github.com/tendermint/basecoin/cmd/...
|
||||
```
|
||||
|
||||
If you have trouble, see the [installation guide](install.md).
|
||||
|
||||
Note the above command installs two binaries: `basecoin` and `basecli`.
|
||||
The former is the running node. The latter is a command-line light-client.
|
||||
This tutorial assumes you have a 'fresh' working environment. See [how to clean up, below](#clean-up).
|
||||
|
||||
## Generate some keys
|
||||
|
||||
Let's generate two keys, one to receive an initial allocation of coins,
|
||||
and one to send some coins to later:
|
||||
|
||||
```shelldown[1]
|
||||
basecli keys new cool
|
||||
basecli keys new friend
|
||||
```
|
||||
|
||||
You'll need to enter passwords. You can view your key names and addresses with
|
||||
`basecli keys list`, or see a particular key's address with `basecli keys get
|
||||
<NAME>`.
|
||||
|
||||
## Initialize Basecoin
|
||||
|
||||
To initialize a new Basecoin blockchain, run:
|
||||
|
||||
```shelldown[2]
|
||||
basecoin init <ADDRESS>
|
||||
```
|
||||
|
||||
If you prefer not to copy-paste, you can provide the address programatically:
|
||||
|
||||
```shelldown[3]
|
||||
basecoin init $(basecli keys get cool | awk '{print $2}')
|
||||
```
|
||||
|
||||
This will create the necessary files for a Basecoin blockchain with one
|
||||
validator and one account (corresponding to your key) in `~/.basecoin`. For
|
||||
more options on setup, see the [guide to using the Basecoin
|
||||
tool](/docs/guide/basecoin-tool.md).
|
||||
|
||||
If you like, you can manually add some more accounts to the blockchain by
|
||||
generating keys and editing the `~/.basecoin/genesis.json`.
|
||||
|
||||
## Start
|
||||
|
||||
Now we can start Basecoin:
|
||||
|
||||
```shelldown[4]
|
||||
basecoin start
|
||||
```
|
||||
|
||||
You should see blocks start streaming in!
|
||||
|
||||
## Initialize Light-Client
|
||||
|
||||
Now that Basecoin is running we can initialize `basecli`, the light-client
|
||||
utility. Basecli is used for sending transactions and querying the state.
|
||||
Leave Basecoin running and open a new terminal window. Here run:
|
||||
|
||||
```shelldown[5]
|
||||
basecli init --node=tcp://localhost:46657 --genesis=$HOME/.basecoin/genesis.json
|
||||
```
|
||||
|
||||
If you provide the genesis file to basecli, it can calculate the proper chainID
|
||||
and validator hash. Basecli needs to get this information from some trusted
|
||||
source, so all queries done with `basecli` can be cryptographically proven to
|
||||
be correct according to a known validator set.
|
||||
|
||||
Note: that --genesis only works if there have been no validator set changes
|
||||
since genesis. If there are validator set changes, you need to find the current
|
||||
set through some other method.
|
||||
|
||||
## Send transactions
|
||||
|
||||
Now we are ready to send some transactions. First Let's check the balance of
|
||||
the two accounts we setup earlier:
|
||||
|
||||
```shelldown[6]
|
||||
ME=$(basecli keys get cool | awk '{print $2}')
|
||||
YOU=$(basecli keys get friend | awk '{print $2}')
|
||||
basecli query account $ME
|
||||
basecli query account $YOU
|
||||
```
|
||||
|
||||
The first account is flush with cash, while the second account doesn't exist.
|
||||
Let's send funds from the first account to the second:
|
||||
|
||||
```shelldown[7]
|
||||
basecli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
```
|
||||
|
||||
Now if we check the second account, it should have `1000` 'mycoin' coins!
|
||||
|
||||
```shelldown[8]
|
||||
basecli query account $YOU
|
||||
```
|
||||
|
||||
We can send some of these coins back like so:
|
||||
|
||||
```shelldown[9]
|
||||
basecli tx send --name=friend --amount=500mycoin --to=$ME --sequence=1
|
||||
```
|
||||
|
||||
Note how we use the `--name` flag to select a different account to send from.
|
||||
|
||||
If we try to send too much, we'll get an error:
|
||||
|
||||
```shelldown[10]
|
||||
basecli tx send --name=friend --amount=500000mycoin --to=$ME --sequence=2
|
||||
```
|
||||
|
||||
Let's send another transaction:
|
||||
|
||||
```shelldown[11]
|
||||
basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2
|
||||
```
|
||||
|
||||
Note the `hash` value in the response - this is the hash of the transaction.
|
||||
We can query for the transaction by this hash:
|
||||
|
||||
```shelldown[12]
|
||||
basecli query tx <HASH>
|
||||
```
|
||||
|
||||
See `basecli tx send --help` for additional details.
|
||||
|
||||
## Proof
|
||||
|
||||
Even if you don't see it in the UI, the result of every query comes with a
|
||||
proof. This is a Merkle proof that the result of the query is actually
|
||||
contained in the state. And the state's Merkle root is contained in a recent
|
||||
block header. Behind the scenes, `countercli` will not only verify that this
|
||||
state matches the header, but also that the header is properly signed by the
|
||||
known validator set. It will even update the validator set as needed, so long
|
||||
as there have not been major changes and it is secure to do so. So, if you
|
||||
wonder why the query may take a second... there is a lot of work going on in
|
||||
the background to make sure even a lying full node can't trick your client.
|
||||
|
||||
In a latter [guide on InterBlockchain Communication](ibc.md), we'll use these
|
||||
proofs to post transactions to other chains.
|
||||
|
||||
## Accounts and Transactions
|
||||
|
||||
For a better understanding of how to further use the tools, it helps to
|
||||
understand the underlying data structures.
|
||||
|
||||
### Accounts
|
||||
|
||||
The Basecoin state consists entirely of a set of accounts. Each account
|
||||
contains a public key, a balance in many different coin denominations, and a
|
||||
strictly increasing sequence number for replay protection. This type of
|
||||
account was directly inspired by accounts in Ethereum, and is unlike Bitcoin's
|
||||
use of Unspent Transaction Outputs (UTXOs). Note Basecoin is a multi-asset
|
||||
cryptocurrency, so each account can have many different kinds of tokens.
|
||||
|
||||
```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"`
|
||||
}
|
||||
```
|
||||
|
||||
If you want to add more coins to a blockchain, you can do so manually in the
|
||||
`~/.basecoin/genesis.json` before you start the blockchain for the first time.
|
||||
|
||||
Accounts are serialized and stored in a Merkle tree under the key
|
||||
`base/a/<address>`, where `<address>` 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 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 Basecoin, the `Fee` is meant to be used by the validators to inform the
|
||||
ordering of transactions, like in Bitcoin. And the `Gas` is meant to be used
|
||||
by the application plugin to control its execution. There is currently no
|
||||
means to pass `Fee` information to the Tendermint validators, but it will come
|
||||
soon...
|
||||
|
||||
Note also that the `PubKey` only needs to be sent for `Sequence == 0`. After
|
||||
that, it is stored under the account in the Merkle tree and subsequent
|
||||
transactions can exclude it, using only the `Address` to refer to the sender.
|
||||
Ethereum does not require public keys to be sent in transactions as it uses a
|
||||
different elliptic curve scheme which enables the public key to be derived from
|
||||
the signature itself.
|
||||
|
||||
Finally, note that the use of multiple inputs and multiple outputs allows us to
|
||||
send many different types of tokens between many different accounts at once in
|
||||
an atomic transaction. Thus, the `SendTx` can serve as a basic unit of
|
||||
decentralized exchange. When using multiple inputs and outputs, you must make
|
||||
sure that the sum of coins of the inputs equals the sum of coins of the outputs
|
||||
(no creating money), and that all accounts that provide inputs have signed the
|
||||
transaction.
|
||||
|
||||
## Clean Up
|
||||
|
||||
**WARNING:** Running these commands will wipe out any existing information in both the `~/.basecli` and `~/.basecoin` directories, including private keys.
|
||||
|
||||
To remove all the files created and refresh your environment (e.g., if starting this tutorial again or trying something new), the following commands are run:
|
||||
|
||||
```shelldown[end-of-tutorials]
|
||||
basecli reset_all
|
||||
rm -rf ~/.basecoin
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this guide, we introduced the `basecoin` and `basecli` tools, demonstrated
|
||||
how to start a new basecoin blockchain and how to send tokens between accounts,
|
||||
and discussed the underlying data types for accounts and transactions,
|
||||
specifically the `Account` and the `SendTx`. In the [next
|
||||
guide](basecoin-plugins.md), we introduce the Basecoin plugin system, which
|
||||
uses a new transaction type, the `AppTx`, to extend the functionality of the
|
||||
Basecoin system with arbitrary logic.
|
|
@ -1,258 +0,0 @@
|
|||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinPlugins() {
|
||||
|
||||
#Initialization
|
||||
#shelldown[0][1]
|
||||
#shelldown[0][2]
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
#Making Keys
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][4])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][5])
|
||||
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
|
||||
|
||||
#shelldown[0][7] >/dev/null
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[0][9] >>/dev/null 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
|
||||
RES=$((echo y) | #shelldown[1][0] $1)
|
||||
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
|
||||
|
||||
#shelldown[1][2]
|
||||
assertTrue "Expected true for line $LINENO" $?
|
||||
RES=$((echo $KEYPASS) | #shelldown[1][3] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[2][0])
|
||||
assertTrue "Line $LINENO: Expected to contain Valid error, got $RES" \
|
||||
'[[ $RES == *"Counter Tx marked invalid"* ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[2][1] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
|
||||
RES=$(#shelldown[3][-1] | jq '.data.counter')
|
||||
assertTrue "Line $LINENO: Expected Counter of 1, got $RES" '[[ $RES == 1 ]]'
|
||||
|
||||
RES=$((echo $KEYPASS) | #shelldown[4][0] | jq '.deliver_tx.code')
|
||||
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
|
||||
RES=$(#shelldown[4][1])
|
||||
RESCOUNT=$(printf "$RES" | jq '.data.counter')
|
||||
RESFEE=$(printf "$RES" | jq '.data.total_fees[0].amount')
|
||||
assertTrue "Line $LINENO: Expected Counter of 2, got $RES" '[[ $RESCOUNT == 2 ]]'
|
||||
assertTrue "Line $LINENO: Expected TotalFees of 2, got $RES" '[[ $RESFEE == 2 ]]'
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
# Basecoin Plugins
|
||||
|
||||
In the [previous guide](basecoin-basics.md), we saw how to use the `basecoin`
|
||||
tool to start a blockchain and the `basecli` tools to send transactions. We
|
||||
also learned about `Account` and `SendTx`, the basic data types giving us a
|
||||
multi-asset cryptocurrency. Here, we will demonstrate how to extend the tools
|
||||
to use another transaction type, the `AppTx`, so we can send data to a custom
|
||||
plugin. In this example we explore a simple plugin named `counter`.
|
||||
|
||||
## Example Plugin
|
||||
|
||||
The design of the `basecoin` tool makes it easy to extend for custom
|
||||
functionality. The Counter plugin is bundled with basecoin, so if you have
|
||||
already [installed basecoin](install.md) and run `make install` then you should
|
||||
be able to run a full node with `counter` and the a light-client `countercli`
|
||||
from terminal. The Counter plugin is just like the `basecoin` tool. They
|
||||
both use the same library of commands, including one for signing and
|
||||
broadcasting `SendTx`.
|
||||
|
||||
Counter transactions take two custom inputs, a boolean argument named `valid`,
|
||||
and a coin amount named `countfee`. The transaction is only accepted if both
|
||||
`valid` is set to true and the transaction input coins is greater than
|
||||
`countfee` that the user provides.
|
||||
|
||||
A new blockchain can be initialized and started just like in the [previous
|
||||
guide](basecoin-basics.md):
|
||||
|
||||
```shelldown[0]
|
||||
# WARNING: this wipes out data - but counter is only for demos...
|
||||
rm -rf ~/.counter
|
||||
countercli reset_all
|
||||
|
||||
countercli keys new cool
|
||||
countercli keys new friend
|
||||
|
||||
counter init $(countercli keys get cool | awk '{print $2}')
|
||||
|
||||
counter start
|
||||
```
|
||||
|
||||
The default files are stored in `~/.counter`. In another window we can
|
||||
initialize the light-client and send a transaction:
|
||||
|
||||
```shelldown[1]
|
||||
countercli init --node=tcp://localhost:46657 --genesis=$HOME/.counter/genesis.json
|
||||
|
||||
YOU=$(countercli keys get friend | awk '{print $2}')
|
||||
countercli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
```
|
||||
|
||||
But the Counter has an additional command, `countercli tx counter`, which
|
||||
crafts an `AppTx` specifically for this plugin:
|
||||
|
||||
```shelldown[2]
|
||||
countercli tx counter --name cool
|
||||
countercli tx counter --name cool --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:
|
||||
|
||||
```shelldown[3]
|
||||
countercli query counter
|
||||
```
|
||||
|
||||
Tada! We can now see that our custom counter plugin transactions went through.
|
||||
You should see a Counter value of 1 representing the number of valid
|
||||
transactions. If we send another transaction, and then query again, we will
|
||||
see the value increment. Note that we need the sequence number here to send the
|
||||
coins (it didn't increment when we just pinged the counter)
|
||||
|
||||
```shelldown[4]
|
||||
countercli tx counter --name cool --countfee=2mycoin --sequence=2 --valid
|
||||
countercli query counter
|
||||
```
|
||||
|
||||
The Counter value should be 2, because we sent a second valid transaction.
|
||||
And this time, since we sent a countfee (which must be less than or equal to the
|
||||
total amount sent with the tx), it stores the `TotalFees` on the counter as well.
|
||||
|
||||
Keep it mind that, just like with `basecli`, the `countercli` verifies a proof
|
||||
that the query response is correct and up-to-date.
|
||||
|
||||
Now, before we implement our own plugin and tooling, it helps to understand the
|
||||
`AppTx` and the design of the plugin system.
|
||||
|
||||
## 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/counter`, and modify the code accordingly. Here, we will
|
||||
briefly describe the design and the changes to be made, but see the code for
|
||||
more details.
|
||||
|
||||
First is the `cmd/counter/main.go`, which drives the program. It can be left
|
||||
alone, but you should change any occurrences of `counter` to whatever your
|
||||
plugin tool is going to be called. You must also register your plugin(s) with
|
||||
the basecoin app with `RegisterStartPlugin`.
|
||||
|
||||
The light-client is located in `cmd/countercli/main.go` and allows for
|
||||
transaction and query commands. This file can also be left mostly alone besides replacing the application name and adding
|
||||
references to new plugin commands.
|
||||
|
||||
Next is the custom commands in `cmd/countercli/commands/`. These files are
|
||||
where we extend the tool with any new commands and flags we need to send
|
||||
transactions or queries to our plugin. You define custom `tx` and `query`
|
||||
subcommands, which are registered in `main.go` (avoiding `init()`
|
||||
auto-registration, for less magic and more control in the main executable).
|
||||
|
||||
Finally is `plugins/counter/counter.go`, where we provide an implementation of
|
||||
the `Plugin` interface. The most important part of the implementation is the
|
||||
`RunTx` method, which determines the meaning of the data sent along in the
|
||||
`AppTx`. In our example, we define a new transaction type, the `CounterTx`,
|
||||
which we expect to be encoded in the `AppTx.Data`, and thus to be decoded in
|
||||
the `RunTx` method, and used to update the plugin state.
|
||||
|
||||
For more examples and inspiration, see our [repository of example
|
||||
plugins](https://github.com/tendermint/basecoin-examples).
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this guide, we demonstrated how to create a new plugin and how to extend the
|
||||
`basecoin` tool to start a blockchain with the plugin enabled and send
|
||||
transactions to it. In the next guide, we introduce a [plugin for Inter
|
||||
Blockchain Communication](ibc.md), which allows us to publish proofs of the
|
||||
state of one blockchain to another, and thus to transfer tokens and data
|
||||
between them.
|
|
@ -1,249 +0,0 @@
|
|||
<!--- shelldown script template, see github.com/rigelrozanski/shelldown
|
||||
#!/bin/bash
|
||||
|
||||
testTutorial_BasecoinTool() {
|
||||
|
||||
rm -rf ~/.basecoin
|
||||
rm -rf ~/.basecli
|
||||
rm -rf example-data
|
||||
KEYPASS=qwertyuiop
|
||||
|
||||
(echo $KEYPASS; echo $KEYPASS) | #shelldown[0][0] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[0][1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[1][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[1][1] ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[1][2] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[2][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[2][1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[3][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[5][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER2=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER $PID_SERVER2 >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[4][-1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[6][0] ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[6][1] >>/dev/null 2>&1 &
|
||||
sleep 5 ; PID_SERVER2=$! ; disown ; assertTrue "Expected true for line $LINENO" $?
|
||||
kill -9 $PID_SERVER $PID_SERVER2 >/dev/null 2>&1 ; sleep 1
|
||||
|
||||
#shelldown[7][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[8][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
(echo $KEYPASS; echo $KEYPASS) | #shelldown[9][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[10][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
#shelldown[11][-1] >/dev/null ; assertTrue "Expected true for line $LINENO" $?
|
||||
|
||||
#cleanup
|
||||
rm -rf example-data
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
-->
|
||||
|
||||
# The Basecoin Tool
|
||||
|
||||
In previous tutorials we learned the [basics of the Basecoin
|
||||
CLI](/docs/guide/basecoin-basics.md) and [how to implement a
|
||||
plugin](/docs/guide/basecoin-plugins.md). In this tutorial, we provide more
|
||||
details on using the Basecoin tool.
|
||||
|
||||
# Generate a Key
|
||||
|
||||
Generate a key using the `basecli` tool:
|
||||
|
||||
```shelldown[0]
|
||||
basecli keys new mykey
|
||||
ME=$(basecli keys get mykey | awk '{print $2}')
|
||||
```
|
||||
|
||||
# Data Directory
|
||||
|
||||
By default, `basecoin` works out of `~/.basecoin`. To change this, set the
|
||||
`BCHOME` environment variable:
|
||||
|
||||
```shelldown[1]
|
||||
export BCHOME=~/.my_basecoin_data
|
||||
basecoin init $ME
|
||||
basecoin start
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shelldown[2]
|
||||
BCHOME=~/.my_basecoin_data basecoin init $ME
|
||||
BCHOME=~/.my_basecoin_data basecoin start
|
||||
```
|
||||
|
||||
# ABCI Server
|
||||
|
||||
So far we have run Basecoin and Tendermint in a single process. However, since
|
||||
we use ABCI, we can actually run them in different processes. First,
|
||||
initialize them:
|
||||
|
||||
```shelldown[3]
|
||||
basecoin init $ME
|
||||
```
|
||||
|
||||
This will create a single `genesis.json` file in `~/.basecoin` with the
|
||||
information for both Basecoin and Tendermint.
|
||||
|
||||
Now, In one window, run
|
||||
|
||||
```shelldown[4]
|
||||
basecoin start --without-tendermint
|
||||
```
|
||||
|
||||
and in another,
|
||||
|
||||
```shelldown[5]
|
||||
TMROOT=~/.basecoin tendermint node
|
||||
```
|
||||
|
||||
You should see Tendermint start making blocks!
|
||||
|
||||
Alternatively, you could ignore the Tendermint details in
|
||||
`~/.basecoin/genesis.json` and use a separate directory by running:
|
||||
|
||||
```shelldown[6]
|
||||
tendermint init
|
||||
tendermint node
|
||||
```
|
||||
|
||||
For more details on using `tendermint`, see [the guide](https://tendermint.com/docs/guides/using-tendermint).
|
||||
|
||||
# Keys and Genesis
|
||||
|
||||
In previous tutorials we used `basecoin init` to initialize `~/.basecoin` with
|
||||
the default configuration. This command creates files both for Tendermint and
|
||||
for Basecoin, and a single `genesis.json` file for both of them. For more
|
||||
information on these files, see the [guide to using
|
||||
Tendermint](https://tendermint.com/docs/guides/using-tendermint).
|
||||
|
||||
Now let's make our own custom Basecoin data.
|
||||
|
||||
First, create a new directory:
|
||||
|
||||
```shelldown[7]
|
||||
mkdir example-data
|
||||
```
|
||||
|
||||
We can tell `basecoin` to use this directory by exporting the `BCHOME`
|
||||
environment variable:
|
||||
|
||||
```shelldown[8]
|
||||
export BCHOME=$(pwd)/example-data
|
||||
```
|
||||
|
||||
If you're going to be using multiple terminal windows, make sure to add this
|
||||
variable to your shell startup scripts (eg. `~/.bashrc`).
|
||||
|
||||
Now, let's create a new key:
|
||||
|
||||
```shelldown[9]
|
||||
basecli keys new foobar
|
||||
```
|
||||
|
||||
The key's info can be retrieved with
|
||||
|
||||
```shelldown[10]
|
||||
basecli keys get foobar -o=json
|
||||
```
|
||||
|
||||
You should get output which looks similar to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foobar",
|
||||
"address": "404C5003A703C7DA888C96A2E901FCE65A6869D9",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Yours will look different - each key is randomly derived. Now we can make a
|
||||
`genesis.json` file and add an account with our public key:
|
||||
|
||||
```json
|
||||
{
|
||||
"app_hash": "",
|
||||
"chain_id": "example-chain",
|
||||
"genesis_time": "0001-01-01T00:00:00.000Z",
|
||||
"validators": [
|
||||
{
|
||||
"amount": 10,
|
||||
"name": "",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
|
||||
}
|
||||
}
|
||||
],
|
||||
"app_options": {
|
||||
"accounts": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "gold",
|
||||
"amount": 1000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here we've granted ourselves `1000000000` units of the `gold` token. Note that
|
||||
we've also set the `chain-id` to be `example-chain`. All transactions must
|
||||
therefore include the `--chain-id example-chain` in order to make sure they are
|
||||
valid for this chain. Previously, we didn't need this flag because we were
|
||||
using the default chain ID ("test_chain_id"). Now that we're using a custom
|
||||
chain, we need to specify the chain explicitly on the command line.
|
||||
|
||||
Note we have also left out the details of the Tendermint genesis. These are
|
||||
documented in the [Tendermint
|
||||
guide](https://tendermint.com/docs/guides/using-tendermint).
|
||||
|
||||
|
||||
# Reset
|
||||
|
||||
You can reset all blockchain data by running:
|
||||
|
||||
```shelldown[11]
|
||||
basecoin unsafe_reset_all
|
||||
```
|
||||
|
||||
Similarly, you can reset client data by running:
|
||||
|
||||
```shelldown[12]
|
||||
basecli reset_all
|
||||
```
|
||||
|
||||
# Genesis
|
||||
|
||||
Any required plugin initialization should be constructed using `SetOption` on
|
||||
genesis. When starting a new chain for the first time, `SetOption` will be
|
||||
called for each item the genesis file. Within genesis.json file entries are
|
||||
made in the format: `"<plugin>/<key>", "<value>"`, where `<plugin>` is the
|
||||
plugin 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.
|
||||
|
|
@ -1,398 +0,0 @@
|
|||
# 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. We implemented IBC as a basecoin plugin, and we'll show you how to
|
||||
use it to send tokens across blockchains!
|
||||
|
||||
Please note, this tutorial assumes you are familiar with [Basecoin
|
||||
plugins](/docs/guide/basecoin-plugins.md), but we'll explain how IBC works. You
|
||||
may also want to see [our repository of example
|
||||
plugins](https://github.com/tendermint/basecoin-examples).
|
||||
|
||||
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 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
|
||||
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:
|
||||
|
||||
```golang
|
||||
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:
|
||||
|
||||
```golang
|
||||
type IBCUpdateChainTx struct {
|
||||
Header tm.Header
|
||||
Commit tm.Commit
|
||||
}
|
||||
```
|
||||
|
||||
In the future, it needs to be updated to include changes to the validator set
|
||||
as well. Anyone can relay an `IBCUpdateChainTx`, and they only need to do so
|
||||
as frequently as packets are being sent or the validator set is changing.
|
||||
|
||||
### IBCPacketCreateTx
|
||||
|
||||
The `IBCPacketCreateTx` is used to create an outgoing packet on one chain. The
|
||||
packet itself contains the source and destination chain IDs, a sequence number
|
||||
(i.e. an integer that increments with every message sent between this pair of
|
||||
chains), a packet type (e.g. coin, data, etc.), and a payload.
|
||||
|
||||
```golang
|
||||
type IBCPacketCreateTx struct {
|
||||
Packet
|
||||
}
|
||||
|
||||
type Packet struct {
|
||||
SrcChainID string
|
||||
DstChainID string
|
||||
Sequence uint64
|
||||
Type string
|
||||
Payload []byte
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
type IBCPacketPostTx struct {
|
||||
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
|
||||
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet
|
||||
Proof *merkle.IAVLProof
|
||||
}
|
||||
```
|
||||
|
||||
The proof is a Merkle proof in an IAVL tree, our implementation of a balanced,
|
||||
Merklized binary search tree. It contains a list of nodes in the tree, which
|
||||
can be hashed together to get the Merkle root hash. This hash must match the
|
||||
`AppHash` contained in the header at `FromChainHeight + 1`
|
||||
|
||||
- note the `+ 1` is necessary since `FromChainHeight` is the height in which
|
||||
the packet was committed, and the resulting state root is not included until
|
||||
the next block.
|
||||
|
||||
### 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`.
|
||||
|
||||
## Relay
|
||||
|
||||
While we need all these packet types internally to keep track of all the proofs
|
||||
on both chains in a secure manner, for the normal work-flow, we can run a relay
|
||||
node that handles the cross-chain interaction.
|
||||
|
||||
In this case, there are only two steps. First `basecoin relay init`, which
|
||||
must be run once to register each chain with the other one, and make sure they
|
||||
are ready to send and recieve. And then `basecoin relay start`, which is a
|
||||
long-running process polling the queue on each side, and relaying all new
|
||||
message to the other block.
|
||||
|
||||
This requires that the relay has access to accounts with some funds on both
|
||||
chains to pay for all the ibc packets it will be forwarding.
|
||||
|
||||
## Try it out
|
||||
|
||||
Now that we have all the background knowledge, let's actually walk through the
|
||||
tutorial.
|
||||
|
||||
Make sure you have installed [basecoin and basecli](/docs/guide/install.md).
|
||||
|
||||
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.
|
||||
|
||||
If you have any trouble with this, you can also look at the [test
|
||||
scripts](/tests/cli/ibc.sh) or just run `make test_cli` in basecoin repo.
|
||||
Otherwise, open up 5 (yes 5!) terminal tabs....
|
||||
|
||||
### Preliminaries
|
||||
|
||||
```
|
||||
# first, clean up any old garbage for a fresh slate...
|
||||
rm -rf ~/.ibcdemo/
|
||||
```
|
||||
|
||||
Let's start by setting up some environment variables and aliases:
|
||||
|
||||
```
|
||||
export BCHOME1_CLIENT=~/.ibcdemo/chain1/client
|
||||
export BCHOME1_SERVER=~/.ibcdemo/chain1/server
|
||||
export BCHOME2_CLIENT=~/.ibcdemo/chain2/client
|
||||
export BCHOME2_SERVER=~/.ibcdemo/chain2/server
|
||||
alias basecli1="basecli --home $BCHOME1_CLIENT"
|
||||
alias basecli2="basecli --home $BCHOME2_CLIENT"
|
||||
alias basecoin1="basecoin --home $BCHOME1_SERVER"
|
||||
alias basecoin2="basecoin --home $BCHOME2_SERVER"
|
||||
```
|
||||
|
||||
This will give us some new commands to use instead of raw `basecli` and
|
||||
`basecoin` to ensure we're using the right configuration for the chain we want
|
||||
to talk to.
|
||||
|
||||
We also want to set some chain IDs:
|
||||
|
||||
```
|
||||
export CHAINID1="test-chain-1"
|
||||
export CHAINID2="test-chain-2"
|
||||
```
|
||||
|
||||
And since we will run two different chains on one machine, we need to maintain
|
||||
different sets of ports:
|
||||
|
||||
```
|
||||
export PORT_PREFIX1=1234
|
||||
export PORT_PREFIX2=2345
|
||||
export RPC_PORT1=${PORT_PREFIX1}7
|
||||
export RPC_PORT2=${PORT_PREFIX2}7
|
||||
```
|
||||
|
||||
### Setup Chain 1
|
||||
|
||||
Now, let's create some keys that we can use for accounts on test-chain-1:
|
||||
|
||||
```
|
||||
basecli1 keys new money
|
||||
basecli1 keys new gotnone
|
||||
export MONEY=$(basecli1 keys get money | awk '{print $2}')
|
||||
export GOTNONE=$(basecli1 keys get gotnone | awk '{print $2}')
|
||||
```
|
||||
|
||||
and create an initial configuration giving lots of coins to the $MONEY key:
|
||||
|
||||
```
|
||||
basecoin1 init --chain-id $CHAINID1 $MONEY
|
||||
```
|
||||
|
||||
Now start basecoin:
|
||||
|
||||
```
|
||||
sed -ie "s/4665/$PORT_PREFIX1/" $BCHOME1_SERVER/config.toml
|
||||
|
||||
basecoin1 start &> basecoin1.log &
|
||||
```
|
||||
|
||||
Note the `sed` command to replace the ports in the config file.
|
||||
You can follow the logs with `tail -f basecoin1.log`
|
||||
|
||||
Now we can attach the client to the chain and verify the state.
|
||||
The first account should have money, the second none:
|
||||
|
||||
```
|
||||
basecli1 init --node=tcp://localhost:${RPC_PORT1} --genesis=${BCHOME1_SERVER}/genesis.json
|
||||
basecli1 query account $MONEY
|
||||
basecli1 query account $GOTNONE
|
||||
```
|
||||
|
||||
### Setup Chain 2
|
||||
|
||||
This is the same as above, except with `basecli2`, `basecoin2`, and
|
||||
`$CHAINID2`. We will also need to change the ports, since we're running
|
||||
another chain on the same local machine.
|
||||
|
||||
Let's create new keys for test-chain-2:
|
||||
|
||||
```
|
||||
basecli2 keys new moremoney
|
||||
basecli2 keys new broke
|
||||
MOREMONEY=$(basecli2 keys get moremoney | awk '{print $2}')
|
||||
BROKE=$(basecli2 keys get broke | awk '{print $2}')
|
||||
```
|
||||
|
||||
And prepare the genesis block, and start the server:
|
||||
|
||||
```
|
||||
basecoin2 init --chain-id $CHAINID2 $(basecli2 keys get moremoney | awk '{print $2}')
|
||||
|
||||
sed -ie "s/4665/$PORT_PREFIX2/" $BCHOME2_SERVER/config.toml
|
||||
|
||||
basecoin2 start &> basecoin2.log &
|
||||
```
|
||||
|
||||
Now attach the client to the chain and verify the state.
|
||||
The first account should have money, the second none:
|
||||
|
||||
```
|
||||
basecli2 init --node=tcp://localhost:${RPC_PORT2} --genesis=${BCHOME2_SERVER}/genesis.json
|
||||
basecli2 query account $MOREMONEY
|
||||
basecli2 query account $BROKE
|
||||
```
|
||||
|
||||
### Connect these chains
|
||||
|
||||
OK! So we have two chains running on your local machine, with different keys on
|
||||
each. Let's hook them up together by starting a relay process to forward
|
||||
messages from one chain to the other.
|
||||
|
||||
The relay account needs some money in it to pay for the ibc messages, so for
|
||||
now, we have to transfer some cash from the rich accounts before we start the
|
||||
actual relay.
|
||||
|
||||
```
|
||||
# note that this key.json file is a hardcoded demo for all chains, this will
|
||||
# be updated in a future release
|
||||
RELAY_KEY=$BCHOME1_SERVER/key.json
|
||||
RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
||||
|
||||
basecli1 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR--name=money
|
||||
basecli1 query account $RELAY_ADDR
|
||||
|
||||
basecli2 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney
|
||||
basecli2 query account $RELAY_ADDR
|
||||
```
|
||||
|
||||
Now we can start the relay process.
|
||||
|
||||
```
|
||||
basecoin relay init --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--genesis1=${BCHOME1_SERVER}/genesis.json --genesis2=${BCHOME2_SERVER}/genesis.json \
|
||||
--from=$RELAY_KEY
|
||||
|
||||
basecoin relay start --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--from=$RELAY_KEY &> relay.log &
|
||||
```
|
||||
|
||||
This should start up the relay, and assuming no error messages came out,
|
||||
the two chains are now fully connected over IBC. Let's use this to send
|
||||
our first tx accross the chains...
|
||||
|
||||
### Sending cross-chain payments
|
||||
|
||||
The hard part is over, we set up two blockchains, a few private keys, and
|
||||
a secure relay between them. Now we can enjoy the fruits of our labor...
|
||||
|
||||
```
|
||||
# Here's an empty account on test-chain-2
|
||||
basecli2 query account $BROKE
|
||||
```
|
||||
|
||||
```
|
||||
# Let's send some funds from test-chain-1
|
||||
basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money
|
||||
```
|
||||
|
||||
```
|
||||
# give it time to arrive...
|
||||
sleep 2
|
||||
# now you should see 12345 coins!
|
||||
basecli2 query account $BROKE
|
||||
```
|
||||
|
||||
You're no longer broke! Cool, huh?
|
||||
Now have fun exploring and sending coins across the chains.
|
||||
And making more accounts as you want to.
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this tutorial we explained how IBC works, and demonstrated how to use it to
|
||||
communicate between two chains. We did the simplest communciation possible: a
|
||||
one way transfer of data from chain1 to chain2. The most important part was
|
||||
that we updated chain2 with the latest state (i.e. header and commit) of
|
||||
chain1, and then were able to post a proof to chain2 that a packet was
|
||||
committed to the outgoing state of chain1.
|
||||
|
||||
In 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!
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Install
|
||||
|
||||
If you aren't used to compile go programs and just want the released
|
||||
version of the code, please head to our [downloads](https://tendermint.com/download)
|
||||
page to get a pre-compiled binary for your platform.
|
||||
|
||||
Usually, Cosmos SDK can be installed like a normal Go program:
|
||||
|
||||
```
|
||||
go get -u github.com/cosmos/cosmos-sdk/cmd/...
|
||||
```
|
||||
|
||||
If the dependencies have been updated with breaking changes,
|
||||
or if another branch is required, `glide` is used for dependency management.
|
||||
Thus, assuming you've already run `go get` or otherwise cloned the repo,
|
||||
the correct way to install is:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||
git pull origin master
|
||||
make all
|
||||
```
|
||||
|
||||
This will create the `basecoin` binary in `$GOPATH/bin`.
|
||||
`make all` implies `make get_vendor_deps` and uses `glide` to install the
|
||||
correct version of all dependencies. It also tests the code, including
|
||||
some cli tests to make sure your binary behaves properly.
|
||||
|
||||
If you need another branch, make sure to run `git checkout <branch>`
|
||||
before `make all`. And if you switch branches a lot, especially
|
||||
touching other tendermint repos, you may need to `make fresh` sometimes
|
||||
so glide doesn't get confused with all the branches and versions lying around.
|
|
@ -1,184 +0,0 @@
|
|||
# Key Management
|
||||
|
||||
Here we explain a bit how to work with your keys, using the `basecli keys` subcommand.
|
||||
|
||||
**Note:** This keys tooling is not considered production ready and is for dev only.
|
||||
|
||||
We'll look at what you can do using the six sub-commands of `basecli keys`:
|
||||
|
||||
```
|
||||
new
|
||||
list
|
||||
get
|
||||
delete
|
||||
recover
|
||||
update
|
||||
```
|
||||
|
||||
## Create keys
|
||||
|
||||
`basecli keys new` has two inputs (name, password) and two outputs (address, seed).
|
||||
|
||||
First, we name our key:
|
||||
|
||||
```shelldown
|
||||
basecli keys new alice
|
||||
```
|
||||
|
||||
This will prompt (10 character minimum) password entry which must be re-typed.
|
||||
You'll see:
|
||||
|
||||
```
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
alice A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
**Important** write this seed phrase in a safe place.
|
||||
It is the only way to recover your account if you ever forget your password.
|
||||
|
||||
pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset
|
||||
```
|
||||
|
||||
which shows the address of your key named `alice`, and its recovery seed. We'll use these shortly.
|
||||
|
||||
Adding the `--output json` flag to the above command would give this output:
|
||||
|
||||
```
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
{
|
||||
"key": {
|
||||
"name": "alice",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
"seed": "pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset"
|
||||
}
|
||||
```
|
||||
|
||||
To avoid the prompt, it's possible to pipe the password into the command, e.g.:
|
||||
|
||||
```
|
||||
echo 1234567890 | basecli keys new fred --output json
|
||||
```
|
||||
|
||||
After trying each of the three ways to create a key, look at them, use:
|
||||
|
||||
```
|
||||
basecli keys list
|
||||
```
|
||||
|
||||
to list all the keys:
|
||||
|
||||
```
|
||||
All keys:
|
||||
alice 6FEA9C99E2565B44FCC3C539A293A1378CDA7609
|
||||
bob A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
charlie 784D623E0C15DE79043C126FA6449B68311339E5
|
||||
```
|
||||
|
||||
Again, we can use the `--output json` flag:
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name": "alice",
|
||||
"address": "6FEA9C99E2565B44FCC3C539A293A1378CDA7609",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "878B297F1E863CC30CAD71E04A8B3C23DB71C18F449F39E35B954EDB2276D32D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bob",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "2127CAAB96C08E3042C5B33C8B5A820079AAE8DD50642DCFCC1E8B74821B2BB9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "charlie",
|
||||
"address": "784D623E0C15DE79043C126FA6449B68311339E5",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
to get machine readable output.
|
||||
|
||||
If we want information about one specific key, then:
|
||||
|
||||
```
|
||||
basecli keys get charlie --output json
|
||||
```
|
||||
|
||||
will, for example, return the info for only the "charlie" key returned from the previous `basecoin keys list` command.
|
||||
|
||||
The keys tooling can support different types of keys with a flag:
|
||||
|
||||
```
|
||||
basecli keys new bit --type secp256k1
|
||||
```
|
||||
|
||||
and you'll see the difference in the `"type": field from `basecli keys get`
|
||||
|
||||
Before moving on, let's set an enviroment variable to make `--output json` the default.
|
||||
|
||||
Either run or put in your `~/.bash_profile` the following line:
|
||||
|
||||
```
|
||||
export BC_OUTPUT=json
|
||||
```
|
||||
|
||||
## Recover a key
|
||||
|
||||
Let's say, for whatever reason, you lose a key or forget the password. On creation, you were given a seed. We'll use it to recover a lost key.
|
||||
|
||||
First, let's simulate the loss by deleting a key:
|
||||
|
||||
```
|
||||
basecli keys delete alice
|
||||
```
|
||||
|
||||
which prompts for your current password, now rendered obsolete, and gives a warning message. The only way you can recover your key now is using the 12 word seed given on initial creation of the key. Let's try it:
|
||||
|
||||
```
|
||||
basecli keys recover alice-again
|
||||
```
|
||||
|
||||
which prompts for a new password then the seed:
|
||||
|
||||
```
|
||||
Enter the new passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
strike alien praise vendor term left market practice junior better deputy divert front calm
|
||||
alice-again CBF5D9CE6DDCC32806162979495D07B851C53451
|
||||
```
|
||||
|
||||
and voila! You've recovered your key. Note that the seed can be typed our, pasted in, or piped into the command alongside the password.
|
||||
|
||||
To change the password of a key, we can:
|
||||
|
||||
```
|
||||
basecli keys update alice-again
|
||||
```
|
||||
|
||||
and follow the prompts.
|
||||
|
||||
That covers most features of the keys sub command.
|
||||
|
||||
<!-- use later in a test script, or more advance tutorial?
|
||||
SEED=$(echo 1234567890 | basecli keys new fred -o json | jq .seed | tr -d \")
|
||||
echo $SEED
|
||||
(echo qwertyuiop; echo $SEED stamp) | basecli keys recover oops
|
||||
(echo qwertyuiop; echo $SEED) | basecli keys recover derf
|
||||
basecli keys get fred -o json
|
||||
basecli keys get derf -o json
|
||||
```
|
||||
-->
|
|
@ -1,269 +0,0 @@
|
|||
This guide uses the roles functionality provided by `basecli` to create a multi-sig wallet. It builds upon the basecoin basics and key management guides. You should have `basecoin` started with blocks streaming in, and three accounts: `rich, poor, igor` where `rich` was the account used on `basecoin init`, _and_ run `basecli init` with the appropriate flags. Review the intro guides for more information.
|
||||
|
||||
In this example, `rich` will create the role and send it some coins (i.e., fill the multi-sig wallet). Then, `poor` will prepare a transaction to withdraw coins, which will be approved by `igor`. Let's look at our keys:
|
||||
|
||||
```
|
||||
basecli keys list
|
||||
```
|
||||
|
||||
```
|
||||
All keys:
|
||||
igor 5E4CB7A4E729BA0A8B18DE99E21409B6D706D0F1
|
||||
poor 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
rich CB76F4092D1B13475272B36585EBD15D22A2848D
|
||||
```
|
||||
|
||||
Using the `basecli query account` command, you'll see that `rich` has plenty of coins:
|
||||
|
||||
```
|
||||
{
|
||||
"height": 81,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
whereas `poor` and `igor` have no coins (in fact, the chain doesn't know about them yet):
|
||||
|
||||
```
|
||||
ERROR: Account bytes are empty for address 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
```
|
||||
|
||||
## Create Role
|
||||
|
||||
This first step defines the parameters of a new role, which will have control of any coins sent to it, and only release them if correct conditions are met. In this example, we are going to make a 2/3 multi-sig wallet. Let's look a the command and dissect it below:
|
||||
|
||||
```
|
||||
basecli tx create-role --role=10CAFE4E --min-sigs=2 --members=5E4CB7A4E729BA0A8B18DE99E21409B6D706D0F1,65D406E028319289A0706E294F3B764F44EBA3CF,CB76F4092D1B13475272B36585EBD15D22A2848D --sequence=1 --name=rich
|
||||
```
|
||||
|
||||
In the first part we are sending a transaction that creates a role, rather than transfering coins. The `--role` flag is the name of the role (in hex only) and must be in double quotes. The `--min-sigs` and `--members` define your multi-sig parameters. Here, we require a minimum of 2 signatures out of 3 members but we could easily say 3 of 5 or 9 of 10, or whatever your application requires. The `--members` flag requires a comma-seperated list of addresses that will be signatories on the role. Then we set the `--sequence` number for the transaction, which will start at 1 and must be incremented by 1 for every transaction from an account. Finally, we use the name of the key/account that will be used to create the role, in this case the account `rich`.
|
||||
|
||||
Remember that `rich`'s address was used on `basecoin init` and is included in the `--members` list. The command above will prompt for a password (which can also be piped into the command if desired) then - if executed correctly - return some data:
|
||||
|
||||
```
|
||||
{
|
||||
"check_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"hash": "4849DA762E19CE599460B9882DD42C7F19655DC1",
|
||||
"height": 321
|
||||
}
|
||||
```
|
||||
showing the block height at which the transaction was committed and its hash. A quick review of what we did: 1) created a role, essentially an account, that requires a minimum of two (2) signatures from three (3) accounts (members). And since it was the account named `rich`'s first transaction, the sequence was set to 1.
|
||||
|
||||
Let's look at the balance of the role that we've created:
|
||||
|
||||
```
|
||||
basecli query account role:10CAFE4E
|
||||
```
|
||||
|
||||
and it should be empty:
|
||||
|
||||
```
|
||||
ERROR: Account bytes are empty for address role:10CAFE4E
|
||||
```
|
||||
|
||||
Next, we want to send coins _to_ that role. Notice that because this is the second transaction being sent by rich, we need to increase `--sequence` to `2`:
|
||||
|
||||
```
|
||||
basecli tx send --fee=90mycoin --amount=10000mycoin --to=role:10CAFE4E --sequence=2 --name=rich
|
||||
```
|
||||
|
||||
We need to pay a transaction fee to the validators, in this case 90 `mycoin` to send 10000 `mycoin` Notice that for the `--to` flag, to specify that we are sending to a role instead of an account, the `role:` prefix is added before the role. Because it's `rich`'s second transaction, we've incremented the sequence. The output will be nearly identical to the output from `create-role` above.
|
||||
|
||||
Now the role has coins (think of it like a bank).
|
||||
|
||||
Double check with:
|
||||
|
||||
```
|
||||
basecli query account role:10CAFE4E
|
||||
```
|
||||
|
||||
and this time you'll see the coins in the role's account:
|
||||
|
||||
```
|
||||
{
|
||||
"height": 2453,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 10000
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Poor` decides to initiate a multi-sig transaction to himself from the role's account. First, it must be prepared like so:
|
||||
|
||||
```
|
||||
basecli tx send --amount=6000mycoin --from=role:10CAFE4E --to=65D406E028319289A0706E294F3B764F44EBA3CF --sequence=1 --assume-role=10CAFE4E --name=poor --multi --prepare=tx.json
|
||||
```
|
||||
|
||||
you'll be prompted for `poor`'s password and there won't be any `stdout` to the terminal. Note that the address in the `--to` flag matches the address of `poor`'s account from the beginning of the tutorial. The main output is the `tx.json` file that has just been created. In the above command, the `--assume-role` flag is used to evaluate account permissions on the transaction, while the `--multi` flag is used in combination with `--prepare`, to specify the file that is prepared for a multi-sig transaction.
|
||||
|
||||
The `tx.json` file will look like this:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "sigs/multi",
|
||||
"data": {
|
||||
"tx": {
|
||||
"type": "chain/tx",
|
||||
"data": {
|
||||
"chain_id": "test_chain_id",
|
||||
"expires_at": 0,
|
||||
"tx": {
|
||||
"type": "nonce",
|
||||
"data": {
|
||||
"sequence": 1,
|
||||
"signers": [
|
||||
{
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "65D406E028319289A0706E294F3B764F44EBA3CF"
|
||||
}
|
||||
],
|
||||
"tx": {
|
||||
"type": "role/assume",
|
||||
"data": {
|
||||
"role": "10CAFE4E",
|
||||
"tx": {
|
||||
"type": "coin/send",
|
||||
"data": {
|
||||
"inputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "role",
|
||||
"addr": "10CAFE4E"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 6000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "65D406E028319289A0706E294F3B764F44EBA3CF"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 6000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"Sig": {
|
||||
"type": "ed25519",
|
||||
"data": "A38F73BF2D109015E4B0B6782C84875292D5FAA75F0E3362C9BD29B16CB15D57FDF0553205E7A33C740319397A434B7C31CBB10BE7F8270C9984C5567D2DC002"
|
||||
},
|
||||
"Pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "6ED38C7453148DD90DFC41D9339CE45BEFA5EB505FD7E93D85E71DFFDAFD9B8F"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and it is loaded by the next command.
|
||||
|
||||
With the transaction prepared, but not sent, we'll have `igor` sign and send the prepared transaction:
|
||||
|
||||
```
|
||||
basecli tx --in=tx.json --name=igor
|
||||
```
|
||||
|
||||
which will give output similar to:
|
||||
|
||||
```
|
||||
{
|
||||
"check_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"hash": "E345BDDED9517EB2CAAF5E30AFF3AB38A1172833",
|
||||
"height": 2673
|
||||
}
|
||||
```
|
||||
|
||||
and voila! That's the basics for creating roles and sending multi-sig transactions. For 3 of 3, you'd add an intermediate transactions like:
|
||||
|
||||
```
|
||||
basecli tx --in=tx.json --name=igor --prepare=tx2.json
|
||||
```
|
||||
|
||||
before having rich sign and send the transaction. The `--prepare` flag writes files to disk rather than sending the transaction and can be used to chain together multiple transactions.
|
||||
|
||||
We can check the balance of the role:
|
||||
|
||||
```
|
||||
basecli query account role:10CAFE4E
|
||||
```
|
||||
|
||||
and get the result:
|
||||
|
||||
```
|
||||
{
|
||||
"height": 2683,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 4000
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and see that `poor` now has 6000 `mycoin`:
|
||||
|
||||
```
|
||||
basecli query account 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
```
|
||||
|
||||
to confirm that everything worked as expected.
|
|
@ -0,0 +1,425 @@
|
|||
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. We implemented IBC as a
|
||||
basecoin plugin, and we'll show you how to use it to send tokens across
|
||||
blockchains!
|
||||
|
||||
Please note, this tutorial assumes you are familiar with `Basecoin
|
||||
plugins </docs/guide/basecoin-plugins.md>`__, but we'll explain how IBC
|
||||
works. You may also want to see `our repository of example
|
||||
plugins <https://github.com/tendermint/basecoin-examples>`__.
|
||||
|
||||
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 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 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:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
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:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
type IBCUpdateChainTx struct {
|
||||
Header tm.Header
|
||||
Commit tm.Commit
|
||||
}
|
||||
|
||||
In the future, it needs to be updated to include changes to the
|
||||
validator set as well. Anyone can relay an ``IBCUpdateChainTx``, and
|
||||
they only need to do so as frequently as packets are being sent or the
|
||||
validator set is changing.
|
||||
|
||||
IBCPacketCreateTx
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``IBCPacketCreateTx`` is used to create an outgoing packet on one
|
||||
chain. The packet itself contains the source and destination chain IDs,
|
||||
a sequence number (i.e. an integer that increments with every message
|
||||
sent between this pair of chains), a packet type (e.g. coin, data,
|
||||
etc.), and a payload.
|
||||
|
||||
.. code:: golang
|
||||
|
||||
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:
|
||||
|
||||
.. code:: golang
|
||||
|
||||
type IBCPacketPostTx struct {
|
||||
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
|
||||
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet
|
||||
Proof *merkle.IAVLProof
|
||||
}
|
||||
|
||||
The proof is a Merkle proof in an IAVL tree, our implementation of a
|
||||
balanced, Merklized binary search tree. It contains a list of nodes in
|
||||
the tree, which can be hashed together to get the Merkle root hash. This
|
||||
hash must match the ``AppHash`` contained in the header at
|
||||
``FromChainHeight + 1``
|
||||
|
||||
- note the ``+ 1`` is necessary since ``FromChainHeight`` is the height
|
||||
in which the packet was committed, and the resulting state root is
|
||||
not included until the next block.
|
||||
|
||||
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``.
|
||||
|
||||
Relay
|
||||
-----
|
||||
|
||||
While we need all these packet types internally to keep track of all the
|
||||
proofs on both chains in a secure manner, for the normal work-flow, we
|
||||
can run a relay node that handles the cross-chain interaction.
|
||||
|
||||
In this case, there are only two steps. First ``basecoin relay init``,
|
||||
which must be run once to register each chain with the other one, and
|
||||
make sure they are ready to send and recieve. And then
|
||||
``basecoin relay start``, which is a long-running process polling the
|
||||
queue on each side, and relaying all new message to the other block.
|
||||
|
||||
This requires that the relay has access to accounts with some funds on
|
||||
both chains to pay for all the ibc packets it will be forwarding.
|
||||
|
||||
Try it out
|
||||
----------
|
||||
|
||||
Now that we have all the background knowledge, let's actually walk
|
||||
through the tutorial.
|
||||
|
||||
Make sure you have installed `basecoin and
|
||||
basecli </docs/guide/install.md>`__.
|
||||
|
||||
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.
|
||||
|
||||
If you have any trouble with this, you can also look at the `test
|
||||
scripts </tests/cli/ibc.sh>`__ or just run ``make test_cli`` in basecoin
|
||||
repo. Otherwise, open up 5 (yes 5!) terminal tabs....
|
||||
|
||||
Preliminaries
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
# first, clean up any old garbage for a fresh slate...
|
||||
rm -rf ~/.ibcdemo/
|
||||
|
||||
Let's start by setting up some environment variables and aliases:
|
||||
|
||||
::
|
||||
|
||||
export BCHOME1_CLIENT=~/.ibcdemo/chain1/client
|
||||
export BCHOME1_SERVER=~/.ibcdemo/chain1/server
|
||||
export BCHOME2_CLIENT=~/.ibcdemo/chain2/client
|
||||
export BCHOME2_SERVER=~/.ibcdemo/chain2/server
|
||||
alias basecli1="basecli --home $BCHOME1_CLIENT"
|
||||
alias basecli2="basecli --home $BCHOME2_CLIENT"
|
||||
alias basecoin1="basecoin --home $BCHOME1_SERVER"
|
||||
alias basecoin2="basecoin --home $BCHOME2_SERVER"
|
||||
|
||||
This will give us some new commands to use instead of raw ``basecli``
|
||||
and ``basecoin`` to ensure we're using the right configuration for the
|
||||
chain we want to talk to.
|
||||
|
||||
We also want to set some chain IDs:
|
||||
|
||||
::
|
||||
|
||||
export CHAINID1="test-chain-1"
|
||||
export CHAINID2="test-chain-2"
|
||||
|
||||
And since we will run two different chains on one machine, we need to
|
||||
maintain different sets of ports:
|
||||
|
||||
::
|
||||
|
||||
export PORT_PREFIX1=1234
|
||||
export PORT_PREFIX2=2345
|
||||
export RPC_PORT1=${PORT_PREFIX1}7
|
||||
export RPC_PORT2=${PORT_PREFIX2}7
|
||||
|
||||
Setup Chain 1
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Now, let's create some keys that we can use for accounts on
|
||||
test-chain-1:
|
||||
|
||||
::
|
||||
|
||||
basecli1 keys new money
|
||||
basecli1 keys new gotnone
|
||||
export MONEY=$(basecli1 keys get money | awk '{print $2}')
|
||||
export GOTNONE=$(basecli1 keys get gotnone | awk '{print $2}')
|
||||
|
||||
and create an initial configuration giving lots of coins to the $MONEY
|
||||
key:
|
||||
|
||||
::
|
||||
|
||||
basecoin1 init --chain-id $CHAINID1 $MONEY
|
||||
|
||||
Now start basecoin:
|
||||
|
||||
::
|
||||
|
||||
sed -ie "s/4665/$PORT_PREFIX1/" $BCHOME1_SERVER/config.toml
|
||||
|
||||
basecoin1 start &> basecoin1.log &
|
||||
|
||||
Note the ``sed`` command to replace the ports in the config file. You
|
||||
can follow the logs with ``tail -f basecoin1.log``
|
||||
|
||||
Now we can attach the client to the chain and verify the state. The
|
||||
first account should have money, the second none:
|
||||
|
||||
::
|
||||
|
||||
basecli1 init --node=tcp://localhost:${RPC_PORT1} --genesis=${BCHOME1_SERVER}/genesis.json
|
||||
basecli1 query account $MONEY
|
||||
basecli1 query account $GOTNONE
|
||||
|
||||
Setup Chain 2
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This is the same as above, except with ``basecli2``, ``basecoin2``, and
|
||||
``$CHAINID2``. We will also need to change the ports, since we're
|
||||
running another chain on the same local machine.
|
||||
|
||||
Let's create new keys for test-chain-2:
|
||||
|
||||
::
|
||||
|
||||
basecli2 keys new moremoney
|
||||
basecli2 keys new broke
|
||||
MOREMONEY=$(basecli2 keys get moremoney | awk '{print $2}')
|
||||
BROKE=$(basecli2 keys get broke | awk '{print $2}')
|
||||
|
||||
And prepare the genesis block, and start the server:
|
||||
|
||||
::
|
||||
|
||||
basecoin2 init --chain-id $CHAINID2 $(basecli2 keys get moremoney | awk '{print $2}')
|
||||
|
||||
sed -ie "s/4665/$PORT_PREFIX2/" $BCHOME2_SERVER/config.toml
|
||||
|
||||
basecoin2 start &> basecoin2.log &
|
||||
|
||||
Now attach the client to the chain and verify the state. The first
|
||||
account should have money, the second none:
|
||||
|
||||
::
|
||||
|
||||
basecli2 init --node=tcp://localhost:${RPC_PORT2} --genesis=${BCHOME2_SERVER}/genesis.json
|
||||
basecli2 query account $MOREMONEY
|
||||
basecli2 query account $BROKE
|
||||
|
||||
Connect these chains
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OK! So we have two chains running on your local machine, with different
|
||||
keys on each. Let's hook them up together by starting a relay process to
|
||||
forward messages from one chain to the other.
|
||||
|
||||
The relay account needs some money in it to pay for the ibc messages, so
|
||||
for now, we have to transfer some cash from the rich accounts before we
|
||||
start the actual relay.
|
||||
|
||||
::
|
||||
|
||||
# note that this key.json file is a hardcoded demo for all chains, this will
|
||||
# be updated in a future release
|
||||
RELAY_KEY=$BCHOME1_SERVER/key.json
|
||||
RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
||||
|
||||
basecli1 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR--name=money
|
||||
basecli1 query account $RELAY_ADDR
|
||||
|
||||
basecli2 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney
|
||||
basecli2 query account $RELAY_ADDR
|
||||
|
||||
Now we can start the relay process.
|
||||
|
||||
::
|
||||
|
||||
basecoin relay init --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--genesis1=${BCHOME1_SERVER}/genesis.json --genesis2=${BCHOME2_SERVER}/genesis.json \
|
||||
--from=$RELAY_KEY
|
||||
|
||||
basecoin relay start --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--from=$RELAY_KEY &> relay.log &
|
||||
|
||||
This should start up the relay, and assuming no error messages came out,
|
||||
the two chains are now fully connected over IBC. Let's use this to send
|
||||
our first tx accross the chains...
|
||||
|
||||
Sending cross-chain payments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The hard part is over, we set up two blockchains, a few private keys,
|
||||
and a secure relay between them. Now we can enjoy the fruits of our
|
||||
labor...
|
||||
|
||||
::
|
||||
|
||||
# Here's an empty account on test-chain-2
|
||||
basecli2 query account $BROKE
|
||||
|
||||
::
|
||||
|
||||
# Let's send some funds from test-chain-1
|
||||
basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money
|
||||
|
||||
::
|
||||
|
||||
# give it time to arrive...
|
||||
sleep 2
|
||||
# now you should see 12345 coins!
|
||||
basecli2 query account $BROKE
|
||||
|
||||
You're no longer broke! Cool, huh? Now have fun exploring and sending
|
||||
coins across the chains. And making more accounts as you want to.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In this tutorial we explained how IBC works, and demonstrated how to use
|
||||
it to communicate between two chains. We did the simplest communciation
|
||||
possible: a one way transfer of data from chain1 to chain2. The most
|
||||
important part was that we updated chain2 with the latest state (i.e.
|
||||
header and commit) of chain1, and then were able to post a proof to
|
||||
chain2 that a packet was committed to the outgoing state of chain1.
|
||||
|
||||
In 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!
|
|
@ -0,0 +1,35 @@
|
|||
Install
|
||||
=======
|
||||
|
||||
If you aren't used to compile go programs and just want the released
|
||||
version of the code, please head to our
|
||||
`downloads <https://tendermint.com/download>`__ page to get a
|
||||
pre-compiled binary for your platform.
|
||||
|
||||
Usually, Cosmos SDK can be installed like a normal Go program:
|
||||
|
||||
::
|
||||
|
||||
go get -u github.com/cosmos/cosmos-sdk/cmd/...
|
||||
|
||||
If the dependencies have been updated with breaking changes, or if
|
||||
another branch is required, ``glide`` is used for dependency management.
|
||||
Thus, assuming you've already run ``go get`` or otherwise cloned the
|
||||
repo, the correct way to install is:
|
||||
|
||||
::
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||
git pull origin master
|
||||
make all
|
||||
|
||||
This will create the ``basecoin`` binary in ``$GOPATH/bin``.
|
||||
``make all`` implies ``make get_vendor_deps`` and uses ``glide`` to
|
||||
install the correct version of all dependencies. It also tests the code,
|
||||
including some cli tests to make sure your binary behaves properly.
|
||||
|
||||
If you need another branch, make sure to run ``git checkout <branch>``
|
||||
before ``make all``. And if you switch branches a lot, especially
|
||||
touching other tendermint repos, you may need to ``make fresh``
|
||||
sometimes so glide doesn't get confused with all the branches and
|
||||
versions lying around.
|
|
@ -0,0 +1,204 @@
|
|||
Key Management
|
||||
==============
|
||||
|
||||
Here we explain a bit how to work with your keys, using the
|
||||
``basecli keys`` subcommand.
|
||||
|
||||
**Note:** This keys tooling is not considered production ready and is
|
||||
for dev only.
|
||||
|
||||
We'll look at what you can do using the six sub-commands of
|
||||
``basecli keys``:
|
||||
|
||||
::
|
||||
|
||||
new
|
||||
list
|
||||
get
|
||||
delete
|
||||
recover
|
||||
update
|
||||
|
||||
Create keys
|
||||
-----------
|
||||
|
||||
``basecli keys new`` has two inputs (name, password) and two outputs
|
||||
(address, seed).
|
||||
|
||||
First, we name our key:
|
||||
|
||||
.. code:: shelldown
|
||||
|
||||
basecli keys new alice
|
||||
|
||||
This will prompt (10 character minimum) password entry which must be
|
||||
re-typed. You'll see:
|
||||
|
||||
::
|
||||
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
alice A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
**Important** write this seed phrase in a safe place.
|
||||
It is the only way to recover your account if you ever forget your password.
|
||||
|
||||
pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset
|
||||
|
||||
which shows the address of your key named ``alice``, and its recovery
|
||||
seed. We'll use these shortly.
|
||||
|
||||
Adding the ``--output json`` flag to the above command would give this
|
||||
output:
|
||||
|
||||
::
|
||||
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
{
|
||||
"key": {
|
||||
"name": "alice",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
"seed": "pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset"
|
||||
}
|
||||
|
||||
To avoid the prompt, it's possible to pipe the password into the
|
||||
command, e.g.:
|
||||
|
||||
::
|
||||
|
||||
echo 1234567890 | basecli keys new fred --output json
|
||||
|
||||
After trying each of the three ways to create a key, look at them, use:
|
||||
|
||||
::
|
||||
|
||||
basecli keys list
|
||||
|
||||
to list all the keys:
|
||||
|
||||
::
|
||||
|
||||
All keys:
|
||||
alice 6FEA9C99E2565B44FCC3C539A293A1378CDA7609
|
||||
bob A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
charlie 784D623E0C15DE79043C126FA6449B68311339E5
|
||||
|
||||
Again, we can use the ``--output json`` flag:
|
||||
|
||||
::
|
||||
|
||||
[
|
||||
{
|
||||
"name": "alice",
|
||||
"address": "6FEA9C99E2565B44FCC3C539A293A1378CDA7609",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "878B297F1E863CC30CAD71E04A8B3C23DB71C18F449F39E35B954EDB2276D32D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bob",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "2127CAAB96C08E3042C5B33C8B5A820079AAE8DD50642DCFCC1E8B74821B2BB9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "charlie",
|
||||
"address": "784D623E0C15DE79043C126FA6449B68311339E5",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
to get machine readable output.
|
||||
|
||||
If we want information about one specific key, then:
|
||||
|
||||
::
|
||||
|
||||
basecli keys get charlie --output json
|
||||
|
||||
will, for example, return the info for only the "charlie" key returned
|
||||
from the previous ``basecoin keys list`` command.
|
||||
|
||||
The keys tooling can support different types of keys with a flag:
|
||||
|
||||
::
|
||||
|
||||
basecli keys new bit --type secp256k1
|
||||
|
||||
and you'll see the difference in the ``"type": field from``\ basecli
|
||||
keys get\`
|
||||
|
||||
Before moving on, let's set an enviroment variable to make
|
||||
``--output json`` the default.
|
||||
|
||||
Either run or put in your ``~/.bash_profile`` the following line:
|
||||
|
||||
::
|
||||
|
||||
export BC_OUTPUT=json
|
||||
|
||||
Recover a key
|
||||
-------------
|
||||
|
||||
Let's say, for whatever reason, you lose a key or forget the password.
|
||||
On creation, you were given a seed. We'll use it to recover a lost key.
|
||||
|
||||
First, let's simulate the loss by deleting a key:
|
||||
|
||||
::
|
||||
|
||||
basecli keys delete alice
|
||||
|
||||
which prompts for your current password, now rendered obsolete, and
|
||||
gives a warning message. The only way you can recover your key now is
|
||||
using the 12 word seed given on initial creation of the key. Let's try
|
||||
it:
|
||||
|
||||
::
|
||||
|
||||
basecli keys recover alice-again
|
||||
|
||||
which prompts for a new password then the seed:
|
||||
|
||||
::
|
||||
|
||||
Enter the new passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
strike alien praise vendor term left market practice junior better deputy divert front calm
|
||||
alice-again CBF5D9CE6DDCC32806162979495D07B851C53451
|
||||
|
||||
and voila! You've recovered your key. Note that the seed can be typed
|
||||
our, pasted in, or piped into the command alongside the password.
|
||||
|
||||
To change the password of a key, we can:
|
||||
|
||||
::
|
||||
|
||||
basecli keys update alice-again
|
||||
|
||||
and follow the prompts.
|
||||
|
||||
That covers most features of the keys sub command.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- use later in a test script, or more advance tutorial?
|
||||
SEED=$(echo 1234567890 | basecli keys new fred -o json | jq .seed | tr -d \")
|
||||
echo $SEED
|
||||
(echo qwertyuiop; echo $SEED stamp) | basecli keys recover oops
|
||||
(echo qwertyuiop; echo $SEED) | basecli keys recover derf
|
||||
basecli keys get fred -o json
|
||||
basecli keys get derf -o json
|
||||
```
|
||||
-->
|
103
docs/overview.md
103
docs/overview.md
|
@ -1,103 +0,0 @@
|
|||
# Quark Overview
|
||||
|
||||
The quark middleware design optimizes flexibility and security. The framework
|
||||
is designed around a modular execution stack which allows applications to mix
|
||||
and match modular elements as desired. Along side, all modules are permissioned
|
||||
and sandboxed to isolate modules for greater application security.
|
||||
|
||||
For more explanation please see the [standard
|
||||
library](stdlib.md)
|
||||
and
|
||||
[glossary](glossary.md)
|
||||
documentation.
|
||||
|
||||
For a more interconnected schematics see these
|
||||
[framework](graphics/overview-framework.png)
|
||||
and
|
||||
[security](graphics/overview-security.png)
|
||||
overviews.
|
||||
|
||||
## Framework Overview
|
||||
|
||||
### Transactions (tx)
|
||||
|
||||
Each transaction passes through the middleware stack which can be defined
|
||||
uniquely by each application. From the multiple layers of transaction, each
|
||||
middleware may strip off one level, like an onion. As such, the transaction
|
||||
must be constructed to mirror the execution stack, and each middleware module
|
||||
should allow an arbitrary transaction to be embedded for the next layer in
|
||||
the stack.
|
||||
|
||||
<img src="graphics/tx.png" width=250>
|
||||
|
||||
### Execution Stack
|
||||
|
||||
Middleware components allow for code reusability and integrability. A standard
|
||||
set of middleware are provided and can be mix-and-matched with custom
|
||||
middleware. Some of the [standard library](stdlib.md)
|
||||
middlewares provided in this package include:
|
||||
- Logging
|
||||
- Recovery
|
||||
- Signatures
|
||||
- Chain
|
||||
- Nonce
|
||||
- Fees
|
||||
- Roles
|
||||
- Inter-Blockchain-Communication (IBC)
|
||||
|
||||
As a part of stack execution the state space provided to each middleware is
|
||||
isolated (see [Data Store](overview.md#data-store)). When executing the stack,
|
||||
state-recovery checkpoints can be assigned for stack execution of `CheckTx`
|
||||
or `DeliverTx`. This means, that all state changes will be reverted to the
|
||||
checkpoint state on failure when either being run as a part of `CheckTx`
|
||||
or `DeliverTx`. Example usage of the checkpoints is when we may want to deduct
|
||||
a fee even if the end business logic fails; under this situation we would add
|
||||
the `DeliverTx` checkpoint after the fee middleware but before the business
|
||||
logic. This diagram displays a typical process flow through an execution stack.
|
||||
|
||||
<img src="graphics/middleware.png" width=500>
|
||||
|
||||
### Dispatcher
|
||||
|
||||
The dispatcher handler aims to allow for reusable business logic. As a
|
||||
transaction is passed to the end handler, the dispatcher routes the logic to
|
||||
the correct module. To use the dispatcher tool, all transaction types must
|
||||
first be registered with the dispatcher. Once registered the middleware stack
|
||||
or any other handler can call the dispatcher to execute a transaction.
|
||||
Similarly to the execution stack, when executing a transaction the dispatcher
|
||||
isolates the state space available to the designated module (see [Data
|
||||
Store](overview.md#data-store)).
|
||||
|
||||
<img src="graphics/dispatcher.png" width=600>
|
||||
|
||||
## Security Overview
|
||||
|
||||
### Permission
|
||||
|
||||
Each application is run in a sandbox to isolate security risks. When
|
||||
interfacing between applications, if one of those applications is compromised
|
||||
the entire network should still be secure. This is achieved through actor
|
||||
permissioning whereby each chain, account, or application can provided a
|
||||
designated permission for the transaction context to perform a specific action.
|
||||
|
||||
Context is passed through the middleware and dispatcher, allowing one to add
|
||||
permissions on this app-space, and check current permissions.
|
||||
|
||||
<img src="graphics/permission.png" width=500>
|
||||
|
||||
### Data Store
|
||||
|
||||
The entire merkle tree can access all data. When we call a module (or
|
||||
middleware), we give them access to a subtree corresponding to their app. This
|
||||
is achieved through the use of unique prefix assigned to each module. From the
|
||||
module's perspective it is no different, the module need-not have regard for
|
||||
the prefix as it is assigned outside of the modules scope. For example, if a
|
||||
module named `foo` wanted to write to the store it could save records under the
|
||||
key `bar`, however, the dispatcher would register that record in the persistent
|
||||
state under `foo/bar`. Next time the `foo` app was called that record would be
|
||||
accessible to it under the assigned key `bar`. This effectively makes app
|
||||
prefixing invisible to each module while preventing each module from affecting
|
||||
each other module. Under this model no two registered modules are permitted to
|
||||
have the same namespace.
|
||||
|
||||
<img src="graphics/datastore.png" width=500>
|
|
@ -0,0 +1,97 @@
|
|||
Quark Overview
|
||||
==============
|
||||
|
||||
The quark middleware design optimizes flexibility and security. The
|
||||
framework is designed around a modular execution stack which allows
|
||||
applications to mix and match modular elements as desired. Along side,
|
||||
all modules are permissioned and sandboxed to isolate modules for
|
||||
greater application security.
|
||||
|
||||
For more explanation please see the `standard library <stdlib.md>`__ and
|
||||
`glossary <glossary.md>`__ documentation.
|
||||
|
||||
For a more interconnected schematics see these
|
||||
`framework <graphics/overview-framework.png>`__ and
|
||||
`security <graphics/overview-security.png>`__ overviews.
|
||||
|
||||
Framework Overview
|
||||
------------------
|
||||
|
||||
Transactions (tx)
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Each transaction passes through the middleware stack which can be
|
||||
defined uniquely by each application. From the multiple layers of
|
||||
transaction, each middleware may strip off one level, like an onion. As
|
||||
such, the transaction must be constructed to mirror the execution stack,
|
||||
and each middleware module should allow an arbitrary transaction to be
|
||||
embedded for the next layer in the stack.
|
||||
|
||||
Execution Stack
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Middleware components allow for code reusability and integrability. A
|
||||
standard set of middleware are provided and can be mix-and-matched with
|
||||
custom middleware. Some of the `standard library <stdlib.md>`__
|
||||
middlewares provided in this package include: - Logging - Recovery -
|
||||
Signatures - Chain - Nonce - Fees - Roles -
|
||||
Inter-Blockchain-Communication (IBC)
|
||||
|
||||
As a part of stack execution the state space provided to each middleware
|
||||
is isolated (see `Data Store <overview.md#data-store>`__). When
|
||||
executing the stack, state-recovery checkpoints can be assigned for
|
||||
stack execution of ``CheckTx`` or ``DeliverTx``. This means, that all
|
||||
state changes will be reverted to the checkpoint state on failure when
|
||||
either being run as a part of ``CheckTx`` or ``DeliverTx``. Example
|
||||
usage of the checkpoints is when we may want to deduct a fee even if the
|
||||
end business logic fails; under this situation we would add the
|
||||
``DeliverTx`` checkpoint after the fee middleware but before the
|
||||
business logic. This diagram displays a typical process flow through an
|
||||
execution stack.
|
||||
|
||||
Dispatcher
|
||||
~~~~~~~~~~
|
||||
|
||||
The dispatcher handler aims to allow for reusable business logic. As a
|
||||
transaction is passed to the end handler, the dispatcher routes the
|
||||
logic to the correct module. To use the dispatcher tool, all transaction
|
||||
types must first be registered with the dispatcher. Once registered the
|
||||
middleware stack or any other handler can call the dispatcher to execute
|
||||
a transaction. Similarly to the execution stack, when executing a
|
||||
transaction the dispatcher isolates the state space available to the
|
||||
designated module (see `Data Store <overview.md#data-store>`__).
|
||||
|
||||
Security Overview
|
||||
-----------------
|
||||
|
||||
Permission
|
||||
~~~~~~~~~~
|
||||
|
||||
Each application is run in a sandbox to isolate security risks. When
|
||||
interfacing between applications, if one of those applications is
|
||||
compromised the entire network should still be secure. This is achieved
|
||||
through actor permissioning whereby each chain, account, or application
|
||||
can provided a designated permission for the transaction context to
|
||||
perform a specific action.
|
||||
|
||||
Context is passed through the middleware and dispatcher, allowing one to
|
||||
add permissions on this app-space, and check current permissions.
|
||||
|
||||
Data Store
|
||||
~~~~~~~~~~
|
||||
|
||||
The entire merkle tree can access all data. When we call a module (or
|
||||
middleware), we give them access to a subtree corresponding to their
|
||||
app. This is achieved through the use of unique prefix assigned to each
|
||||
module. From the module's perspective it is no different, the module
|
||||
need-not have regard for the prefix as it is assigned outside of the
|
||||
modules scope. For example, if a module named ``foo`` wanted to write to
|
||||
the store it could save records under the key ``bar``, however, the
|
||||
dispatcher would register that record in the persistent state under
|
||||
``foo/bar``. Next time the ``foo`` app was called that record would be
|
||||
accessible to it under the assigned key ``bar``. This effectively makes
|
||||
app prefixing invisible to each module while preventing each module from
|
||||
affecting each other module. Under this model no two registered modules
|
||||
are permitted to have the same namespace.
|
||||
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
This guide uses the roles functionality provided by ``basecli`` to
|
||||
create a multi-sig wallet. It builds upon the basecoin basics and key
|
||||
management guides. You should have ``basecoin`` started with blocks
|
||||
streaming in, and three accounts: ``rich, poor, igor`` where ``rich``
|
||||
was the account used on ``basecoin init``, *and* run ``basecli init``
|
||||
with the appropriate flags. Review the intro guides for more
|
||||
information.
|
||||
|
||||
In this example, ``rich`` will create the role and send it some coins
|
||||
(i.e., fill the multi-sig wallet). Then, ``poor`` will prepare a
|
||||
transaction to withdraw coins, which will be approved by ``igor``. Let's
|
||||
look at our keys:
|
||||
|
||||
::
|
||||
|
||||
basecli keys list
|
||||
|
||||
::
|
||||
|
||||
All keys:
|
||||
igor 5E4CB7A4E729BA0A8B18DE99E21409B6D706D0F1
|
||||
poor 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
rich CB76F4092D1B13475272B36585EBD15D22A2848D
|
||||
|
||||
Using the ``basecli query account`` command, you'll see that ``rich``
|
||||
has plenty of coins:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"height": 81,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
|
||||
whereas ``poor`` and ``igor`` have no coins (in fact, the chain doesn't
|
||||
know about them yet):
|
||||
|
||||
::
|
||||
|
||||
ERROR: Account bytes are empty for address 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
|
||||
Create Role
|
||||
-----------
|
||||
|
||||
This first step defines the parameters of a new role, which will have
|
||||
control of any coins sent to it, and only release them if correct
|
||||
conditions are met. In this example, we are going to make a 2/3
|
||||
multi-sig wallet. Let's look a the command and dissect it below:
|
||||
|
||||
::
|
||||
|
||||
basecli tx create-role --role=10CAFE4E --min-sigs=2 --members=5E4CB7A4E729BA0A8B18DE99E21409B6D706D0F1,65D406E028319289A0706E294F3B764F44EBA3CF,CB76F4092D1B13475272B36585EBD15D22A2848D --sequence=1 --name=rich
|
||||
|
||||
In the first part we are sending a transaction that creates a role,
|
||||
rather than transfering coins. The ``--role`` flag is the name of the
|
||||
role (in hex only) and must be in double quotes. The ``--min-sigs`` and
|
||||
``--members`` define your multi-sig parameters. Here, we require a
|
||||
minimum of 2 signatures out of 3 members but we could easily say 3 of 5
|
||||
or 9 of 10, or whatever your application requires. The ``--members``
|
||||
flag requires a comma-seperated list of addresses that will be
|
||||
signatories on the role. Then we set the ``--sequence`` number for the
|
||||
transaction, which will start at 1 and must be incremented by 1 for
|
||||
every transaction from an account. Finally, we use the name of the
|
||||
key/account that will be used to create the role, in this case the
|
||||
account ``rich``.
|
||||
|
||||
Remember that ``rich``'s address was used on ``basecoin init`` and is
|
||||
included in the ``--members`` list. The command above will prompt for a
|
||||
password (which can also be piped into the command if desired) then - if
|
||||
executed correctly - return some data:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"check_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"hash": "4849DA762E19CE599460B9882DD42C7F19655DC1",
|
||||
"height": 321
|
||||
}
|
||||
|
||||
showing the block height at which the transaction was committed and its
|
||||
hash. A quick review of what we did: 1) created a role, essentially an
|
||||
account, that requires a minimum of two (2) signatures from three (3)
|
||||
accounts (members). And since it was the account named ``rich``'s first
|
||||
transaction, the sequence was set to 1.
|
||||
|
||||
Let's look at the balance of the role that we've created:
|
||||
|
||||
::
|
||||
|
||||
basecli query account role:10CAFE4E
|
||||
|
||||
and it should be empty:
|
||||
|
||||
::
|
||||
|
||||
ERROR: Account bytes are empty for address role:10CAFE4E
|
||||
|
||||
Next, we want to send coins *to* that role. Notice that because this is
|
||||
the second transaction being sent by rich, we need to increase
|
||||
``--sequence`` to ``2``:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --fee=90mycoin --amount=10000mycoin --to=role:10CAFE4E --sequence=2 --name=rich
|
||||
|
||||
We need to pay a transaction fee to the validators, in this case 90
|
||||
``mycoin`` to send 10000 ``mycoin`` Notice that for the ``--to`` flag,
|
||||
to specify that we are sending to a role instead of an account, the
|
||||
``role:`` prefix is added before the role. Because it's ``rich``'s
|
||||
second transaction, we've incremented the sequence. The output will be
|
||||
nearly identical to the output from ``create-role`` above.
|
||||
|
||||
Now the role has coins (think of it like a bank).
|
||||
|
||||
Double check with:
|
||||
|
||||
::
|
||||
|
||||
basecli query account role:10CAFE4E
|
||||
|
||||
and this time you'll see the coins in the role's account:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"height": 2453,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 10000
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
|
||||
``Poor`` decides to initiate a multi-sig transaction to himself from the
|
||||
role's account. First, it must be prepared like so:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --amount=6000mycoin --from=role:10CAFE4E --to=65D406E028319289A0706E294F3B764F44EBA3CF --sequence=1 --assume-role=10CAFE4E --name=poor --multi --prepare=tx.json
|
||||
|
||||
you'll be prompted for ``poor``'s password and there won't be any
|
||||
``stdout`` to the terminal. Note that the address in the ``--to`` flag
|
||||
matches the address of ``poor``'s account from the beginning of the
|
||||
tutorial. The main output is the ``tx.json`` file that has just been
|
||||
created. In the above command, the ``--assume-role`` flag is used to
|
||||
evaluate account permissions on the transaction, while the ``--multi``
|
||||
flag is used in combination with ``--prepare``, to specify the file that
|
||||
is prepared for a multi-sig transaction.
|
||||
|
||||
The ``tx.json`` file will look like this:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"type": "sigs/multi",
|
||||
"data": {
|
||||
"tx": {
|
||||
"type": "chain/tx",
|
||||
"data": {
|
||||
"chain_id": "test_chain_id",
|
||||
"expires_at": 0,
|
||||
"tx": {
|
||||
"type": "nonce",
|
||||
"data": {
|
||||
"sequence": 1,
|
||||
"signers": [
|
||||
{
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "65D406E028319289A0706E294F3B764F44EBA3CF"
|
||||
}
|
||||
],
|
||||
"tx": {
|
||||
"type": "role/assume",
|
||||
"data": {
|
||||
"role": "10CAFE4E",
|
||||
"tx": {
|
||||
"type": "coin/send",
|
||||
"data": {
|
||||
"inputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "role",
|
||||
"addr": "10CAFE4E"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 6000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "65D406E028319289A0706E294F3B764F44EBA3CF"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 6000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"Sig": {
|
||||
"type": "ed25519",
|
||||
"data": "A38F73BF2D109015E4B0B6782C84875292D5FAA75F0E3362C9BD29B16CB15D57FDF0553205E7A33C740319397A434B7C31CBB10BE7F8270C9984C5567D2DC002"
|
||||
},
|
||||
"Pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "6ED38C7453148DD90DFC41D9339CE45BEFA5EB505FD7E93D85E71DFFDAFD9B8F"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
and it is loaded by the next command.
|
||||
|
||||
With the transaction prepared, but not sent, we'll have ``igor`` sign
|
||||
and send the prepared transaction:
|
||||
|
||||
::
|
||||
|
||||
basecli tx --in=tx.json --name=igor
|
||||
|
||||
which will give output similar to:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"check_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 0,
|
||||
"data": "",
|
||||
"log": ""
|
||||
},
|
||||
"hash": "E345BDDED9517EB2CAAF5E30AFF3AB38A1172833",
|
||||
"height": 2673
|
||||
}
|
||||
|
||||
and voila! That's the basics for creating roles and sending multi-sig
|
||||
transactions. For 3 of 3, you'd add an intermediate transactions like:
|
||||
|
||||
::
|
||||
|
||||
basecli tx --in=tx.json --name=igor --prepare=tx2.json
|
||||
|
||||
before having rich sign and send the transaction. The ``--prepare`` flag
|
||||
writes files to disk rather than sending the transaction and can be used
|
||||
to chain together multiple transactions.
|
||||
|
||||
We can check the balance of the role:
|
||||
|
||||
::
|
||||
|
||||
basecli query account role:10CAFE4E
|
||||
|
||||
and get the result:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"height": 2683,
|
||||
"data": {
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 4000
|
||||
}
|
||||
],
|
||||
"credit": []
|
||||
}
|
||||
}
|
||||
|
||||
and see that ``poor`` now has 6000 ``mycoin``:
|
||||
|
||||
::
|
||||
|
||||
basecli query account 65D406E028319289A0706E294F3B764F44EBA3CF
|
||||
|
||||
to confirm that everything worked as expected.
|
128
docs/stdlib.md
128
docs/stdlib.md
|
@ -1,128 +0,0 @@
|
|||
# Standard Library
|
||||
|
||||
The Cosmos-SDK comes bundled with a number of standard modules that
|
||||
provide common functionality useful across a wide variety of applications.
|
||||
See examples below. It is recommended to investigate if desired
|
||||
functionality is already provided before developing new modules.
|
||||
|
||||
## Basic Middleware
|
||||
|
||||
### Logging
|
||||
|
||||
`modules.base.Logger` is a middleware that records basic info on `CheckTx`,
|
||||
`DeliverTx`, and `SetOption`, along with timing in microseconds. It can be
|
||||
installed standard at the top of all middleware stacks, or replaced with your
|
||||
own middleware if you want to record custom information with each request.
|
||||
|
||||
### Recovery
|
||||
|
||||
To avoid accidental panics (e.g. bad go-wire decoding) killing the ABCI app,
|
||||
wrap the stack with `stack.Recovery`, which catches all panics and returns
|
||||
them as errors, so they can be handled normally.
|
||||
|
||||
### Signatures
|
||||
|
||||
The first layer of the transaction contains the signatures to authorize it.
|
||||
This is then verified by `modules.auth.Signatures`. All transactions may
|
||||
have one or multiple signatures which are then processed and verified by this
|
||||
middleware and then passed down the stack.
|
||||
|
||||
### Chain
|
||||
|
||||
The next layer of a transaction (in the standard stack) binds the transaction
|
||||
to a specific chain with a block height that has an optional expiration. This
|
||||
keeps the transactions from being replayed on a fork or other such chain, as
|
||||
well as a partially signed multi-sig being delayed months before being
|
||||
committed to the chain. This functionality is provided in `modules.base.Chain`
|
||||
|
||||
### Nonce
|
||||
|
||||
To avoid replay attacks, a nonce can be associated with each actor. A separate
|
||||
nonce is used for each distinct group signers required for a transaction as
|
||||
well as for each separate application and chain-id. This creates replay
|
||||
protection cross-IBC and cross-plugins and also allows signing parties to not
|
||||
be bound to waiting for a particular transaction to be completed before being
|
||||
able to sign a separate transaction.
|
||||
|
||||
Rather than force each module to implement its own replay protection, a
|
||||
transaction stack may contain a nonce wrap and the account it belongs to. The
|
||||
nonce must contain a signed sequence number which is incremented one higher
|
||||
than the last request or the request is rejected. This is implemented in
|
||||
`modules.nonce.ReplayCheck`.
|
||||
|
||||
If you're interested checkout this [design
|
||||
discussion](https://github.com/cosmos/cosmos-sdk/issues/160).
|
||||
|
||||
### Fees
|
||||
|
||||
An optional - but useful - feature on many chains, is charging transaction fees.
|
||||
A simple implementation of this is provided in `modules.fee.SimpleFeeMiddleware`.
|
||||
A fee currency and minimum amount are defined in the constructor (eg. in code).
|
||||
If the minimum amount is 0, then the fee is optional. If it is above 0, then
|
||||
every transaction with insufficient fee is rejected. This fee is deducted from the
|
||||
payers account before executing any other transaction.
|
||||
|
||||
This module is dependent on the `coin` module.
|
||||
|
||||
## Other Apps
|
||||
|
||||
### Coin
|
||||
|
||||
What would a crypto-currency be without tokens? The `SendTx` logic from earlier
|
||||
implementations of basecoin was extracted into one module, which is now
|
||||
optional, meaning most of the other functionality will also work in a system
|
||||
with no built-in tokens, such as a private network that provides other access
|
||||
control mechanisms.
|
||||
|
||||
`modules.coin.Handler` defines a Handler that maintains a number of accounts
|
||||
along with a set of various tokens, supporting multiple token denominations.
|
||||
The main access is `SendTx`, which can support any type of actor (other apps as
|
||||
well as public key addresses) and is a building block for any other app that
|
||||
requires some payment solution, like fees or trader.
|
||||
|
||||
### Roles
|
||||
|
||||
Roles encapsulate what are typically called N-of-M multi-signatures accounts
|
||||
in the crypto world. However, I view this as a type of role or group, which can
|
||||
be the basis for building a permission system. For example, a set of people
|
||||
could be called registrars, which can authorize a new IBC chain, and need eg. 2
|
||||
out of 7 signatures to approve it.
|
||||
|
||||
Currently, one can create a role with `modules.roles.Handler`, and assume one
|
||||
of those roles by wrapping another transaction with `AssumeRoleTx`, which is
|
||||
processed by `modules.roles.Middleware`. Updating the set of actors in
|
||||
a role is planned in the near future.
|
||||
|
||||
### Inter-Blockchain Communication (IBC)
|
||||
|
||||
IBC, is the cornerstone of The Cosmos Network, and is built into the Cosmos-SDK
|
||||
framework as a basic primitive. To fully grasp these concepts requires
|
||||
a much longer explanation, but in short, the chain works as a light-client to
|
||||
another chain and maintains input and output queue to send packets with that
|
||||
chain. This mechanism allows blockchains to prove the state of their respective
|
||||
blockchains to each other ultimately invoke inter-blockchain transactions.
|
||||
|
||||
Most functionality is implemented in `modules.ibc.Handler`. Registering a chain
|
||||
is a seed of trust that requires verification of the proper seed (or genesis
|
||||
block), and this generally requires approval of an authorized registrar (which
|
||||
may be a multi-sig role). Updating a registered chain can be done by anyone,
|
||||
as the new header can be completely verified by the existing knowledge of the
|
||||
chain. Also, modules can initiate an outgoing IBC message to another chain
|
||||
by calling `CreatePacketTx` over IPC (inter-plugin communication) with a
|
||||
transaction that belongs to their module. (This must be explicitly authorized
|
||||
by the same module, so only the eg. coin module can authorize a `SendTx` to
|
||||
another chain).
|
||||
|
||||
`PostPacketTx` can post a transaction that was created on another chain along
|
||||
with the merkle proof, which must match an already registered header. If this
|
||||
chain can verify the authenticity, it will accept the packet, along with all
|
||||
the permissions from the other chain, and execute it on this stack. This is the
|
||||
only way to get permissions that belong to another chain.
|
||||
|
||||
These various pieces can be combined in a relay, which polls for new packets
|
||||
on one chain, and then posts the packets along with the new headers on the
|
||||
other chain.
|
||||
|
||||
## Example Apps
|
||||
|
||||
See the [Cosmos Academy](https://github.com/cosmos/cosmos-academy) for example applications.
|
|
@ -0,0 +1,150 @@
|
|||
Standard Library
|
||||
================
|
||||
|
||||
The Cosmos-SDK comes bundled with a number of standard modules that
|
||||
provide common functionality useful across a wide variety of
|
||||
applications. See examples below. It is recommended to investigate if
|
||||
desired functionality is already provided before developing new modules.
|
||||
|
||||
Basic Middleware
|
||||
----------------
|
||||
|
||||
Logging
|
||||
~~~~~~~
|
||||
|
||||
``modules.base.Logger`` is a middleware that records basic info on
|
||||
``CheckTx``, ``DeliverTx``, and ``SetOption``, along with timing in
|
||||
microseconds. It can be installed standard at the top of all middleware
|
||||
stacks, or replaced with your own middleware if you want to record
|
||||
custom information with each request.
|
||||
|
||||
Recovery
|
||||
~~~~~~~~
|
||||
|
||||
To avoid accidental panics (e.g. bad go-wire decoding) killing the ABCI
|
||||
app, wrap the stack with ``stack.Recovery``, which catches all panics
|
||||
and returns them as errors, so they can be handled normally.
|
||||
|
||||
Signatures
|
||||
~~~~~~~~~~
|
||||
|
||||
The first layer of the transaction contains the signatures to authorize
|
||||
it. This is then verified by ``modules.auth.Signatures``. All
|
||||
transactions may have one or multiple signatures which are then
|
||||
processed and verified by this middleware and then passed down the
|
||||
stack.
|
||||
|
||||
Chain
|
||||
~~~~~
|
||||
|
||||
The next layer of a transaction (in the standard stack) binds the
|
||||
transaction to a specific chain with a block height that has an optional
|
||||
expiration. This keeps the transactions from being replayed on a fork or
|
||||
other such chain, as well as a partially signed multi-sig being delayed
|
||||
months before being committed to the chain. This functionality is
|
||||
provided in ``modules.base.Chain``
|
||||
|
||||
Nonce
|
||||
~~~~~
|
||||
|
||||
To avoid replay attacks, a nonce can be associated with each actor. A
|
||||
separate nonce is used for each distinct group signers required for a
|
||||
transaction as well as for each separate application and chain-id. This
|
||||
creates replay protection cross-IBC and cross-plugins and also allows
|
||||
signing parties to not be bound to waiting for a particular transaction
|
||||
to be completed before being able to sign a separate transaction.
|
||||
|
||||
Rather than force each module to implement its own replay protection, a
|
||||
transaction stack may contain a nonce wrap and the account it belongs
|
||||
to. The nonce must contain a signed sequence number which is incremented
|
||||
one higher than the last request or the request is rejected. This is
|
||||
implemented in ``modules.nonce.ReplayCheck``.
|
||||
|
||||
If you're interested checkout this `design
|
||||
discussion <https://github.com/cosmos/cosmos-sdk/issues/160>`__.
|
||||
|
||||
Fees
|
||||
~~~~
|
||||
|
||||
An optional - but useful - feature on many chains, is charging
|
||||
transaction fees. A simple implementation of this is provided in
|
||||
``modules.fee.SimpleFeeMiddleware``. A fee currency and minimum amount
|
||||
are defined in the constructor (eg. in code). If the minimum amount is
|
||||
0, then the fee is optional. If it is above 0, then every transaction
|
||||
with insufficient fee is rejected. This fee is deducted from the payers
|
||||
account before executing any other transaction.
|
||||
|
||||
This module is dependent on the ``coin`` module.
|
||||
|
||||
Other Apps
|
||||
----------
|
||||
|
||||
Coin
|
||||
~~~~
|
||||
|
||||
What would a crypto-currency be without tokens? The ``SendTx`` logic
|
||||
from earlier implementations of basecoin was extracted into one module,
|
||||
which is now optional, meaning most of the other functionality will also
|
||||
work in a system with no built-in tokens, such as a private network that
|
||||
provides other access control mechanisms.
|
||||
|
||||
``modules.coin.Handler`` defines a Handler that maintains a number of
|
||||
accounts along with a set of various tokens, supporting multiple token
|
||||
denominations. The main access is ``SendTx``, which can support any type
|
||||
of actor (other apps as well as public key addresses) and is a building
|
||||
block for any other app that requires some payment solution, like fees
|
||||
or trader.
|
||||
|
||||
Roles
|
||||
~~~~~
|
||||
|
||||
Roles encapsulate what are typically called N-of-M multi-signatures
|
||||
accounts in the crypto world. However, I view this as a type of role or
|
||||
group, which can be the basis for building a permission system. For
|
||||
example, a set of people could be called registrars, which can authorize
|
||||
a new IBC chain, and need eg. 2 out of 7 signatures to approve it.
|
||||
|
||||
Currently, one can create a role with ``modules.roles.Handler``, and
|
||||
assume one of those roles by wrapping another transaction with
|
||||
``AssumeRoleTx``, which is processed by ``modules.roles.Middleware``.
|
||||
Updating the set of actors in a role is planned in the near future.
|
||||
|
||||
Inter-Blockchain Communication (IBC)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
IBC, is the cornerstone of The Cosmos Network, and is built into the
|
||||
Cosmos-SDK framework as a basic primitive. To fully grasp these concepts
|
||||
requires a much longer explanation, but in short, the chain works as a
|
||||
light-client to another chain and maintains input and output queue to
|
||||
send packets with that chain. This mechanism allows blockchains to prove
|
||||
the state of their respective blockchains to each other ultimately
|
||||
invoke inter-blockchain transactions.
|
||||
|
||||
Most functionality is implemented in ``modules.ibc.Handler``.
|
||||
Registering a chain is a seed of trust that requires verification of the
|
||||
proper seed (or genesis block), and this generally requires approval of
|
||||
an authorized registrar (which may be a multi-sig role). Updating a
|
||||
registered chain can be done by anyone, as the new header can be
|
||||
completely verified by the existing knowledge of the chain. Also,
|
||||
modules can initiate an outgoing IBC message to another chain by calling
|
||||
``CreatePacketTx`` over IPC (inter-plugin communication) with a
|
||||
transaction that belongs to their module. (This must be explicitly
|
||||
authorized by the same module, so only the eg. coin module can authorize
|
||||
a ``SendTx`` to another chain).
|
||||
|
||||
``PostPacketTx`` can post a transaction that was created on another
|
||||
chain along with the merkle proof, which must match an already
|
||||
registered header. If this chain can verify the authenticity, it will
|
||||
accept the packet, along with all the permissions from the other chain,
|
||||
and execute it on this stack. This is the only way to get permissions
|
||||
that belong to another chain.
|
||||
|
||||
These various pieces can be combined in a relay, which polls for new
|
||||
packets on one chain, and then posts the packets along with the new
|
||||
headers on the other chain.
|
||||
|
||||
Example Apps
|
||||
------------
|
||||
|
||||
See the `Cosmos Academy <https://github.com/cosmos/cosmos-academy>`__
|
||||
for example applications.
|
Loading…
Reference in New Issue