Correct spelling

This reverts commit 3c7d194a7f1bc53b60cf09473f0ac50d2192e2af.
This commit is contained in:
Ethan Frey 2018-02-26 13:17:42 +01:00 committed by Christopher Goes
parent 32ffd2d316
commit a8d3b3ef19
No known key found for this signature in database
GPG Key ID: E828D98232D328D3
6 changed files with 128 additions and 530 deletions

View File

@ -1,21 +1,7 @@
## 5 Conclusion
We have demonstrated a secure, performant, and flexible protocol for connecting
two blockchains with complete finality using a secure, reliable messaging
queue. The algorithm and semantics of all data types have been defined above,
which provides a solid basis for reasoning about correctness and efficiency of
the algorithm.
We have demonstrated a secure, performant, and flexible protocol for connecting two blockchains with complete finality using a secure, reliable messaging queue. The algorithm and semantics of all data types have been defined above, which provides a solid basis for reasoning about correctness and efficiency of the algorithm.
The observant reader may note that while we have defined a message queue
protocol, we have not yet defined how to use that to transfer value within the
Cosmos ecosystem. We will shortly release a separate paper on Cosmos IBC that
defines the application logic used for direct value transfer as well as routing
over the Cosmos hub. That paper builds upon the IBC protocol defined here and
provides a first example of how to reason about application logic and global
invariants in the context of IBC.
The observant reader may note that while we have defined a message queue protocol, we have not yet defined how to use that to transfer value within the Cosmos ecosystem. We will shortly release a separate paper on Cosmos IBC that defines the application logic used for direct value transfer as well as routing over the Cosmos hub. That paper builds upon the IBC protocol defined here and provides a first example of how to reason about application logic and global invariants in the context of IBC.
There is a reference implementation of the Cosmos IBC protocol as part of the
Cosmos SDK, written in go and freely usable under the Apache license. For those
wish to write an implementation of IBC in another language, or who want to
analyze the specification further, the following appendixes define the exact
message formats and binary encoding.
There is a reference implementation of the Cosmos IBC protocol as part of the Cosmos SDK, written in go and freely usable under the Apache license. For those wish to write an implementation of IBC in another language, or who want to analyze the specification further, the following appendixes define the exact message formats and binary encoding.

View File

@ -2,214 +2,104 @@
([Back to table of contents](specification.md#contents))
The above sections describe a secure messaging protocol that can handle all
normal situations between two blockchains. It guarantees that all messages are
processed exactly once and in order, and provides a mechanism for non-blocking
atomic transactions spanning two blockchains. However, to increase efficiency
over millions of messages with many possible failure modes on both sides of the
connection, we can extend the protocol. These extensions allow us to clean up
the receipt queue to avoid state bloat, as well as more gracefully recover from
cases where large numbers of messages are not being relayed, or other failure
modes in the remote chain.
The above sections describe a secure messaging protocol that can handle all normal situations between two blockchains. It guarantees that all messages are processed exactly once and in order, and provides a mechanism for non-blocking atomic transactions spanning two blockchains. However, to increase efficiency over millions of messages with many possible failure modes on both sides of the connection, we can extend the protocol. These extensions allow us to clean up the receipt queue to avoid state bloat, as well as more gracefully recover from cases where large numbers of messages are not being relayed, or other failure modes in the remote chain.
### 4.1 Timeouts
Sometimes it is desirable to have some timeout, an upper limit to how long you
will wait for a transaction to be processed before considering it an error. At
the same time, this is an obvious attack vector for a double spend, just
delaying the relay of the receipt or waiting to send the message in the first
place and then relaying it right after the cutoff to take advantage of
different local clocks on the two chains.
Sometimes it is desirable to have some timeout, an upper limit to how long you will wait for a transaction to be processed before considering it an error. At the same time, this is an obvious attack vector for a double spend, just delaying the relay of the receipt or waiting to send the message in the first place and then relaying it right after the cutoff to take advantage of different local clocks on the two chains.
One solution to this is to include a timeout in the IBC message itself. When
sending it, one can specify a block height or timestamp on the **receiving**
chain after which it is no longer valid. If the message is posted before the
cutoff, it will be processed normally. If it is posted after that cutoff, it
will be a guaranteed error. Note that to make this secure, the timeout must be
relative to a condition on the **receiving** chain, and the sending chain must
have proof of the state of the receiving chain after the cutoff.
One solution to this is to include a timeout in the IBC message itself. When sending it, one can specify a block height or timestamp on the **receiving** chain after which it is no longer valid. If the message is posted before the cutoff, it will be processed normally. If it is posted after that cutoff, it will be a guaranteed error. Note that to make this secure, the timeout must be relative to a condition on the **receiving** chain, and the sending chain must have proof of the state of the receiving chain after the cutoff.
For a sending chain _A_ and a receiving chain _B_, with _k=(\_, \_, i)_ for
_A:q<sub>B.send</sub>_ or _B:q<sub>A.receipt</sub>_ we currently have the
following guarantees:
For a sending chain _A_ and a receiving chain _B_, with _k=(\_, \_, i)_ for _A:q<sub>B.send</sub>_ or _B:q<sub>A.receipt</sub>_ we currently have the following guarantees:
_A:M<sub>k,v,h</sub> =_ &#8709; _if message i was not sent before height h_
_A:M<sub>k,v,h</sub> =_ &#8709; _if message i was sent and receipt received
before height h (and the receipts for all messages j < i were also handled)_
_A:M<sub>k,v,h</sub> =_ &#8709; _if message i was sent and receipt received before height h (and the receipts for all messages j < i were also handled)_
_A:M<sub>k,v,h </sub>_ &#8800; &#8709; _otherwise (message result is not yet
processed)_
_A:M<sub>k,v,h </sub>_ &#8800; &#8709; _otherwise (message result is not yet processed)_
_B:M<sub>k,v,h</sub> =_ &#8709; _if message i was not received before height h_
_B:M<sub>k,v,h </sub>_ &#8800; &#8709; _if message i was received before height
h (and all messages j < i were received)_
_B:M<sub>k,v,h </sub>_ &#8800; &#8709; _if message i was received before height h (and all messages j < i were received)_
Based on these guarantees, we can make a few modifications of the above
protocol to allow us to prove timeouts, by adding some fields to the messages
in the send queue, and defining an expired function that returns true iff
_h > maxHeight_ or _timestamp(H<sub>h </sub>) > maxTime_.
Based on these guarantees, we can make a few modifications of the above protocol to allow us to prove timeouts, by adding some fields to the messages in the send queue, and defining an expired function that returns true iff _h > maxHeight_ or _timestamp(H<sub>h </sub>) > maxTime_.
_V<sub>send</sub> = (maxHeight, maxTime, type, data)_
_expired(H<sub>h </sub>,V<sub>send </sub>)_ &#8658; _[true|false]_
We then update message handling in _IBCreceive_, so it doesn't even call the
handler function if the timeout was reached, but rather directly writes and
error in the receipt queue:
We then update message handling in _IBCreceive_, so it doesn't even call the handler function if the timeout was reached, but rather directly writes and error in the receipt queue:
_IBCreceive:_
* ….
* _expired(latestHeader, v)_ &#8658; _push(q<sub>S.receipt </sub>, (None, TimeoutError)),_
* _v = (\_, \_, type, data)_ &#8658; _(result, err) := f<sub>type</sub>(data); push(q<sub>S.receipt </sub>, (result, err));_
and add a new _IBCtimeout_ function to accept tail proofs to demonstrate that
the message was not processed at some given header on the recipient chain. This
allows the sender chain to assert timeouts locally.
and add a new _IBCtimeout_ function to accept tail proofs to demonstrate that the message was not processed at some given header on the recipient chain. This allows the sender chain to assert timeouts locally.
_S:IBCtimeout(A, M<sub>k,v,h</sub>)_ &#8658; _match_
* _q<sub>A.send</sub> =_ &#8709; &#8658; _Error("unregistered sender"),_
* _k = (\_, send, \_)_ &#8658; _Error("must be a receipt"),_
* _k = (d, \_, \_) and d_ &#8800; _S_ &#8658; _Error("sent to a different
chain"),_
* _H<sub>h</sub>_ &#8713; _T<sub>A</sub>_ &#8658; _Error("must submit header
for height h"),_
* _not valid(H<sub>h</sub> , M<sub>k,v,h </sub>)_ &#8658; _Error("invalid
merkle proof"),_
* _k = (d, \_, \_) and d_ &#8800; _S_ &#8658; _Error("sent to a different chain"),_
* _H<sub>h</sub>_ &#8713; _T<sub>A</sub>_ &#8658; _Error("must submit header for height h"),_
* _not valid(H<sub>h</sub> , M<sub>k,v,h </sub>)_ &#8658; _Error("invalid merkle proof"),_
* _k = (S, receipt, tail)_ &#8658; _match_
* _tail_ &#8805; _head(q<sub>S.send </sub>)_ &#8658; _Error("receipt
exists, no timeout proof")_
* _not expired(peek(q<sub>S.send </sub>))_ &#8658; _Error("message timeout
not yet reached")_
* _default_ &#8658; _(\_, \_, type, data) := pop(q<sub>S.send </sub>);
rollback<sub>type</sub>(data); Success_
* _tail_ &#8805; _head(q<sub>S.send </sub>)_ &#8658; _Error("receipt exists, no timeout proof")_
* _not expired(peek(q<sub>S.send </sub>))_ &#8658; _Error("message timeout not yet reached")_
* _default_ &#8658; _(\_, \_, type, data) := pop(q<sub>S.send </sub>); rollback<sub>type</sub>(data); Success_
* _default_ &#8658; _Error("must be a tail proof")_
which processes timeouts in order, and adds one more condition to the queues:
_A:M<sub>k,v,h</sub> =_ &#8709; _if message i was sent and timeout proven
before height h (and the receipts for all messages j < i were also handled)_
_A:M<sub>k,v,h</sub> =_ &#8709; _if message i was sent and timeout proven before height h (and the receipts for all messages j < i were also handled)_
Now chain A can rollback all transactions that were blocked by this flood of
unrelayed messages, without waiting for chain B to process them and return a
receipt. Adding reasonable timeouts to all packets allows us to gracefully
handle any errors with the IBC relay processes, or a flood of unrelayed "spam"
IBC packets. If a blockchain requires a timeout on all messages, and imposes
some reasonable upper limit (or just assigns it automatically), we can
guarantee that if message _i_ is not processed by the upper limit of the
timeout period, then all previous messages must also have either been processed
or reached the timeout period.
Now chain A can rollback all transactions that were blocked by this flood of unrelayed messages, without waiting for chain B to process them and return a receipt. Adding reasonable time outs to all packets allows us to gracefully handle any errors with the IBC relay processes, or a flood of unrelayed "spam" IBC packets. If a blockchain requires a timeout on all messages, and imposes some reasonable upper limit (or just assigns it automatically), we can guarantee that if message _i_ is not processed by the upper limit of the timeout period, then all previous messages must also have either been processed or reached the timeout period.
Note that in order to avoid any possible "double-spend" attacks, the timeout
algorithm requires that the destination chain is running and reachable. One can
prove nothing in a complete network partition, and must wait to connect; the
timeout must be proven on the recipient chain, not simply the absence of a
response on the sending chain.
Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain.
### 4.2 Clean up
While we clean up the _send queue_ upon getting a receipt, if left to run
indefinitely, the _receipt queues_ could grow without limit and create a major
storage requirement for the chains. However, we must not delete receipts until
they have been proven to be processed by the sending chain, or we lose
important information and sacrifice reliability.
While we clean up the _send queue_ upon getting a receipt, if left to run indefinitely, the _receipt queues_ could grow without limit and create a major storage requirement for the chains. However, we must not delete receipts until they have been proven to be processed by the sending chain, or we lose important information and sacrifice reliability.
The observant reader may also notice, that when we perform the timeout on the
sending chain, we do not update the _receipt queue_ on the receiving chain, and
now it is blocked waiting for a message _i_, which **no longer exists** on the
sending chain. We can update the guarantees of the receipt queue as follows to
allow us to handle both:
The observant reader may also notice, that when we perform the timeout on the sending chain, we do not update the _receipt queue_ on the receiving chain, and now it is blocked waiting for a message _i_, which **no longer exists** on the sending chain. We can update the guarantees of the receipt queue as follows to allow us to handle both:
_B:M<sub>k,v,h</sub> =_ &#8709; _if message i was not received before height h_
_B:M<sub>k,v,h</sub> =_ &#8709; _if message i was provably resolved on the
sending chain before height h_
_B:M<sub>k,v,h</sub> =_ &#8709; _if message i was provably resolved on the sending chain before height h_
_B:M<sub>k,v,h </sub>_ &#8800; &#8709; _otherwise (if message i was processed
before height h, and no ack of receipt from the sending chain)_
_B:M<sub>k,v,h </sub>_ &#8800; &#8709; _otherwise (if message i was processed before height h, and no ack of receipt from the sending chain)_
Consider a connection where many messages have been sent, and their receipts
processed on the sending chain, either explicitly or through a timeout. We wish
to quickly advance over all the processed messages, either for a normal
cleanup, or to prepare the queue for normal use again after timeouts.
Consider a connection where many messages have been sent, and their receipts processed on the sending chain, either explicitly or through a timeout. We wish to quickly advance over all the processed messages, either for a normal cleanup, or to prepare the queue for normal use again after timeouts.
Through the definition of the send queue above, we see that all messages
_i < head_ have been fully processed, and all messages _head <= i < tail_ are
awaiting processing. By proving a much advanced _head_ of the _send queue_, we
can demonstrate that the sending chain already handled all messages. Thus, we
can safely advance our local _receipt queue_ to the new head of the remote
_send queue_.
Through the definition of the send queue above, we see that all messages _i < head_ have been fully processed, and all messages _head <= i < tail_ are awaiting processing. By proving a much advanced _head_ of the _send queue_, we can demonstrate that the sending chain already handled all messages. Thus, we can safely advance our local _receipt queue_ to the new head of the remote _send queue_.
_S:IBCcleanup(A, M<sub>k,v,h</sub>)_ &#8658; _match_
* _q<sub>A.receipt</sub> =_ &#8709; &#8658; _Error("unknown sender"),_
* _k = (\_, send, \_)_ &#8658; _Error("must be for the send queue"),_
* _k = (d, \_, \_) and d_ &#8800; _S_ &#8658; _Error("sent to a different
chain"),_
* _k_ &#8800; _(\_, \_, head)_ &#8658; _Error("Need a proof of the head of
the queue"),_
* _H<sub>h</sub>_ &#8713; _T<sub>A</sub>_ &#8658; _Error("must submit header
for height h"),_
* _not valid(H<sub>h</sub> ,M<sub>k,v,h </sub>)_ &#8658; _Error("invalid
merkle proof"),_
* _k = (d, \_, \_) and d_ &#8800; _S_ &#8658; _Error("sent to a different chain"),_
* _k_ &#8800; _(\_, \_, head)_ &#8658; _Error("Need a proof of the head of the queue"),_
* _H<sub>h</sub>_ &#8713; _T<sub>A</sub>_ &#8658; _Error("must submit header for height h"),_
* _not valid(H<sub>h</sub> ,M<sub>k,v,h </sub>)_ &#8658; _Error("invalid merkle proof"),_
* _head := v_ &#8658; _match_
* _head <= head(q<sub>A.receipt</sub>)_ &#8658; _Error("cleanup must go
forward"),_
* _head <= head(q<sub>A.receipt</sub>)_ &#8658; _Error("cleanup must go forward"),_
* _default_ &#8658; _advance(q<sub>A.receipt </sub>, head); Success_
This allows us to invoke the _IBCcleanup_ function to resolve all outstanding
messages up to and including _head_ with one merkle proof. Note that this
handles both recovering from a blocked queue after timeouts, as well as a
routine cleanup method to recover space. In the cleanup scenario, we assume
that there may also be a number of messages that have been processed by the
receiving chain, but not yet posted to the sending chain,
_tail(B:q<sub>A.reciept </sub>) > head(A:q<sub>B.send </sub>)_. As such, the
_advance_ function must not modify any messages between the head and the tail.
This allows us to invoke the _IBCcleanup _function to resolve all outstanding messages up to and including _head_ with one merkle proof. Note that if this handles both recovering from a blocked queue after timeouts, as well as a routine cleanup method to recover space. In the cleanup scenario, we assume that there may also be a number of messages that have been processed by the receiving chain, but not yet posted to the sending chain, _tail(B:q<sub>A.reciept </sub>) > head(A:q<sub>B.send </sub>)_. As such, the _advance_ function must not modify any messages between the head and the tail.
![Cleaning up Packets](images/CleanUp.png)
### 4.3 Handling Byzantine Failures
While every message is guaranteed reliable in the face of malicious nodes or
relays, all guarantees break down when the entire blockchain on the other end
of the connection exhibits byzantine faults. These can be in two forms:
While every message is guaranteed reliable in the face of malicious nodes or relays, all guarantees break down when the entire blockchain on the other end of the connection exhibits byzantine faults. These can be in two forms: failures of the consensus mechanism (reversing "final" blocks), or failure at the application level (not performing the action defined by the message).
* failures of the consensus mechanism (reversing "final" blocks)
* failure at the application level (not performing the action defined by the
message).
The IBC protocol can only detect byzantine faults at the consensus level, and is designed to halt with an error upon detecting any such fault. That is, if it ever sees two different headers for the same height (or any evidence that headers belong to different forks), then it must freeze the connection immediately. The resolution of the fault must be handled by the blockchain governance, as this is a serious incident and cannot be predefined.
The IBC protocol can only detect byzantine faults at the consensus level, and
is designed to halt with an error upon detecting any such fault. That is, if it
ever sees two different headers for the same height (or any evidence that
headers belong to different forks), then it must freeze the connection
immediately. The resolution of the fault must be handled by the blockchain
governance, as this is a serious incident and cannot be predefined.
If there is a big divide in the remote chain and they split eg. 60-40 as to the direction of the chain, then the light-client protocol will refuses to follow either fork. If both sides declare a hard fork and continue with new validator sets that are not compatible with the consensus engine (they don't have ⅔ support from the previous block), then users will have to manually tell their local client which chain to follow (or fork and follow both with different IDs).
If there is a big divide in the remote chain and they split eg. 60-40 as to the
direction of the chain, then the light-client protocol will refuses to follow
either fork. If both sides declare a hard fork and continue with new validator
sets that are not compatible with the consensus engine (they don't have ⅔
support from the previous block), then users will have to manually tell their
local client which chain to follow (or fork and follow both with different IDs).
The IBC protocol doesn't have the option to follow both chains as the queue and associated state must map to exactly one remote chain. In a fork, the chain can continue the connection with one fork, and optionally make a fresh connection with the other fork (which will also have to adjust internally to wipe its view of the connection clean).
The IBC protocol doesn't have the option to follow both chains as the queue and
associated state must map to exactly one remote chain. In a fork, the chain can
continue the connection with one fork, and optionally make a fresh connection
with the other fork (which will also have to adjust internally to wipe its view
of the connection clean).
The other major byzantine action is at the application level. Let us assume messages represent transfer of value. If chain A sends a message with X tokens to chain B, then it promises to remove X tokens from the local supply. And if chain B handles this message with a success code, it promises to credit X tokens to the account mentioned in the message. What if A isn't actually removing tokens from the supply, or if B is not actually crediting accounts?
The other major byzantine action is at the application level. Let us assume
messages represent transfer of value. If chain A sends a message with X tokens
to chain B, then it promises to remove X tokens from the local supply. And if
chain B handles this message with a success code, it promises to credit X
tokens to the account mentioned in the message. What if A isn't actually
removing tokens from the supply, or if B is not actually crediting accounts?
Such application level issues cannot be proven in a generic sense, but must be
handled individually by each application. The activity should be provable in
some manner (as it is all in an auditable blockchain), but there are too many
failure modes to attempt to enumerate, so we rely on the vigilance of the
participants in the extremely rare case of a rogue blockchain. Of course, this
misbehavior is provable and can negatively impact the value of the offending
chain, providing economic incentives for any normal chain not to run malicious
applications over IBC.
Such application level issues cannot be proven in a generic sense, but must be handled individually by each application. The activity should be provable in some manner (as it is all in an auditable blockchain), but there are too many failure modes to attempt to enumerate, so we rely on the vigilance of the participants in the extremely rare case of a rogue blockchain. Of course, this misbehavior is provable and can negatively impact the value of the offending chain, providing economic incentives for any normal chain not to run malicious applications over IBC.

View File

@ -2,109 +2,38 @@
([Back to table of contents](specification.md#contents))
The IBC protocol creates a mechanism by which multiple sovereign replicated
fault tolerant state machines may pass messages to each other. These messages
provide a base layer for the creation of communicating the blockchain
architecture that overcomes challenges in the scalability and extensibility of
computing blockchain environments.
The IBC protocol creates a mechanism by which multiple sovereign replicated fault tolerant state machines my pass messages to each other. These messages provide a base layer for the creation of communicating blockchain architecture that overcomes challenges in the scalability and extensibility of computing blockchain environments.
The IBC protocol assumes that multiple applications are running on their own
blockchain with their own state and logic. Communication is achieved over a
secure message queue protocol, allowing the creation of complex inter-chain
processes without trusted parties. This architecture can be seen as a parallel
to microservices in the blockchain space, and the IBC protocol can be seen as
an analog to the AMQP messaging protocol[[2](./footnotes.md#2)], used by
StormMQ, RabbitMQ, etc.
The IBC protocol assumes that multiple applications are running on their own blockchain with their own state and own logic. Communication is achieved over an extremely secure message queue protocol, allowing the creation of complex inter-chain processes without trusted parties. This architecture can be seen as a parallel to microservices in the blockchain space, and the IBC protocol can be seen as an analog to the AMQP messaging protocol[[2](./footnotes.md#2)], used by StormMQ, RabbitMQ, etc.
The message packets are not signed by one pseudonymous account, or even
multiple. Rather, IBC effectively assigns authorization of the packets to the
blockchain's consensus algorithm itself. Not only are blockchains highly
secure, they are auditable and have an extremely high creation cost in
comparison to cryptographic key pairs. This prevents sybil attacks and allows
out-of-protocol accountability, since any byzantine behavior is provable and
can be published to damage the reputation/value of the other blockchain. By
using registered blockchains as "actors" in the system, we can achieve
extremely high security through a combination of cryptography and incentives.
The message packets are not signed by one psuedonymous account, or even multiple. Rather, IBC effectively assigns authorization of the packets to the blockchain's consensus algorithm itself. Not only are blockchains highly secure, they are auditable and have an extremely high creation cost in comparison to cryptographic key pairs. This prevents Sybil attacks and allows out-of-protocol accountability, since any byzantine behavior is provable and can be published to damage the reputation/value of the other blockchain. By using registered blockchains as "actors" in the system, we can achieve extremely high security through a combination of cryptography and incentives.
In this paper, we define a process of posting block headers and merkle proofs
to enable secure verification of individual packets. We then describe how to
combine these packets into a messaging queue to guarantee reliable, in-order
delivery of messages. We then explain how to securely handle receipts
(response/error), which enables the creation of asynchronous RPC-like
protocols. Finally, we detail some optimizations and how to handle byzantine
blockchains.
In this paper, we define a process of posting block headers and merkle proofs to enable secure verification of individual packets. We then describe how to combine these packets into a messaging queue to guarantee reliable, in-order delivery of message. We then explain how to securely handle receipts (response/error), which enables the creation of asynchronous RPC-like protocols. Finally, we detail some optimizations and how to handle byzantine blockchains.
### 1.1 Definitions
_Blockchain_ - an immutable ledger created through distributed consensus,
coupled with a deterministic state machine to process the transactions on the
ledger. The smallest unit produced through consensus is a block, which may
contain many transactions.
_Blockchain_ - an immutable ledger created through distributed consensus, coupled with a deterministic state machine to process the transactions on the ledger. The smallest unit produced through consensus is a block, which may contain many transactions.
_Module_ - we assume that the state machine of the blockchain is comprised of
multiple components (modules or smart contracts) that have limited rights, and
that they can only interact over pre-defined interfaces rather than directly
mutating internal state.
_Module_ - we assume that the state machine of the blockchain is comprised of multiple components (modules or smart contracts) that have limited rights, and they can only interact over pre-defined interfaces rather than directly mutating internal state.
_Finality_ - a guarantee that a given block will not be reverted within some
predefined conditions. All proof of work systems offer probabilistic finality,
which means the probability of that a block will be reverted approaches 0. A
"better", alternative chain could exist, but the cost of creation increases
rapidly over time. Many proof of stake systems offer much weaker guarantees,
based only on the honesty of the validators. However, BFT algorithms such as
Tendermint guarantee complete finality upon production of a block, unless over
two thirds of the validators collude to break consensus. This collusion is
provable and can be punished.
_Finality_ - a guarantee that a given block will not be reverted within some predefined conditions. All proof of work systems offer probabilistic finality, which means the probability of that a block will be reverted approaches 0. A "better", alternative chain could exist, but the cost of creation increases rapidly over time. Many "proof of stake" systems offer much weaker guarantees, based only on the honesty of the miners. However, BFT algorithms such as Tendermint guarantee complete finality upon production of a block, unless over two thirds of the validators collude to break consensus. This collusion is provable and can be punished.
_Knowledge_ - what is certain to be true.
_Provable_ - the existence of irrefutable mathematical (often cryptographic)
proof of the truth of a given statement. These can be expressed as:
given knowledge **A** and a statement **s**, then **B** must be true.
This is a form of deductive proof and they can be chained together without
losing validity.
_Provable_ - the existence of irrefutable mathematical (often cryptographic) proof of the truth of a given statement. These can be expressed as: given knowledge **A** and a statement **s**, then **B** must be true. This is a form of deductive proof and they can be chained together without losing validity.
_Attributable_ - provable knowledge of who made a statement. If a statement is
provably false, then it is known which actor lied. Attributable statements
allow us to build incentives against lying. This is also referred to as
accountability.
_Attributable_ - provable knowledge of who made a statement. If a statement is provably false, then it is known which actor lied. Attributable statements allow us to build incentives against lying, which help enforce finality. This is also referred to as accountability.
_Root of Trust_ - any proof depends on some prior assumptions, however simple
they are. We refer to the first assumption we make as the root of trust, and
all our knowledge of the system is derived from this root through a provable
chain of information. We seek to make this root of trust as simple and
verifiable as possible, since if the original assignment of trust is false, all
conclusions drawn will also be false.
_Root of Trust_ - any proof depends on some prior assumptions, however simple they are. We refer to the first assumption we make as the root of trust, and all our knowledge of the system is derived from this root through a provable chain of information. We seek to make this root of trust as simple and a verifiable as possible, since if the original assignment of trust is false, all conclusions drawn will also be false.
_Unbonding Period_ - Proof of Stake algorithms need to freeze the stake for
some time to provide a lower bound for the length of a long-range
attack [[3](./footnotes.md#3)]. Since complete finality is associated with a
subset of the proof of stake class of consensus algorithms, I will assume all
implementations that support IBC have some unbonding period P, such that if my
last knowledge of the blockchain is older than P, I can no longer trust any
message without a new root of trust.
_Unbonding Period_ - Proof of Stake algorithms need to freeze the stake for some time to provide a lower bound for the length of a long-range attack [[3](./footnotes.md#3)]. Since complete finality is associated with a subset of the Proof of Stake class of consensus algorithms, I will assume all implementations that support IBC have some unbonding period P, such that if my last knowledge of the blockchain is older than P, I can no longer trust any message without a new root of trust.
The IBC protocol requires each actor to be a blockchain with deterministic
finality. All transitions must be provable and attributable to (at least) one
actor. That implies the smallest unit of trust is the consensus algorithm of a
blockchain.
The IBC protocol requires each actor to be a blockchain with complete finality. All transitions must be provable and attributable to (at least) one actor. That implies the smallest unit of trust is the consensus algorithm of a blockchain.
### 1.2 Threat Models
_False statements_ - any information we receive may be false, all actors must
have enough knowledge be able to prove its correctness without external
dependencies. All statements should be attributable.
_False statements_ - any information we receive may be false, all actors must have enough knowledge be able to prove its correctness without external dependencies. All statements should be attributable.
_Network partitions and delays_ - we assume an asynchronous, adversarial
network. Any message may or may not reach the destination. They may be modified
or selectively dropped. Messages may reach the destination out of order and may
arrive multiple times. There is no upper limit to the time it takes for a
message to be received. Actors may be arbitrarily partitioned by an adversary.
The protocol favors correctness over liveness. That is, it only acts upon
information that is provably correct.
_Network partitions and delays_ - we assume an asynchronous, adversarial network. Any message may or may not reach the destination. They may be modified or selectively dropped. Messages may reach the destination out of order and may arrive multiple times. There is no upper limit to the time it takes for a message to be received. Actors may be arbitrarily partitioned by a powerful adversary. The protocol favors correctness over liveness. That is, it only acts upon information that is provably correct.
_Byzantine actors_ - it is possible that an entire blockchain is not acting
according to protocol. This must be detectable and provable, allowing the
communicating blockchain to revoke trust and take necessary action.
Furthermore, we should design application-level protocols on top of IBC to
minimize the risk exposure in the face of Byzantine actors.
_Byzantine actors_ - it is possible that an entire blockchain is not acting according to protocol. This must be detectable and provable, allowing the communicating blockchain to revoke trust and take necessary action. Furthermore, we should design application-level protocols on top of IBC to minimize risk exposure in the face of Byzantine actors.

View File

@ -2,98 +2,47 @@
([Back to table of contents](specification.md#contents))
The basis of IBC is the ability to perform efficient proofs of a message packet
on-chain and deterministically. All transactions must be attributable and
provable without depending on any information outside of the blockchain. We
define the following variables: _H<sub>h</sub>_ is the signed header at height
_h_, _C<sub>h</sub>_ are the consensus rules at height _h_, and _P_ is the
unbonding period of this blockchain. _V<sub>k,h</sub>_ is the value stored
under key _k_ at height _h_. Note that out of all of these, only
_H<sub>h</sub>_ defines a signature and is thus attributable.
The basis of IBC is the ability to perform efficient proofs of a message packet on-chain and deterministically. All transactions must be attributable and provable without depending on any information outside of the blockchain. We define the following variables: _H<sub>h</sub>_ is the signed header at height _h_, _C<sub>h</sub>_ are the consensus rules at height _h_, and _P_ is the unbonding period of this blockchain. _V<sub>k,h</sub>_ is the value stored under key _k_ at height _h_. Note that of all these, only _H<sub>h</sub>_ defines a signature and is thus attributable.
To support an IBC connection, two actors must be able to make the following
proofs to each other:
To support an IBC connection, two actors must be able to make the following proofs to each other:
* given a trusted _H<sub>h</sub>_ and _C<sub>h</sub>_ and an attributable
update message _U<sub>h'</sub>_ it is possible to prove _H<sub>h'</sub>_
where _C<sub>h'</sub> = C<sub>h</sub>_ and &#916;_(now, H<sub>h</sub>) < P_
* given a trusted _H<sub>h</sub>_ and _C<sub>h</sub>_ and an attributable
change message _X<sub>h'</sub>_ it is possible to prove _H<sub>h'</sub>_
where _C<sub>h'</sub>_ &#8800; _C<sub>h</sub>_ and
&#916; _(now, H<sub>h</sub>) < P_
* given a trusted _H<sub>h</sub>_ and a merkle proof _M<sub>k,v,h</sub>_ it is
possible to prove _V<sub>k,h</sub>_
* given a trusted _H<sub>h</sub>_ and _C<sub>h</sub>_ and an attributable update message _U<sub>h'</sub>_ it is possible to prove _H<sub>h'</sub>_ where _C<sub>h'</sub> = C<sub>h</sub>_ and &#916;_(now, H<sub>h</sub>) < P_
* given a trusted _H<sub>h</sub>_ and _C<sub>h</sub>_ and an attributable change message _X<sub>h'</sub>_ it is possible to prove _H<sub>h'</sub>_ where _C<sub>h'</sub>_ &#8800; _C<sub>h</sub>_ and &#916; _(now, H<sub>h</sub>) < P_
* given a trusted _H<sub>h</sub>_ and a merkle proof _M<sub>k,v,h</sub>_ it is possible to prove _V<sub>k,h</sub>_
It is possible to make use of the structure of BFT consensus to construct
extremely lightweight and provable messages _U<sub>h'</sub>_ and
_X<sub>h'</sub>_. The implementation of these requirements with Tendermint is
defined in Appendix E. Another engine that is able to provide equally strong
guarantees (such as Casper) is compatible with IBC, and must define its own set
of update/change messages.
It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages _U<sub>h'</sub>_ and _X<sub>h'</sub>_. The implementation of these requirements with Tendermint is defined in Appendix E. Another engine able to provide equally strong guarantees (such as Casper) should be theoretically compatible with IBC, and must define its own set of update/change messages.
The merkle proof _M<sub>k,v,h</sub>_ is a well-defined concept in the
blockchain space, and provides a compact proof that the key value pair (_k, v)_
is consistent with a merkle root stored in _H<sub>h</sub>_. Handling the case
where _k_ is not in the store requires a separate proof of non-existence, which
is not supported by all merkle stores. Thus, we define the proof only as a
proof of existence. There is no valid proof for missing keys, and we design the
algorithm to work without it.
The merkle proof _M<sub>k,v,h</sub>_ is a well-defined concept in the blockchain space, and provides a compact proof that the key value pair (_k, v)_ is consistent with a merkle root stored in _H<sub>h</sub>_. Handling the case where _k_ is not in the store requires a separate proof of non-existence, which is not supported by all merkle stores. Thus, we define the proof only as a proof of existence. There is no valid proof for missing keys, and we design the algorithm to work without it.
_valid(H<sub>h </sub>,M<sub>k,v,h </sub>)_ &#8658; _[true | false]_
### 2.1 Establishing a Root of Trust
As mentioned in the definitions, all proofs are based on an original
assumption. In this case it is _H<sub>h</sub>_ and _C<sub>h</sub>_ for some
_h_, where &#916;_(now, H<sub>h</sub>) < P_.
As mentioned in the definitions, all proofs are based on an original assumption. In this case it is _H<sub>h</sub>_ and _C<sub>h</sub>_ for some _h_, where &#916;_(now, H<sub>h</sub>) < P_.
Any header may be from a malicious chain (eg. shadowing a real chain id with a
fake validator set), so a subjective decision is required before establishing a
connection. This should be performed by on-chain governance to avoid an
exploitable position of trust. Establishing a bidirectional root of trust
between two blockchains (A trusts B and B trusts A) is a necessary and
sufficient prerequisite for all other IBC activity.
Any header may be from a malicious chain (eg. shadowing a real chain id with a fake validator set), so a subjective decision is required before establishing a connection. This should be performed by on-chain governance to avoid an exploitable position of trust. Establishing a bidirectional root of trust between two blockchains (A trusts B and B trusts A) is a necessary and sufficient prerequisite for all other IBC activity.
Development of a fully open and decentralized PKI for tracking blockchains is
an open research question for future iterations of the IBC protocol.
Development of a fully open and decentralized PKI for tracking blockchains is an open research question for future iterations of the IBC protocol.
### 2.2 Following Block Headers
We define two messages _U<sub>h</sub>_ and _X<sub>h</sub>_, which together
allow us to securely advance our trust from some known _H<sub>n</sub>_ to a
future _H<sub>h</sub>_ where _h > n_. Some implementations may provide the
additional limitation that _h = n + 1_, which requires us to process every
header. Tendermint allows us to exploit knowledge of the BFT algorithm to only
require the additional limitation
&#916;_<sub>vals</sub>(C<sub>n</sub>, C<sub>h</sub> ) < ⅓_, that each step must
have a change of less than one-third of the validator
set[[4](./footnotes.md#4)].
We define two messages _U<sub>h</sub>_ and _X<sub>h</sub>_, which together allow us to securely advance our trust from some known _H<sub>n</sub>_ to a future _H<sub>h</sub>_ where _h > n_. Some implementations may provide the additional limitation that _h = n + 1_, which requires us to process every header. Tendermint allows us to exploit knowledge of the BFT algorithm to only require the additional limitation
Any of these requirements allows us to support IBC for the given block chain.
However, by supporting proofs where _h_-_n > 1_, we can follow the block
headers much more efficiently in situations where the majority of blocks do not
include an IBC message between chains A and B, and enable low-bandwidth
connections to be implemented at very low cost. If there are messages to relay
every block, then these collapse to the same case, relaying every header.
&#916;_<sub>vals</sub>(C<sub>n</sub>, C<sub>h</sub> ) < ⅓_, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)].
Since these messages _U<sub>h</sub>_ and _X<sub>h</sub>_ provide all knowledge
of the remote blockchain, we require that they not just be provable, but also
attributable. As such any attempt to violate the finality guarantees or provide
fake proof can be submitted to the remote blockchain for punishment, in the
same manner that any violation of the internal consensus algorithm is punished.
This incentive enhances the security guarantees and avoids the nothing-at-stake
issue in IBC as well.
Any of these requirements allows us to support IBC for the given block chain. However, by supporting proofs where _h_-_n > 1_, we can follow the block headers much more efficiently in situations where the majority of blocks do not include an IBC message between chains A and B, and enable low-bandwidth connections to be implemented at very low cost. If there are messages to relay every block, then these collapse to the same case, relaying every header.
Since these messages _U<sub>h</sub>_ and _X<sub>h</sub>_ provide all knowledge of the remote blockchain, we require that they not just be provable, but also attributable. As such any attempt to violate the finality guarantees or provide fake proof can be submitted to the remote blockchain for punishment, in the same manner that any violation of the internal consensus algorithm is punished. This incentive enhances the security guarantees and avoids the nothing-at-stake issue in IBC as well.
More formally, given existing set of trust _T_ = _{(H<sub>i </sub>, C<sub>i </sub>), (H<sub>j </sub>, C<sub>j </sub>), …}_, we must provide:
More formally, given existing set of trust
_T_ = _{(H<sub>i </sub>, C<sub>i </sub>), (H<sub>j </sub>, C<sub>j </sub>), …}_
we must provide:
_valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>)_ &#8658; _[true | false | unknown]_
_if H<sub>h-1</sub>_ &#8712; _T then_:
* _valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>)_ &#8658; _[true | false]_
* _there must exist some U<sub>h</sub> or X<sub>h</sub> that evaluates to true_
_if C<sub>h</sub>_ &#8713; _T then_:
_if C<sub>h</sub>_ &#8713; _T then_
* _valid(T, U<sub>h </sub>)_ &#8658; _false_
and can process update transactions as follows:
@ -104,17 +53,6 @@ _ match valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>)_
* _unknown_ &#8658; _return Error("need a proof between current and h")_
* _true_ &#8658; _T_ &#8746; _(H<sub>h </sub>,C<sub>h </sub>)_
We define _max(T)_ as _max(h, where H<sub>h</sub>_ &#8712; _T)_ for any _T_
with _max(T) = h-1_. And from above, there must exist some
_X<sub>h </sub>|<sub> </sub>U<sub>h</sub>_ so that
_max(update(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>)) = h_. By induction,
we can see there must exist a set of proofs, such that
_max(update…(T,...)) = h+n_ for any n.
We define _max(T)_ as _max(h, where H<sub>h</sub>_ &#8712; _T)_ for any _T_ with _max(T) = h-1_. And from above, there must exist some _X<sub>h </sub>|<sub> </sub>U<sub>h</sub>_ so that _max(update(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>)) = h_. By induction, we can see there must exist a set of proofs, such that _max(update…(T,...)) = h+n_ for any n.
We also can see the validity of using bisection as an optimization to discover
this set of proofs. That is, given _max(T) = n_ and
_valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>) = unknown_, we then try
_update(T, X<sub>b </sub>|<sub> </sub>U<sub>b </sub>)_, where _b = (h+n)/2_.
The base case is where
_valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>) = true_ and is guaranteed
to exist if _h=max(T)+1_.
We also can see the validity of using bisection as an optimization to discover this set of proofs. That is, given _max(T) = n_ and _valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>) = unknown_, we then try _update(T, X<sub>b </sub>|<sub> </sub>U<sub>b </sub>)_, where _b = (h+n)/2_. The base case is where _valid(T, X<sub>h </sub>|<sub> </sub>U<sub>h </sub>) = true_ and is guaranteed to exist if _h=max(T)+1_.

View File

@ -2,72 +2,41 @@
([Back to table of contents](specification.md#contents))
Messaging in distributed systems is a deeply researched field and a primitive
upon which many other systems are built. We can model asynchronous message
passing, and make no timing assumptions on the communication channels. By doing
this, we allow each zone to move at its own speed, unblocked by any other zone,
but able to communicate as fast as the network allows at that moment.
Messaging in distributed systems is a deeply researched field and a primitive upon which many other systems are built. We can model asynchronous message passing, and make no timing assumptions on the communication channels. By doing this, we allow each zone to move at its own speed, unblocked by any other zone, but able to communicate as fast as the network allows at that moment.
Another benefit of using message passing as our primitive, is that the receiver
decides how to act upon the incoming message. Just because one zone sends a
message and we have an IBC connection with this zone, doesn't mean we have to
execute the requested action. Each zone can run its own business logic upon
receiving the message to decide whether to accept or reject the message. To
maintain consistency, both sides must only agree on the proper state
transitions associated with accepting or rejecting.
Another benefit of using message passing as our primitive, is that the receiver decides how to act upon the incoming message. Just because one zone sends a message and we have an IBC connection with this zone, doesn't mean we have to execute the requested action. Each zone can add its own business logic upon receiving the message to decide whether to accept or reject the message. To maintain consistency, both sides must only agree on the proper state transitions associated with accepting or rejecting.
This encapsulation is very difficult to impossible to achieve in a shared-state
scenario. Message passing allows each zone to ensure its security and autonomy,
while simultaneously allowing the different systems to work as one whole. This
can be seen as an analogue to a micro-services architecture, but across
organizational boundaries.
This encapsulation is very difficult to impossible to achieve in a shared-state scenario. Message passing allows each zone to ensure its security and autonomy, while simultaneously allowing the different systems to work as one whole. This can be seen as an analogue to a microservices architecture, but across organizational boundaries.
To build useful algorithms upon a provable asynchronous messaging primitive, we
introduce a reliable messaging queue (hereafter just referred to as a queue),
typical in asynchronous message passing, to allow us to guarantee a causal
ordering[[5](./footnotes.md#5)], and avoid blocking.
To build useful algorithms upon a provable asynchronous messaging primitive, we introduce a reliable messaging queue (hereafter just referred to as a queue), typical in asynchronous message passing, to allow us to guarantee a causal ordering[[5](./footnotes.md#5)], and avoid blocking.
Causal ordering means that if _x_ is causally before _y_ on chain A, it must
also be on chain B. Many events may happen concurrently (unrelated tx on two
different blockchains) with no causality relation, but every transaction on the
same chain has a clear causality relation (same as the order in the
blockchain).
Causal ordering means that if _x_ is causally before _y_ on chain A, it must also be on chain B. Many events may happen concurrently (unrelated tx on two different blockchains) with no causality relation, but every transaction on the same chain has a clear causality relation (same as the order in the blockchain).
Message passing implies a causal ordering over multiple chains and these can be
important for reasoning on the system. Given _x_ &#8594; _y_ means _x_ is
causally before _y_, and chains A and B, and _a_ &#8658; _b_ means _a_ implies
_b_:
Message passing implies a causal ordering over multiple chains and these can be important for reasoning on the system. Given _x_ &#8594; _y_ means _x_ is causally before _y_, and chains A and B, and _a_ &#8658; _b_ means _a_ implies _b_:
_A:send(msg<sub>i </sub>)_ &#8594; _B:receive(msg<sub>i </sub>)_
_B:receive(msg<sub>i </sub>)_ &#8594; _A:receipt(msg<sub>i </sub>)_
_A:send(msg<sub>i </sub>)_ &#8594; _A:send(msg<sub>i+1 </sub>)_
_x_ &#8594; _A:send(msg<sub>i </sub>)_ &#8658;
_x_ &#8594; _B:receive(msg<sub>i </sub>)_
_y_ &#8594; _B:receive(msg<sub>i </sub>)_ &#8658;
_y_ &#8594; _A:receipt(msg<sub>i </sub>)_
* _A:send(msg<sub>i </sub>)_ &#8594; _B:receive(msg<sub>i </sub>)_
* _B:receive(msg<sub>i </sub>)_ &#8594; _A:receipt(msg<sub>i </sub>)_
* _A:send(msg<sub>i </sub>)_ &#8594; _A:send(msg<sub>i+1 </sub>)_
* _x_ &#8594; _A:send(msg<sub>i </sub>)_ &#8658;
* _x_ &#8594; _B:receive(msg<sub>i </sub>)_
* _y_ &#8594; _B:receive(msg<sub>i </sub>)_ &#8658;
* _y_ &#8594; _A:receipt(msg<sub>i </sub>)_
![Vector Clock image](https://upload.wikimedia.org/wikipedia/commons/5/55/Vector_Clock.svg)
![](https://en.wikipedia.org/wiki/Vector_clock)
In this section, we define an efficient implementation of a secure, reliable
messaging queue.
([https://en.wikipedia.org/wiki/Vector_clock](https://en.wikipedia.org/wiki/Vector_clock))
In this section, we define an efficient implementation of a secure, reliable messaging queue.
### 3.1 Merkle Proofs for Queues
Given the three proofs we have available, we make use of the most flexible one,
_M<sub>k,v,h</sub>_, to provide proofs for a message queue. To do so, we must
define a unique, deterministic, and predictable key in the merkle store for
each message in the queue. We also define a clearly defined format for the
content of each message in the queue, which can be parsed by all chains
participating in IBC. The key format and queue ordering are conceptually
explained here. The binary encoding format can be found in Appendix C.
Given the three proofs we have available, we make use of the most flexible one, _M<sub>k,v,h</sub>_, to provide proofs for a message queue. To do so, we must define a unique, deterministic, and predictable key in the merkle store for each message in the queue. We also define a clearly defined format for the content of each message in the queue, which can be parsed by all chains participating in IBC. The key format and queue ordering are conceptually explained here. The binary encoding format can be found in Appendix C.
We can visualize a queue as a slice pointing into an infinite sized array. It
maintains a head and a tail pointing to two indexes, such that there is data
for every index where _head <= index < tail_. Data is pushed to the tail and
popped from the head. Another method, _advance_, is introduced to pop all
messages until _i_, and is useful for cleanup:
We can visualize a queue as a slice pointing into an infinite sized array. It maintains a head and a tail pointing to two indexes, such that there is data for every index where _head <= index < tail_. Data is pushed to the tail and popped from the head. Another method, _advance_, is introduced to pop all messages until _i_, and is useful for cleanup:
**init**: _q<sub>head</sub> = q<sub>tail</sub> = 0_
@ -83,68 +52,32 @@ messages until _i_, and is useful for cleanup:
**tail** &#8658; **i**: _q<sub>tail</sub>_
Based upon this needed functionality, we define a set of keys to be stored in
the merkle tree, which allows us to efficiently implement and prove any of the
above queries.
Based upon this needed functionality, we define a set of keys to be stored in the merkle tree, which allows us to efficiently implement and prove any of the above queries.
**Key:** _(queue name, [head|tail|index])_
The index is stored as a fixed-length unsigned integer in big endian format, so
that the lexicographical order of the byte representation of the key is
consistent with their sequence number. This allows us to quickly iterate over
the queue, as well as prove the content of a packet (or lack of packet) at a
given sequence. _head_ and _tail_ are two special constants that store an
integer index, and are chosen such that their serialization cannot collide with
any possible index.
The index is stored as a fixed-length unsigned integer in big endian format, so that the lexicographical order of the byte representation of the key is consistent with their sequence number. This allows us to quickly iterate over the queue, as well as prove the content of a packet (or lack of packet) at a given sequence. _head_ and _tail_ are two special constants that store an integer index, and are chosen such that their serialization cannot collide with any possible index.
A message queue is simply a set of serialized packets stored at predefined keys
in a merkle store, which can produce proofs for any key. Once a packet is
written it must be immutable (except for deleting when popped from the queue).
That is, if a value _v_ is written to a queue, then every valid proof
_M<sub>k,v,h </sub>_ must refer to the same _v_. This property is essential to
safely process asynchronous messages.
A message queue is simply a set of serialized packets stored at predefined keys in a merkle store, which can produce proofs for any key. Once a packet is written it must be immutable (except for deleting when popped from the queue). That is, if a value _v_ is written to a queue, then every valid proof _M<sub>k,v,h </sub>_ must refer to the same _v_. This property is essential to safely process asynchronous messages.
Every IBC implementation must provide a protected subspace of the merkle store
for use by each queue that cannot be affected by other modules.
Every IBC implementation must provide a protected subspace of the merkle store for use by each queue that cannot be affected by other modules.
### 3.2 Naming Queues
As mentioned above, in order for the receiver to unambiguously interpret the
merkle proofs, we need a unique, deterministic, and predictable key in the
merkle store for each message in the queue. We explained how the indexes are
generated to provide each message in a queue a unique key, and mentioned the
need for a unique name for each queue.
As mentioned above, in order for the receiver to unambiguously interpret the merkle proofs, we need a unique, deterministic, and predictable key in the merkle store for each message in the queue. We explained how the indexes are generated to provide each message in a queue a unique key, and mentioned the need for a unique name for each queue.
The queue name must be unambiguously associated with a given connection to
another chain, so an observer can prove if a message was intended for chain A
or chain B. In order to do so, upon registration of a connection with a remote
chain, we create two queues with different names (prefixes).
The queue name must be unambiguously associated with a given connection to another chain, so an observer can prove if a message was intended for chain A or chain B. In order to do so, upon registration of a connection with a remote chain, we create two queues with different names (prefixes).
* _ibc:<chain id of A>:send_ - all outgoing packets destined to chain A
* _ibc:<chain id of A>:receipt_ - the results of executing the packets received from chain A
These two queues have different purposes and store messages of different types.
By parsing the key of a merkle proof, a recipient can uniquely identify which
queue, if any, this message belongs to. We now define
_k =_ _(remote id, [send|receipt], index)_. This tuple is used to route and
verify every message, before the contents of the packet are processed by the
appropriate application logic.
These two queues have different purposes and store messages of different types. By parsing the key of a merkle proof, a recipient can uniquely identify which queue, if any, this message belongs to. We now define _k =_ _(remote id, [send|receipt], index)_. This tuple is used to route and verify every message, before the contents of the packet are processed by the appropriate application logic.
### 3.3 Message Contents
Up to this point, we have focused on the semantics of the message key, and how
we can produce a unique identifier for every possible message in every possible
connection. The actual data written at the location has been left as an opaque
blob, but by providing some structure to the messages, we can enable more
functionality.
Up to this point, we have focused on the semantics of the message key, and how we can produce a unique identifier for every possible message in every possible connection. The actual data written at the location has been left as an opaque blob, put by providing some structure to the messages, we can enable more functionality.
We define every message in a _send queue_ to consist of a well-known type and
opaque data. The IBC protocol relies on the type for routing, and lets the
appropriate module process the data as it sees fit. The _receipt queue_ stores
if it was an error, an optional error code, and an optional return value. We
use the same index as the received message, so that the results of
_A:q<sub>B.send</sub>[i]_ are stored at _B:q<sub>A.receipt</sub>[i]_. (read:
the message at index _i_ in the _send_ queue for chain B as stored on chain A)
We define every message in a _send queue_ to consist of a well-known type and opaque data. The IBC protocol relies on the type for routing, and lets the appropriate module process the data as it sees fit. The _receipt queue_ stores if it was an error, an optional error code, and an optional return value. We use the same index as the received message, so that the results of _A:q<sub>B.send</sub>[i]_ are stored at _B:q<sub>A.receipt</sub>[i]_. (read: the message at index _i_ in the _send_ queue for chain B as stored on chain A)
_V<sub>send</sub> = (type, data)_
@ -152,32 +85,16 @@ _V<sub>receipt</sub> = (result, [success|error code])_
### 3.4 Sending a Message
A proper implementation of IBC requires all relevant state to be encapsulated,
so that other modules can only interact with it via a fixed API (to be defined
in the next sections) rather than directly mutating internal state. This allows
the IBC module to provide security guarantees.
A proper implementation of IBC requires all relevant state to be encapsulated, so that other modules can only interact with it via a fixed API (to be defined in the next sections) rather than directly mutating internal state. This allows the IBC module to provide security guarantees.
Sending an IBC packet involves an application module calling the send method of
the IBC module with a packet and a destination chain id. The IBC module must
ensure that the destination chain was already properly registered, and that the
calling module has permission to write this packet. If so, the IBC module
simply pushes the packet to the tail of the _send queue_, which enables all the
proofs described above.
Sending an IBC packet involves an application module calling the send method of the IBC module with a packet and a destination chain id. The IBC module must ensure that the destination chain was already properly registered, and that the calling module has permission to write this packet. If so, the IBC module simply pushes the packet to the tail of the _send queue_, which enables all the proofs described above.
The permissioning of which module can write which packet can be defined per
type, so this module can maintain any application-level invariants related to
this area. Thus, the "coin" module can maintain the constant supply of tokens,
while another module can maintain its own invariants, without IBC messages
providing a means to escape their encapsulations. The IBC module must associate
every supported message type with a particular handler (_f<sub>type</sub>_) and
return an error for unsupported types.
The permissioning of which module can write which packet can be defined per type, so this module can maintain any application-level invariants related to this area. Thus, the "coin" module can maintain the constant supply of tokens, while another module can maintain its own invariants, without IBC messages providing a means to escape their encapsulations. The IBC module must associate every supported message type with a particular handler (_f<sub>type</sub>_) and return an error for unsupported types.
_(IBCsend(D, type, data)_ &#8658; _Success)_
&#8658; _push(q<sub>D.send</sub> ,V<sub>send</sub>{type, data})_
We also consider how a given blockchain _A_ is expected to receive the packet
from a source chain _S_ with a merkle proof, given the current set of trusted
headers for that chain, _T<sub>S</sub>_:
We also consider how a given blockchain _A_ is expected to receive the packet from a source chain _S_ with a merkle proof, given the current set of trusted headers for that chain, _T<sub>S</sub>_:
_A:IBCreceive(S, M<sub>k,v,h</sub>)_ &#8658; _match_
* _q<sub>S.receipt</sub> =_ &#8709; &#8658; _Error("unregistered sender"),_
@ -188,30 +105,13 @@ _A:IBCreceive(S, M<sub>k,v,h</sub>)_ &#8658; _match_
* _valid(H<sub>h</sub> ,M<sub>k,v,h </sub>) = false_ &#8658; _Error("invalid merkle proof"),_
* _v = (type, data)_ &#8658; _(result, err) := f<sub>type</sub>(data); push(q<sub>S.receipt </sub>, (result, err)); Success_
Note that this requires not only a valid proof, but also that the proper header
as well as all prior messages were previously submitted. This returns success
upon accepting a proper message, even if the message execution returned an
error (which must then be relayed to the sender).
Note that this requires not only an valid proof, but also that the proper header as well as all prior messages were previously submitted. This returns success upon accepting a proper message, even if the message execution returned an error (which must then be relayed to the sender).
### 3.5 Receipts
When we wish to create a transaction that atomically commits or rolls back
across two chains, we must look at the receipts from sending the original
message. For example, if I want to send tokens from Alice on chain A to Bob on
chain B, chain A must decrement Alice's account _if and only if_ Bob's account
was incremented on chain B. We can achieve that by storing a protected
intermediate state on chain A, which is then committed or rolled back based on
the result of executing the transaction on chain B.
When we wish to create a transaction that atomically commits or rolls back across two chains, we must look at the receipts from sending the original message. For example, if I want to send tokens from Alice on chain A to Bob on chain B, chain A must decrement Alice's account _if and only if_ Bob's account was incremented on chain B. We can achieve that by storing a protected intermediate state on chain A, which is then committed or rolled back based on the result of executing the transaction on chain B.
To do this requires that we not only provable send a message from chain A to
chain B, but provably return the result of that message (the receipt) from
chain B to chain A. As one noticed above in the implementation of _IBCreceive_,
if the valid IBC message was sent from A to B, then the result of executing it,
even if it was an error, is stored in _B:q<sub>A.receipt</sub>_. Since the
receipts are stored in a queue with the same key construction as the sending
queue, we can generate the same set of proofs for them, and perform a similar
sequence of steps to handle a receipt coming back to _S_ for a message
previously sent to _A_:
To do this requires that we not only provable send a message from chain A to chain B, but provably return the result of that message (the receipt) from chain B to chain A. As one noticed above in the implementation of _IBCreceive_, if the valid IBC message was sent from A to B, then the result of executing it, even if it was an error, is stored in _B:q<sub>A.receipt</sub>_. Since the receipts are stored in a queue with the same key construction as the sending queue, we can generate the same set of proofs for them, and perform a similar sequence of steps to handle a receipt coming back to _S_ for a message previously sent to _A_:
_S:IBCreceipt(A, M<sub>k,v,h</sub>)_ &#8658; _match_
* _q<sub>A.send</sub> =_ &#8709; &#8658; _Error("unregistered sender"),_
@ -224,10 +124,7 @@ _S:IBCreceipt(A, M<sub>k,v,h</sub>)_ &#8658; _match_
* _v = (\_, error)_ &#8658; _(type, data) := pop(q<sub>S.send </sub>); rollback<sub>type</sub>(data); Success_
* _v = (res, success)_ &#8658; _(type, data) := pop(q<sub>S.send </sub>); commit<sub>type</sub>(data, res); Success_
This enforces that the receipts are processed in order, to allow some the
applications to make use of some basic assumptions about ordering. It also
removes the message from the send queue, as there is now proof it was processed
on the receiving chain and there is no more need to store this information.
This enforces that the receipts are processed in order, to allow some the application to make use of some basic assumptions about ordering. It also removes the message from the send queue, as there is now proof it was processed on the receiving chain and there is no more need to store this information.
![Successful Transaction](images/Receipts.png)
@ -236,26 +133,13 @@ on the receiving chain and there is no more need to store this information.
### 3.6 Relay Process
The blockchain itself only records the _intention_ to send the given message to
the recipient chain, it doesn't make any network connections as that would add
unbounded delays and non-determinism into the state machine. We define the
concept of a _relay_ process that connects two chain by querying one for all
proofs needed to prove outgoing messages and submit these proofs to the
recipient chain.
The blockchain itself only records the _intention_ to send the given message to the recipient chain, it doesn't make any network connections as that would add unbounded delays and non-determinism into the state machine. We define the concept of a _relay_ process that connects two chain by querying one for all proofs needed to prove outgoing messages and submit these proofs to the recipient chain.
The relay process must have access to accounts on both chains with sufficient
balance to pay for transaction fees but needs no other permissions. Many
_relay_ processes may run in parallel without violating any safety
consideration. However, they will consume unnecessary fees if they submit the
same proof multiple times, so some minimal coordination is ideal.
The relay process must have access to accounts on both chains with sufficient balance to pay for transaction fees but needs no other permissions. Many _relay_ processes may run in parallel without violating any safety consideration. However, they will consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination is ideal.
As an example, here is a naive algorithm for relaying send messages from A to
B, without error handling. We must also concurrently run the relay of receipts
from B back to A, in order to complete the cycle. Note that all reads of
variables belonging to a chain imply queries and all function calls imply
submitting a transaction to the blockchain.
As an example, here is a naive algorithm for relaying send messages from A to B, without error handling. We must also concurrently run the relay of receipts from B back to A, in order to complete the cycle. Note that all reads of variables belonging to a chain imply queries and all function calls imply submitting a transaction to the blockchain.
```go
```
while true
pending := tail(A:q<sub>B.send</sub>)
received := tail(B:q<sub>A.receive</sub>)
@ -269,19 +153,8 @@ while true
sleep(desiredLatency)
```
Note that updating a header is a costly transaction compared to posting a
merkle proof for a known header. Thus, a process could wait until many messages
are pending, then submit one header along with multiple merkle proofs, rather
than a separate header for each message. This decreases total computation cost
(and fees) at the price of additional latency and is a trade-off each relay can
dynamically adjust.
Note that updating a header is a costly transaction compared to posting a merkle proof for a known header. Thus, a process could wait until many messages are pending, then submit one header along with multiple merkle proofs, rather than a separate header for each message. This decreases total computation cost (and fees) at the price of additional latency and is a trade-off each relay can dynamically adjust.
In the presence of multiple concurrent relays, any given relay can perform
local optimizations to minimize the number of headers it submits, but remember
the frequency of header submissions defines the latency of the packet transfer.
In the presence of multiple concurrent relays, any given relay can perform local optimizations to minimize the number of headers it submits, but remember the frequency of header submissions defines the latency of the packet transfer.
Indeed, it is ideal if each user that initiates the creation of an IBC packet
also relays it to the recipient chain. The only constraint is that the relay
must be able to pay the appropriate fees on the destination chain. However, in
order to avoid bottlenecks, a group may sponsor an account to pay fees for a
public relayer that moves all unrelayed packets (perhaps with a high latency).
Indeed, it is ideal if each user that initiates the creation of an IBC packet also relays it to the recipient chain. The only constraint is that the relay must be able to pay the appropriate fees on the destination chain. However, in order to avoid bottlenecks, a group may sponsor an account to pay fees for a public relayer that moves all unrelayed packets (perhaps with a high latency).

View File

@ -6,32 +6,14 @@ _v0.4.0 / Feb. 13, 2018_
## Abstract
This paper specifies the IBC (inter blockchain communication) protocol, which
was first described in the Cosmos white paper [[1](./footnotes.md#1)] in June
2016. The IBC protocol uses authenticated message passing to simultaneously
solve two problems:
* transferring value (and state) between two distinct chains
* sharding one chain securely.
IBC follows the message-passing paradigm and assumes that the participating
chains are independent.
This paper specifies the IBC (inter blockchain communication) protocol, which was first described in the Cosmos white paper [[1](./footnotes.md#1)] in June 2016. The IBC protocol uses authenticated message passing to simultaneously solve two problems: transferring value (and state) between two distinct chains, as well as sharding one chain securely. IBC follows the message-passing paradigm and assumes the participating chains are independent.
Each chain maintains a local partial order, while inter-chain messages track
any cross-chain causality relations. Once two chains have registered a trust
relationship, cryptographically provable packets can be securely sent between
the chains, due to Tendermint's instant finality property.
Each chain maintains a local partial order, while inter-chain messages track any cross-chain causality relations. Once two chains have registered a trust relationship, cryptographically provable packets can be securely sent between the chains, using Tendermint's instant finality for quick and efficient transmission.
We currently use this protocol for secure value transfer in the Cosmos Hub, but
the protocol can support arbitrary application logic. Designing secure
communication logic for other types of applications is still an area of active
research.
The protocol makes no assumptions about block times or network delays in the
transmission of the packets between chains and requires cryptographic proofs
for every message, and thus is highly robust in a heterogeneous environment
with Byzantine actors. This paper explains the requirements and structure of
the Cosmos IBC protocol. It aims to provide enough detail to fully understand
and analyze the security of the protocol.
We currently use this protocol for secure value transfer in the Cosmos Hub, but the protocol can support arbitrary application logic. Details of how Cosmos Hub uses IBC to securely route and transfer value ar
e provided in a separate paper, along with a framework for expressing global invariants. Designing secure communication logic for other types of applications is still an area of research.
The protocol makes no assumptions of block times or network delays in the transmission of the packets between chains and requires cryptographic proofs for every message, and thus is highly robust in a heterogeneous environment with Byzantine actors. This paper explains the requirements and structure of the Cosmos IBC protocol. It aims to provide enough detail to fully understand and analyze the security of the protocol.
## Contents