docs: rename all files from .md to .rst
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user