From a8d3b3ef19e38db63f41c185f72f684c8271f77d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 26 Feb 2018 13:17:42 +0100 Subject: [PATCH] Correct spelling This reverts commit 3c7d194a7f1bc53b60cf09473f0ac50d2192e2af. --- docs/spec/ibc/conclusion.md | 20 +-- docs/spec/ibc/optimizations.md | 188 ++++++---------------------- docs/spec/ibc/overview.md | 101 +++------------ docs/spec/ibc/proofs.md | 102 +++------------ docs/spec/ibc/queues.md | 219 +++++++-------------------------- docs/spec/ibc/specification.md | 28 +---- 6 files changed, 128 insertions(+), 530 deletions(-) diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md index db5833ee1..37d555e6c 100644 --- a/docs/spec/ibc/conclusion.md +++ b/docs/spec/ibc/conclusion.md @@ -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. diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index ef6259a0e..e6c2f8cbc 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -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:qB.send_ or _B:qA.receipt_ we currently have the -following guarantees: +For a sending chain _A_ and a receiving chain _B_, with _k=(\_, \_, i)_ for _A:qB.send_ or _B:qA.receipt_ we currently have the following guarantees: _A:Mk,v,h =_ ∅ _if message i was not sent before height h_ -_A:Mk,v,h =_ ∅ _if message i was sent and receipt received -before height h (and the receipts for all messages j < i were also handled)_ +_A:Mk,v,h =_ ∅ _if message i was sent and receipt received before height h (and the receipts for all messages j < i were also handled)_ -_A:Mk,v,h _ ≠ ∅ _otherwise (message result is not yet -processed)_ +_A:Mk,v,h _ ≠ ∅ _otherwise (message result is not yet processed)_ _B:Mk,v,h =_ ∅ _if message i was not received before height h_ -_B:Mk,v,h _ ≠ ∅ _if message i was received before height -h (and all messages j < i were received)_ +_B:Mk,v,h _ ≠ ∅ _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(Hh ) > 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(Hh ) > maxTime_. _Vsend = (maxHeight, maxTime, type, data)_ _expired(Hh ,Vsend )_ ⇒ _[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)_ ⇒ _push(qS.receipt , (None, TimeoutError)),_ * _v = (\_, \_, type, data)_ ⇒ _(result, err) := ftype(data); push(qS.receipt , (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, Mk,v,h)_ ⇒ _match_ * _qA.send =_ ∅ ⇒ _Error("unregistered sender"),_ * _k = (\_, send, \_)_ ⇒ _Error("must be a receipt"),_ - * _k = (d, \_, \_) and d_ ≠ _S_ ⇒ _Error("sent to a different - chain"),_ - * _Hh_ ∉ _TA_ ⇒ _Error("must submit header - for height h"),_ - * _not valid(Hh , Mk,v,h )_ ⇒ _Error("invalid - merkle proof"),_ + * _k = (d, \_, \_) and d_ ≠ _S_ ⇒ _Error("sent to a different chain"),_ + * _Hh_ ∉ _TA_ ⇒ _Error("must submit header for height h"),_ + * _not valid(Hh , Mk,v,h )_ ⇒ _Error("invalid merkle proof"),_ * _k = (S, receipt, tail)_ ⇒ _match_ - * _tail_ ≥ _head(qS.send )_ ⇒ _Error("receipt - exists, no timeout proof")_ - * _not expired(peek(qS.send ))_ ⇒ _Error("message timeout - not yet reached")_ - * _default_ ⇒ _(\_, \_, type, data) := pop(qS.send ); - rollbacktype(data); Success_ + * _tail_ ≥ _head(qS.send )_ ⇒ _Error("receipt exists, no timeout proof")_ + * _not expired(peek(qS.send ))_ ⇒ _Error("message timeout not yet reached")_ + * _default_ ⇒ _(\_, \_, type, data) := pop(qS.send ); rollbacktype(data); Success_ * _default_ ⇒ _Error("must be a tail proof")_ which processes timeouts in order, and adds one more condition to the queues: -_A:Mk,v,h =_ ∅ _if message i was sent and timeout proven -before height h (and the receipts for all messages j < i were also handled)_ +_A:Mk,v,h =_ ∅ _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:Mk,v,h =_ ∅ _if message i was not received before height h_ -_B:Mk,v,h =_ ∅ _if message i was provably resolved on the -sending chain before height h_ +_B:Mk,v,h =_ ∅ _if message i was provably resolved on the sending chain before height h_ -_B:Mk,v,h _ ≠ ∅ _otherwise (if message i was processed -before height h, and no ack of receipt from the sending chain)_ +_B:Mk,v,h _ ≠ ∅ _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, Mk,v,h)_ ⇒ _match_ * _qA.receipt =_ ∅ ⇒ _Error("unknown sender"),_ * _k = (\_, send, \_)_ ⇒ _Error("must be for the send queue"),_ - * _k = (d, \_, \_) and d_ ≠ _S_ ⇒ _Error("sent to a different - chain"),_ - * _k_ ≠ _(\_, \_, head)_ ⇒ _Error("Need a proof of the head of - the queue"),_ - * _Hh_ ∉ _TA_ ⇒ _Error("must submit header - for height h"),_ - * _not valid(Hh ,Mk,v,h )_ ⇒ _Error("invalid - merkle proof"),_ + * _k = (d, \_, \_) and d_ ≠ _S_ ⇒ _Error("sent to a different chain"),_ + * _k_ ≠ _(\_, \_, head)_ ⇒ _Error("Need a proof of the head of the queue"),_ + * _Hh_ ∉ _TA_ ⇒ _Error("must submit header for height h"),_ + * _not valid(Hh ,Mk,v,h )_ ⇒ _Error("invalid merkle proof"),_ * _head := v_ ⇒ _match_ - * _head <= head(qA.receipt)_ ⇒ _Error("cleanup must go - forward"),_ + * _head <= head(qA.receipt)_ ⇒ _Error("cleanup must go forward"),_ * _default_ ⇒ _advance(qA.receipt , 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:qA.reciept ) > head(A:qB.send )_. 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:qA.reciept ) > head(A:qB.send )_. 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. diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index 266fc9202..9c07d8369 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -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. diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md index e13f2bb45..c402eae39 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/proofs.md @@ -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: _Hh_ is the signed header at height -_h_, _Ch_ are the consensus rules at height _h_, and _P_ is the -unbonding period of this blockchain. _Vk,h_ is the value stored -under key _k_ at height _h_. Note that out of all of these, only -_Hh_ 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: _Hh_ is the signed header at height _h_, _Ch_ are the consensus rules at height _h_, and _P_ is the unbonding period of this blockchain. _Vk,h_ is the value stored under key _k_ at height _h_. Note that of all these, only _Hh_ 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 _Hh_ and _Ch_ and an attributable - update message _Uh'_ it is possible to prove _Hh'_ - where _Ch' = Ch_ and Δ_(now, Hh) < P_ -* given a trusted _Hh_ and _Ch_ and an attributable - change message _Xh'_ it is possible to prove _Hh'_ - where _Ch'_ ≠ _Ch_ and - Δ _(now, Hh) < P_ -* given a trusted _Hh_ and a merkle proof _Mk,v,h_ it is - possible to prove _Vk,h_ +* given a trusted _Hh_ and _Ch_ and an attributable update message _Uh'_ it is possible to prove _Hh'_ where _Ch' = Ch_ and Δ_(now, Hh) < P_ +* given a trusted _Hh_ and _Ch_ and an attributable change message _Xh'_ it is possible to prove _Hh'_ where _Ch'_ ≠ _Ch_ and Δ _(now, Hh) < P_ +* given a trusted _Hh_ and a merkle proof _Mk,v,h_ it is possible to prove _Vk,h_ -It is possible to make use of the structure of BFT consensus to construct -extremely lightweight and provable messages _Uh'_ and -_Xh'_. 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 _Uh'_ and _Xh'_. 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 _Mk,v,h_ 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 _Hh_. 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 _Mk,v,h_ 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 _Hh_. 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(Hh ,Mk,v,h )_ ⇒ _[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 _Hh_ and _Ch_ for some -_h_, where Δ_(now, Hh) < P_. +As mentioned in the definitions, all proofs are based on an original assumption. In this case it is _Hh_ and _Ch_ for some _h_, where Δ_(now, Hh) < 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 _Uh_ and _Xh_, which together -allow us to securely advance our trust from some known _Hn_ to a -future _Hh_ 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 -Δ_vals(Cn, Ch ) < ⅓_, that each step must -have a change of less than one-third of the validator -set[[4](./footnotes.md#4)]. +We define two messages _Uh_ and _Xh_, which together allow us to securely advance our trust from some known _Hn_ to a future _Hh_ 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. +Δ_vals(Cn, Ch ) < ⅓_, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. -Since these messages _Uh_ and _Xh_ 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 _Uh_ and _Xh_ 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_ = _{(Hi , Ci ), (Hj , Cj ), …}_, we must provide: -More formally, given existing set of trust -_T_ = _{(Hi , Ci ), (Hj , Cj ), …}_ -we must provide: _valid(T, Xh | Uh )_ ⇒ _[true | false | unknown]_ _if Hh-1_ ∈ _T then_: * _valid(T, Xh | Uh )_ ⇒ _[true | false]_ * _there must exist some Uh or Xh that evaluates to true_ -_if Ch_ ∉ _T then_: +_if Ch_ ∉ _T then_ * _valid(T, Uh )_ ⇒ _false_ and can process update transactions as follows: @@ -104,17 +53,6 @@ _ match valid(T, Xh | Uh )_ * _unknown_ ⇒ _return Error("need a proof between current and h")_ * _true_ ⇒ _T_ ∪ _(Hh ,Ch )_ -We define _max(T)_ as _max(h, where Hh_ ∈ _T)_ for any _T_ -with _max(T) = h-1_. And from above, there must exist some -_Xh | Uh_ so that -_max(update(T, Xh | Uh )) = 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 Hh_ ∈ _T)_ for any _T_ with _max(T) = h-1_. And from above, there must exist some _Xh | Uh_ so that _max(update(T, Xh | Uh )) = 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, Xh | Uh ) = unknown_, we then try -_update(T, Xb | Ub )_, where _b = (h+n)/2_. -The base case is where -_valid(T, Xh | Uh ) = 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, Xh | Uh ) = unknown_, we then try _update(T, Xb | Ub )_, where _b = (h+n)/2_. The base case is where _valid(T, Xh | Uh ) = true_ and is guaranteed to exist if _h=max(T)+1_. diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index 4f46fb669..b0f459c40 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -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_ → _y_ means _x_ is -causally before _y_, and chains A and B, and _a_ ⇒ _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_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: + +_A:send(msgi )_ → _B:receive(msgi )_ + +_B:receive(msgi )_ → _A:receipt(msgi )_ + +_A:send(msgi )_ → _A:send(msgi+1 )_ + +_x_ → _A:send(msgi )_ ⇒ +_x_ → _B:receive(msgi )_ + +_y_ → _B:receive(msgi )_ ⇒ +_y_ → _A:receipt(msgi )_ -* _A:send(msgi )_ → _B:receive(msgi )_ -* _B:receive(msgi )_ → _A:receipt(msgi )_ -* _A:send(msgi )_ → _A:send(msgi+1 )_ -* _x_ → _A:send(msgi )_ ⇒ -* _x_ → _B:receive(msgi )_ -* _y_ → _B:receive(msgi )_ ⇒ -* _y_ → _A:receipt(msgi )_ ![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, -_Mk,v,h_, 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, _Mk,v,h_, 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**: _qhead = qtail = 0_ @@ -83,68 +52,32 @@ messages until _i_, and is useful for cleanup: **tail** ⇒ **i**: _qtail_ -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 -_Mk,v,h _ 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 _Mk,v,h _ 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::send_ - all outgoing packets destined to chain A * _ibc::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:qB.send[i]_ are stored at _B:qA.receipt[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:qB.send[i]_ are stored at _B:qA.receipt[i]_. (read: the message at index _i_ in the _send_ queue for chain B as stored on chain A) _Vsend = (type, data)_ @@ -152,32 +85,16 @@ _Vreceipt = (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 (_ftype_) 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 (_ftype_) and return an error for unsupported types. _(IBCsend(D, type, data)_ ⇒ _Success)_ ⇒ _push(qD.send ,Vsend{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, _TS_: +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, _TS_: _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ * _qS.receipt =_ ∅ ⇒ _Error("unregistered sender"),_ @@ -188,30 +105,13 @@ _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ * _valid(Hh ,Mk,v,h ) = false_ ⇒ _Error("invalid merkle proof"),_ * _v = (type, data)_ ⇒ _(result, err) := ftype(data); push(qS.receipt , (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:qA.receipt_. 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:qA.receipt_. 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, Mk,v,h)_ ⇒ _match_ * _qA.send =_ ∅ ⇒ _Error("unregistered sender"),_ @@ -224,10 +124,7 @@ _S:IBCreceipt(A, Mk,v,h)_ ⇒ _match_ * _v = (\_, error)_ ⇒ _(type, data) := pop(qS.send ); rollbacktype(data); Success_ * _v = (res, success)_ ⇒ _(type, data) := pop(qS.send ); committype(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:qB.send) received := tail(B:qA.receive) @@ -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). diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md index 9d7b302a8..5864337df 100644 --- a/docs/spec/ibc/specification.md +++ b/docs/spec/ibc/specification.md @@ -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