docs: remove reference to middleware/isolation
This commit is contained in:
parent
5ed30b2a62
commit
b655d902de
|
@ -44,8 +44,7 @@ Context (ctx)
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
As a request passes through the system, it may pick up information such
|
As a request passes through the system, it may pick up information such
|
||||||
as the authorization it has received from another middleware, or the
|
as the block height the request runs at. In order to carry this information
|
||||||
block height the request runs at. In order to carry this information
|
|
||||||
between modules it is saved to the context. Further, all information
|
between modules it is saved to the context. Further, all information
|
||||||
must be deterministic from the context in which the request runs (based
|
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
|
on the transaction and the block it was included in) and can be used to
|
||||||
|
@ -90,18 +89,18 @@ separate module.
|
||||||
|
|
||||||
The second is to add permissions to the transaction context. The
|
The second is to add permissions to the transaction context. The
|
||||||
transaction context can specify that the tx has been signed by one or
|
transaction context can specify that the tx has been signed by one or
|
||||||
multiple specific
|
multiple specific actors.
|
||||||
`actors <https://github.com/tendermint/basecoin/blob/unstable/context.go#L18>`__.
|
|
||||||
A transactions will only be executed if the permission requirements have
|
A transactions will only be executed if the permission requirements have
|
||||||
been fulfilled. For example the sender of funds must have signed, or 2
|
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
|
out of 3 multi-signature actors must have signed a joint account. To
|
||||||
prevent the forgery of account signatures from unintended modules each
|
prevent the forgery of account signatures from unintended modules each
|
||||||
permission is associated with the module that granted it (in this case
|
permission is associated with the module that granted it (in this case
|
||||||
`auth <https://github.com/cosmos/cosmos-sdk/tree/master/modules/auth>`__),
|
`auth <https://github.com/cosmos/cosmos-sdk/tree/master/x/auth>`__),
|
||||||
and if a module tries to add a permission for another module, it will
|
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
|
panic. There is also protection if a module creates a brand new fake
|
||||||
context to trick the downstream modules. Each context enforces the rules
|
context to trick the downstream modules. Each context enforces the rules
|
||||||
on how to make child contexts, and the stack middleware builder enforces
|
on how to make child contexts, and the stack builder enforces
|
||||||
that the context passed from one level to the next is a valid child of
|
that the context passed from one level to the next is a valid child of
|
||||||
the original one.
|
the original one.
|
||||||
|
|
||||||
|
@ -152,7 +151,7 @@ the super minimal
|
||||||
`negroni <https://github.com/urfave/negroni/blob/master/README.md>`__
|
`negroni <https://github.com/urfave/negroni/blob/master/README.md>`__
|
||||||
package, we just provide one more ``Middleware`` interface, which has an
|
package, we just provide one more ``Middleware`` interface, which has an
|
||||||
extra ``next`` parameter, and a ``Stack`` that can wire all the levels
|
extra ``next`` parameter, and a ``Stack`` that can wire all the levels
|
||||||
together (which also gives us a place to perform isolation of each
|
together (which also gives us a place to perform seperation of each
|
||||||
step).
|
step).
|
||||||
|
|
||||||
.. code:: golang
|
.. code:: golang
|
||||||
|
@ -165,6 +164,8 @@ step).
|
||||||
Modules
|
Modules
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
TODO: update (s/Modules/handlers+mappers+stores/g) & add Msg + Tx (a signed message)
|
||||||
|
|
||||||
A module is a set of functionality which should be typically designed as
|
A module is a set of functionality which should be typically designed as
|
||||||
self-sufficient. Common elements of a module are:
|
self-sufficient. Common elements of a module are:
|
||||||
|
|
||||||
|
@ -172,13 +173,6 @@ self-sufficient. Common elements of a module are:
|
||||||
- custom error codes
|
- custom error codes
|
||||||
- data models (to persist in the kv-store)
|
- data models (to persist in the kv-store)
|
||||||
- handler (to handle any end transactions)
|
- 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
|
Dispatcher
|
||||||
----------
|
----------
|
||||||
|
@ -189,7 +183,7 @@ have ``coin`` sending money, ``roles`` to create multi-sig accounts, and
|
||||||
``ibc`` for following other chains all working together without
|
``ibc`` for following other chains all working together without
|
||||||
interference.
|
interference.
|
||||||
|
|
||||||
After the chain of middleware, we can register a ``Dispatcher``, which
|
We can then register a ``Dispatcher``, which
|
||||||
also implements the ``Handler`` interface. We then register a list of
|
also implements the ``Handler`` interface. We then register a list of
|
||||||
modules with the dispatcher. Every module has a unique ``Name()``, which
|
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
|
is used for isolating its state space. We use this same name for routing
|
||||||
|
@ -300,8 +294,3 @@ 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
|
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
|
fee, however both modules do not need to check the nonce. This can occur
|
||||||
as a separate module earlier in the stack.
|
as a separate module earlier in the stack.
|
||||||
|
|
||||||
IBC (Inter-Blockchain Communication)
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
Stay tuned!
|
|
||||||
|
|
|
@ -0,0 +1,424 @@
|
||||||
|
InterBlockchain Communication with the Cosmos SDK
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
TODO: update in light of latest SDK (this document is currently out of date)
|
||||||
|
|
||||||
|
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 familiarity with the Cosmos SDK.
|
||||||
|
|
||||||
|
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,399 @@
|
||||||
|
SDK Overview
|
||||||
|
============
|
||||||
|
|
||||||
|
The SDK 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. In addition,
|
||||||
|
all modules are sandboxed for greater application security.
|
||||||
|
|
||||||
|
Framework Overview
|
||||||
|
------------------
|
||||||
|
|
||||||
|
### Object-Capability Model
|
||||||
|
|
||||||
|
When thinking about security, it's good to start with a specific threat model. Our threat model is the following:
|
||||||
|
|
||||||
|
> We want to assume a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application. Some of these modules will be faulty or malicious.
|
||||||
|
|
||||||
|
The Cosmos-SDK is designed to address this threat by being the foundation of an object capability system.
|
||||||
|
|
||||||
|
```
|
||||||
|
The structural properties of object capability systems favor
|
||||||
|
modularity in code design and ensure reliable encapsulation in
|
||||||
|
code implementation.
|
||||||
|
|
||||||
|
These structural properties facilitate the analysis of some
|
||||||
|
security properties of an object-capability program or operating
|
||||||
|
system. Some of these — in particular, information flow properties
|
||||||
|
— can be analyzed at the level of object references and
|
||||||
|
connectivity, independent of any knowledge or analysis of the code
|
||||||
|
that determines the behavior of the objects. As a consequence,
|
||||||
|
these security properties can be established and maintained in the
|
||||||
|
presence of new objects that contain unknown and possibly
|
||||||
|
malicious code.
|
||||||
|
|
||||||
|
These structural properties stem from the two rules governing
|
||||||
|
access to existing objects:
|
||||||
|
|
||||||
|
1) An object A can send a message to B only if object A holds a
|
||||||
|
reference to B.
|
||||||
|
|
||||||
|
2) An object A can obtain a reference to C only
|
||||||
|
if object A receives a message containing a reference to C. As a
|
||||||
|
consequence of these two rules, an object can obtain a reference
|
||||||
|
to another object only through a preexisting chain of references.
|
||||||
|
In short, "Only connectivity begets connectivity."
|
||||||
|
|
||||||
|
- https://en.wikipedia.org/wiki/Object-capability_model
|
||||||
|
```
|
||||||
|
|
||||||
|
Strictly speaking, Golang does not implement object capabilities completely, because of several issues:
|
||||||
|
|
||||||
|
* pervasive ability to import primitive modules (e.g. "unsafe", "os")
|
||||||
|
* pervasive ability to override module vars https://github.com/golang/go/issues/23161
|
||||||
|
* data-race vulnerability where 2+ goroutines can create illegal interface values
|
||||||
|
|
||||||
|
The first is easy to catch by auditing imports and using a proper dependency version control system like Glide. The second and third are unfortunate but it can be audited with some cost.
|
||||||
|
|
||||||
|
Perhaps [Go2 will implement the object capability model](https://github.com/golang/go/issues/23157).
|
||||||
|
|
||||||
|
### What does it look like?
|
||||||
|
|
||||||
|
Only reveal what is necessary to get the work done.
|
||||||
|
|
||||||
|
For example, the following code snippet violates the object capabilities principle:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type AppAccount struct {...}
|
||||||
|
var account := &AppAccount{
|
||||||
|
Address: pub.Address(),
|
||||||
|
Coins: sdk.Coins{{"ATM", 100}},
|
||||||
|
}
|
||||||
|
var sumValue := externalModule.ComputeSumValue(account)
|
||||||
|
```
|
||||||
|
|
||||||
|
The method "ComputeSumValue" implies a pure function, yet the implied capability of accepting a pointer value is the capability to modify that value. The preferred method signature should take a copy instead.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
var sumValue := externalModule.ComputeSumValue(*account)
|
||||||
|
```
|
||||||
|
|
||||||
|
In the Cosmos SDK, you can see the application of this principle in the basecoin examples folder.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/sketchy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *BasecoinApp) initRouterHandlers() {
|
||||||
|
|
||||||
|
// All handlers must be added here.
|
||||||
|
// The order matters.
|
||||||
|
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||||
|
app.router.AddRoute("sketchy", sketchy.NewHandler())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the Basecoin example, the sketchy handler isn't provided an account mapper, which does provide the bank handler with the capability (in conjunction with the context of a transaction run).
|
||||||
|
|
||||||
|
Security Overview
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you want to see some examples, take a look at the [examples/basecoin](/examples/basecoin) directory.
|
||||||
|
|
||||||
|
## Design Goals
|
||||||
|
|
||||||
|
The design of the Cosmos SDK is based on the principles of "capabilities systems".
|
||||||
|
|
||||||
|
## Capabilities systems
|
||||||
|
|
||||||
|
### Need for module isolation
|
||||||
|
### Capability is implied permission
|
||||||
|
### TODO Link to thesis
|
||||||
|
|
||||||
|
## Tx & Msg
|
||||||
|
|
||||||
|
The SDK distinguishes between transactions (Tx) and messages
|
||||||
|
(Msg). A Tx is a Msg wrapped with authentication and fee data.
|
||||||
|
|
||||||
|
### Messages
|
||||||
|
|
||||||
|
Users can create messages containing arbitrary information by
|
||||||
|
implementing the `Msg` interface:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type Msg interface {
|
||||||
|
|
||||||
|
// Return the message type.
|
||||||
|
// Must be alphanumeric or empty.
|
||||||
|
Type() string
|
||||||
|
|
||||||
|
// Get some property of the Msg.
|
||||||
|
Get(key interface{}) (value interface{})
|
||||||
|
|
||||||
|
// Get the canonical byte representation of the Msg.
|
||||||
|
GetSignBytes() []byte
|
||||||
|
|
||||||
|
// ValidateBasic does a simple validation check that
|
||||||
|
// doesn't require access to any other information.
|
||||||
|
ValidateBasic() error
|
||||||
|
|
||||||
|
// Signers returns the addrs of signers that must sign.
|
||||||
|
// CONTRACT: All signatures must be present to be valid.
|
||||||
|
// CONTRACT: Returns addrs in some deterministic order.
|
||||||
|
GetSigners() []crypto.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Messages must specify their type via the `Type()` method. The type should
|
||||||
|
correspond to the messages handler, so there can be many messages with the same
|
||||||
|
type.
|
||||||
|
|
||||||
|
Messages must also specify how they are to be authenticated. The `GetSigners()`
|
||||||
|
method return a list of addresses that must sign the message, while the
|
||||||
|
`GetSignBytes()` method returns the bytes that must be signed for a signature
|
||||||
|
to be valid.
|
||||||
|
|
||||||
|
Addresses in the SDK are arbitrary byte arrays that are hex-encoded when
|
||||||
|
displayed as a string or rendered in JSON.
|
||||||
|
|
||||||
|
Messages can specify basic self-consistency checks using the `ValidateBasic()`
|
||||||
|
method to enforce that message contents are well formed before any actual logic
|
||||||
|
begins.
|
||||||
|
|
||||||
|
Finally, messages can provide generic access to their contents via `Get(key)`,
|
||||||
|
but this is mostly for convenience and not type-safe.
|
||||||
|
|
||||||
|
For instance, the `Basecoin` message types are defined in `x/bank/tx.go`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type SendMsg struct {
|
||||||
|
Inputs []Input `json:"inputs"`
|
||||||
|
Outputs []Output `json:"outputs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IssueMsg struct {
|
||||||
|
Banker crypto.Address `json:"banker"`
|
||||||
|
Outputs []Output `json:"outputs"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each specifies the addresses that must sign the message:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
func (msg SendMsg) GetSigners() []crypto.Address {
|
||||||
|
addrs := make([]crypto.Address, len(msg.Inputs))
|
||||||
|
for i, in := range msg.Inputs {
|
||||||
|
addrs[i] = in.Address
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg IssueMsg) GetSigners() []crypto.Address {
|
||||||
|
return []crypto.Address{msg.Banker}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
A transaction is a message with additional information for authentication:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type Tx interface {
|
||||||
|
|
||||||
|
GetMsg() Msg
|
||||||
|
|
||||||
|
// The address that pays the base fee for this message. The fee is
|
||||||
|
// deducted before the Msg is processed.
|
||||||
|
GetFeePayer() crypto.Address
|
||||||
|
|
||||||
|
// Get the canonical byte representation of the Tx.
|
||||||
|
// Includes any signatures (or empty slots).
|
||||||
|
GetTxBytes() []byte
|
||||||
|
|
||||||
|
// Signatures returns the signature of signers who signed the Msg.
|
||||||
|
// CONTRACT: Length returned is same as length of
|
||||||
|
// pubkeys returned from MsgKeySigners, and the order
|
||||||
|
// matches.
|
||||||
|
// CONTRACT: If the signature is missing (ie the Msg is
|
||||||
|
// invalid), then the corresponding signature is
|
||||||
|
// .Empty().
|
||||||
|
GetSignatures() []StdSignature
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `tx.GetSignatures()` method returns a list of signatures, which must match
|
||||||
|
the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in
|
||||||
|
a standard form:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type StdSignature struct {
|
||||||
|
crypto.PubKey // optional
|
||||||
|
crypto.Signature
|
||||||
|
Sequence int64
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It contains the signature itself, as well as the corresponding account's
|
||||||
|
sequence number. The sequence number is expected to increment every time a
|
||||||
|
message is signed by a given account. This prevents "replay attacks", where
|
||||||
|
the same message could be executed over and over again.
|
||||||
|
|
||||||
|
The `StdSignature` can also optionally include the public key for verifying the
|
||||||
|
signature. An application can store the public key for each address it knows
|
||||||
|
about, making it optional to include the public key in the transaction. In the
|
||||||
|
case of Basecoin, the public key only needs to be included in the first
|
||||||
|
transaction send by a given account - after that, the public key is forever
|
||||||
|
stored by the application and can be left out of transactions.
|
||||||
|
|
||||||
|
Transactions can also specify the address responsible for paying the
|
||||||
|
transaction's fees using the `tx.GetFeePayer()` method.
|
||||||
|
|
||||||
|
The standard way to create a transaction from a message is to use the `StdTx`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type StdTx struct {
|
||||||
|
Msg
|
||||||
|
Signatures []StdSignature
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Encoding and Decoding Transactions
|
||||||
|
|
||||||
|
Messages and transactions are designed to be generic enough for developers to
|
||||||
|
specify their own encoding schemes. This enables the SDK to be used as the
|
||||||
|
framwork for constructing already specified cryptocurrency state machines, for
|
||||||
|
instance Ethereum.
|
||||||
|
|
||||||
|
When initializing an application, a developer must specify a `TxDecoder`
|
||||||
|
function which determines how an arbitrary byte array should be unmarshalled
|
||||||
|
into a `Tx`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type TxDecoder func(txBytes []byte) (Tx, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
In `Basecoin`, we use the Tendermint wire format and the `go-wire` library for
|
||||||
|
encoding and decoding all message types. The `go-wire` library has the nice
|
||||||
|
property that it can unmarshal into interface types, but it requires the
|
||||||
|
relevant types to be registered ahead of type. Registration happens on a
|
||||||
|
`Codec` object, so as not to taint the global name space.
|
||||||
|
|
||||||
|
For instance, in `Basecoin`, we wish to register the `SendMsg` and `IssueMsg`
|
||||||
|
types:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||||
|
cdc.RegisterConcrete(bank.SendMsg{}, "cosmos-sdk/SendMsg", nil)
|
||||||
|
cdc.RegisterConcrete(bank.IssueMsg{}, "cosmos-sdk/IssueMsg", nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how each concrete type is given a name - these name determine the type's
|
||||||
|
unique "prefix bytes" during encoding. A registered type will always use the
|
||||||
|
same prefix-bytes, regardless of what interface it is satisfying. For more
|
||||||
|
details, see the [go-wire documentation](https://github.com/tendermint/go-wire/blob/develop).
|
||||||
|
|
||||||
|
|
||||||
|
## MultiStore
|
||||||
|
|
||||||
|
### MultiStore is like a filesystem
|
||||||
|
### Mounting an IAVLStore
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO:
|
||||||
|
- IAVLStore: Fast balanced dynamic Merkle store.
|
||||||
|
- supports iteration.
|
||||||
|
- MultiStore: multiple Merkle tree backends in a single store
|
||||||
|
- allows using Ethereum Patricia Trie and Tendermint IAVL in same app
|
||||||
|
- Provide caching for intermediate state during execution of blocks and transactions (including for iteration)
|
||||||
|
- Historical state pruning and snapshotting.
|
||||||
|
- Query proofs (existence, absence, range, etc.) on current and retained historical state.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The SDK uses a `Context` to propogate common information across functions. The
|
||||||
|
`Context` is modelled after the Golang `context.Context` object, which has
|
||||||
|
become ubiquitous in networking middleware and routing applications as a means
|
||||||
|
to easily propogate request context through handler functions.
|
||||||
|
|
||||||
|
The main information stored in the `Context` includes the application
|
||||||
|
MultiStore (see below), the last block header, and the transaction bytes.
|
||||||
|
Effectively, the context contains all data that may be necessary for processing
|
||||||
|
a transaction.
|
||||||
|
|
||||||
|
Many methods on SDK objects receive a context as the first argument.
|
||||||
|
|
||||||
|
## Handler
|
||||||
|
|
||||||
|
Transaction processing in the SDK is defined through `Handler` functions:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type Handler func(ctx Context, tx Tx) Result
|
||||||
|
```
|
||||||
|
|
||||||
|
A handler takes a context and a transaction and returns a result. All
|
||||||
|
information necessary for processing a transaction should be available in the
|
||||||
|
context.
|
||||||
|
|
||||||
|
While the context holds the entire application state (all referenced from the
|
||||||
|
root MultiStore), a particular handler only needs a particular kind of access
|
||||||
|
to a particular store (or two or more). Access to stores is managed using
|
||||||
|
capabilities keys and mappers. When a handler is initialized, it is passed a
|
||||||
|
key or mapper that gives it access to the relevant stores.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// File: cosmos-sdk/examples/basecoin/app/init_stores.go
|
||||||
|
app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL)
|
||||||
|
app.accountMapper = auth.NewAccountMapper(
|
||||||
|
app.capKeyMainStore, // target store
|
||||||
|
&types.AppAccount{}, // prototype
|
||||||
|
)
|
||||||
|
|
||||||
|
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||||
|
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||||
|
|
||||||
|
// File: cosmos-sdk/x/bank/handler.go
|
||||||
|
// NOTE: Technically, NewHandler only needs a CoinMapper
|
||||||
|
func NewHandler(am sdk.AccountMapper) sdk.Handler {
|
||||||
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
cm := CoinMapper{am}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## AnteHandler
|
||||||
|
|
||||||
|
### Handling Fee payment
|
||||||
|
### Handling Authentication
|
||||||
|
|
||||||
|
## Accounts and x/auth
|
||||||
|
|
||||||
|
### sdk.Account
|
||||||
|
### auth.BaseAccount
|
||||||
|
### auth.AccountMapper
|
||||||
|
|
||||||
|
## Wire codec
|
||||||
|
|
||||||
|
### Why another codec?
|
||||||
|
### vs encoding/json
|
||||||
|
### vs protobuf
|
||||||
|
|
||||||
|
## Dummy example
|
||||||
|
|
||||||
|
## Basecoin example
|
||||||
|
|
||||||
|
The quintessential SDK application is Basecoin - a simple
|
||||||
|
multi-asset cryptocurrency. Basecoin consists of a set of
|
||||||
|
accounts stored in a Merkle tree, where each account may have
|
||||||
|
many coins. There are two message types: SendMsg and IssueMsg.
|
||||||
|
SendMsg allows coins to be sent around, while IssueMsg allows a
|
||||||
|
set of predefined users to issue new coins.
|
||||||
|
|
||||||
|
## Conclusion
|
Loading…
Reference in New Issue