From f40335342f219150a3527b181ec14e9bf49c39c1 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 12 Feb 2018 15:32:52 +0100 Subject: [PATCH 01/77] Started IBC spec --- docs/spec/README.md | 4 +++- docs/spec/ibc/README.md | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 docs/spec/ibc/README.md diff --git a/docs/spec/README.md b/docs/spec/README.md index 5f3942ff9b..6492dc9392 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -5,14 +5,16 @@ the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. +<<<<<<< HEAD - [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. - [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. - [Governance](governance) - Governance related specifications including proposals and voting. +- [IBC](ibc) - Specification of the Cosmos inter-blockchain communication (IBC) protocol. - [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. -The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), +The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), i.e. the underlying blockchain, can be found elsewhere. diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md new file mode 100644 index 0000000000..8fe1b7f3d7 --- /dev/null +++ b/docs/spec/ibc/README.md @@ -0,0 +1,7 @@ +# Inter Blockchain Communication protocol + +IBC was defined in the [cosmos whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), +and then in detail in a [specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). + +This package builds on that and includes detailed specifications, pseudocode and protocol specification. + From 06ec4b4a1ac305347ab7bff595e6111a2ccd9f66 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 12 Feb 2018 15:36:30 +0100 Subject: [PATCH 02/77] Added protobuf specifications for ibc messages --- docs/spec/ibc/protobuf/.gitignore | 1 + docs/spec/ibc/protobuf/Makefile | 7 +++ docs/spec/ibc/protobuf/merkle.proto | 79 +++++++++++++++++++++++++++ docs/spec/ibc/protobuf/messages.proto | 29 ++++++++++ docs/spec/ibc/protobuf/queue.proto | 57 +++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 docs/spec/ibc/protobuf/.gitignore create mode 100644 docs/spec/ibc/protobuf/Makefile create mode 100644 docs/spec/ibc/protobuf/merkle.proto create mode 100644 docs/spec/ibc/protobuf/messages.proto create mode 100644 docs/spec/ibc/protobuf/queue.proto diff --git a/docs/spec/ibc/protobuf/.gitignore b/docs/spec/ibc/protobuf/.gitignore new file mode 100644 index 0000000000..c61a5e8b01 --- /dev/null +++ b/docs/spec/ibc/protobuf/.gitignore @@ -0,0 +1 @@ +*.pb.go diff --git a/docs/spec/ibc/protobuf/Makefile b/docs/spec/ibc/protobuf/Makefile new file mode 100644 index 0000000000..a1a2ef9e11 --- /dev/null +++ b/docs/spec/ibc/protobuf/Makefile @@ -0,0 +1,7 @@ +.PHONEY: proto test + +proto: + protoc --gogo_out=. *.proto + +test: proto + go install . diff --git a/docs/spec/ibc/protobuf/merkle.proto b/docs/spec/ibc/protobuf/merkle.proto new file mode 100644 index 0000000000..866e0a8d08 --- /dev/null +++ b/docs/spec/ibc/protobuf/merkle.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +package protobuf; + + +// HashOp is the hashing algorithm we use at each level +enum HashOp { + RIPEMD160 = 0; + SHA224 = 1; + SHA256 = 2; + SHA384 = 3; + SHA512 = 4; + SHA3_224 = 5; + SHA3_256 = 6; + SHA3_384 = 7; + SHA3_512 = 8; + SHA256_X2 = 9; +}; + +// Op represents one hash in a chain of hashes. +// An operation takes the output of the last level and returns +// a hash for the next level: +// Op(last) => Operation(prefix + last + sufix) +// +// A simple left/right hash would simply set prefix=left or +// suffix=right and leave the other blank. However, one could +// also represent the a Patricia trie proof by setting +// prefix to the rlp encoding of all nodes before the branch +// we select, and suffix to all those after the one we select. +message Op { + bytes prefix = 1; + bytes suffix = 2; + HashOp op = 3; +} + +// Data is the end value stored, +// used to generate the initial hash store +message Data { + // optional prefix allows second preimage resistance + bytes prefix = 1; + bytes key = 2; + bytes value = 3; + HashOp op = 4; + // If it is KeyValue, this is the data we want + // If it is SubTree, key is name of the tree, + // value is root hash + enum DataType { + KeyValue = 0; + SubTree = 1; + } + DataType dataType = 5; +} + +// Branch will hash data and then pass it through operations +// from first to last in order to calculate the root node. +// +// Visualize Branch as representing the data closest to +// root as the first item, and the leaf as the last item. +message Branch { + // if either are non-empty, enforce this prefix on all + // leaf/inner nodes to provide second preimage resistence + bytes prefixLeaf = 1; + bytes prefixInner = 2; + // this is the data to get the original hash, + // and a set of operations to calculate the root hash + Data data = 3; + repeated Op operations = 4; +} + +message MerkleProof { + // TODO: root, height, chain_id, etc... + + // branches start from the value, and then may + // include multiple subtree branches to embed it + // + // The first branch must have dataType KeyValue + // Following branches must have dataType SubTree + repeated Branch branches = 1; +} diff --git a/docs/spec/ibc/protobuf/messages.proto b/docs/spec/ibc/protobuf/messages.proto new file mode 100644 index 0000000000..035c962e5b --- /dev/null +++ b/docs/spec/ibc/protobuf/messages.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package protobuf; + +import "merkle.proto"; + + +// IBCPacket sends a proven key/value pair from an IBCQueue. +// Depending on the type of message, we require a certain type +// of key (MessageKey at a given height, or StateKey). +// +// Includes src_chain and src_height to look up the proper +// header to verify the merkle proof. +message IBCPacket { + // chain id it is coming from + string src_chain = 1; + // height for the header the proof belongs to + uint64 src_height = 2; + // the message type, which determines what key/value mean + enum MsgType { + RECEIVE = 0; + RECEIPT = 1; + TIMEOUT = 2; + CLEANUP = 3; + } + MsgType msgType = 3; + // the proof of the message, includes key and value + MerkleProof proof = 6; +} diff --git a/docs/spec/ibc/protobuf/queue.proto b/docs/spec/ibc/protobuf/queue.proto new file mode 100644 index 0000000000..5d1c0ad33c --- /dev/null +++ b/docs/spec/ibc/protobuf/queue.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package protobuf; + +import "google/protobuf/timestamp.proto"; + +message QueueName { + // chain_id is which chain this queue is + // associated with + string chain_id = 1; + enum Purpose { + SEND = 0; + RECEIPT = 1; + } + Purpose purpose = 2; +} + +// StateKey is a key for the head/tail of a given queue +message StateKey { + QueueName queue = 1; + // both encode into one byte with varint encoding + // never clash with 8 byte message indexes + enum State { + HEAD = 0; + TAIL = 0x7f; + } + State state = 2; +} + +// StateValue is the type stored under a StateKey +message StateValue { + fixed64 index = 1; +} + +// MessageKey is the key for message *index* in a given queue +message MessageKey { + QueueName queue = 1; + fixed64 index = 2; +} + +// SendValue is stored under a MessageKey in the SEND queue +message SendValue { + uint64 maxHeight = 1; + google.protobuf.Timestamp maxTime = 2; + // use kind instead of type to avoid keyword conflict + bytes kind = 3; + bytes data = 4; +} + +// ReceiptValue is stored under a MessageKey in the RECEIPT queue +message ReceiptValue { + // 0 is success, others are application-defined errors + int32 errorCode = 1; + // contains result on success, optional info on error + bytes data = 2; +} + From 7610cca7c9dc854ff16646f309e017b84cdb3a3b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 17:36:44 +0100 Subject: [PATCH 03/77] Rough import of google doc to markdown --- docs/spec/ibc/specification.md | 1189 ++++++++++++++++++++++++++++++++ 1 file changed, 1189 insertions(+) create mode 100644 docs/spec/ibc/specification.md diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md new file mode 100644 index 0000000000..c5ed3a75fc --- /dev/null +++ b/docs/spec/ibc/specification.md @@ -0,0 +1,1189 @@ +# IBC Protocol Specification + +v0.4.0 / Feb. 13, 2018 + +Ethan Frey + +## Abstract + +This paper specifies the IBC (inter blockchain communication) protocol, which was first described in the Cosmos white paper[^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, 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. Details of how Cosmos Hub uses IBC to securely route and transfer value are 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 + +1. **Overview** + 1. Definitions + 1. Threat Models +1. **Proofs** + 1. Establishing a Root of Trust + 1. Following Block Headers +1. **Messaging Queue** + 1. Merkle Proofs for Queues + 1. Naming Queues + 1. Message Contents + 1. Sending a Packet + 1. Receipts + 1. Relay Process +1. **Optimizations** + 1. Cleanup + 1. Timeout + 1. Handling Byzantine Failures +1. **Conclusion** + +**Appendix A: Encoding Libraries** + +**Appendix B: IBC Queue Format** + +**Appendix C: Merkle Proof Format** + +**Appendix D: Universal IBC Packets** + +**Appendix E: Tendermint Header Proofs** + + + +## 1 Overview + +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 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], used by StormMQ, RabbitMQ, etc. + +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 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. + +_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 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. + +_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 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]. 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 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. + +_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 risk exposure in the face of Byzantine actors. + +## 2 Proofs + +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: + +* given a trusted _Hh_ and _Ch_ and an attributable update message _Uh'_ it is possible to prove _Hh'_ where _Ch' = Ch_ and + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(now, Hh) < P_ +* given a trusted _Hh_ and _Ch_ and an attributable change message _Xh'_ it is possible to prove _Hh'_ where _Ch' _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Ch_ and + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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 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. + +_valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [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 + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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. + +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 + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +vals(Cn, Ch ) < ⅓, that each step must have a change of less than one-third of the validator set[^4]. + +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: + +_valid(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [true | false | unknown]_ + +_if Hh-1 _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_T then _ + +_valid(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [true | false]_ + +_there must exist some Uh or Xh that evaluates to true_ + +_if Ch _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_T then_ + +_ valid(T, Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ false_ + +and can process update transactions as follows: + +_update(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match valid(T, Xh | Uh )_ + +_ false _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ return Error("invalid proof")_ + +_ unknown _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ return Error("need a proof between current and h")_ + +_ true _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ T _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(Hh ,Ch )_ + +We define _max(T)_ as _max(h, where Hh_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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_. + +## 3 Messaging Queue + +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 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 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], 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). + +Message passing implies a causal ordering over multiple chains and these can be important for reasoning on the system. Given _x _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ y_ means _x_ is causally before _y_, and chains A and B, and _a_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_b_ means _a_ implies _b_: + +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ B:receive(msgi )_ + +_B:receive(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ A:receipt(msgi )_ + +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:send(msgi+1 )_ + +_x_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + _x_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_B:receive(msgi )_ + +_y_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_B:receive(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + _y_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:receipt(msgi )_ + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC0.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC0.png "image_tooltip") + + +([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. + +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_ + +**peek ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**m**: _if qhead = qtail { return None } else { return q[qhead] }_ + +**pop ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ + +**push(m)**: _q[qtail] = m; qtail++_ + +**advance(i)**: _qhead = i; qtail = max(qtail , i)_ + +**head ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**i**: _qhead_ + +**tail** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**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. + +**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. + +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. + +### 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. + +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. + +### 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, 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) + +_Vsend = (type, data)_ + +_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. + +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. + +_(IBCsend(D, type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ Success)_ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ 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_: + +_A:IBCreceive(S, Mk,v,h) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qS.receipt =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, reciept, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a send"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ k = (_, send, i) and head(qS.receipt) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_i_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("out of order"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TS _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ valid(Hh ,Mk,v,h ) = false _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ v = (type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(result, err) :=ftype(data); push(qS.receipt , (result, err)); Success _ + +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. + +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) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.send =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a recipient"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ k = (_, receipt, head|tail)_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("only accepts message proofs"),_ + +_ k = (_, receipt, i) and head(qS.send) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_i_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("out of order"),_ + +_ v = (_, error) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(type, data) := pop(qS.send ); rollbacktype(data); Success_ + +_ v = (res, success) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(type, data) := pop(qS.send ); committype(data, res); Success_ + +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. + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC1.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC1.png "image_tooltip") + + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC2.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC2.png "image_tooltip") + + +### 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 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. + +_while true_ + +_ pending := tail(A:qB.send)_ + +_ received := tail(B:qA.receive)_ + +_ if pending > received_ + +_ Uh := A:latestHeader_ + +_ B:updateHeader(Uh)_ + +_ for i :=received...pending_ + +_ k := (B, send, i)_ + +_ packet := A:Mk,v,h_ + +_ B:IBCreceive(A, packet)_ + +_ 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. + +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). + +## 4 Optimizations + +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. + +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: + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not sent before height h_ + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_otherwise (message result is not yet processed)_ + +_B:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not received before height h_ + +_B:Mk,v,h _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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._ + +_Vsend = (maxHeight, maxTime, type, data)_ + +_expired(Hh ,Vsend ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_[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: + +_IBCreceive:_ + +_ …._ + +_ expired(latestHeader, v) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_push(qS.receipt , (None, TimeoutError));_ + +_ v = (_, _, type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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. + +_S:IBCtimeout(A, Mk,v,h) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.send =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a receipt"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ k = (S, receipt, tail)_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_match_ + + + _tail _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_head(qS.send )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("receipt exists, no timeout proof")_ + + + _not expired(peek(qS.send )) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("message timeout not yet reached")_ + + + _default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(_, _, type, data) := pop(qS.send ); rollbacktype(data); Success_ + + + _default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a tail proof")_ + +which processes timeouts in order, and adds one more condition to the queues: + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 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. + +### 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. + +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 = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not received before height h_ + +_B:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was provably resolved on the sending chain before height h_ + +_B:Mk,v,h _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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. + +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) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.receipt =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unknown sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be for the send queue"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ k_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ (_, _, head) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("Need a proof of the head of the queue"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ head := v _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_match_ + +_ head <= head(qA.receipt) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("cleanup must go forward"),_ + +_ default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 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. + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC3.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC3.png "image_tooltip") + + +### 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: failures of the consensus mechanism (reversing "final" blocks), or 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. + +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 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. + +## 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. + +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. + + + +## Appendix A: Encoding Libraries + +The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. + +In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[^6] and google's protobuf[^7]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. + +The tendermint-specific data structures are encoded with go-wire[^8], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. + +For the following appendixes, the data structure specifications will be in proto3[^9] format. + +## Appendix B: IBC Queue Format + +The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: + +_key = _(_remote id, [send|receipt], [head|tail|index])_ + +_Vsend = (maxHeight, maxTime, type, data)_ + +_Vreceipt = (result, [success|error code])_ + + +``` + message QueueName { + // chain_id is which chain this queue is + // associated with + string chain_id = 1; + enum Purpose { + SEND = 0; + RECEIPT = 1; + } + Purpose purpose = 2; + } + // StateKey is a key for the head/tail of a given queue + message StateKey { + QueueName queue = 1; + // both encode into one byte with varint encoding + // never clash with 8 byte message indexes + enum State { + HEAD = 0; + TAIL = 0x7f; + } + State state = 2; + } + // StateValue is the type stored under a StateKey + message StateValue { + fixed64 index = 1; + } + // MessageKey is the key for message *index* in a given queue + message MessageKey { + QueueName queue = 1; + fixed64 index = 2; + } + // SendValue is stored under a MessageKey in the SEND queue + message SendValue { + uint64 maxHeight = 1; + google.protobuf.Timestamp maxTime = 2; + // use kind instead of type to avoid keyword conflict + bytes kind = 3; + bytes data = 4; + } + // ReceiptValue is stored under a MessageKey in the RECEIPT queue + message ReceiptValue { + // 0 is success, others are application-defined errors + int32 errorCode = 1; + // contains result on success, optional info on error + bytes data = 2; + } +``` + +Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. + +## Appendix C: Merkle Proof Formats + +A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[^10]. + +There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[^11], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. + +We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. + +The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. + +A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. + +For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[^12], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. + +``` + // HashOp is the hashing algorithm we use at each level + enum HashOp { + RIPEMD160 = 0; + SHA224 = 1; + SHA256 = 2; + SHA384 = 3; + SHA512 = 4; + SHA3_224 = 5; + SHA3_256 = 6; + SHA3_384 = 7; + SHA3_512 = 8; + SHA256_X2 = 9; + }; + // Op represents one hash in a chain of hashes. + // An operation takes the output of the last level and returns + // a hash for the next level: + // Op(last) => Operation(prefix + last + sufix) + // + // A simple left/right hash would simply set prefix=left or + // suffix=right and leave the other blank. However, one could + // also represent the a Patricia trie proof by setting + // prefix to the rlp encoding of all nodes before the branch + // we select, and suffix to all those after the one we select. + message Op { + bytes prefix = 1; + bytes suffix = 2; + HashOp op = 3; + } + // Data is the end value stored, used to generate the initial hash + message Data { + bytes prefix = 1; + bytes key = 2; + bytes value = 3; + HashOp op = 4; + // If it is KeyValue, this is the data we want + // If it is SubTree, key is name of the tree, value is root hash + // Expect another branch to follow + enum DataType { + KeyValue = 0; + SubTree = 1; + } + DataType dataType = 5; + } + // Branch will hash data and then pass it through operations from + // last to first in order to calculate the root node. + // + // Visualize Branch as representing the data closest to root as the + // first item, and the leaf as the last item. + message Branch { + repeated Op operations = 1; + Data data = 2; + } + // MerkleProof shows a veriable path from the data to + // a root hash (potentially spanning multiple sub-trees). + message MerkleProof { + // identify the header this is rooted in + string chainId = 1; + uint64 height = 2; + // this hash must match the header as well as the + // calculation from below + bytes rootHash = 3; + // branches start from the value, and then may + // include multiple subtree branches to embed it + // + // The first branch must have dataType KeyValue + // Following branches must have dataType SubTree + repeated Branch branches = 1; + } + ``` + +## Appendix D: Universal IBC Packets + +The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. + +``` + // IBCPacket sends a proven key/value pair from an IBCQueue. + // Depending on the type of message, we require a certain type + // of key (MessageKey at a given height, or StateKey). + // + // Includes src_chain and src_height to look up the proper + // header to verify the merkle proof. + message IBCPacket { + // chain id it is coming from + string src_chain = 1; + // height for the header the proof belongs to + uint64 src_height = 2; + // the message type, which determines what key/value mean + enum MsgType { + RECEIVE = 0; + RECEIPT = 1; + TIMEOUT = 2; + CLEANUP = 3; + } + MsgType msgType = 3; + bytes key = 4; + bytes value = 5; + // the proof of the message + MerkleProof proof = 6; + } +``` + +## Appendix E: Tendermint Header Proofs + +TODO: clean this all up + +This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. + +In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. + +**Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet** + +**-> needs input/support from Tendermint Core team (and go-crypto)** + +**Registering Chain** + +**Updating Header** + +**Validator Changes** + +ROOT of trust + +As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain. + +When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header. + +From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust. + +VERIFYING HEADERS + +Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign). + +This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message. + +UPDATING VALIDATORS SET + +If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier. + +A validator change in Tendermint can be securely verified with the following checks: + + + +* First, that the new header, validators, and signatures are internally consistent + * We have a new set of validators that matches the hash on the new header + * At least 2/3 of the voting power of the new set validates the new header +* Second, that the new header is also valid in the eyes of our trust set + * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header + +In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). + + + +## Notes + +[^1]: + [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) + +[^2]: + [http://www.amqp.org/sites/amqp.org/files/amqp.pdf](http://www.amqp.org/sites/amqp.org/files/amqp.pdf) + +[^3]: + [https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) + +[^4]: + [https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104](https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104) + +[^5]: + [http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/](http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/) + +[^6]: + [https://github.com/ethereum/wiki/wiki/RLP](https://github.com/ethereum/wiki/wiki/RLP) + +[^7]: + [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) + +[^8]: + [https://github.com/tendermint/go-wire](https://github.com/tendermint/go-wire) + +[^9]: + [https://developers.google.com/protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3) + +[^10]: + [https://en.wikipedia.org/wiki/Merkle_tree](https://en.wikipedia.org/wiki/Merkle_tree) + +[^11]: + [https://chainpoint.org/](https://chainpoint.org/) + +[^12]: + [https://github.com/tendermint/iavl](https://github.com/tendermint/iavl) From 97e61a6f7b83a2c2e9d07f1bff77bef581ee6c07 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 18:11:10 +0100 Subject: [PATCH 04/77] Break IBC spec into multiple md files --- docs/spec/ibc/README.md | 2 +- docs/spec/ibc/appendix-a.md | 11 + docs/spec/ibc/appendix-b.md | 62 ++ docs/spec/ibc/appendix-c.md | 87 +++ docs/spec/ibc/appendix-d.md | 33 + docs/spec/ibc/appendix-e.md | 49 ++ docs/spec/ibc/conclusion.md | 7 + docs/spec/ibc/footnotes.md | 37 + docs/spec/ibc/optimizations.md | 321 +++++++++ docs/spec/ibc/overview.md | 39 ++ docs/spec/ibc/proofs.md | 129 ++++ docs/spec/ibc/queues.md | 371 ++++++++++ docs/spec/ibc/specification.md | 1168 +------------------------------- 13 files changed, 1160 insertions(+), 1156 deletions(-) create mode 100644 docs/spec/ibc/appendix-a.md create mode 100644 docs/spec/ibc/appendix-b.md create mode 100644 docs/spec/ibc/appendix-c.md create mode 100644 docs/spec/ibc/appendix-d.md create mode 100644 docs/spec/ibc/appendix-e.md create mode 100644 docs/spec/ibc/conclusion.md create mode 100644 docs/spec/ibc/footnotes.md create mode 100644 docs/spec/ibc/optimizations.md create mode 100644 docs/spec/ibc/overview.md create mode 100644 docs/spec/ibc/proofs.md create mode 100644 docs/spec/ibc/queues.md diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 8fe1b7f3d7..43183251e9 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -3,5 +3,5 @@ IBC was defined in the [cosmos whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and then in detail in a [specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). -This package builds on that and includes detailed specifications, pseudocode and protocol specification. +This package builds on that and includes detailed specifications, pseudocode and protocol specification. Please read the [table of contents](specification.md) of the IBC specification. diff --git a/docs/spec/ibc/appendix-a.md b/docs/spec/ibc/appendix-a.md new file mode 100644 index 0000000000..289699aff1 --- /dev/null +++ b/docs/spec/ibc/appendix-a.md @@ -0,0 +1,11 @@ +## Appendix A: Encoding Libraries + +([Back to table of contents](specification.md#contents)) + +The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. + +In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./footnotes.md#6)] and google's protobuf[[7](./footnotes.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. + +The tendermint-specific data structures are encoded with go-wire[[8](./footnotes.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. + +For the following appendixes, the data structure specifications will be in proto3[[9](./footnotes.md#9)] format. diff --git a/docs/spec/ibc/appendix-b.md b/docs/spec/ibc/appendix-b.md new file mode 100644 index 0000000000..226f590606 --- /dev/null +++ b/docs/spec/ibc/appendix-b.md @@ -0,0 +1,62 @@ +## Appendix B: IBC Queue Format + +([Back to table of contents](specification.md#contents)) + +The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: + +_key = _(_remote id, [send|receipt], [head|tail|index])_ + +_Vsend = (maxHeight, maxTime, type, data)_ + +_Vreceipt = (result, [success|error code])_ + + +``` + message QueueName { + // chain_id is which chain this queue is + // associated with + string chain_id = 1; + enum Purpose { + SEND = 0; + RECEIPT = 1; + } + Purpose purpose = 2; + } + // StateKey is a key for the head/tail of a given queue + message StateKey { + QueueName queue = 1; + // both encode into one byte with varint encoding + // never clash with 8 byte message indexes + enum State { + HEAD = 0; + TAIL = 0x7f; + } + State state = 2; + } + // StateValue is the type stored under a StateKey + message StateValue { + fixed64 index = 1; + } + // MessageKey is the key for message *index* in a given queue + message MessageKey { + QueueName queue = 1; + fixed64 index = 2; + } + // SendValue is stored under a MessageKey in the SEND queue + message SendValue { + uint64 maxHeight = 1; + google.protobuf.Timestamp maxTime = 2; + // use kind instead of type to avoid keyword conflict + bytes kind = 3; + bytes data = 4; + } + // ReceiptValue is stored under a MessageKey in the RECEIPT queue + message ReceiptValue { + // 0 is success, others are application-defined errors + int32 errorCode = 1; + // contains result on success, optional info on error + bytes data = 2; + } +``` + +Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. diff --git a/docs/spec/ibc/appendix-c.md b/docs/spec/ibc/appendix-c.md new file mode 100644 index 0000000000..3eb870d715 --- /dev/null +++ b/docs/spec/ibc/appendix-c.md @@ -0,0 +1,87 @@ +## Appendix C: Merkle Proof Formats + +([Back to table of contents](specification.md#contents)) + +A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./footnotes.md#10)]. + +There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./footnotes.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. + +We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. + +The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. + +A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. + +For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./footnotes.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. + +``` + // HashOp is the hashing algorithm we use at each level + enum HashOp { + RIPEMD160 = 0; + SHA224 = 1; + SHA256 = 2; + SHA384 = 3; + SHA512 = 4; + SHA3_224 = 5; + SHA3_256 = 6; + SHA3_384 = 7; + SHA3_512 = 8; + SHA256_X2 = 9; + }; + // Op represents one hash in a chain of hashes. + // An operation takes the output of the last level and returns + // a hash for the next level: + // Op(last) => Operation(prefix + last + sufix) + // + // A simple left/right hash would simply set prefix=left or + // suffix=right and leave the other blank. However, one could + // also represent the a Patricia trie proof by setting + // prefix to the rlp encoding of all nodes before the branch + // we select, and suffix to all those after the one we select. + message Op { + bytes prefix = 1; + bytes suffix = 2; + HashOp op = 3; + } + // Data is the end value stored, used to generate the initial hash + message Data { + bytes prefix = 1; + bytes key = 2; + bytes value = 3; + HashOp op = 4; + // If it is KeyValue, this is the data we want + // If it is SubTree, key is name of the tree, value is root hash + // Expect another branch to follow + enum DataType { + KeyValue = 0; + SubTree = 1; + } + DataType dataType = 5; + } + // Branch will hash data and then pass it through operations from + // last to first in order to calculate the root node. + // + // Visualize Branch as representing the data closest to root as the + // first item, and the leaf as the last item. + message Branch { + repeated Op operations = 1; + Data data = 2; + } + // MerkleProof shows a veriable path from the data to + // a root hash (potentially spanning multiple sub-trees). + message MerkleProof { + // identify the header this is rooted in + string chainId = 1; + uint64 height = 2; + // this hash must match the header as well as the + // calculation from below + bytes rootHash = 3; + // branches start from the value, and then may + // include multiple subtree branches to embed it + // + // The first branch must have dataType KeyValue + // Following branches must have dataType SubTree + repeated Branch branches = 1; + } + ``` + diff --git a/docs/spec/ibc/appendix-d.md b/docs/spec/ibc/appendix-d.md new file mode 100644 index 0000000000..64b8565fcd --- /dev/null +++ b/docs/spec/ibc/appendix-d.md @@ -0,0 +1,33 @@ +## Appendix D: Universal IBC Packets + +([Back to table of contents](specification.md#contents)) + +The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. + +``` + // IBCPacket sends a proven key/value pair from an IBCQueue. + // Depending on the type of message, we require a certain type + // of key (MessageKey at a given height, or StateKey). + // + // Includes src_chain and src_height to look up the proper + // header to verify the merkle proof. + message IBCPacket { + // chain id it is coming from + string src_chain = 1; + // height for the header the proof belongs to + uint64 src_height = 2; + // the message type, which determines what key/value mean + enum MsgType { + RECEIVE = 0; + RECEIPT = 1; + TIMEOUT = 2; + CLEANUP = 3; + } + MsgType msgType = 3; + bytes key = 4; + bytes value = 5; + // the proof of the message + MerkleProof proof = 6; + } +``` + diff --git a/docs/spec/ibc/appendix-e.md b/docs/spec/ibc/appendix-e.md new file mode 100644 index 0000000000..f07360850d --- /dev/null +++ b/docs/spec/ibc/appendix-e.md @@ -0,0 +1,49 @@ +## Appendix E: Tendermint Header Proofs + +TODO: clean this all up + +This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. + +In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. + +**Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet** + +**-> needs input/support from Tendermint Core team (and go-crypto)** + +**Registering Chain** + +**Updating Header** + +**Validator Changes** + +ROOT of trust + +As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain. + +When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header. + +From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust. + +VERIFYING HEADERS + +Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign). + +This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message. + +UPDATING VALIDATORS SET + +If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier. + +A validator change in Tendermint can be securely verified with the following checks: + + + +* First, that the new header, validators, and signatures are internally consistent + * We have a new set of validators that matches the hash on the new header + * At least 2/3 of the voting power of the new set validates the new header +* Second, that the new header is also valid in the eyes of our trust set + * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header + +In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). + + diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md new file mode 100644 index 0000000000..37d555e6c4 --- /dev/null +++ b/docs/spec/ibc/conclusion.md @@ -0,0 +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. + +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. diff --git a/docs/spec/ibc/footnotes.md b/docs/spec/ibc/footnotes.md new file mode 100644 index 0000000000..eecd528412 --- /dev/null +++ b/docs/spec/ibc/footnotes.md @@ -0,0 +1,37 @@ +## Footnotes + +##### 1: +[https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) + +##### 2: +[http://www.amqp.org/sites/amqp.org/files/amqp.pdf](http://www.amqp.org/sites/amqp.org/files/amqp.pdf) + +##### 3: +[https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) + +##### 4: +[https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104](https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104) + +##### 5: +[http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/](http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/) + +##### 6: +[https://github.com/ethereum/wiki/wiki/RLP](https://github.com/ethereum/wiki/wiki/RLP) + +##### 7: +[https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) + +##### 8: +[https://github.com/tendermint/go-wire](https://github.com/tendermint/go-wire) + +##### 9: +[https://developers.google.com/protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3) + +##### 10: +[https://en.wikipedia.org/wiki/Merkle_tree](https://en.wikipedia.org/wiki/Merkle_tree) + +##### 11: +[https://chainpoint.org/](https://chainpoint.org/) + +##### 12: +[https://github.com/tendermint/iavl](https://github.com/tendermint/iavl) diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md new file mode 100644 index 0000000000..5724554963 --- /dev/null +++ b/docs/spec/ibc/optimizations.md @@ -0,0 +1,321 @@ +## 4 Optimizations + +([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. + +### 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. + +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: + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not sent before height h_ + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_otherwise (message result is not yet processed)_ + +_B:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not received before height h_ + +_B:Mk,v,h _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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._ + +_Vsend = (maxHeight, maxTime, type, data)_ + +_expired(Hh ,Vsend ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_[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: + +_IBCreceive:_ + +_ …._ + +_ expired(latestHeader, v) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_push(qS.receipt , (None, TimeoutError));_ + +_ v = (_, _, type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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. + +_S:IBCtimeout(A, Mk,v,h) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.send =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a receipt"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ k = (S, receipt, tail)_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_match_ + + + _tail _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_head(qS.send )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("receipt exists, no timeout proof")_ + + + _not expired(peek(qS.send )) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("message timeout not yet reached")_ + + + _default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(_, _, type, data) := pop(qS.send ); rollbacktype(data); Success_ + + + _default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a tail proof")_ + +which processes timeouts in order, and adds one more condition to the queues: + +_A:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 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. + +### 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. + +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 = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was not received before height h_ + +_B:Mk,v,h = _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_if message i was provably resolved on the sending chain before height h_ + +_B:Mk,v,h _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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. + +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) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.receipt =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unknown sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be for the send queue"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ k_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ (_, _, head) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("Need a proof of the head of the queue"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ head := v _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_match_ + +_ head <= head(qA.receipt) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("cleanup must go forward"),_ + +_ default _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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 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. + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC3.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC3.png "image_tooltip") + + +### 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: failures of the consensus mechanism (reversing "final" blocks), or 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. + +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 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. diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md new file mode 100644 index 0000000000..9c07d83691 --- /dev/null +++ b/docs/spec/ibc/overview.md @@ -0,0 +1,39 @@ +## 1 Overview + +([Back to table of contents](specification.md#contents)) + +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 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 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 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. + +_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 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. + +_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 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. + +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. + +_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 risk exposure in the face of Byzantine actors. diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md new file mode 100644 index 0000000000..59ab961428 --- /dev/null +++ b/docs/spec/ibc/proofs.md @@ -0,0 +1,129 @@ +## 2 Proofs + +([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 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: + +* given a trusted _Hh_ and _Ch_ and an attributable update message _Uh'_ it is possible to prove _Hh'_ where _Ch' = Ch_ and + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(now, Hh) < P_ +* given a trusted _Hh_ and _Ch_ and an attributable change message _Xh'_ it is possible to prove _Hh'_ where _Ch' _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Ch_ and + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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 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. + +_valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [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 + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(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. + +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 + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +vals(Cn, Ch ) < ⅓, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. + +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: + +_valid(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [true | false | unknown]_ + +_if Hh-1 _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_T then _ + +_valid(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ [true | false]_ + +_there must exist some Uh or Xh that evaluates to true_ + +_if Ch _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_T then_ + +_ valid(T, Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ false_ + +and can process update transactions as follows: + +_update(T, Xh | Uh ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match valid(T, Xh | Uh )_ + +_ false _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ return Error("invalid proof")_ + +_ unknown _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ return Error("need a proof between current and h")_ + +_ true _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ T _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(Hh ,Ch )_ + +We define _max(T)_ as _max(h, where Hh_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_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_. diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md new file mode 100644 index 0000000000..f118361e65 --- /dev/null +++ b/docs/spec/ibc/queues.md @@ -0,0 +1,371 @@ +## 3 Messaging Queue + +([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. + +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 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. + +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 _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ y_ means _x_ is causally before _y_, and chains A and B, and _a_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_b_ means _a_ implies _b_: + +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ B:receive(msgi )_ + +_B:receive(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ A:receipt(msgi )_ + +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:send(msgi+1 )_ + +_x_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:send(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + _x_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_B:receive(msgi )_ + +_y_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_B:receive(msgi )_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + _y_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A:receipt(msgi )_ + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC0.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC0.png "image_tooltip") + + +([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. + +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_ + +**peek ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**m**: _if qhead = qtail { return None } else { return q[qhead] }_ + +**pop ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ + +**push(m)**: _q[qtail] = m; qtail++_ + +**advance(i)**: _qhead = i; qtail = max(qtail , i)_ + +**head ** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**i**: _qhead_ + +**tail** + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +**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. + +**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. + +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. + +### 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. + +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. + +### 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, 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) + +_Vsend = (type, data)_ + +_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. + +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. + +_(IBCsend(D, type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ Success)_ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ 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_: + +_A:IBCreceive(S, Mk,v,h) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qS.receipt =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, reciept, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a send"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_A_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_ k = (_, send, i) and head(qS.receipt) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_i_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("out of order"),_ + +_ Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TS _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ valid(Hh ,Mk,v,h ) = false _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ v = (type, data) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(result, err) :=ftype(data); push(qS.receipt , (result, err)); Success _ + +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. + +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) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_ match_ + +_qA.send =_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ + + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("unregistered sender"), _ + +_ k = (_, send, _) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must be a recipient"),_ + +_ k = (d, _, _) and d _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_S_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("sent to a different chain"),_ + +_Hh _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_TA _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("must submit header for height h"),_ + +_ not valid(Hh ,Mk,v,h ) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("invalid merkle proof"),_ + +_ k = (_, receipt, head|tail)_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("only accepts message proofs"),_ + +_ k = (_, receipt, i) and head(qS.send) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_i_ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_Error("out of order"),_ + +_ v = (_, error) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(type, data) := pop(qS.send ); rollbacktype(data); Success_ + +_ v = (res, success) _ + +

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+ +_(type, data) := pop(qS.send ); committype(data, res); Success_ + +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. + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC1.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC1.png "image_tooltip") + + + + +

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC2.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

+ + +![alt_text](images/Cosmos-IBC2.png "image_tooltip") + + +### 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 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. + +_while true_ + +_ pending := tail(A:qB.send)_ + +_ received := tail(B:qA.receive)_ + +_ if pending > received_ + +_ Uh := A:latestHeader_ + +_ B:updateHeader(Uh)_ + +_ for i :=received...pending_ + +_ k := (B, send, i)_ + +_ packet := A:Mk,v,h_ + +_ B:IBCreceive(A, packet)_ + +_ 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. + +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). diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md index c5ed3a75fc..21cbe0b6b2 100644 --- a/docs/spec/ibc/specification.md +++ b/docs/spec/ibc/specification.md @@ -1,12 +1,12 @@ # IBC Protocol Specification -v0.4.0 / Feb. 13, 2018 +_v0.4.0 / Feb. 13, 2018_ -Ethan Frey +**Ethan Frey** ## Abstract -This paper specifies the IBC (inter blockchain communication) protocol, which was first described in the Cosmos white paper[^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. +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, using Tendermint's instant finality for quick and efficient transmission. @@ -16,1174 +16,32 @@ The protocol makes no assumptions of block times or network delays in the transm ## Contents -1. **Overview** +1. **[Overview](overview.md)** 1. Definitions 1. Threat Models -1. **Proofs** +1. **[Proofs](proofs.md)** 1. Establishing a Root of Trust 1. Following Block Headers -1. **Messaging Queue** +1. **[Messaging Queue](queues.md)** 1. Merkle Proofs for Queues 1. Naming Queues 1. Message Contents 1. Sending a Packet 1. Receipts 1. Relay Process -1. **Optimizations** +1. **[Optimizations](optimizations.md)** 1. Cleanup 1. Timeout 1. Handling Byzantine Failures -1. **Conclusion** +1. **[Conclusion](conclusion.md)** -**Appendix A: Encoding Libraries** +**[Appendix A: Encoding Libraries](appendix-a.md)** -**Appendix B: IBC Queue Format** +**[Appendix B: IBC Queue Format](appendix-b.md)** -**Appendix C: Merkle Proof Format** +**[Appendix C: Merkle Proof Format](appendix-c.md)** -**Appendix D: Universal IBC Packets** +**[Appendix D: Universal IBC Packets](appendix-d.md)** -**Appendix E: Tendermint Header Proofs** +**[Appendix E: Tendermint Header Proofs](appendix-e.md)** - - -## 1 Overview - -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 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], used by StormMQ, RabbitMQ, etc. - -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 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. - -_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 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. - -_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 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]. 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 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. - -_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 risk exposure in the face of Byzantine actors. - -## 2 Proofs - -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: - -* given a trusted _Hh_ and _Ch_ and an attributable update message _Uh'_ it is possible to prove _Hh'_ where _Ch' = Ch_ and - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(now, Hh) < P_ -* given a trusted _Hh_ and _Ch_ and an attributable change message _Xh'_ it is possible to prove _Hh'_ where _Ch' _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Ch_ and - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(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 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. - -_valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ [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 - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(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. - -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 - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -vals(Cn, Ch ) < ⅓, that each step must have a change of less than one-third of the validator set[^4]. - -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: - -_valid(T, Xh | Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ [true | false | unknown]_ - -_if Hh-1 _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_T then _ - -_valid(T, Xh | Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ [true | false]_ - -_there must exist some Uh or Xh that evaluates to true_ - -_if Ch _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_T then_ - -_ valid(T, Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ false_ - -and can process update transactions as follows: - -_update(T, Xh | Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match valid(T, Xh | Uh )_ - -_ false _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ return Error("invalid proof")_ - -_ unknown _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ return Error("need a proof between current and h")_ - -_ true _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ T _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(Hh ,Ch )_ - -We define _max(T)_ as _max(h, where Hh_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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_. - -## 3 Messaging Queue - -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 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 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], 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). - -Message passing implies a causal ordering over multiple chains and these can be important for reasoning on the system. Given _x _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ y_ means _x_ is causally before _y_, and chains A and B, and _a_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_b_ means _a_ implies _b_: - -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ B:receive(msgi )_ - -_B:receive(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ A:receipt(msgi )_ - -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:send(msgi+1 )_ - -_x_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - _x_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_B:receive(msgi )_ - -_y_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_B:receive(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - _y_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:receipt(msgi )_ - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC0.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC0.png "image_tooltip") - - -([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. - -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_ - -**peek ** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**m**: _if qhead = qtail { return None } else { return q[qhead] }_ - -**pop ** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ - -**push(m)**: _q[qtail] = m; qtail++_ - -**advance(i)**: _qhead = i; qtail = max(qtail , i)_ - -**head ** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**i**: _qhead_ - -**tail** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**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. - -**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. - -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. - -### 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. - -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. - -### 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, 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) - -_Vsend = (type, data)_ - -_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. - -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. - -_(IBCsend(D, type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ Success)_ - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ 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_: - -_A:IBCreceive(S, Mk,v,h) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qS.receipt =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, reciept, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a send"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ k = (_, send, i) and head(qS.receipt) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_i_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("out of order"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TS _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ valid(Hh ,Mk,v,h ) = false _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ v = (type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(result, err) :=ftype(data); push(qS.receipt , (result, err)); Success _ - -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. - -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) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.send =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a recipient"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ k = (_, receipt, head|tail)_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("only accepts message proofs"),_ - -_ k = (_, receipt, i) and head(qS.send) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_i_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("out of order"),_ - -_ v = (_, error) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(type, data) := pop(qS.send ); rollbacktype(data); Success_ - -_ v = (res, success) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(type, data) := pop(qS.send ); committype(data, res); Success_ - -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. - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC1.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC1.png "image_tooltip") - - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC2.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC2.png "image_tooltip") - - -### 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 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. - -_while true_ - -_ pending := tail(A:qB.send)_ - -_ received := tail(B:qA.receive)_ - -_ if pending > received_ - -_ Uh := A:latestHeader_ - -_ B:updateHeader(Uh)_ - -_ for i :=received...pending_ - -_ k := (B, send, i)_ - -_ packet := A:Mk,v,h_ - -_ B:IBCreceive(A, packet)_ - -_ 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. - -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). - -## 4 Optimizations - -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. - -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: - -_A:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was not sent before height h_ - -_A:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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 _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_otherwise (message result is not yet processed)_ - -_B:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was not received before height h_ - -_B:Mk,v,h _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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._ - -_Vsend = (maxHeight, maxTime, type, data)_ - -_expired(Hh ,Vsend ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_[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: - -_IBCreceive:_ - -_ …._ - -_ expired(latestHeader, v) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_push(qS.receipt , (None, TimeoutError));_ - -_ v = (_, _, type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(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. - -_S:IBCtimeout(A, Mk,v,h) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.send =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a receipt"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ k = (S, receipt, tail)_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_match_ - - - _tail _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_head(qS.send )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("receipt exists, no timeout proof")_ - - - _not expired(peek(qS.send )) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("message timeout not yet reached")_ - - - _default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(_, _, type, data) := pop(qS.send ); rollbacktype(data); Success_ - - - _default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a tail proof")_ - -which processes timeouts in order, and adds one more condition to the queues: - -_A:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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 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. - -### 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. - -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 = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was not received before height h_ - -_B:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was provably resolved on the sending chain before height h_ - -_B:Mk,v,h _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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. - -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) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.receipt =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unknown sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be for the send queue"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ k_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ (_, _, head) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("Need a proof of the head of the queue"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ head := v _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_match_ - -_ head <= head(qA.receipt) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("cleanup must go forward"),_ - -_ default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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 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. - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC3.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC3.png "image_tooltip") - - -### 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: failures of the consensus mechanism (reversing "final" blocks), or 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. - -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 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. - -## 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. - -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. - - - -## Appendix A: Encoding Libraries - -The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. - -In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[^6] and google's protobuf[^7]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. - -The tendermint-specific data structures are encoded with go-wire[^8], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. - -For the following appendixes, the data structure specifications will be in proto3[^9] format. - -## Appendix B: IBC Queue Format - -The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: - -_key = _(_remote id, [send|receipt], [head|tail|index])_ - -_Vsend = (maxHeight, maxTime, type, data)_ - -_Vreceipt = (result, [success|error code])_ - - -``` - message QueueName { - // chain_id is which chain this queue is - // associated with - string chain_id = 1; - enum Purpose { - SEND = 0; - RECEIPT = 1; - } - Purpose purpose = 2; - } - // StateKey is a key for the head/tail of a given queue - message StateKey { - QueueName queue = 1; - // both encode into one byte with varint encoding - // never clash with 8 byte message indexes - enum State { - HEAD = 0; - TAIL = 0x7f; - } - State state = 2; - } - // StateValue is the type stored under a StateKey - message StateValue { - fixed64 index = 1; - } - // MessageKey is the key for message *index* in a given queue - message MessageKey { - QueueName queue = 1; - fixed64 index = 2; - } - // SendValue is stored under a MessageKey in the SEND queue - message SendValue { - uint64 maxHeight = 1; - google.protobuf.Timestamp maxTime = 2; - // use kind instead of type to avoid keyword conflict - bytes kind = 3; - bytes data = 4; - } - // ReceiptValue is stored under a MessageKey in the RECEIPT queue - message ReceiptValue { - // 0 is success, others are application-defined errors - int32 errorCode = 1; - // contains result on success, optional info on error - bytes data = 2; - } -``` - -Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. - -## Appendix C: Merkle Proof Formats - -A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[^10]. - -There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[^11], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. - -We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. - -The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. - -A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. - -For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[^12], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. - -``` - // HashOp is the hashing algorithm we use at each level - enum HashOp { - RIPEMD160 = 0; - SHA224 = 1; - SHA256 = 2; - SHA384 = 3; - SHA512 = 4; - SHA3_224 = 5; - SHA3_256 = 6; - SHA3_384 = 7; - SHA3_512 = 8; - SHA256_X2 = 9; - }; - // Op represents one hash in a chain of hashes. - // An operation takes the output of the last level and returns - // a hash for the next level: - // Op(last) => Operation(prefix + last + sufix) - // - // A simple left/right hash would simply set prefix=left or - // suffix=right and leave the other blank. However, one could - // also represent the a Patricia trie proof by setting - // prefix to the rlp encoding of all nodes before the branch - // we select, and suffix to all those after the one we select. - message Op { - bytes prefix = 1; - bytes suffix = 2; - HashOp op = 3; - } - // Data is the end value stored, used to generate the initial hash - message Data { - bytes prefix = 1; - bytes key = 2; - bytes value = 3; - HashOp op = 4; - // If it is KeyValue, this is the data we want - // If it is SubTree, key is name of the tree, value is root hash - // Expect another branch to follow - enum DataType { - KeyValue = 0; - SubTree = 1; - } - DataType dataType = 5; - } - // Branch will hash data and then pass it through operations from - // last to first in order to calculate the root node. - // - // Visualize Branch as representing the data closest to root as the - // first item, and the leaf as the last item. - message Branch { - repeated Op operations = 1; - Data data = 2; - } - // MerkleProof shows a veriable path from the data to - // a root hash (potentially spanning multiple sub-trees). - message MerkleProof { - // identify the header this is rooted in - string chainId = 1; - uint64 height = 2; - // this hash must match the header as well as the - // calculation from below - bytes rootHash = 3; - // branches start from the value, and then may - // include multiple subtree branches to embed it - // - // The first branch must have dataType KeyValue - // Following branches must have dataType SubTree - repeated Branch branches = 1; - } - ``` - -## Appendix D: Universal IBC Packets - -The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. - -``` - // IBCPacket sends a proven key/value pair from an IBCQueue. - // Depending on the type of message, we require a certain type - // of key (MessageKey at a given height, or StateKey). - // - // Includes src_chain and src_height to look up the proper - // header to verify the merkle proof. - message IBCPacket { - // chain id it is coming from - string src_chain = 1; - // height for the header the proof belongs to - uint64 src_height = 2; - // the message type, which determines what key/value mean - enum MsgType { - RECEIVE = 0; - RECEIPT = 1; - TIMEOUT = 2; - CLEANUP = 3; - } - MsgType msgType = 3; - bytes key = 4; - bytes value = 5; - // the proof of the message - MerkleProof proof = 6; - } -``` - -## Appendix E: Tendermint Header Proofs - -TODO: clean this all up - -This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. - -In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. - -**Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet** - -**-> needs input/support from Tendermint Core team (and go-crypto)** - -**Registering Chain** - -**Updating Header** - -**Validator Changes** - -ROOT of trust - -As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain. - -When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header. - -From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust. - -VERIFYING HEADERS - -Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign). - -This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message. - -UPDATING VALIDATORS SET - -If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier. - -A validator change in Tendermint can be securely verified with the following checks: - - - -* First, that the new header, validators, and signatures are internally consistent - * We have a new set of validators that matches the hash on the new header - * At least 2/3 of the voting power of the new set validates the new header -* Second, that the new header is also valid in the eyes of our trust set - * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header - -In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). - - - -## Notes - -[^1]: - [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) - -[^2]: - [http://www.amqp.org/sites/amqp.org/files/amqp.pdf](http://www.amqp.org/sites/amqp.org/files/amqp.pdf) - -[^3]: - [https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) - -[^4]: - [https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104](https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104) - -[^5]: - [http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/](http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/) - -[^6]: - [https://github.com/ethereum/wiki/wiki/RLP](https://github.com/ethereum/wiki/wiki/RLP) - -[^7]: - [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - -[^8]: - [https://github.com/tendermint/go-wire](https://github.com/tendermint/go-wire) - -[^9]: - [https://developers.google.com/protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3) - -[^10]: - [https://en.wikipedia.org/wiki/Merkle_tree](https://en.wikipedia.org/wiki/Merkle_tree) - -[^11]: - [https://chainpoint.org/](https://chainpoint.org/) - -[^12]: - [https://github.com/tendermint/iavl](https://github.com/tendermint/iavl) From cc17a3e9636dea69e8412b7e9be6b19a14cf01a9 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 18:46:13 +0100 Subject: [PATCH 05/77] Clean up all formulas in proofs --- docs/spec/ibc/proofs.md | 105 +++++++--------------------------------- 1 file changed, 17 insertions(+), 88 deletions(-) diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md index 59ab961428..c402eae393 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/proofs.md @@ -2,43 +2,23 @@ ([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 of all 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: -* given a trusted _Hh_ and _Ch_ and an attributable update message _Uh'_ it is possible to prove _Hh'_ where _Ch' = Ch_ and - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(now, Hh) < P_ -* given a trusted _Hh_ and _Ch_ and an attributable change message _Xh'_ it is possible to prove _Hh'_ where _Ch' _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Ch_ and - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(now, Hh) < P_ +* 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 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. -_valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ [true | false]_ +_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 - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(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. @@ -48,9 +28,7 @@ Development of a fully open and decentralized PKI for tracking blockchains is an 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 -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -vals(Cn, Ch ) < ⅓, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. +Δ_vals(Cn, Ch ) < ⅓_, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. 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. @@ -58,72 +36,23 @@ Since these messages _Uh_ and _Xh_ provide all knowledge o More formally, given existing set of trust _T_ = _{(Hi , Ci ), (Hj , Cj ), …}_, we must provide: -_valid(T, Xh | Uh ) _ +_valid(T, Xh | Uh )_ ⇒ _[true | false | unknown]_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+_if Hh-1_ ∈ _T then_: +* _valid(T, Xh | Uh )_ ⇒ _[true | false]_ +* _there must exist some Uh or Xh that evaluates to true_ -_ [true | false | unknown]_ - -_if Hh-1 _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_T then _ - -_valid(T, Xh | Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ [true | false]_ - -_there must exist some Uh or Xh that evaluates to true_ - -_if Ch _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_T then_ - -_ valid(T, Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ false_ +_if Ch_ ∉ _T then_ +* _valid(T, Uh )_ ⇒ _false_ and can process update transactions as follows: -_update(T, Xh | Uh ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- +_update(T, Xh | Uh )_ ⇒ _ match valid(T, Xh | Uh )_ +* _false_ ⇒ _return Error("invalid proof")_ +* _unknown_ ⇒ _return Error("need a proof between current and h")_ +* _true_ ⇒ _T_ ∪ _(Hh ,Ch )_ -_ false _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ return Error("invalid proof")_ - -_ unknown _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ return Error("need a proof between current and h")_ - -_ true _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ T _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(Hh ,Ch )_ - -We define _max(T)_ as _max(h, where Hh_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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_. From 1feb84e2726c268e183ba6e4aa2f5a2ca2631b68 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 20:20:22 +0100 Subject: [PATCH 06/77] Cleaned up queue section --- docs/spec/ibc/images/ReceiptError.png | Bin 0 -> 26687 bytes docs/spec/ibc/images/Receipts.png | Bin 0 -> 26870 bytes docs/spec/ibc/queues.md | 317 +++++--------------------- docs/spec/ibc/specification.md | 3 +- 4 files changed, 55 insertions(+), 265 deletions(-) create mode 100644 docs/spec/ibc/images/ReceiptError.png create mode 100644 docs/spec/ibc/images/Receipts.png diff --git a/docs/spec/ibc/images/ReceiptError.png b/docs/spec/ibc/images/ReceiptError.png new file mode 100644 index 0000000000000000000000000000000000000000..6ffa3f9558eaf0bc27f54ab449484320e24ea527 GIT binary patch literal 26687 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 NUMi?RuaGkf`7eI6vKIgV literal 0 HcmV?d00001 diff --git a/docs/spec/ibc/images/Receipts.png b/docs/spec/ibc/images/Receipts.png new file mode 100644 index 0000000000000000000000000000000000000000..19306867c5d1e03338a8809f2407e9af5ac8904b GIT binary patch literal 26870 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 Cisyj< literal 0 HcmV?d00001 diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index f118361e65..8a2b9beb36 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -12,70 +12,22 @@ To build useful algorithms upon a provable asynchronous messaging primitive, we 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 _ +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 )_ -_ y_ means _x_ is causally before _y_, and chains A and B, and _a_ +_B:receive(msgi )_ → _A:receipt(msgi )_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+_A:send(msgi )_ → _A:send(msgi+1 )_ -_b_ means _a_ implies _b_: +_x_ → _A:send(msgi )_ ⇒ +_x_ → _B:receive(msgi )_ -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ B:receive(msgi )_ - -_B:receive(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ A:receipt(msgi )_ - -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:send(msgi+1 )_ - -_x_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:send(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - _x_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_B:receive(msgi )_ - -_y_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_B:receive(msgi )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - _y_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A:receipt(msgi )_ - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC0.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC0.png "image_tooltip") +_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](https://en.wikipedia.org/wiki/Vector_clock)) In this section, we define an efficient implementation of a secure, reliable messaging queue. @@ -88,37 +40,21 @@ We can visualize a queue as a slice pointing into an infinite sized array. It ma **init**: _qhead = qtail = 0_ -**peek ** +**peek** ⇒ **m**: _if qhead = qtail { return None } else { return q[qhead] }_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**m**: _if qhead = qtail { return None } else { return q[qhead] }_ - -**pop ** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ +**pop** ⇒ **m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ **push(m)**: _q[qtail] = m; qtail++_ **advance(i)**: _qhead = i; qtail = max(qtail , i)_ -**head ** +**head** ⇒ **i**: _qhead_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**i**: _qhead_ - -**tail** - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -**i**: _qtail_ +**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. -**Key:_ (queue name, [head|tail|index])_** +**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. @@ -132,10 +68,10 @@ As mentioned above, in order for the receiver to unambiguously interpret the mer 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 +* _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 @@ -151,86 +87,23 @@ _Vreceipt = (result, [success|error code])_ 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. -_(IBCsend(D, type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ Success)_ - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ push(qD.send ,Vsend{type, data})_ +_(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_: -_A:IBCreceive(S, Mk,v,h) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qS.receipt =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, reciept, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a send"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_A_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ k = (_, send, i) and head(qS.receipt) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_i_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("out of order"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TS _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ valid(Hh ,Mk,v,h ) = false _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ v = (type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(result, err) :=ftype(data); push(qS.receipt , (result, err)); Success _ +_A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ + * _qS.receipt =_ ∅ ⇒ _Error("unregistered sender"),_ + * _k = (\_, reciept, \_)_ ⇒ _Error("must be a send"),_ + * _k = (d, \_, \_) and d_ ≠ _A_ ⇒ _Error("sent to a different chain"),_ + * _k = (\_, send, i) and head(qS.receipt)_ ≠ _i_ ⇒ _Error("out of order"),_ + * _Hh_ ∉ _TS_ ⇒ _Error("must submit header for height h"),_ + * _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 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). @@ -240,98 +113,22 @@ When we wish to create a transaction that atomically commits or rolls back acros 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) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.send =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a recipient"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ k = (_, receipt, head|tail)_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("only accepts message proofs"),_ - -_ k = (_, receipt, i) and head(qS.send) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_i_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("out of order"),_ - -_ v = (_, error) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(type, data) := pop(qS.send ); rollbacktype(data); Success_ - -_ v = (res, success) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(type, data) := pop(qS.send ); committype(data, res); Success_ +_S:IBCreceipt(A, Mk,v,h)_ ⇒ _match_ + * _qA.send =_ ∅ ⇒ _Error("unregistered sender"), _ + * _k = (\_, send, \_)_ ⇒ _Error("must be a recipient"),_ + * _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 = (\_, receipt, head|tail)_ ⇒ _Error("only accepts message proofs"),_ + * _k = (\_, receipt, i) and head(qS.send)_ ≠ _i_ ⇒ _Error("out of order"),_ + * _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 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) - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC1.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC1.png "image_tooltip") - - - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC2.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC2.png "image_tooltip") +![Rejected Transaction](images/ReceiptError.png) ### 3.6 Relay Process @@ -342,27 +139,19 @@ The relay process must have access to accounts on both chains with sufficient ba 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. -_while true_ - -_ pending := tail(A:qB.send)_ - -_ received := tail(B:qA.receive)_ - -_ if pending > received_ - -_ Uh := A:latestHeader_ - -_ B:updateHeader(Uh)_ - -_ for i :=received...pending_ - -_ k := (B, send, i)_ - -_ packet := A:Mk,v,h_ - -_ B:IBCreceive(A, packet)_ - -_ sleep(desiredLatency)_ +``` +while true + pending := tail(A:qB.send) + received := tail(B:qA.receive) + if pending > received + Uh := A:latestHeader + B:updateHeader(Uh) + for i :=received...pending + k := (B, send, i) + packet := A:Mk,v,h + B:IBCreceive(A, packet) + 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. diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md index 21cbe0b6b2..9674abbb32 100644 --- a/docs/spec/ibc/specification.md +++ b/docs/spec/ibc/specification.md @@ -10,7 +10,8 @@ This paper specifies the IBC (inter blockchain communication) protocol, which wa 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. Details of how Cosmos Hub uses IBC to securely route and transfer value are 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. +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. From f5a45a94f6e5741bedf7c3da4f1a0ac92ad82bb1 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 21:05:23 +0100 Subject: [PATCH 07/77] Clean up optimization section --- docs/spec/ibc/images/CleanUp.png | Bin 0 -> 38843 bytes docs/spec/ibc/optimizations.md | 288 ++++--------------------------- 2 files changed, 36 insertions(+), 252 deletions(-) create mode 100644 docs/spec/ibc/images/CleanUp.png diff --git a/docs/spec/ibc/images/CleanUp.png b/docs/spec/ibc/images/CleanUp.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b5a1d348bdf5deb6a38fd80bca90bfd67f870e GIT binary patch literal 38843 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.send_ or _B:qA.receipt_ we currently have the following guarantees: -_A:Mk,v,h = _ +_A:Mk,v,h =_ ∅ _if message i was not sent before height h_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+_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)_ -_if message i was not sent before height h_ +_A:Mk,v,h _ ≠ ∅ _otherwise (message result is not yet processed)_ -_A:Mk,v,h = _ +_B:Mk,v,h =_ ∅ _if message i was not received before height h_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+_B:Mk,v,h _ ≠ ∅ _if message i was received before height h (and all messages j < i were received)_ -_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 _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_otherwise (message result is not yet processed)_ - -_B:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was not received before height h_ - -_B:Mk,v,h _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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 ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_[true/false]_ +_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: _IBCreceive:_ - -_ …._ - -_ expired(latestHeader, v) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_push(qS.receipt , (None, TimeoutError));_ - -_ v = (_, _, type, data) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(result, err) :=ftype(data); push(qS.receipt , (result, err)); _ + * …. + * _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. -_S:IBCtimeout(A, Mk,v,h) _ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.send =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unregistered sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a receipt"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ k = (S, receipt, tail)_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_match_ - - - _tail _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_head(qS.send )_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("receipt exists, no timeout proof")_ - - - _not expired(peek(qS.send )) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("message timeout not yet reached")_ - - - _default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_(_, _, type, data) := pop(qS.send ); rollbacktype(data); Success_ - - - _default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be a tail proof")_ +_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 = (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_ + * _default_ ⇒ _Error("must be a tail proof")_ which processes timeouts in order, and adds one more condition to the queues: -_A:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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 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. @@ -192,118 +64,30 @@ While we clean up the _send queue_ upon getting a receipt, if left to run indefi 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 = _ +_B:Mk,v,h =_ ∅ _if message i was not received before height h_ -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

+_B:Mk,v,h =_ ∅ _if message i was provably resolved on the sending chain before height h_ -_if message i was not received before height h_ - -_B:Mk,v,h = _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_if message i was provably resolved on the sending chain before height h_ - -_B:Mk,v,h _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_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. 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) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ match_ - -_qA.receipt =_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- - - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("unknown sender"), _ - -_ k = (_, send, _) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must be for the send queue"),_ - -_ k = (d, _, _) and d _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_S_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("sent to a different chain"),_ - -_ k_ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_ (_, _, head) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("Need a proof of the head of the queue"),_ - -_ Hh _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_TA _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("must submit header for height h"),_ - -_ not valid(Hh ,Mk,v,h ) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("invalid merkle proof"),_ - -_ head := v _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_match_ - -_ head <= head(qA.receipt) _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_Error("cleanup must go forward"),_ - -_ default _ - -

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(Back to top)(Next alert)
>>>>>

- -_advance(qA.receipt , head); Success_ +_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"),_ + * _head := v_ ⇒ _match_ + * _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 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. - - -

>>>>> gd2md-html alert: inline image link here (to images/Cosmos-IBC3.png). Store image on your image server and adjust path/filename if necessary.
(Back to top)(Next alert)
>>>>>

- - -![alt_text](images/Cosmos-IBC3.png "image_tooltip") +![Cleaning up Packets](images/CleanUp.png) ### 4.3 Handling Byzantine Failures From cdf08ecdb7b719eaf02eb95a4ea563ed7d330efc Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 21:10:30 +0100 Subject: [PATCH 08/77] Fix typos --- docs/spec/ibc/optimizations.md | 4 ++-- docs/spec/ibc/queues.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index a6e45d3589..e6c2f8cbcf 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -10,7 +10,7 @@ Sometimes it is desirable to have some timeout, an upper limit to how long you w 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_ @@ -26,7 +26,7 @@ Based on these guarantees, we can make a few modifications of the above protocol _Vsend = (maxHeight, maxTime, type, data)_ -_expired(Hh ,Vsend )_ ⇒ _[true/false]_ +_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: diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index 8a2b9beb36..b0f459c40a 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -58,7 +58,7 @@ Based upon this needed functionality, we define a set of keys to be stored in th 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. @@ -77,7 +77,7 @@ These two queues have different purposes and store messages of different types. 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)_ @@ -94,7 +94,7 @@ The permissioning of which module can write which packet can be defined per type _(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"),_ @@ -114,7 +114,7 @@ When we wish to create a transaction that atomically commits or rolls back acros 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"), _ + * _qA.send =_ ∅ ⇒ _Error("unregistered sender"),_ * _k = (\_, send, \_)_ ⇒ _Error("must be a recipient"),_ * _k = (d, \_, \_) and d_ ≠ _S_ ⇒ _Error("sent to a different chain"),_ * _Hh_ ∉ _TA_ ⇒ _Error("must submit header for height h"),_ From ecb1f93e190d22822b2f1150697a3898b25b84c7 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 13 Feb 2018 21:17:27 +0100 Subject: [PATCH 09/77] Join appendices into one file --- docs/spec/ibc/appendix-a.md | 11 ---- docs/spec/ibc/appendix-b.md | 62 ---------------------- docs/spec/ibc/appendix-c.md | 87 ------------------------------ docs/spec/ibc/appendix-d.md | 33 ------------ docs/spec/ibc/appendix-e.md | 49 ----------------- docs/spec/ibc/appendix.md | 97 ++++++++++++++++++++++++++++++++++ docs/spec/ibc/specification.md | 10 ++-- 7 files changed, 102 insertions(+), 247 deletions(-) delete mode 100644 docs/spec/ibc/appendix-a.md delete mode 100644 docs/spec/ibc/appendix-b.md delete mode 100644 docs/spec/ibc/appendix-c.md delete mode 100644 docs/spec/ibc/appendix-d.md delete mode 100644 docs/spec/ibc/appendix-e.md create mode 100644 docs/spec/ibc/appendix.md diff --git a/docs/spec/ibc/appendix-a.md b/docs/spec/ibc/appendix-a.md deleted file mode 100644 index 289699aff1..0000000000 --- a/docs/spec/ibc/appendix-a.md +++ /dev/null @@ -1,11 +0,0 @@ -## Appendix A: Encoding Libraries - -([Back to table of contents](specification.md#contents)) - -The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. - -In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./footnotes.md#6)] and google's protobuf[[7](./footnotes.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. - -The tendermint-specific data structures are encoded with go-wire[[8](./footnotes.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. - -For the following appendixes, the data structure specifications will be in proto3[[9](./footnotes.md#9)] format. diff --git a/docs/spec/ibc/appendix-b.md b/docs/spec/ibc/appendix-b.md deleted file mode 100644 index 226f590606..0000000000 --- a/docs/spec/ibc/appendix-b.md +++ /dev/null @@ -1,62 +0,0 @@ -## Appendix B: IBC Queue Format - -([Back to table of contents](specification.md#contents)) - -The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: - -_key = _(_remote id, [send|receipt], [head|tail|index])_ - -_Vsend = (maxHeight, maxTime, type, data)_ - -_Vreceipt = (result, [success|error code])_ - - -``` - message QueueName { - // chain_id is which chain this queue is - // associated with - string chain_id = 1; - enum Purpose { - SEND = 0; - RECEIPT = 1; - } - Purpose purpose = 2; - } - // StateKey is a key for the head/tail of a given queue - message StateKey { - QueueName queue = 1; - // both encode into one byte with varint encoding - // never clash with 8 byte message indexes - enum State { - HEAD = 0; - TAIL = 0x7f; - } - State state = 2; - } - // StateValue is the type stored under a StateKey - message StateValue { - fixed64 index = 1; - } - // MessageKey is the key for message *index* in a given queue - message MessageKey { - QueueName queue = 1; - fixed64 index = 2; - } - // SendValue is stored under a MessageKey in the SEND queue - message SendValue { - uint64 maxHeight = 1; - google.protobuf.Timestamp maxTime = 2; - // use kind instead of type to avoid keyword conflict - bytes kind = 3; - bytes data = 4; - } - // ReceiptValue is stored under a MessageKey in the RECEIPT queue - message ReceiptValue { - // 0 is success, others are application-defined errors - int32 errorCode = 1; - // contains result on success, optional info on error - bytes data = 2; - } -``` - -Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. diff --git a/docs/spec/ibc/appendix-c.md b/docs/spec/ibc/appendix-c.md deleted file mode 100644 index 3eb870d715..0000000000 --- a/docs/spec/ibc/appendix-c.md +++ /dev/null @@ -1,87 +0,0 @@ -## Appendix C: Merkle Proof Formats - -([Back to table of contents](specification.md#contents)) - -A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./footnotes.md#10)]. - -There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./footnotes.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. - -We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. - -The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. - -A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. - -For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./footnotes.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. - -``` - // HashOp is the hashing algorithm we use at each level - enum HashOp { - RIPEMD160 = 0; - SHA224 = 1; - SHA256 = 2; - SHA384 = 3; - SHA512 = 4; - SHA3_224 = 5; - SHA3_256 = 6; - SHA3_384 = 7; - SHA3_512 = 8; - SHA256_X2 = 9; - }; - // Op represents one hash in a chain of hashes. - // An operation takes the output of the last level and returns - // a hash for the next level: - // Op(last) => Operation(prefix + last + sufix) - // - // A simple left/right hash would simply set prefix=left or - // suffix=right and leave the other blank. However, one could - // also represent the a Patricia trie proof by setting - // prefix to the rlp encoding of all nodes before the branch - // we select, and suffix to all those after the one we select. - message Op { - bytes prefix = 1; - bytes suffix = 2; - HashOp op = 3; - } - // Data is the end value stored, used to generate the initial hash - message Data { - bytes prefix = 1; - bytes key = 2; - bytes value = 3; - HashOp op = 4; - // If it is KeyValue, this is the data we want - // If it is SubTree, key is name of the tree, value is root hash - // Expect another branch to follow - enum DataType { - KeyValue = 0; - SubTree = 1; - } - DataType dataType = 5; - } - // Branch will hash data and then pass it through operations from - // last to first in order to calculate the root node. - // - // Visualize Branch as representing the data closest to root as the - // first item, and the leaf as the last item. - message Branch { - repeated Op operations = 1; - Data data = 2; - } - // MerkleProof shows a veriable path from the data to - // a root hash (potentially spanning multiple sub-trees). - message MerkleProof { - // identify the header this is rooted in - string chainId = 1; - uint64 height = 2; - // this hash must match the header as well as the - // calculation from below - bytes rootHash = 3; - // branches start from the value, and then may - // include multiple subtree branches to embed it - // - // The first branch must have dataType KeyValue - // Following branches must have dataType SubTree - repeated Branch branches = 1; - } - ``` - diff --git a/docs/spec/ibc/appendix-d.md b/docs/spec/ibc/appendix-d.md deleted file mode 100644 index 64b8565fcd..0000000000 --- a/docs/spec/ibc/appendix-d.md +++ /dev/null @@ -1,33 +0,0 @@ -## Appendix D: Universal IBC Packets - -([Back to table of contents](specification.md#contents)) - -The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. - -``` - // IBCPacket sends a proven key/value pair from an IBCQueue. - // Depending on the type of message, we require a certain type - // of key (MessageKey at a given height, or StateKey). - // - // Includes src_chain and src_height to look up the proper - // header to verify the merkle proof. - message IBCPacket { - // chain id it is coming from - string src_chain = 1; - // height for the header the proof belongs to - uint64 src_height = 2; - // the message type, which determines what key/value mean - enum MsgType { - RECEIVE = 0; - RECEIPT = 1; - TIMEOUT = 2; - CLEANUP = 3; - } - MsgType msgType = 3; - bytes key = 4; - bytes value = 5; - // the proof of the message - MerkleProof proof = 6; - } -``` - diff --git a/docs/spec/ibc/appendix-e.md b/docs/spec/ibc/appendix-e.md deleted file mode 100644 index f07360850d..0000000000 --- a/docs/spec/ibc/appendix-e.md +++ /dev/null @@ -1,49 +0,0 @@ -## Appendix E: Tendermint Header Proofs - -TODO: clean this all up - -This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. - -In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. - -**Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet** - -**-> needs input/support from Tendermint Core team (and go-crypto)** - -**Registering Chain** - -**Updating Header** - -**Validator Changes** - -ROOT of trust - -As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain. - -When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header. - -From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust. - -VERIFYING HEADERS - -Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign). - -This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message. - -UPDATING VALIDATORS SET - -If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier. - -A validator change in Tendermint can be securely verified with the following checks: - - - -* First, that the new header, validators, and signatures are internally consistent - * We have a new set of validators that matches the hash on the new header - * At least 2/3 of the voting power of the new set validates the new header -* Second, that the new header is also valid in the eyes of our trust set - * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header - -In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). - - diff --git a/docs/spec/ibc/appendix.md b/docs/spec/ibc/appendix.md new file mode 100644 index 0000000000..e818ad9ef5 --- /dev/null +++ b/docs/spec/ibc/appendix.md @@ -0,0 +1,97 @@ +# Appendices + +([Back to table of contents](specification.md#contents)) + +## Appendix A: Encoding Libraries + +The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. + +In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./footnotes.md#6)] and google's protobuf[[7](./footnotes.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. + +The tendermint-specific data structures are encoded with go-wire[[8](./footnotes.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. + +For the following appendixes, the data structure specifications will be in proto3[[9](./footnotes.md#9)] format. + +## Appendix B: IBC Queue Format + +The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: + +_key = _(_remote id, [send|receipt], [head|tail|index])_ + +_Vsend = (maxHeight, maxTime, type, data)_ + +_Vreceipt = (result, [success|error code])_ + +Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. + +See [binary format as protobuf specification](./protobuf/queue.proto) + +## Appendix C: Merkle Proof Formats + +A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./footnotes.md#10)]. + +There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./footnotes.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. + +We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. + +The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. + +A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. + +For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./footnotes.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. + +See [binary format as protobuf specification](./protobuf/merkle.proto) + +## Appendix D: Universal IBC Packets + +The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. + +See [binary format as protobuf specification](./protobuf/messages.proto) + +## Appendix E: Tendermint Header Proofs + +**TODO: clean this all up** + +This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. + +In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks. + +Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet + +-> needs input/support from Tendermint Core team (and go-crypto) + +Registering Chain + +Updating Header + +Validator Changes + +**ROOT of trust** + +As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain. + +When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header. + +From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust. + +**VERIFYING HEADERS** + +Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign). + +This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message. + +**UPDATING VALIDATORS SET** + +If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier. + +A validator change in Tendermint can be securely verified with the following checks: + +* First, that the new header, validators, and signatures are internally consistent + * We have a new set of validators that matches the hash on the new header + * At least 2/3 of the voting power of the new set validates the new header +* Second, that the new header is also valid in the eyes of our trust set + * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header + +In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). + + diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md index 9674abbb32..5864337df0 100644 --- a/docs/spec/ibc/specification.md +++ b/docs/spec/ibc/specification.md @@ -36,13 +36,13 @@ The protocol makes no assumptions of block times or network delays in the transm 1. Handling Byzantine Failures 1. **[Conclusion](conclusion.md)** -**[Appendix A: Encoding Libraries](appendix-a.md)** +**[Appendix A: Encoding Libraries](appendix.md#appendix-a-encoding-libraries)** -**[Appendix B: IBC Queue Format](appendix-b.md)** +**[Appendix B: IBC Queue Format](appendix.md#appendix-b-ibc-queue-format)** -**[Appendix C: Merkle Proof Format](appendix-c.md)** +**[Appendix C: Merkle Proof Format](appendix.md#appendix-c-merkle-proof-formats)** -**[Appendix D: Universal IBC Packets](appendix-d.md)** +**[Appendix D: Universal IBC Packets](appendix.md#appendix-d-universal-ibc-packets)** -**[Appendix E: Tendermint Header Proofs](appendix-e.md)** +**[Appendix E: Tendermint Header Proofs](appendix.md#appendix-e-tendermint-header-proofs)** From 32ffd2d316e74ca49c9d733030dba439c5568f2c Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 26 Feb 2018 10:48:12 +0100 Subject: [PATCH 10/77] Review the IBC specification --- 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, 530 insertions(+), 128 deletions(-) diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md index 37d555e6c4..db5833ee1c 100644 --- a/docs/spec/ibc/conclusion.md +++ b/docs/spec/ibc/conclusion.md @@ -1,7 +1,21 @@ ## 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 e6c2f8cbcf..ef6259a0e1 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -2,104 +2,214 @@ ([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 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. +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. -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 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. +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. ![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: failures of the consensus mechanism (reversing "final" blocks), or failure at the application level (not performing the action defined by the message). +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: -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. +* failures of the consensus mechanism (reversing "final" blocks) +* failure at the application level (not performing the action defined by the + message). -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 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 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). +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 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 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). -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. +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. diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index 9c07d83691..266fc92024 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -2,38 +2,109 @@ ([Back to table of contents](specification.md#contents)) -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 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 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 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 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. +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. -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. +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. ### 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 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 +that 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 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. +_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. _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, which help enforce finality. 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. 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 a 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 +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 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. +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. ### 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 a powerful 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 an 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 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 the risk exposure in the face of Byzantine actors. diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md index c402eae393..e13f2bb458 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/proofs.md @@ -2,47 +2,98 @@ ([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 of all 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 out of all of 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 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. +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. -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 +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)]. -Δ_vals(Cn, Ch ) < ⅓_, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. +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. -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: +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: _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: @@ -53,6 +104,17 @@ _ 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 b0f459c40a..4f46fb6697 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -2,41 +2,72 @@ ([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 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. +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. -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. +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. -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_: - -_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 )_ +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 )_ ![Vector Clock image](https://upload.wikimedia.org/wikipedia/commons/5/55/Vector_Clock.svg) -([https://en.wikipedia.org/wiki/Vector_clock](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. -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_ @@ -52,32 +83,68 @@ We can visualize a queue as a slice pointing into an infinite sized array. It ma **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, put 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, but 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)_ @@ -85,16 +152,32 @@ _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"),_ @@ -105,13 +188,30 @@ _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 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). +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). ### 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"),_ @@ -124,7 +224,10 @@ _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 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. +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. ![Successful Transaction](images/Receipts.png) @@ -133,13 +236,26 @@ This enforces that the receipts are processed in order, to allow some the applic ### 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) @@ -153,8 +269,19 @@ 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 5864337df0..9d7b302a8b 100644 --- a/docs/spec/ibc/specification.md +++ b/docs/spec/ibc/specification.md @@ -6,14 +6,32 @@ _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, as well as sharding one chain securely. IBC follows the message-passing paradigm and assumes 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 +* sharding one chain securely. +IBC follows the message-passing paradigm and assumes that 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, using Tendermint's instant finality for quick and efficient transmission. +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. -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. +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. -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 From a8d3b3ef19e38db63f41c185f72f684c8271f77d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 26 Feb 2018 13:17:42 +0100 Subject: [PATCH 11/77] 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 db5833ee1c..37d555e6c4 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 ef6259a0e1..e6c2f8cbcf 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 266fc92024..9c07d83691 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 e13f2bb458..c402eae393 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 4f46fb6697..b0f459c40a 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 9d7b302a8b..5864337df0 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 From 6500728dd283f8ff79376b83cf2611cfe1880d0a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 12 Apr 2018 13:32:49 +0200 Subject: [PATCH 12/77] Rebase onto develop --- docs/spec/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/spec/README.md b/docs/spec/README.md index 6492dc9392..e7507bf95e 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -5,7 +5,6 @@ the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. -<<<<<<< HEAD - [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. - [Staking](staking) - Proof of Stake related specifications including bonding From dc2c638f7f86dfd4fa4f42f7c19c27e349f0a05e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 12 Apr 2018 18:46:16 +0200 Subject: [PATCH 13/77] Move specification.md to README.md, cleanup overview, separate MVP directory --- docs/spec/ibc/README.md | 45 ++++++++++++++++++++++++++++--- docs/spec/ibc/appendix.md | 2 +- docs/spec/ibc/{ => mvp}/ibc.md | 0 docs/spec/ibc/{ => mvp}/mvp1.md | 0 docs/spec/ibc/{ => mvp}/mvp2.md | 0 docs/spec/ibc/{ => mvp}/mvp3.md | 0 docs/spec/ibc/optimizations.md | 2 +- docs/spec/ibc/overview.md | 2 +- docs/spec/ibc/proofs.md | 2 +- docs/spec/ibc/queues.md | 2 +- docs/spec/ibc/specification.md | 48 --------------------------------- 11 files changed, 46 insertions(+), 57 deletions(-) rename docs/spec/ibc/{ => mvp}/ibc.md (100%) rename docs/spec/ibc/{ => mvp}/mvp1.md (100%) rename docs/spec/ibc/{ => mvp}/mvp2.md (100%) rename docs/spec/ibc/{ => mvp}/mvp3.md (100%) delete mode 100644 docs/spec/ibc/specification.md diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 43183251e9..b9251a5189 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -1,7 +1,44 @@ -# Inter Blockchain Communication protocol +# Cosmos Inter-Blockchain Communication (IBC) Protocol -IBC was defined in the [cosmos whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), -and then in detail in a [specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). +## Abstract -This package builds on that and includes detailed specifications, pseudocode and protocol specification. Please read the [table of contents](specification.md) of the IBC specification. +This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms. +The protocol requires two blockchains with cheaply verifiable instant finality. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. + +Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. + +IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), +and then later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). +This documentation replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. + +## Contents + +1. **[Overview](overview.md)** + 1. Definitions + 1. Threat Models +1. **[Proofs](proofs.md)** + 1. Establishing a Root of Trust + 1. Following Block Headers +1. **[Messaging Queue](queues.md)** + 1. Merkle Proofs for Queues + 1. Naming Queues + 1. Message Contents + 1. Sending a Packet + 1. Receipts + 1. Relay Process +1. **[Optimizations](optimizations.md)** + 1. Cleanup + 1. Timeout + 1. Handling Byzantine Failures +1. **[Conclusion](conclusion.md)** + +**[Appendix A: Encoding Libraries](appendix.md#appendix-a-encoding-libraries)** + +**[Appendix B: IBC Queue Format](appendix.md#appendix-b-ibc-queue-format)** + +**[Appendix C: Merkle Proof Format](appendix.md#appendix-c-merkle-proof-formats)** + +**[Appendix D: Universal IBC Packets](appendix.md#appendix-d-universal-ibc-packets)** + +**[Appendix E: Tendermint Header Proofs](appendix.md#appendix-e-tendermint-header-proofs)** diff --git a/docs/spec/ibc/appendix.md b/docs/spec/ibc/appendix.md index e818ad9ef5..bd277ff704 100644 --- a/docs/spec/ibc/appendix.md +++ b/docs/spec/ibc/appendix.md @@ -1,6 +1,6 @@ # Appendices -([Back to table of contents](specification.md#contents)) +([Back to table of contents](README.md#contents)) ## Appendix A: Encoding Libraries diff --git a/docs/spec/ibc/ibc.md b/docs/spec/ibc/mvp/ibc.md similarity index 100% rename from docs/spec/ibc/ibc.md rename to docs/spec/ibc/mvp/ibc.md diff --git a/docs/spec/ibc/mvp1.md b/docs/spec/ibc/mvp/mvp1.md similarity index 100% rename from docs/spec/ibc/mvp1.md rename to docs/spec/ibc/mvp/mvp1.md diff --git a/docs/spec/ibc/mvp2.md b/docs/spec/ibc/mvp/mvp2.md similarity index 100% rename from docs/spec/ibc/mvp2.md rename to docs/spec/ibc/mvp/mvp2.md diff --git a/docs/spec/ibc/mvp3.md b/docs/spec/ibc/mvp/mvp3.md similarity index 100% rename from docs/spec/ibc/mvp3.md rename to docs/spec/ibc/mvp/mvp3.md diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index e6c2f8cbcf..17967c0111 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -1,6 +1,6 @@ ## 4 Optimizations -([Back to table of contents](specification.md#contents)) +([Back to table of contents](README.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. diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index 9c07d83691..48d739796d 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -1,6 +1,6 @@ ## 1 Overview -([Back to table of contents](specification.md#contents)) +([Back to table of contents](README.md#contents)) 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. diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md index c402eae393..d8fa8f7e94 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/proofs.md @@ -1,6 +1,6 @@ ## 2 Proofs -([Back to table of contents](specification.md#contents)) +([Back to table of contents](README.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 of all these, only _Hh_ defines a signature and is thus attributable. diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index b0f459c40a..780d382859 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -1,6 +1,6 @@ ## 3 Messaging Queue -([Back to table of contents](specification.md#contents)) +([Back to table of contents](README.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. diff --git a/docs/spec/ibc/specification.md b/docs/spec/ibc/specification.md deleted file mode 100644 index 5864337df0..0000000000 --- a/docs/spec/ibc/specification.md +++ /dev/null @@ -1,48 +0,0 @@ -# IBC Protocol Specification - -_v0.4.0 / Feb. 13, 2018_ - -**Ethan Frey** - -## 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, 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, 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. 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 - -1. **[Overview](overview.md)** - 1. Definitions - 1. Threat Models -1. **[Proofs](proofs.md)** - 1. Establishing a Root of Trust - 1. Following Block Headers -1. **[Messaging Queue](queues.md)** - 1. Merkle Proofs for Queues - 1. Naming Queues - 1. Message Contents - 1. Sending a Packet - 1. Receipts - 1. Relay Process -1. **[Optimizations](optimizations.md)** - 1. Cleanup - 1. Timeout - 1. Handling Byzantine Failures -1. **[Conclusion](conclusion.md)** - -**[Appendix A: Encoding Libraries](appendix.md#appendix-a-encoding-libraries)** - -**[Appendix B: IBC Queue Format](appendix.md#appendix-b-ibc-queue-format)** - -**[Appendix C: Merkle Proof Format](appendix.md#appendix-c-merkle-proof-formats)** - -**[Appendix D: Universal IBC Packets](appendix.md#appendix-d-universal-ibc-packets)** - -**[Appendix E: Tendermint Header Proofs](appendix.md#appendix-e-tendermint-header-proofs)** - From f1c7d1cceaee423336da1b53fdba0fdb0250093f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 13 Apr 2018 16:27:32 +0200 Subject: [PATCH 14/77] Cleanup & clarification in progress --- docs/spec/ibc/README.md | 8 ++--- docs/spec/ibc/conclusion.md | 6 ++-- docs/spec/ibc/footnotes.md | 3 -- docs/spec/ibc/overview.md | 36 ++++++++------------- docs/spec/ibc/proofs.md | 64 ++++++++++++++++++++++--------------- 5 files changed, 58 insertions(+), 59 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index b9251a5189..9378ba93cd 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -4,13 +4,11 @@ This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms. -The protocol requires two blockchains with cheaply verifiable instant finality. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. +IBC requires two blockchains with cheaply verifiable rapid finality. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. -Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. +The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. -IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), -and then later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). -This documentation replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. +IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This documentation replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. ## Contents diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md index 37d555e6c4..9e58c248ea 100644 --- a/docs/spec/ibc/conclusion.md +++ b/docs/spec/ibc/conclusion.md @@ -1,7 +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 cross-blockchain messaging, and provided sufficient detail to reason about the correctness and efficiency of the protocol. -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. +This document defines solely a message queue protocol - not the application-level semantics which must sit on top of it to enable asset transfer between two chains. 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 Golang and released under the Apache license. To facilitate implementations in other langauages which are wire-compatible with the Cosmos implementation, the following appendices define exact message and binary encoding formats. diff --git a/docs/spec/ibc/footnotes.md b/docs/spec/ibc/footnotes.md index eecd528412..bb263fb635 100644 --- a/docs/spec/ibc/footnotes.md +++ b/docs/spec/ibc/footnotes.md @@ -3,9 +3,6 @@ ##### 1: [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) -##### 2: -[http://www.amqp.org/sites/amqp.org/files/amqp.pdf](http://www.amqp.org/sites/amqp.org/files/amqp.pdf) - ##### 3: [https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index 48d739796d..e909bc39e4 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -2,38 +2,30 @@ ([Back to table of contents](README.md#contents)) -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 creates a mechanism by which two replicated fault-tolerant state machines may 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 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 IBC protocol assumes that multiple applications are running on their own blockchain with their own state and own logic. Communication is achieved over an ordered message queue primitive, allowing the creation of complex inter-chain processes without trusted third parties. -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. +The message packets are not signed by one psuedonymous account, or even multiple, as in multi-signature sidechain implementations. Rather, IBC assigns authorization of the packets to the source blockchain's consensus algorithm, performing light-client style verification on the destination chain. The Byzantine-fault-tolerant properties of the underlying blockchains are preserved: a user transferring assets between two chains using IBC must trust only the consensus algorithms of both chains. -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. +In this paper, we define a process of posting block headers and Merkle tree proofs to enable secure verification of individual packets. We then describe how to combine these packets into a messaging queue to guarantee ordered delivery. We then explain how to handle packet receipts (response/error) on the source chain, which enables the creation of asynchronous RPC-like protocols on top of IBC. Finally, we detail some optimizations and how to handle Byzantine blockchains. -### 1.1 Definitions +### 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_ - A replicated fault-tolerant state machine with a distributed consensus algorithm. The smallest unit produced through consensus is a block, which may contain many transactions, each applying some arbitrary mutation to the 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. +_Module_ - We assume that the state machine of each blockchain is comprised of multiple components that have limited rights to execute some particular set of state transfers (these are modules in the Cosmos SDK or smart contracts in Ethereum). -_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. +_Finality_ - The guarantee that a given block will not be reverted within some predefined conditions of a consensus algorithm. All proof-of-work systems offer probabilistic finality, which means that the difficulty of reverting a block increases as the block is embedded more deeply in the chain. Many proof-of-stake systems offer much weaker guarantees, based only on the honesty of the block producers. BFT algorithms such as Tendermint guarantee complete finality upon production of a block (unless over two thirds of the validators collude to break consensus, in which case the offenders can be identified and punished - further discussion of that scenario is outside the scope of this document). -_Knowledge_ - what is certain to be true. +_Attributable_ - Knowledge of the pseudonymous identity which made a statement, whom we can punish with some deduction of value (slashing) if the statement is false. Synonymous with accountability. -_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. +_Unbonding Period_ - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./footnotes.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P. -_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. +### 1.2 Threat Models -_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. +_False statements_ - Any information we receive may 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. +_Network partitions and delays_ - We assume an asynchronous, adversarial network with unbounded latency. Network messages may be modified, reordered, duplicated, or selectively dropped. Actors may be arbitrarily partitioned by a powerful adversary. The IBC protocol favors correctness over liveness (and provides no particular guarantees of the latter). -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. - -_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 risk exposure in the face of Byzantine actors. +_Byzantine actors_ - An entire blockchain may not act according to protocol. This must be detectable and provable, allowing the communicating blockchain to revoke trust and take necessary action. Application-level protocols designed on top of IBC should consider this risk and mitigate it as possible in a manner suitable to their application. diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/proofs.md index d8fa8f7e94..3065f51fc9 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/proofs.md @@ -2,37 +2,49 @@ ([Back to table of contents](README.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 of all these, only _Hh_ defines a signature and is thus attributable. +The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain _B_ that a message packet received on chain _B_ was correctly generated on chain _A_. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain _B_ we know that the packet has been executed on chain _A_ and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain _B_ (such as generating vouchers on chain _B_ for the chain _A_ assets which can later be redeemed with a packet in the opposite direction). -To support an IBC connection, two actors must be able to make the following proofs to each other: +### 2.1 Definitions -* 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_ +- Chain _A_ is the source blockchain from which the IBC packet is sent +- Chain _B_ is the destination blockchain on which the IBC packet is received +- _Hh_ is the signed header of chain _A_ at height _h_ +- _Ch_ is the consensus ruleset of chain _A_ at height _h_ +- _Vk,h_ is the value stored on chain _A_ under key _k_ at height _h_ +- _P_ is the unbonding period of chain _A_, in units of time +- Δ_(a, b)_ is the time difference between events _a_ and _b_ -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. +Note that of all these, only _Hh_ defines a signature and is thus attributable. + +### 2.2 Basics + +To facilitate an IBC connection, the two blockchains must provide the following proofs: + +1. 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_ +2. 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_ +3. 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 consensus is defined in Appendix E. Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and 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. _valid(Hh ,Mk,v,h )_ ⇒ _[true | false]_ -### 2.1 Establishing a Root of Trust +### 2.3 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_. +All proofs require an initial _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 (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed by on-chain governance or a similar decentralized mechanism if desired. Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) is necessary before any IBC packets can be sent. -Development of a fully open and decentralized PKI for tracking blockchains is an open research question for future iterations of the IBC protocol. +### 2.4 Following Block Headers -### 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 some future _Hh_ where _h > n_. Some implementations may require that _h = n + 1_ (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that Δ_vals(Cn, Ch ) < ⅓_ (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 +Either requirement is compatible with IBC. 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 packet between chains _A_ and _B_, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). -Δ_vals(Cn, Ch ) < ⅓_, that each step must have a change of less than one-third of the validator set[[4](./footnotes.md#4)]. - -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. +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 in headers posted to chain _B_ can be submitted back to chain _A_ for punishment, in the same manner that chain _A_ would independently punish (slash) identified Byzantine actors. More formally, given existing set of trust _T_ = _{(Hi , Ci ), (Hj , Cj ), …}_, we must provide: @@ -40,19 +52,19 @@ _valid(T, Xh | Uh )_ ⇒ _[true | false | u _if Hh-1_ ∈ _T then_: * _valid(T, Xh | Uh )_ ⇒ _[true | false]_ -* _there must exist some Uh or Xh that evaluates to true_ +* ∃ (Uh | Xh) ⇒ valid(T, Xh | Uh) {aren't there infinite? why is this necessary} _if Ch_ ∉ _T then_ * _valid(T, Uh )_ ⇒ _false_ -and can process update transactions as follows: +We can then process update transactions as follows: -_update(T, Xh | Uh )_ ⇒ -_ match valid(T, Xh | Uh )_ -* _false_ ⇒ _return Error("invalid proof")_ -* _unknown_ ⇒ _return Error("need a proof between current and h")_ -* _true_ ⇒ _T_ ∪ _(Hh ,Ch )_ +_update(T, Xh | Uh )_ ⇒ match _valid(T, Xh | Uh )_ with +* _false_ ⇒ fail with `invalid proof` +* _unknown_ ⇒ fail with `need a proof between current and h` +* _true_ ⇒ set _T_ = _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. +Define _max(T)_ as _max(h, where Hh_ ∈ _T)_. For any _T_ with _max(T) = h-1_, there must exist some _Xh | Uh_ so that _max(update(T, Xh | Uh )) = h_. +By induction, 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_. +Bisection can be used 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_. From f01ab5e4c930e59ef1dc5652bbc3d263f6c40ac3 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 13 Apr 2018 19:01:40 +0200 Subject: [PATCH 15/77] Start editing of Queues section --- docs/spec/ibc/queues.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index 780d382859..c86b9fb2a5 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -1,18 +1,14 @@ -## 3 Messaging Queue +## 3 Packet Queue ([Back to table of contents](README.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. +IBC uses an asynchronous message passing model that makes no assumptions about network synchrony. Chain _A_ and chain _B_ confirm new blocks independently, and IBC packets from one chain to the other may be delayed or censored arbitrarily. The speed of the IBC packet queue is limited only by the speed of the underlying chains. -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. +The IBC packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply (or not). Both chains must only agree that the packet has been received and either accepted or rejected, which is determined independently of any application logic. -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 facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./footnotes.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. Every transaction on the same chain already has a well-defined causality relation (order in history). The IBC protocol provides an ordering guarantee across two chains. -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). - -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 causal ordering over multiple chains can be used to reason about the combined state of both chains as a whole. 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 )_ @@ -30,7 +26,7 @@ _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](https://en.wikipedia.org/wiki/Vector_clock)) -In this section, we define an efficient implementation of a secure, reliable messaging queue. +In this section, we define an efficient implementation of a reliable ordered messaging queue. ### 3.1 Merkle Proofs for Queues From 8e9615a40313e78dcf320c3b07e15954982778bd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 12:59:09 +0200 Subject: [PATCH 16/77] Pluralize --- docs/spec/ibc/README.md | 16 ++++++---------- docs/spec/ibc/{appendix.md => appendices.md} | 10 ++++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) rename docs/spec/ibc/{appendix.md => appendices.md} (98%) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 9378ba93cd..e542b8b1a8 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -30,13 +30,9 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. Timeout 1. Handling Byzantine Failures 1. **[Conclusion](conclusion.md)** - -**[Appendix A: Encoding Libraries](appendix.md#appendix-a-encoding-libraries)** - -**[Appendix B: IBC Queue Format](appendix.md#appendix-b-ibc-queue-format)** - -**[Appendix C: Merkle Proof Format](appendix.md#appendix-c-merkle-proof-formats)** - -**[Appendix D: Universal IBC Packets](appendix.md#appendix-d-universal-ibc-packets)** - -**[Appendix E: Tendermint Header Proofs](appendix.md#appendix-e-tendermint-header-proofs)** +1. **[Appendices](appendices.md)** + 1. [Appendix A: Encoding Libraries](appendices.md#appendix-a-encoding-libraries) + 1. [Appendix B: IBC Queue Format](appendices.md#appendix-b-ibc-queue-format) + 1. [Appendix C: Merkle Proof Format](appendices.md#appendix-c-merkle-proof-formats) + 1. [Appendix D: Universal IBC Packets](appendices.md#appendix-d-universal-ibc-packets) + 1. [Appendix E: Tendermint Header Proofs](appendices.md#appendix-e-tendermint-header-proofs) diff --git a/docs/spec/ibc/appendix.md b/docs/spec/ibc/appendices.md similarity index 98% rename from docs/spec/ibc/appendix.md rename to docs/spec/ibc/appendices.md index bd277ff704..2a19e43682 100644 --- a/docs/spec/ibc/appendix.md +++ b/docs/spec/ibc/appendices.md @@ -4,6 +4,8 @@ ## Appendix A: Encoding Libraries +{ figure out what encoding IBC actually uses } + The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./footnotes.md#6)] and google's protobuf[[7](./footnotes.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. @@ -14,6 +16,8 @@ For the following appendixes, the data structure specifications will be in proto ## Appendix B: IBC Queue Format +{ include queue details here instead of in the other section } + The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: _key = _(_remote id, [send|receipt], [head|tail|index])_ @@ -28,6 +32,8 @@ See [binary format as protobuf specification](./protobuf/queue.proto) ## Appendix C: Merkle Proof Formats +{ link to the implementation } + A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./footnotes.md#10)]. There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./footnotes.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. @@ -44,12 +50,16 @@ See [binary format as protobuf specification](./protobuf/merkle.proto) ## Appendix D: Universal IBC Packets +{ what is this } + The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. See [binary format as protobuf specification](./protobuf/messages.proto) ## Appendix E: Tendermint Header Proofs +{ is this finalized? } + **TODO: clean this all up** This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team. From 0b00dbfdcd704ba7cc842d2d740b0c14b631f7e2 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 14:37:11 +0200 Subject: [PATCH 17/77] Clarify queue interface --- docs/spec/ibc/queues.md | 69 +++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index c86b9fb2a5..e90d7593f1 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -4,11 +4,11 @@ IBC uses an asynchronous message passing model that makes no assumptions about network synchrony. Chain _A_ and chain _B_ confirm new blocks independently, and IBC packets from one chain to the other may be delayed or censored arbitrarily. The speed of the IBC packet queue is limited only by the speed of the underlying chains. -The IBC packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply (or not). Both chains must only agree that the packet has been received and either accepted or rejected, which is determined independently of any application logic. +The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply (or not). Both chains must only agree that the packet has been received and either accepted or rejected, which is determined independently of any application logic. -To facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./footnotes.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. Every transaction on the same chain already has a well-defined causality relation (order in history). The IBC protocol provides an ordering guarantee across two chains. +To facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./footnotes.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. IBC implements a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). -A causal ordering over multiple chains can be used to reason about the combined state of both chains as a whole. Given _x_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: +Formally, 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 )_ @@ -22,34 +22,60 @@ _x_ → _B:receive(msgi )_ _y_ → _B:receive(msgi )_ ⇒ _y_ → _A:receipt(msgi )_ +Every transaction on the same chain already has a well-defined causality relation (order in history). IBC provides an ordering guarantee across two chains which can be used to reason about the combined state of both chains as a whole. -![Vector Clock image](https://upload.wikimedia.org/wikipedia/commons/5/55/Vector_Clock.svg) -([https://en.wikipedia.org/wiki/Vector_clock](https://en.wikipedia.org/wiki/Vector_clock)) +For example, an application may wish to allow a single fungible asset to be transferred between and held on multiple blockchains while preserving conservation of supply. The application can mint asset vouchers on chain _B_ when a particular IBC packet is committed to chain _B_, and require outgoing sends of that packet on chain _A_ to escrow an equal amount of the asset on chain _A_ until the vouchers are later redeemed back to chain _A_ with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain _B_ can later be redeemed back to chain _A_. -In this section, we define an efficient implementation of a reliable ordered messaging queue. -### 3.1 Merkle Proofs for Queues +### 3.1 Queue Specification -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. +A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue 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: +Each IBC-supporting blockchain must implement a reliable ordered packet queue with the following interface specification: -**init**: _qhead = qtail = 0_ +**init** +> set _qhead_ = _0_ +> set _qtail_ = _0_ -**peek** ⇒ **m**: _if qhead = qtail { return None } else { return q[qhead] }_ +**peek** ⇒ **e** +> match _qhead == qtail_ with +> _true_ ⇒ return _nil_ +> _false_ ⇒ return _q[qhead]_ -**pop** ⇒ **m**: _if qhead = qtail { return None } else { qhead++; return q[qhead-1] }_ +**pop** ⇒ **e** +> match _qhead == qtail_ with +> _true_ ⇒ return _nil_ +> _false_ ⇒ set _qhead_ = _qhead + 1_; return _q[qhead-1]_ -**push(m)**: _q[qtail] = m; qtail++_ +**retrieve(i)** ⇒ **e** +> match _qhead <= i < qtail_ with +> _true_ ⇒ return _qi_ +> _false_ ⇒ return _nil_ -**advance(i)**: _qhead = i; qtail = max(qtail , i)_ +**push(e)** +> set _q[qtail]_ = _e_; set _qtail_ = _qtail + 1_ -**head** ⇒ **i**: _qhead_ +**advance(i)** +> set _qhead_ = _i_; set _qtail_ = _max(qtail, i)_ -**tail** ⇒ **i**: _qtail_ +**head** ⇒ **i** +> return _qhead_ + +**tail** ⇒ **i** +> return _qtail_ + +{ two queues, one send, one receive } + +### 3.2 Merkle Proofs for Queues + +In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that IBC packets have been stored at particular indices in the incoming and outgoing packet queues. + +We make use of the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. To do so, we must define a unique, deterministic key in the Merkle store for each message in the queue. Packet types and proofs are conceptually explained here. An example binary encoding format can be found in Appendix C. 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. +{ todo: rewrite the rest of this section } + **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. @@ -58,8 +84,6 @@ A message queue is simply a set of serialized packets stored at predefined keys 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. 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). @@ -71,6 +95,8 @@ These two queues have different purposes and store messages of different types. ### 3.3 Message Contents +{ todo: clarify about payload-agnostic } + 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) @@ -81,6 +107,8 @@ _Vreceipt = (result, [success|error code])_ ### 3.4 Sending a Message +{ todo: cleanup wording } + 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. @@ -105,6 +133,8 @@ Note that this requires not only an valid proof, but also that the proper header ### 3.5 Receipts +{ todo: cleanup logic } + 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_: @@ -126,9 +156,10 @@ This enforces that the receipts are processed in order, to allow some the applic ![Rejected Transaction](images/ReceiptError.png) - ### 3.6 Relay Process +{ todo: cleanup wording } + 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. From 1cdfef81cdd1b6e9591edace3ab64df3d8f23519 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 15:08:53 +0200 Subject: [PATCH 18/77] Reorder Merkle proof section --- docs/spec/ibc/queues.md | 47 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/queues.md index e90d7593f1..a7a7c7b98f 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/queues.md @@ -26,6 +26,7 @@ Every transaction on the same chain already has a well-defined causality relatio For example, an application may wish to allow a single fungible asset to be transferred between and held on multiple blockchains while preserving conservation of supply. The application can mint asset vouchers on chain _B_ when a particular IBC packet is committed to chain _B_, and require outgoing sends of that packet on chain _A_ to escrow an equal amount of the asset on chain _A_ until the vouchers are later redeemed back to chain _A_ with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain _B_ can later be redeemed back to chain _A_. +This section provides a high-level specification of the queue interface and a list of the necessary proofs. To implement wire-compatible IBC, chain _A_ and chain _B_ must also use a common encoding format. An example binary encoding format can be found in Appendix C. ### 3.1 Queue Specification @@ -64,48 +65,48 @@ Each IBC-supporting blockchain must implement a reliable ordered packet queue wi **tail** ⇒ **i** > return _qtail_ -{ two queues, one send, one receive } +### 3.2 Connection Abstraction -### 3.2 Merkle Proofs for Queues +We introduce the abstraction of an IBC **connection**: a set of the required components to facilitate bidirectional communication between two blockchains _A_ and _B_. -In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that IBC packets have been stored at particular indices in the incoming and outgoing packet queues. +An IBC connection consists of four distinct queues, two on each chain: -We make use of the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. To do so, we must define a unique, deterministic key in the Merkle store for each message in the queue. Packet types and proofs are conceptually explained here. An example binary encoding format can be found in Appendix C. +_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ -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. +_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ -{ todo: rewrite the rest of this section } +_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ -**Key:** _(queue name, [head|tail|index])_ +_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ -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. +### 3.3 Merkle Proofs for Queues -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. +In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that particular IBC packets have been stored at particular indices in the outgoing packet queue, and particular IBC packet execution results have been stored at particular indices in the incoming packet queue. -Every IBC implementation must provide a protected subspace of the merkle store for use by each queue that cannot be affected by other modules. +We use the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the 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. +**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 serializated representation cannot collide with that of any possible index. + +Once written to the queue, a packet must be immutable (except for deletion 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_. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. 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 +* _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 elements 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 +### 3.4 Message Contents -{ todo: clarify about payload-agnostic } - -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 two fields: an enumerable _type_, and an opaque _payload_. 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)_ _Vreceipt = (result, [success|error code])_ -### 3.4 Sending a Message +### 3.5 Sending a Message { todo: cleanup wording } @@ -131,7 +132,7 @@ _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ 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 +### 3.6 Receipts { todo: cleanup logic } @@ -156,7 +157,7 @@ This enforces that the receipts are processed in order, to allow some the applic ![Rejected Transaction](images/ReceiptError.png) -### 3.6 Relay Process +### 3.7 Relay Process { todo: cleanup wording } From be3fa5672e351364d664133f25a5e9f035892bfd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 16:27:55 +0200 Subject: [PATCH 19/77] Update layout & fix anchor links --- docs/spec/ibc/README.md | 37 ++++++++------ docs/spec/ibc/appendices.md | 12 ++--- docs/spec/ibc/{proofs.md => connections.md} | 16 ++++-- docs/spec/ibc/optimizations.md | 7 ++- docs/spec/ibc/overview.md | 8 +-- docs/spec/ibc/{queues.md => packets.md} | 51 ++++++++----------- docs/spec/ibc/{footnotes.md => references.md} | 0 7 files changed, 68 insertions(+), 63 deletions(-) rename docs/spec/ibc/{proofs.md => connections.md} (96%) rename docs/spec/ibc/{queues.md => packets.md} (91%) rename docs/spec/ibc/{footnotes.md => references.md} (100%) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index e542b8b1a8..0969346c6a 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -4,7 +4,7 @@ This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms. -IBC requires two blockchains with cheaply verifiable rapid finality. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. +IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. @@ -13,23 +13,28 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm ## Contents 1. **[Overview](overview.md)** - 1. Definitions - 1. Threat Models -1. **[Proofs](proofs.md)** - 1. Establishing a Root of Trust - 1. Following Block Headers -1. **[Messaging Queue](queues.md)** - 1. Merkle Proofs for Queues - 1. Naming Queues - 1. Message Contents - 1. Sending a Packet - 1. Receipts - 1. Relay Process + 1. [Summary](overview.md#11-summary) + 1. [Requirements](overview.md#12-requirements) + 1. [Threat Models](overview.md#13-threat-models) +1. **[Connections](connections.md)** + 1. [Definitions](connections.md#21-definitions) + 1. [Requirements](connections.md#22-requirements) + 1. [Connection lifecycle](connections.md#23-connection-lifecycle) + 1. [Opening a connection](connections.md#231-opening-a-connection) + 1. [Following block headers](connections.md#232-following-block-headers) + 1. [Closing a connection](connections.md#233-closing-a-connection) +1. **[Packets](packets.md)** + 1. [Definitions](packets.md#31-definitions) + 1. [Requirements](packets.md#32-requirements) + 1. [Sending a packet](packets.md#33-sending-a-packet) + 1. [Receiving a packet](packets.md#34-receiving-a-packet) + 1. [Packet relayer](packets.md#35-packet-relayer) 1. **[Optimizations](optimizations.md)** - 1. Cleanup - 1. Timeout - 1. Handling Byzantine Failures + 1. [Timeouts](optimizations.md#41-timeouts) + 1. [Cleanup](optimizations.md#42-cleanup) + 1. [Handling Byzantine failures](optimizations.md#43-handling-byzantine-failures) 1. **[Conclusion](conclusion.md)** +1. **[References](references.md)** 1. **[Appendices](appendices.md)** 1. [Appendix A: Encoding Libraries](appendices.md#appendix-a-encoding-libraries) 1. [Appendix B: IBC Queue Format](appendices.md#appendix-b-ibc-queue-format) diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 2a19e43682..96feed7091 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -8,11 +8,11 @@ The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. -In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./footnotes.md#6)] and google's protobuf[[7](./footnotes.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. +In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./references.md#6)] and google's protobuf[[7](./references.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. -The tendermint-specific data structures are encoded with go-wire[[8](./footnotes.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. +The tendermint-specific data structures are encoded with go-wire[[8](./references.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. -For the following appendixes, the data structure specifications will be in proto3[[9](./footnotes.md#9)] format. +For the following appendixes, the data structure specifications will be in proto3[[9](./references.md#9)] format. ## Appendix B: IBC Queue Format @@ -34,9 +34,9 @@ See [binary format as protobuf specification](./protobuf/queue.proto) { link to the implementation } -A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./footnotes.md#10)]. +A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./references.md#10)]. -There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./footnotes.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. +There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./references.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. @@ -44,7 +44,7 @@ The proof format also allows for chaining of trees, combining multiple merkle st A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. -For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./footnotes.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. +For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./references.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. See [binary format as protobuf specification](./protobuf/merkle.proto) diff --git a/docs/spec/ibc/proofs.md b/docs/spec/ibc/connections.md similarity index 96% rename from docs/spec/ibc/proofs.md rename to docs/spec/ibc/connections.md index 3065f51fc9..248a9e771a 100644 --- a/docs/spec/ibc/proofs.md +++ b/docs/spec/ibc/connections.md @@ -1,4 +1,4 @@ -## 2 Proofs +## 2 Connections ([Back to table of contents](README.md#contents)) @@ -16,7 +16,7 @@ The basis of IBC is the ability to verify in the on-chain consensus ruleset of c Note that of all these, only _Hh_ defines a signature and is thus attributable. -### 2.2 Basics +### 2.2 Requirements To facilitate an IBC connection, the two blockchains must provide the following proofs: @@ -32,15 +32,17 @@ The merkle proof _Mk,v,h_ is a well-defined concept in the blockchain _valid(Hh ,Mk,v,h )_ ⇒ _[true | false]_ -### 2.3 Establishing a Root of Trust +### 2.3 Connection Lifecycle + +#### 2.3.1 Opening a Connection All proofs require an initial _Hh_ and _Ch_ for some _h_, where Δ_(now, Hh) < P_. Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed by on-chain governance or a similar decentralized mechanism if desired. Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) is necessary before any IBC packets can be sent. -### 2.4 Following Block Headers +#### 2.3.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 some future _Hh_ where _h > n_. Some implementations may require that _h = n + 1_ (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that Δ_vals(Cn, Ch ) < ⅓_ (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 some future _Hh_ where _h > n_. Some implementations may require that _h = n + 1_ (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that Δ_vals(Cn, Ch ) < ⅓_ (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)]. Either requirement is compatible with IBC. 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 packet between chains _A_ and _B_, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). @@ -68,3 +70,7 @@ Define _max(T)_ as _max(h, where Hh_ ∈ _T)_. For any _T_ with _ By induction, there must exist a set of proofs, such that _max(update…(T,...)) = h+n_ for any n. Bisection can be used 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_. + +#### 2.3.3 Closing a Connection + +{ todo } diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index 17967c0111..0c4568e0eb 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -4,7 +4,7 @@ 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 +### 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. @@ -58,7 +58,7 @@ Now chain A can rollback all transactions that were blocked by this flood of unr 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 +### 4.2 Cleanup 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. @@ -89,8 +89,7 @@ This allows us to invoke the _IBCcleanup _function to resolve all outstanding me ![Cleaning up Packets](images/CleanUp.png) - -### 4.3 Handling Byzantine Failures +### 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: failures of the consensus mechanism (reversing "final" blocks), or failure at the application level (not performing the action defined by the message). diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index e909bc39e4..ab8c929188 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -2,6 +2,8 @@ ([Back to table of contents](README.md#contents)) +### 1.1 Summary + The IBC protocol creates a mechanism by which two replicated fault-tolerant state machines may 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 own logic. Communication is achieved over an ordered message queue primitive, allowing the creation of complex inter-chain processes without trusted third parties. @@ -10,7 +12,7 @@ The message packets are not signed by one psuedonymous account, or even multiple In this paper, we define a process of posting block headers and Merkle tree proofs to enable secure verification of individual packets. We then describe how to combine these packets into a messaging queue to guarantee ordered delivery. We then explain how to handle packet receipts (response/error) on the source chain, which enables the creation of asynchronous RPC-like protocols on top of IBC. Finally, we detail some optimizations and how to handle Byzantine blockchains. -### 1.1 Definitions +### 1.2 Definitions _Blockchain_ - A replicated fault-tolerant state machine with a distributed consensus algorithm. The smallest unit produced through consensus is a block, which may contain many transactions, each applying some arbitrary mutation to the state. @@ -20,9 +22,9 @@ _Finality_ - The guarantee that a given block will not be reverted within some p _Attributable_ - Knowledge of the pseudonymous identity which made a statement, whom we can punish with some deduction of value (slashing) if the statement is false. Synonymous with accountability. -_Unbonding Period_ - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./footnotes.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P. +_Unbonding Period_ - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./references.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P. -### 1.2 Threat Models +### 1.3 Threat Models _False statements_ - Any information we receive may be false. diff --git a/docs/spec/ibc/queues.md b/docs/spec/ibc/packets.md similarity index 91% rename from docs/spec/ibc/queues.md rename to docs/spec/ibc/packets.md index a7a7c7b98f..40dbe2e58b 100644 --- a/docs/spec/ibc/queues.md +++ b/docs/spec/ibc/packets.md @@ -1,4 +1,4 @@ -## 3 Packet Queue +## 3 Packets ([Back to table of contents](README.md#contents)) @@ -6,7 +6,7 @@ IBC uses an asynchronous message passing model that makes no assumptions about n The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply (or not). Both chains must only agree that the packet has been received and either accepted or rejected, which is determined independently of any application logic. -To facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./footnotes.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. IBC implements a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). +To facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. IBC implements a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). Formally, given _x_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: @@ -28,7 +28,21 @@ For example, an application may wish to allow a single fungible asset to be tran This section provides a high-level specification of the queue interface and a list of the necessary proofs. To implement wire-compatible IBC, chain _A_ and chain _B_ must also use a common encoding format. An example binary encoding format can be found in Appendix C. -### 3.1 Queue Specification +### 3.1 Definitions + +We introduce the abstraction of an IBC _connection_: a set of the required components to facilitate bidirectional communication between two blockchains _A_ and _B_. + +An IBC connection consists of four distinct queues, two on each chain: + +_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ + +_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ + +_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ + +_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ + +### 3.2 Requirements A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue cleanup. @@ -65,22 +79,6 @@ Each IBC-supporting blockchain must implement a reliable ordered packet queue wi **tail** ⇒ **i** > return _qtail_ -### 3.2 Connection Abstraction - -We introduce the abstraction of an IBC **connection**: a set of the required components to facilitate bidirectional communication between two blockchains _A_ and _B_. - -An IBC connection consists of four distinct queues, two on each chain: - -_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ - -_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ - -_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ - -_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ - -### 3.3 Merkle Proofs for Queues - In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that particular IBC packets have been stored at particular indices in the outgoing packet queue, and particular IBC packet execution results have been stored at particular indices in the incoming packet queue. We use the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the queue: @@ -91,14 +89,9 @@ The index is stored as a fixed-length unsigned integer in big endian format, so Once written to the queue, a packet must be immutable (except for deletion 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_. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. -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). +Each incoming & outgoing queue must be provably associated with another uniquely identified chain, so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with a string unique to the other chain, such as the chain identifier or the hash of the genesis block. -* _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 elements 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.4 Message Contents +These two queues have different purposes and store elements 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. We define every message in a _send queue_ to consist of two fields: an enumerable _type_, and an opaque _payload_. 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) @@ -106,7 +99,7 @@ _Vsend = (type, data)_ _Vreceipt = (result, [success|error code])_ -### 3.5 Sending a Message +### 3.3 Sending a packet { todo: cleanup wording } @@ -132,7 +125,7 @@ _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ 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.6 Receipts +### 3.4 Receiving a packet { todo: cleanup logic } @@ -157,7 +150,7 @@ This enforces that the receipts are processed in order, to allow some the applic ![Rejected Transaction](images/ReceiptError.png) -### 3.7 Relay Process +### 3.5 Packet relayer { todo: cleanup wording } diff --git a/docs/spec/ibc/footnotes.md b/docs/spec/ibc/references.md similarity index 100% rename from docs/spec/ibc/footnotes.md rename to docs/spec/ibc/references.md From eafa484184616d20a4b952a0d6f212fadb534b71 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 16:39:33 +0200 Subject: [PATCH 20/77] Fix a few links --- docs/spec/ibc/README.md | 8 ++++---- docs/spec/ibc/conclusion.md | 2 ++ docs/spec/ibc/references.md | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 0969346c6a..6835915516 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -4,17 +4,17 @@ This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms. -IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically provable packets can be sent between the chains. - The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. -IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This documentation replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. +IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between the chains. + +IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This document replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. ## Contents 1. **[Overview](overview.md)** 1. [Summary](overview.md#11-summary) - 1. [Requirements](overview.md#12-requirements) + 1. [Definitions](overview.md#12-definitions) 1. [Threat Models](overview.md#13-threat-models) 1. **[Connections](connections.md)** 1. [Definitions](connections.md#21-definitions) diff --git a/docs/spec/ibc/conclusion.md b/docs/spec/ibc/conclusion.md index 9e58c248ea..f85ae85994 100644 --- a/docs/spec/ibc/conclusion.md +++ b/docs/spec/ibc/conclusion.md @@ -1,5 +1,7 @@ ## 5 Conclusion +([Back to table of contents](README.md#contents)) + We have demonstrated a secure, performant, and flexible protocol for cross-blockchain messaging, and provided sufficient detail to reason about the correctness and efficiency of the protocol. This document defines solely a message queue protocol - not the application-level semantics which must sit on top of it to enable asset transfer between two chains. 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. diff --git a/docs/spec/ibc/references.md b/docs/spec/ibc/references.md index bb263fb635..f3677494cf 100644 --- a/docs/spec/ibc/references.md +++ b/docs/spec/ibc/references.md @@ -1,4 +1,6 @@ -## Footnotes +## References + +([Back to table of contents](README.md#contents)) ##### 1: [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) From edd5368669a9c46b3a2ad4845bdf3e217e518862 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Apr 2018 18:23:26 +0200 Subject: [PATCH 21/77] Add the concept of a 'channel' --- docs/spec/ibc/README.md | 13 +++++++------ .../ibc/{packets.md => channels-and-packets.md} | 0 2 files changed, 7 insertions(+), 6 deletions(-) rename docs/spec/ibc/{packets.md => channels-and-packets.md} (100%) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 6835915516..6a15619d38 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -23,12 +23,13 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. [Opening a connection](connections.md#231-opening-a-connection) 1. [Following block headers](connections.md#232-following-block-headers) 1. [Closing a connection](connections.md#233-closing-a-connection) -1. **[Packets](packets.md)** - 1. [Definitions](packets.md#31-definitions) - 1. [Requirements](packets.md#32-requirements) - 1. [Sending a packet](packets.md#33-sending-a-packet) - 1. [Receiving a packet](packets.md#34-receiving-a-packet) - 1. [Packet relayer](packets.md#35-packet-relayer) +1. **[Channels & Packets](channels-and-packets.md)** + 1. [Definitions](channels-and-packets.md#31-definitions) + 1. [Requirements](channels-and-packets.md#32-requirements) + 1. [Channels](channels-and-packets.md#33-channels) + 1. [Sending a packet](channels-and-packets.md#34-sending-a-packet) + 1. [Receiving a packet](channels-and-packets.md#35-receiving-a-packet) + 1. [Packet relayer](channels-and-packets.md#36-packet-relayer) 1. **[Optimizations](optimizations.md)** 1. [Timeouts](optimizations.md#41-timeouts) 1. [Cleanup](optimizations.md#42-cleanup) diff --git a/docs/spec/ibc/packets.md b/docs/spec/ibc/channels-and-packets.md similarity index 100% rename from docs/spec/ibc/packets.md rename to docs/spec/ibc/channels-and-packets.md From 2493332509707f673930746bab6d43b5e017d1f5 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Apr 2018 13:22:14 +0200 Subject: [PATCH 22/77] Define IBC packet, IBC channel --- docs/spec/ibc/README.md | 6 +-- docs/spec/ibc/channels-and-packets.md | 60 ++++++++++++++++++--------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 6a15619d38..e84c055e69 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -24,9 +24,9 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. [Following block headers](connections.md#232-following-block-headers) 1. [Closing a connection](connections.md#233-closing-a-connection) 1. **[Channels & Packets](channels-and-packets.md)** - 1. [Definitions](channels-and-packets.md#31-definitions) - 1. [Requirements](channels-and-packets.md#32-requirements) - 1. [Channels](channels-and-packets.md#33-channels) + 1. [Background](channels-and-packets.md#31-background) + 1. [Definitions](channels-and-packets.md#32-definitions) + 1. [Requirements](channels-and-packets.md#33-requirements) 1. [Sending a packet](channels-and-packets.md#34-sending-a-packet) 1. [Receiving a packet](channels-and-packets.md#35-receiving-a-packet) 1. [Packet relayer](channels-and-packets.md#36-packet-relayer) diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 40dbe2e58b..f503372195 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -1,14 +1,16 @@ -## 3 Packets +## 3 Channels & Packets ([Back to table of contents](README.md#contents)) -IBC uses an asynchronous message passing model that makes no assumptions about network synchrony. Chain _A_ and chain _B_ confirm new blocks independently, and IBC packets from one chain to the other may be delayed or censored arbitrarily. The speed of the IBC packet queue is limited only by the speed of the underlying chains. +### 3.1 Background -The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply (or not). Both chains must only agree that the packet has been received and either accepted or rejected, which is determined independently of any application logic. +IBC uses an asynchronous message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain _A_ and chain _B_ confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. -To facilitate building useful application logic, we introduce a reliable messaging queue (hereafter just referred to as a queue) to allow us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. IBC implements a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). +The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received and either accepted or rejected. -Formally, given _x_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: +To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. + +IBC channels implement a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). 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 )_ @@ -24,29 +26,31 @@ _y_ → _A:receipt(msgi )_ Every transaction on the same chain already has a well-defined causality relation (order in history). IBC provides an ordering guarantee across two chains which can be used to reason about the combined state of both chains as a whole. -For example, an application may wish to allow a single fungible asset to be transferred between and held on multiple blockchains while preserving conservation of supply. The application can mint asset vouchers on chain _B_ when a particular IBC packet is committed to chain _B_, and require outgoing sends of that packet on chain _A_ to escrow an equal amount of the asset on chain _A_ until the vouchers are later redeemed back to chain _A_ with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain _B_ can later be redeemed back to chain _A_. +For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain _B_ when a particular IBC packet is committed to chain _B_, and require outgoing sends of that packet on chain _A_ to escrow an equal amount of the asset on chain _A_ until the vouchers are later redeemed back to chain _A_ with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain _B_ can later be redeemed back to chain _A_. -This section provides a high-level specification of the queue interface and a list of the necessary proofs. To implement wire-compatible IBC, chain _A_ and chain _B_ must also use a common encoding format. An example binary encoding format can be found in Appendix C. +This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain _A_ and chain _B_ must also use a common encoding format. An example binary encoding format can be found in Appendix C. -### 3.1 Definitions +### 3.2 Definitions -We introduce the abstraction of an IBC _connection_: a set of the required components to facilitate bidirectional communication between two blockchains _A_ and _B_. +#### 3.1.1 Packet -An IBC connection consists of four distinct queues, two on each chain: +We define an IBC *packet* as the five-tuple *(type, sequence, source, destination, data|receipt)*, where: -_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ +**type** is one of *data* or *receipt* -_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ +**sequence** is an unsigned integer -_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ +**source** is a string uniquely identifying the chain from which this packet was sent -_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ +**destination** is a string uniquely identifying the chain which should receive this packet -### 3.2 Requirements +**data|receipt**, if *type* is *data*, is an opaque application payload, or if *type* is *receipt* is a code of either *success* or *failure* -A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue cleanup. +#### 3.1.2 Queue -Each IBC-supporting blockchain must implement a reliable ordered packet queue with the following interface specification: +To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue cleanup. + +Each IBC-supporting blockchain must provide a queue abstraction with the following functionality: **init** > set _qhead_ = _0_ @@ -79,6 +83,22 @@ Each IBC-supporting blockchain must implement a reliable ordered packet queue wi **tail** ⇒ **i** > return _qtail_ +#### 3.1.3 Channel + +We introduce the abstraction of an IBC _channel_: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains _A_ and _B_. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues, so have independent ordering guarantees. + +An IBC channel consists of four distinct queues, two on each chain: + +_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ + +_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ + +_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ + +_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ + +### 3.3 Requirements + In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that particular IBC packets have been stored at particular indices in the outgoing packet queue, and particular IBC packet execution results have been stored at particular indices in the incoming packet queue. We use the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the queue: @@ -99,7 +119,7 @@ _Vsend = (type, data)_ _Vreceipt = (result, [success|error code])_ -### 3.3 Sending a packet +### 3.4 Sending a packet { todo: cleanup wording } @@ -125,7 +145,7 @@ _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ 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.4 Receiving a packet +### 3.5 Receiving a packet { todo: cleanup logic } @@ -150,7 +170,7 @@ This enforces that the receipts are processed in order, to allow some the applic ![Rejected Transaction](images/ReceiptError.png) -### 3.5 Packet relayer +### 3.6 Packet relayer { todo: cleanup wording } From f9e8018d434884467b86a6c7844644f2ddba520b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Apr 2018 14:35:20 +0200 Subject: [PATCH 23/77] Add receipt definition --- docs/spec/ibc/README.md | 4 ++ docs/spec/ibc/channels-and-packets.md | 58 ++++++++++++++------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index e84c055e69..9427646833 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -26,6 +26,10 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. **[Channels & Packets](channels-and-packets.md)** 1. [Background](channels-and-packets.md#31-background) 1. [Definitions](channels-and-packets.md#32-definitions) + 1. [Packet](channels-and-packets.md#321-packet) + 1. [Receipt](channels-and-packets.md#322-receipt) + 1. [Queue](channels-and-packets.md#323-queue) + 1. [Channel](channels-and-packets.md#324-channel) 1. [Requirements](channels-and-packets.md#33-requirements) 1. [Sending a packet](channels-and-packets.md#34-sending-a-packet) 1. [Receiving a packet](channels-and-packets.md#35-receiving-a-packet) diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index f503372195..aa4dde58aa 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -4,7 +4,7 @@ ### 3.1 Background -IBC uses an asynchronous message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain _A_ and chain _B_ confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. +IBC uses a cross-chain message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain _A_ and chain _B_ confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received and either accepted or rejected. @@ -32,21 +32,33 @@ This section provides definitions for packets and channels, a high-level specifi ### 3.2 Definitions -#### 3.1.1 Packet +#### 3.2.1 Packet -We define an IBC *packet* as the five-tuple *(type, sequence, source, destination, data|receipt)*, where: +We define an IBC *packet* _P_ as the five-tuple *(type, sequence, source, destination, data)*, where: -**type** is one of *data* or *receipt* +**type** is an opaque routing field (an integer or string) -**sequence** is an unsigned integer +**sequence** is an unsigned, arbitrary-precision integer -**source** is a string uniquely identifying the chain from which this packet was sent +**source** is a string uniquely identifying the chain, connection, and channel from which this packet was sent -**destination** is a string uniquely identifying the chain which should receive this packet +**destination** is a string uniquely identifying the chain, connection, and channel which should receive this packet -**data|receipt**, if *type* is *data*, is an opaque application payload, or if *type* is *receipt* is a code of either *success* or *failure* +**data** is an opaque application payload -#### 3.1.2 Queue +#### 3.2.2 Receipt + +We define an IBC *receipt* _R_ as the four-tuple *(sequence, source, destination, result)*, where + +**sequence** is an unsigned, arbitrary-precision integer + +**source** is a string uniquely identifying the chain, connection, and channel from which this packet was sent + +**destination** is a string uniquely identifying the chain, connection, and channel which should receive this packet + +**result** is a code of either *success* or *failure* + +#### 3.2.3 Queue To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue cleanup. @@ -83,9 +95,9 @@ Each IBC-supporting blockchain must provide a queue abstraction with the followi **tail** ⇒ **i** > return _qtail_ -#### 3.1.3 Channel +#### 3.2.4 Channel -We introduce the abstraction of an IBC _channel_: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains _A_ and _B_. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues, so have independent ordering guarantees. +We introduce the abstraction of an IBC _channel_: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains _A_ and _B_. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues and thus independent ordering guarantees. An IBC channel consists of four distinct queues, two on each chain: @@ -109,29 +121,19 @@ The index is stored as a fixed-length unsigned integer in big endian format, so Once written to the queue, a packet must be immutable (except for deletion 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_. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. -Each incoming & outgoing queue must be provably associated with another uniquely identified chain, so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with a string unique to the other chain, such as the chain identifier or the hash of the genesis block. - -These two queues have different purposes and store elements 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. - -We define every message in a _send queue_ to consist of two fields: an enumerable _type_, and an opaque _payload_. 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)_ - -_Vreceipt = (result, [success|error code])_ +Each incoming & outgoing queue for each connection must be provably associated with another uniquely identified chain, so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with a string unique to the other chain, such as the chain identifier or the hash of the genesis block. ### 3.4 Sending a packet -{ todo: cleanup wording } +To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. 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 all is in order, the IBC module simply pushes the packet to the tail of _OutgoingA_, which enables all the proofs described above. -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. - -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. +If desired, the packet payload can contain additional module routing information in the form of a _kind_, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module must associate every supported message with a particular handler (_fkind_) and return an error for unsupported types. _(IBCsend(D, type, data)_ ⇒ _Success)_ ⇒ _push(qD.send ,Vsend{type, data})_ +### 3.5 Receiving a packet + 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_ @@ -145,7 +147,7 @@ _A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ 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 Receiving a packet +### 3.6 Handling a receipt { todo: cleanup logic } @@ -170,7 +172,7 @@ This enforces that the receipts are processed in order, to allow some the applic ![Rejected Transaction](images/ReceiptError.png) -### 3.6 Packet relayer +### 3.7 Packet relayer { todo: cleanup wording } From 773c87e8c0c20c610d55c8b51b4560b0286e2cff Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 19 Apr 2018 16:51:27 +0200 Subject: [PATCH 24/77] Add section on closing IBC connections --- docs/spec/ibc/connections.md | 10 ++++++++-- docs/spec/ibc/references.md | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/spec/ibc/connections.md b/docs/spec/ibc/connections.md index 248a9e771a..3a1c2efc05 100644 --- a/docs/spec/ibc/connections.md +++ b/docs/spec/ibc/connections.md @@ -38,7 +38,9 @@ _valid(Hh ,Mk,v,h )_ ⇒ _[true | false]_ All proofs require an initial _Hh_ and _Ch_ for some _h_, where Δ_(now, Hh) < P_. -Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed by on-chain governance or a similar decentralized mechanism if desired. Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) is necessary before any IBC packets can be sent. +Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) — _HAh_ and _CAh_ stored on chain _B_, and _HBh_ and _CBhh | Uh )_ as defined above to always return _false_. + +Closing a connection may break application invariants (such as fungiblity - token vouchers on chain _B_ will no longer be redeemable for tokens on chain _A_) and should only be undertaken in extreme circumstances such as Byzantine behavior of the connected chain. + +Closure may be permissioned to an on-chain governance system, an identifiable party on the other chain (such as a signer quorum, although this will not work in some Byzantine cases), or any user who submits a connection-specific fraud proof of Byzantine behavior. When a connection is closed, application-specific measures may be undertaken to recover assets held on a Byzantine chain. We defer further discussion to { an appendix }. diff --git a/docs/spec/ibc/references.md b/docs/spec/ibc/references.md index f3677494cf..9262ff99cf 100644 --- a/docs/spec/ibc/references.md +++ b/docs/spec/ibc/references.md @@ -5,6 +5,9 @@ ##### 1: [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) +##### 2: +{ vector clock } + ##### 3: [https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) From 00489162ac4bf7c0ed5c5229bec30ef3895d9f7a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 20 Apr 2018 13:28:17 +0200 Subject: [PATCH 25/77] Clarify connections intro, Byzantine recovery strategies --- docs/spec/ibc/README.md | 9 +++++---- docs/spec/ibc/appendices.md | 10 ++++++++-- docs/spec/ibc/channels-and-packets.md | 8 ++++++-- docs/spec/ibc/connections.md | 10 ++++++---- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 9427646833..eca34c80d3 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -6,9 +6,9 @@ This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. T The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. -IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between the chains. +IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between them. -IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This document replaces and supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation, including example pseudocode. +IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This document supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation. ## Contents @@ -44,5 +44,6 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. [Appendix A: Encoding Libraries](appendices.md#appendix-a-encoding-libraries) 1. [Appendix B: IBC Queue Format](appendices.md#appendix-b-ibc-queue-format) 1. [Appendix C: Merkle Proof Format](appendices.md#appendix-c-merkle-proof-formats) - 1. [Appendix D: Universal IBC Packets](appendices.md#appendix-d-universal-ibc-packets) - 1. [Appendix E: Tendermint Header Proofs](appendices.md#appendix-e-tendermint-header-proofs) + 1. [Appendix D: Byzantine Recovery Strategies](appendices.md#appendix-d-byzantine-recovery-strategies) + 1. [Appendix E: Universal IBC Packets](appendices.md#appendix-e-universal-ibc-packets) + 1. [Appendix F: Tendermint Header Proofs](appendices.md#appendix-f-tendermint-header-proofs) diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 96feed7091..736167d2ae 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -48,7 +48,13 @@ For those who wish to minimize the size of their merkle proofs, we recommend usi See [binary format as protobuf specification](./protobuf/merkle.proto) -## Appendix D: Universal IBC Packets +## Appendix D: Byzantine Recovery Strategies + +- Goal: keep application invariants +- Plasma-like fraud proofs +- Trusted entity - governance + +## Appendix E: Universal IBC Packets { what is this } @@ -56,7 +62,7 @@ The structures above can be used to define standard encodings for the basic IBC See [binary format as protobuf specification](./protobuf/messages.proto) -## Appendix E: Tendermint Header Proofs +## Appendix F: Tendermint Header Proofs { is this finalized? } diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index aa4dde58aa..112f220afa 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -10,7 +10,7 @@ The IBC protocol as defined here is payload-agnostic. The packet receiver on cha To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. -IBC channels implement a [vector clock](https://en.wikipedia.org/wiki/Vector_clock) for the restricted case of two processes (in our case, blockchains). Given _x_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: +IBC channels implement a vector clock [2](references.md#2) for the restricted case of two processes (in our case, blockchains). 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 )_ @@ -125,6 +125,8 @@ Each incoming & outgoing queue for each connection must be provably associated w ### 3.4 Sending a packet +{ todo: unify terms, clarify } + To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. 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 all is in order, the IBC module simply pushes the packet to the tail of _OutgoingA_, which enables all the proofs described above. If desired, the packet payload can contain additional module routing information in the form of a _kind_, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module must associate every supported message with a particular handler (_fkind_) and return an error for unsupported types. @@ -134,6 +136,8 @@ _(IBCsend(D, type, data)_ ⇒ _Success)_ ### 3.5 Receiving a packet +{ todo: unify terms } + 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_ @@ -174,7 +178,7 @@ This enforces that the receipts are processed in order, to allow some the applic ### 3.7 Packet relayer -{ todo: cleanup wording } +{ todo: cleanup wording & terms } 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. diff --git a/docs/spec/ibc/connections.md b/docs/spec/ibc/connections.md index 3a1c2efc05..94993b59d8 100644 --- a/docs/spec/ibc/connections.md +++ b/docs/spec/ibc/connections.md @@ -2,7 +2,9 @@ ([Back to table of contents](README.md#contents)) -The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain _B_ that a message packet received on chain _B_ was correctly generated on chain _A_. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain _B_ we know that the packet has been executed on chain _A_ and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain _B_ (such as generating vouchers on chain _B_ for the chain _A_ assets which can later be redeemed with a packet in the opposite direction). +The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain _B_ that a data packet received on chain _B_ was correctly generated on chain _A_. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain _B_ we know that the packet has been executed on chain _A_ and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain _B_ (such as generating vouchers on chain _B_ for the chain _A_ assets which can later be redeemed with a packet in the opposite direction). + +This section outlines the abstraction of an IBC _connection_: the state and consensus ruleset necessary to perform IBC packet verification. ### 2.1 Definitions @@ -38,9 +40,9 @@ _valid(Hh ,Mk,v,h )_ ⇒ _[true | false]_ All proofs require an initial _Hh_ and _Ch_ for some _h_, where Δ_(now, Hh) < P_. -Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) — _HAh_ and _CAh_ stored on chain _B_, and _HBh_ and _CBhh_ and _CAh_ stored on chain _B_, and _HBh_ and _CBh_ stored on chain _A_ — is necessary before any IBC packets can be sent. -Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed permissionlessly, in which case users later utilizing the IBC channel must check the root-of-trust themselves, or authorized by on-chain governance. +Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed permissionlessly, in which case users later utilizing the IBC channel must check the root-of-trust themselves, or authorized by on-chain governance for additional assurance. #### 2.3.2 Following Block Headers @@ -79,4 +81,4 @@ IBC implementations may optionally include the ability to close an IBC connectio Closing a connection may break application invariants (such as fungiblity - token vouchers on chain _B_ will no longer be redeemable for tokens on chain _A_) and should only be undertaken in extreme circumstances such as Byzantine behavior of the connected chain. -Closure may be permissioned to an on-chain governance system, an identifiable party on the other chain (such as a signer quorum, although this will not work in some Byzantine cases), or any user who submits a connection-specific fraud proof of Byzantine behavior. When a connection is closed, application-specific measures may be undertaken to recover assets held on a Byzantine chain. We defer further discussion to { an appendix }. +Closure may be permissioned to an on-chain governance system, an identifiable party on the other chain (such as a signer quorum, although this will not work in some Byzantine cases), or any user who submits an application-specific fraud proof. When a connection is closed, application-specific measures may be undertaken to recover assets held on a Byzantine chain. We defer further discussion to [Appendix D](appendices.md#appendix-d-byzantine-recovery-strategies). From 37fd166829bf65ff17bb428b4f4198b108c73c39 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 20 Apr 2018 19:22:00 +0200 Subject: [PATCH 26/77] Formatting updates --- docs/spec/ibc/channels-and-packets.md | 219 ++++++++++++++++---------- docs/spec/ibc/connections.md | 93 ++++++----- docs/spec/ibc/overview.md | 16 +- 3 files changed, 192 insertions(+), 136 deletions(-) diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 112f220afa..128a0c01ac 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -4,173 +4,216 @@ ### 3.1 Background -IBC uses a cross-chain message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain _A_ and chain _B_ confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. +IBC uses a cross-chain message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. -The IBC protocol as defined here is payload-agnostic. The packet receiver on chain _B_ decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received and either accepted or rejected. +The IBC protocol as defined here is payload-agnostic. The packet receiver on chain `B` decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received and either accepted or rejected. -To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet _x_ is processed before packet _y_ on chain _A_, packet _x_ must also be processed before packet _y_ on chain _B_. +To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet *x* is processed before packet *y* on chain `A`, packet *x* must also be processed before packet *y* on chain `B`. -IBC channels implement a vector clock [2](references.md#2) for the restricted case of two processes (in our case, blockchains). Given _x_ → _y_ means _x_ is causally before _y_, and chains A and B, and _a_ ⇒ _b_ means _a_ implies _b_: +IBC channels implement a vector clock[[2](references.md#2)] for the restricted case of two processes (in our case, blockchains). Given *x* -> *y* means *x* is causally before *y*, chains `A` and `B`, and *a* => *b* means *a* implies *b*: -_A:send(msgi )_ → _B:receive(msgi )_ +*A:send(msgi )* -> *B:receive(msgi )* -_B:receive(msgi )_ → _A:receipt(msgi )_ +*B:receive(msgi )* -> *A:receipt(msgi )* -_A:send(msgi )_ → _A:send(msgi+1 )_ +*A:send(msgi )* -> *A:send(msgi+1 )* -_x_ → _A:send(msgi )_ ⇒ -_x_ → _B:receive(msgi )_ +*x* -> *A:send(msgi )* => +*x* -> *B:receive(msgi )* -_y_ → _B:receive(msgi )_ ⇒ -_y_ → _A:receipt(msgi )_ +*y* -> *B:receive(msgi )* => +*y* -> *A:receipt(msgi )* Every transaction on the same chain already has a well-defined causality relation (order in history). IBC provides an ordering guarantee across two chains which can be used to reason about the combined state of both chains as a whole. -For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain _B_ when a particular IBC packet is committed to chain _B_, and require outgoing sends of that packet on chain _A_ to escrow an equal amount of the asset on chain _A_ until the vouchers are later redeemed back to chain _A_ with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain _B_ can later be redeemed back to chain _A_. +For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`. -This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain _A_ and chain _B_ must also use a common encoding format. An example binary encoding format can be found in Appendix C. +This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-format). ### 3.2 Definitions #### 3.2.1 Packet -We define an IBC *packet* _P_ as the five-tuple *(type, sequence, source, destination, data)*, where: +We define an IBC *packet* `P` as the five-tuple `(type, sequence, source, destination, data)`, where: -**type** is an opaque routing field (an integer or string) +`type` is an opaque routing field -**sequence** is an unsigned, arbitrary-precision integer +`sequence` is an unsigned, arbitrary-precision integer -**source** is a string uniquely identifying the chain, connection, and channel from which this packet was sent +`source` is a string uniquely identifying the chain, connection, and channel from which this packet was sent -**destination** is a string uniquely identifying the chain, connection, and channel which should receive this packet +`destination` is a string uniquely identifying the chain, connection, and channel which should receive this packet -**data** is an opaque application payload +`data` is an opaque application payload #### 3.2.2 Receipt -We define an IBC *receipt* _R_ as the four-tuple *(sequence, source, destination, result)*, where +We define an IBC *receipt* `R` as the four-tuple `(sequence, source, destination, result)`, where -**sequence** is an unsigned, arbitrary-precision integer +`sequence` is an unsigned, arbitrary-precision integer -**source** is a string uniquely identifying the chain, connection, and channel from which this packet was sent +`source` is a string uniquely identifying the chain, connection, and channel from which this packet was sent -**destination** is a string uniquely identifying the chain, connection, and channel which should receive this packet +`destination` is a string uniquely identifying the chain, connection, and channel which should receive this packet -**result** is a code of either *success* or *failure* +`result` is a code of either `success` or `failure` #### 3.2.3 Queue -To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - _qhead_ and _qtail_ - bound the slice, such that for every _index_ where _head <= index < tail_, there is a queue element _q[qindex]_. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, _advance_, to facilitate efficient queue cleanup. +To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - `q_head` and `q_tail` - bound the slice, such that for every `index` where `q_head <= index < q_tail`, there is a queue element `q[q_index]`. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, `advance`, to facilitate efficient queue cleanup. Each IBC-supporting blockchain must provide a queue abstraction with the following functionality: -**init** -> set _qhead_ = _0_ -> set _qtail_ = _0_ +`init` -**peek** ⇒ **e** -> match _qhead == qtail_ with -> _true_ ⇒ return _nil_ -> _false_ ⇒ return _q[qhead]_ +``` +set q_head = 0 +set q_tail = 0 +``` -**pop** ⇒ **e** -> match _qhead == qtail_ with -> _true_ ⇒ return _nil_ -> _false_ ⇒ set _qhead_ = _qhead + 1_; return _q[qhead-1]_ +`peek => e` -**retrieve(i)** ⇒ **e** -> match _qhead <= i < qtail_ with -> _true_ ⇒ return _qi_ -> _false_ ⇒ return _nil_ +``` +match q_head == q_tail with + true => return nil + false => + return q[q_head] +``` -**push(e)** -> set _q[qtail]_ = _e_; set _qtail_ = _qtail + 1_ +`pop => e` -**advance(i)** -> set _qhead_ = _i_; set _qtail_ = _max(qtail, i)_ +``` +match q_head == q_tail with + true => return nil + false => + set q_head = q_head + 1 + return q_head - 1 +``` -**head** ⇒ **i** -> return _qhead_ +`retrieve(i) => e` -**tail** ⇒ **i** -> return _qtail_ +``` +match q_head <= i < q_tail with + true => return q[i] + false => return nil +``` + +`push(e)` + +``` +set q[q_tail] = e +set q_tail = q_tail + 1 +``` + +`advance(i)` + +``` +set q_head = i +set q_tail = max(q_tail, i) +``` + +`head => i` + +``` +return q_head +``` + +`tail => i` + +``` +return q_tail +``` #### 3.2.4 Channel -We introduce the abstraction of an IBC _channel_: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains _A_ and _B_. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues and thus independent ordering guarantees. +We introduce the abstraction of an IBC *channel*: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains `A` and `B`. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues and thus independent ordering guarantees. An IBC channel consists of four distinct queues, two on each chain: -_OutgoingA_: Outgoing IBC packets from chain _A_ to chain _B_, stored on chain _A_ +`outgoing_A`: Outgoing IBC packets from chain `A` to chain `B`, stored on chain `A` -_IncomingA_: Execution logs for incoming IBC packets from chain _B_, stored on chain _A_ +`incoming_A`: IBC receipts (execution logs) for incoming IBC packets from chain `B`, stored on chain `A` -_OutgoingB_: Outgoing IBC packets from chain _B_ to chain _A_, stored on chain _B_ +`outgoing_B`: Outgoing IBC packets from chain `B` to chain `A`, stored on chain `B` -_IncomingB_: Execution logs for incoming IBC packets from chain _A_, stored on chain _B_ +`incoming_B`: IBC receipts (execution logs) for incoming IBC packets from chain `A`, stored on chain `B` ### 3.3 Requirements In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that particular IBC packets have been stored at particular indices in the outgoing packet queue, and particular IBC packet execution results have been stored at particular indices in the incoming packet queue. -We use the previously-defined Merkle proof _Mk,v,h_ to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the queue: +We use the previously-defined Merkle proof `M_kvh` to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the queue: -**key**: _(queue name, [head|tail|index])_ +`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 serializated representation cannot collide with that of 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 serializated representation cannot collide with that of any possible index. -Once written to the queue, a packet must be immutable (except for deletion 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_. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. +Once written to the queue, a packet must be immutable (except for deletion when popped from the queue). That is, if a value `v` is written to a queue, then every valid proof `M_kvh` must refer to the same `v`. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. -Each incoming & outgoing queue for each connection must be provably associated with another uniquely identified chain, so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with a string unique to the other chain, such as the chain identifier or the hash of the genesis block. +Each incoming & outgoing queue for each connection must be provably associated with another uniquely identified chain, connection, and channel so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with strings unique to the chain (such as chain identifier), connection, and channel. ### 3.4 Sending a packet { todo: unify terms, clarify } -To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. 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 all is in order, the IBC module simply pushes the packet to the tail of _OutgoingA_, which enables all the proofs described above. +To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. 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 all is in order, the IBC module simply pushes the packet to the tail of `outgoing_a`, which enables all the proofs described above. -If desired, the packet payload can contain additional module routing information in the form of a _kind_, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module must associate every supported message with a particular handler (_fkind_) and return an error for unsupported types. +The packet must provide routing information in the `type` field, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module on the destination chain must associate every supported packet type with a particular handler (`f_type`) and return an error for unsupported types. -_(IBCsend(D, type, data)_ ⇒ _Success)_ - ⇒ _push(qD.send ,Vsend{type, data})_ +`send(P{type, sequence, source, destination, data})` + +``` +match source == (A, connection, channel) and sequence == tail(outgoing_A) with + true => push(outgoing_A, P); success + false => fail with "wrong sender" +``` + +Note that the `sequence`, `source`, and `destination` can all be encoded in the Merkle tree key for the channel and do not need to be stored for each packet. ### 3.5 Receiving a packet -{ todo: unify terms } +Upon packet receipt, chain `B` must check that the packet is valid, that it was intended for the destination, and that all previous packets have been processed. `receive` must write the receipt queue upon accepting a valid packet, even if the handler execution returned an error, so that future packets can be processed. -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_: +To receive an IBC packet on blockchain `B` from a source chain `A`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_A`: -_A:IBCreceive(S, Mk,v,h)_ ⇒ _match_ - * _qS.receipt =_ ∅ ⇒ _Error("unregistered sender"),_ - * _k = (\_, reciept, \_)_ ⇒ _Error("must be a send"),_ - * _k = (d, \_, \_) and d_ ≠ _A_ ⇒ _Error("sent to a different chain"),_ - * _k = (\_, send, i) and head(qS.receipt)_ ≠ _i_ ⇒ _Error("out of order"),_ - * _Hh_ ∉ _TS_ ⇒ _Error("must submit header for height h"),_ - * _valid(Hh ,Mk,v,h ) = false_ ⇒ _Error("invalid merkle proof"),_ - * _v = (type, data)_ ⇒ _(result, err) := ftype(data); push(qS.receipt , (result, err)); Success_ +`receive(P{type, sequence, source, destination, data}, M_kvh)` -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). +``` +case + incoming_B == nil => fail with "unregistered sender" + destination /= (B, connection, channel) => fail with "wrong destination" + sequence /= head(Incoming_B) => fail with "out of order" + H_h not in T_A => fail with "must submit header for height h" + valid(H_h, M_kvh) == false => fail with "invalid Merkle proof" + otherwise => + set (result, error) = f_type(data) + push(incoming_B, R{}) +``` + +{ todo: check that v + k == packet, clearly define connection / channel } ### 3.6 Handling a receipt { todo: cleanup logic } -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` (escrowing the assets in question), 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 provably 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 `receive`, 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"),_ - * _k = (\_, send, \_)_ ⇒ _Error("must be a recipient"),_ - * _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 = (\_, receipt, head|tail)_ ⇒ _Error("only accepts message proofs"),_ - * _k = (\_, receipt, i) and head(qS.send)_ ≠ _i_ ⇒ _Error("out of order"),_ +`handle_receipt(A, M_kvh)` + +``` +case + outgoing_A == nil => fail with "unregistered sender" + destination /= (A, connection, channel) => fail with "wrong destination" + sequence /= head(incoming_A) => fail with "out of order" + H_h not in T_B => fail with "must submit header for height h" + valid(H_h, M_kvh) == false => fail with "invalid Merkle proof" * _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 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. +This enforces that the receipts are processed in order, to allow 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 no need to store it. ![Successful Transaction](images/Receipts.png) @@ -180,11 +223,13 @@ This enforces that the receipts are processed in order, to allow some the applic { todo: cleanup wording & terms } -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. +{ todo: one relay process can relay all the things } -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 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. -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. +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. ``` while true diff --git a/docs/spec/ibc/connections.md b/docs/spec/ibc/connections.md index 94993b59d8..c8bf728d25 100644 --- a/docs/spec/ibc/connections.md +++ b/docs/spec/ibc/connections.md @@ -2,83 +2,94 @@ ([Back to table of contents](README.md#contents)) -The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain _B_ that a data packet received on chain _B_ was correctly generated on chain _A_. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain _B_ we know that the packet has been executed on chain _A_ and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain _B_ (such as generating vouchers on chain _B_ for the chain _A_ assets which can later be redeemed with a packet in the opposite direction). +The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain `B` that a data packet received on chain `B` was correctly generated on chain `A`. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain `B` we know that the packet has been executed on chain `A` and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain `B` (such as generating vouchers on chain `B` for the chain `A` assets which can later be redeemed with a packet in the opposite direction). This section outlines the abstraction of an IBC _connection_: the state and consensus ruleset necessary to perform IBC packet verification. ### 2.1 Definitions -- Chain _A_ is the source blockchain from which the IBC packet is sent -- Chain _B_ is the destination blockchain on which the IBC packet is received -- _Hh_ is the signed header of chain _A_ at height _h_ -- _Ch_ is the consensus ruleset of chain _A_ at height _h_ -- _Vk,h_ is the value stored on chain _A_ under key _k_ at height _h_ -- _P_ is the unbonding period of chain _A_, in units of time -- Δ_(a, b)_ is the time difference between events _a_ and _b_ +- Chain `A` is the source blockchain from which the IBC packet is sent +- Chain `B` is the destination blockchain on which the IBC packet is received +- `H_h` is the signed header of chain `A` at height `h` +- `C_h` is a subset of the consensus ruleset of chain `A` at height `h` +- `V_kh` is the value stored on chain `A` under key `k` at height `h` +- `P` is the unbonding period of chain `P`, in units of time +- `dt(a, b)` is the time difference between events `a` and `b` -Note that of all these, only _Hh_ defines a signature and is thus attributable. +Note that of all these, only `H_h` defines a signature and is thus attributable. ### 2.2 Requirements To facilitate an IBC connection, the two blockchains must provide the following proofs: -1. 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_ -2. 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_ -3. Given a trusted _Hh_ and a merkle proof _Mk,v,h_ it is possible to prove _Vk,h_ +1. Given a trusted `H_h` and `C_h` and an attributable update message `U_h`, + it is possible to prove `H_h'` where `C_h' == C_h` and `dt(now, H_h) < P` +2. Given a trusted `H_h` and `C_h` and an attributable change message `X_h`, + it is possible to prove `H_h'` where `C_h' /= C_h` and `dt(now, H_h) < P` +3. Given a trusted `H_h` and a Merkle proof `M_kvh` it is possible to prove `V_kh` -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 consensus is defined in Appendix E. Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages. +It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix E](). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and 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 `M_kvh` 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_h`. 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]_ +`valid(H_h, M_kvh) => true | false` ### 2.3 Connection Lifecycle -#### 2.3.1 Opening a Connection +#### 2.3.1 Opening a connection -All proofs require an initial _Hh_ and _Ch_ for some _h_, where Δ_(now, Hh) < P_. +All proofs require an initial `H_h` and `C_h` for some `h`, where Δ_(now, Hh) < P_. -Establishing a bidirectional initial root-of-trust between the two blockchains (_A_ to _B_ and _B_ to _A_) — _HAh_ and _CAh_ stored on chain _B_, and _HBh_ and _CBh_ stored on chain _A_ — is necessary before any IBC packets can be sent. +Establishing a bidirectional initial root-of-trust between the two blockchains (`A` to `B` and `B` to `A`) — `H_ah` and `C_ah` stored on chain `B`, and `H_bh` and `C_bh` stored on chain `A` — is necessary before any IBC packets can be sent. Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed permissionlessly, in which case users later utilizing the IBC channel must check the root-of-trust themselves, or authorized by on-chain governance for additional assurance. -#### 2.3.2 Following Block Headers +#### 2.3.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 some future _Hh_ where _h > n_. Some implementations may require that _h = n + 1_ (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that Δ_vals(Cn, Ch ) < ⅓_ (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)]. +We define two messages `U_h` and `X_h`, which together allow us to securely advance our trust from some known `H_n` to some future `H_h` where `h > n`. Some implementations may require that `h == n + 1` (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that `delta vals(C_n, C_h) < ⅓` (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)]. -Either requirement is compatible with IBC. 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 packet between chains _A_ and _B_, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). +Either requirement is compatible with IBC. 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 packet between chains `A` and `B`, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). -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 in headers posted to chain _B_ can be submitted back to chain _A_ for punishment, in the same manner that chain _A_ would independently punish (slash) identified Byzantine actors. +Since these messages `U_h` and `X_h` 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 in headers posted to chain `B` can be submitted back to chain `A` for punishment, in the same manner that chain `A` would independently punish (slash) identified Byzantine actors. -More formally, given existing set of trust _T_ = _{(Hi , Ci ), (Hj , Cj ), …}_, we must provide: +More formally, given existing set of trust `T` = `{(H_i, C_i), (H_j, C_j), …}`, we must provide: -_valid(T, Xh | Uh )_ ⇒ _[true | false | unknown]_ +`valid(T, X_h | U_h) => true | false | unknown` -_if Hh-1_ ∈ _T then_: -* _valid(T, Xh | Uh )_ ⇒ _[true | false]_ -* ∃ (Uh | Xh) ⇒ valid(T, Xh | Uh) {aren't there infinite? why is this necessary} +`valid` must fulfill the following properties: -_if Ch_ ∉ _T then_ -* _valid(T, Uh )_ ⇒ _false_ +``` +if H_h-1 ∈ T then + valid(T, X_h | U_h) => true | false + ∃ (U_h | X_h) => valid(T, X_h | U_h) +``` + +``` +if C_h ∉ T then + valid(T, U_h) => false +``` We can then process update transactions as follows: -_update(T, Xh | Uh )_ ⇒ match _valid(T, Xh | Uh )_ with -* _false_ ⇒ fail with `invalid proof` -* _unknown_ ⇒ fail with `need a proof between current and h` -* _true_ ⇒ set _T_ = _T_ ∪ _(Hh ,Ch )_ +`update(T, X_h | U_h) => success | failure` -Define _max(T)_ as _max(h, where Hh_ ∈ _T)_. For any _T_ with _max(T) = h-1_, there must exist some _Xh | Uh_ so that _max(update(T, Xh | Uh )) = h_. -By induction, there must exist a set of proofs, such that _max(update…(T,...)) = h+n_ for any n. +``` +update(T, X_h | U_h) = match valid(T, X_h | U_h) with + false => fail with "invalid proof" + unknown => fail with "need a proof between current and h" + true => + set T = T ∪ (H_h, C_h) +``` -Bisection can be used 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_. +Define `max(T)` as `max(h, where H_h ∈ T)`. For any `T` with `max(T) == h-1`, there must exist some `X_h | U_h` so that `max(update(T, X_h | U_h)) == h`. +By induction, there must exist a set of proofs, such that `max(update…(T,...)) == h + n` for any `n`. -#### 2.3.3 Closing a Connection +Bisection can be used to discover this set of proofs. That is, given `max(T) == n` and `valid(T, X_h | U_h) == unknown`, we then try `update(T, X_b | U_b)`, where _`b == (h + n) / 2`. The base case is where `valid(T, X_h | U_h) == true` and is guaranteed to exist if `h == max(T) + 1`. -IBC implementations may optionally include the ability to close an IBC connection and prevent further header updates, simply causing _update(T, Xh | Uh )_ as defined above to always return _false_. +#### 2.3.3 Closing a connection -Closing a connection may break application invariants (such as fungiblity - token vouchers on chain _B_ will no longer be redeemable for tokens on chain _A_) and should only be undertaken in extreme circumstances such as Byzantine behavior of the connected chain. +IBC implementations may optionally include the ability to close an IBC connection and prevent further header updates, simply causing `update(T, X_h | U_h)` as defined above to always return `false`. + +Closing a connection may break application invariants (such as fungiblity - token vouchers on chain `B` will no longer be redeemable for tokens on chain `A`) and should only be undertaken in extreme circumstances such as Byzantine behavior of the connected chain. Closure may be permissioned to an on-chain governance system, an identifiable party on the other chain (such as a signer quorum, although this will not work in some Byzantine cases), or any user who submits an application-specific fraud proof. When a connection is closed, application-specific measures may be undertaken to recover assets held on a Byzantine chain. We defer further discussion to [Appendix D](appendices.md#appendix-d-byzantine-recovery-strategies). diff --git a/docs/spec/ibc/overview.md b/docs/spec/ibc/overview.md index ab8c929188..344f69c7d7 100644 --- a/docs/spec/ibc/overview.md +++ b/docs/spec/ibc/overview.md @@ -14,20 +14,20 @@ In this paper, we define a process of posting block headers and Merkle tree proo ### 1.2 Definitions -_Blockchain_ - A replicated fault-tolerant state machine with a distributed consensus algorithm. The smallest unit produced through consensus is a block, which may contain many transactions, each applying some arbitrary mutation to the state. +*Blockchain* - A replicated fault-tolerant state machine with a distributed consensus algorithm. The smallest unit produced through consensus is a block, which may contain many transactions, each applying some arbitrary mutation to the state. -_Module_ - We assume that the state machine of each blockchain is comprised of multiple components that have limited rights to execute some particular set of state transfers (these are modules in the Cosmos SDK or smart contracts in Ethereum). +*Module* - We assume that the state machine of each blockchain is comprised of multiple components that have limited rights to execute some particular set of state transfers (these are modules in the Cosmos SDK or smart contracts in Ethereum). -_Finality_ - The guarantee that a given block will not be reverted within some predefined conditions of a consensus algorithm. All proof-of-work systems offer probabilistic finality, which means that the difficulty of reverting a block increases as the block is embedded more deeply in the chain. Many proof-of-stake systems offer much weaker guarantees, based only on the honesty of the block producers. BFT algorithms such as Tendermint guarantee complete finality upon production of a block (unless over two thirds of the validators collude to break consensus, in which case the offenders can be identified and punished - further discussion of that scenario is outside the scope of this document). +*Finality* - The guarantee that a given block will not be reverted within some predefined conditions of a consensus algorithm. All proof-of-work systems offer probabilistic finality, which means that the difficulty of reverting a block increases as the block is embedded more deeply in the chain. Many proof-of-stake systems offer much weaker guarantees, based only on the honesty of the block producers. BFT algorithms such as Tendermint guarantee complete finality upon production of a block (unless over two thirds of the validators collude to break consensus, in which case the offenders can be identified and punished - further discussion of that scenario is outside the scope of this document). -_Attributable_ - Knowledge of the pseudonymous identity which made a statement, whom we can punish with some deduction of value (slashing) if the statement is false. Synonymous with accountability. +*Attributable* - Knowledge of the pseudonymous identity which made a statement, whom we can punish with some deduction of value (slashing) if the statement is false. Synonymous with accountability. -_Unbonding Period_ - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./references.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P. +*Unbonding period* - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./references.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P. ### 1.3 Threat Models -_False statements_ - Any information we receive may be false. +*False statements* - Any information we receive may be false. -_Network partitions and delays_ - We assume an asynchronous, adversarial network with unbounded latency. Network messages may be modified, reordered, duplicated, or selectively dropped. Actors may be arbitrarily partitioned by a powerful adversary. The IBC protocol favors correctness over liveness (and provides no particular guarantees of the latter). +*Network partitions and delays* - We assume an asynchronous, adversarial network with unbounded latency. Network messages may be modified, reordered, duplicated, or selectively dropped. Actors may be arbitrarily partitioned by a powerful adversary. The IBC protocol favors correctness over liveness where applicable. -_Byzantine actors_ - An entire blockchain may not act according to protocol. This must be detectable and provable, allowing the communicating blockchain to revoke trust and take necessary action. Application-level protocols designed on top of IBC should consider this risk and mitigate it as possible in a manner suitable to their application. +*Byzantine actors* - An entire blockchain may not act according to protocol. This must be detectable and provable, allowing the communicating blockchain to revoke trust and take necessary action. Application-level protocols designed on top of IBC should consider and mitigate this risk in a manner suitable to their application. From 1ac8a4b38e7f3ad939686fbe22401fe49350e207 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 21 Apr 2018 14:33:21 +0200 Subject: [PATCH 27/77] Connection / channel / packet clarifications --- docs/spec/ibc/README.md | 2 +- docs/spec/ibc/appendices.md | 2 +- docs/spec/ibc/channels-and-packets.md | 166 +++++++++++++------------- docs/spec/ibc/connections.md | 28 +++-- 4 files changed, 101 insertions(+), 97 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index eca34c80d3..332bf9141b 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -4,7 +4,7 @@ This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms. -The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving, under particular security assumptions of the underlying blockchains, the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. +The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat. IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between them. diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 736167d2ae..14e4ebde68 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -74,7 +74,7 @@ In order to prove a merkle root, we must fully define the headers, signatures, a Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet --> needs input/support from Tendermint Core team (and go-crypto) +→ needs input/support from Tendermint Core team (and go-crypto) Registering Chain diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 128a0c01ac..353bf7d205 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -6,29 +6,29 @@ IBC uses a cross-chain message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains. -The IBC protocol as defined here is payload-agnostic. The packet receiver on chain `B` decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received and either accepted or rejected. +The IBC protocol as defined here is payload-agnostic. The packet receiver on chain `B` decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received. To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet *x* is processed before packet *y* on chain `A`, packet *x* must also be processed before packet *y* on chain `B`. -IBC channels implement a vector clock[[2](references.md#2)] for the restricted case of two processes (in our case, blockchains). Given *x* -> *y* means *x* is causally before *y*, chains `A` and `B`, and *a* => *b* means *a* implies *b*: +IBC channels implement a vector clock[[2](references.md#2)] for the restricted case of two processes (in our case, blockchains). Given *x* → *y* means *x* is causally before *y*, chains `A` and `B`, and *a* ⇒ *b* means *a* implies *b*: -*A:send(msgi )* -> *B:receive(msgi )* +*A:send(msgi )* → *B:receive(msgi )* -*B:receive(msgi )* -> *A:receipt(msgi )* +*B:receive(msgi )* → *A:receipt(msgi )* -*A:send(msgi )* -> *A:send(msgi+1 )* +*A:send(msgi )* → *A:send(msgi+1 )* -*x* -> *A:send(msgi )* => -*x* -> *B:receive(msgi )* +*x* → *A:send(msgi )* ⇒ +*x* → *B:receive(msgi )* -*y* -> *B:receive(msgi )* => -*y* -> *A:receipt(msgi )* +*y* → *B:receive(msgi )* ⇒ +*y* → *A:receipt(msgi )* Every transaction on the same chain already has a well-defined causality relation (order in history). IBC provides an ordering guarantee across two chains which can be used to reason about the combined state of both chains as a whole. For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`. -This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-format). +This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-formats). ### 3.2 Definitions @@ -56,11 +56,11 @@ We define an IBC *receipt* `R` as the four-tuple `(sequence, source, destination `destination` is a string uniquely identifying the chain, connection, and channel which should receive this packet -`result` is a code of either `success` or `failure` +`result` is an opaque application result payload #### 3.2.3 Queue -To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - `q_head` and `q_tail` - bound the slice, such that for every `index` where `q_head <= index < q_tail`, there is a queue element `q[q_index]`. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, `advance`, to facilitate efficient queue cleanup. +To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - `q_head` and `q_tail` - bound the slice, such that for every `index` where `q_head <= index < q_tail`, there is a queue element `q[index]`. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, `advance`, to facilitate efficient queue cleanup. Each IBC-supporting blockchain must provide a queue abstraction with the following functionality: @@ -71,31 +71,31 @@ set q_head = 0 set q_tail = 0 ``` -`peek => e` +`peek ⇒ e` ``` match q_head == q_tail with - true => return nil - false => + true ⇒ return nil + false ⇒ return q[q_head] ``` -`pop => e` +`pop ⇒ e` ``` match q_head == q_tail with - true => return nil - false => + true ⇒ return nil + false ⇒ set q_head = q_head + 1 return q_head - 1 ``` -`retrieve(i) => e` +`retrieve(i) ⇒ e` ``` match q_head <= i < q_tail with - true => return q[i] - false => return nil + true ⇒ return q[i] + false ⇒ return nil ``` `push(e)` @@ -112,13 +112,13 @@ set q_head = i set q_tail = max(q_tail, i) ``` -`head => i` +`head ⇒ i` ``` return q_head ``` -`tail => i` +`tail ⇒ i` ``` return q_tail @@ -132,11 +132,11 @@ An IBC channel consists of four distinct queues, two on each chain: `outgoing_A`: Outgoing IBC packets from chain `A` to chain `B`, stored on chain `A` -`incoming_A`: IBC receipts (execution logs) for incoming IBC packets from chain `B`, stored on chain `A` +`incoming_A`: IBC receipts for incoming IBC packets from chain `B`, stored on chain `A` `outgoing_B`: Outgoing IBC packets from chain `B` to chain `A`, stored on chain `B` -`incoming_B`: IBC receipts (execution logs) for incoming IBC packets from chain `A`, stored on chain `B` +`incoming_B`: IBC receipts for incoming IBC packets from chain `A`, stored on chain `B` ### 3.3 Requirements @@ -148,72 +148,78 @@ We use the previously-defined Merkle proof `M_kvh` to provide the requisite proo 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 serializated representation cannot collide with that of any possible index. -Once written to the queue, a packet must be immutable (except for deletion when popped from the queue). That is, if a value `v` is written to a queue, then every valid proof `M_kvh` must refer to the same `v`. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. This property is essential to safely process asynchronous messages. +Once written to the queue, a packet must be immutable (except for deletion when popped from the queue). That is, if a value `v` is written to a queue, then every valid proof `M_kvh` must refer to the same `v`. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store. Each incoming & outgoing queue for each connection must be provably associated with another uniquely identified chain, connection, and channel so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with strings unique to the chain (such as chain identifier), connection, and channel. ### 3.4 Sending a packet -{ todo: unify terms, clarify } - To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. 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 all is in order, the IBC module simply pushes the packet to the tail of `outgoing_a`, which enables all the proofs described above. -The packet must provide routing information in the `type` field, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module on the destination chain must associate every supported packet type with a particular handler (`f_type`) and return an error for unsupported types. +The packet must provide routing information in the `type` field, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module on the destination chain must associate every supported packet type with a particular handler (`f_type`). -`send(P{type, sequence, source, destination, data})` +To send an IBC packet from blockchain `A` to blockchain `B`: -``` -match source == (A, connection, channel) and sequence == tail(outgoing_A) with - true => push(outgoing_A, P); success - false => fail with "wrong sender" -``` - -Note that the `sequence`, `source`, and `destination` can all be encoded in the Merkle tree key for the channel and do not need to be stored for each packet. - -### 3.5 Receiving a packet - -Upon packet receipt, chain `B` must check that the packet is valid, that it was intended for the destination, and that all previous packets have been processed. `receive` must write the receipt queue upon accepting a valid packet, even if the handler execution returned an error, so that future packets can be processed. - -To receive an IBC packet on blockchain `B` from a source chain `A`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_A`: - -`receive(P{type, sequence, source, destination, data}, M_kvh)` +`send(P{type, sequence, source, destination, data}) ⇒ success | failure` ``` case - incoming_B == nil => fail with "unregistered sender" - destination /= (B, connection, channel) => fail with "wrong destination" - sequence /= head(Incoming_B) => fail with "out of order" - H_h not in T_A => fail with "must submit header for height h" - valid(H_h, M_kvh) == false => fail with "invalid Merkle proof" - otherwise => - set (result, error) = f_type(data) - push(incoming_B, R{}) + source /= (A, connection, channel) ⇒ fail with "wrong sender" + sequence /= tail(outgoing_A) ⇒ fail with "wrong sequence" + otherwise ⇒ + push(outgoing_A, P) + success ``` -{ todo: check that v + k == packet, clearly define connection / channel } +Note that the `sequence`, `source`, and `destination` can all be encoded in the Merkle tree key for the channel and do not need to be stored individually in each packet. + +### 3.5 Receiving a packet + +Upon packet receipt, chain `B` must check that the packet is valid, that it was intended for the destination, and that all previous packets have been processed. `receive` must write the receipt queue upon accepting a valid packet regardless of the result of handler execution so that future packets can be processed. + +To receive an IBC packet on blockchain `B` from a source chain `A`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_A`: + +`receive(P{type, sequence, source, destination, data}, M_kvh) ⇒ success | failure` + +``` +case + incoming_B == nil ⇒ fail with "unregistered sender" + destination /= (B, connection, channel) ⇒ fail with "wrong destination" + sequence /= head(Incoming_B) ⇒ fail with "out of order" + H_h not in T_A ⇒ fail with "must submit header for height h" + valid(H_h, M_kvh) == false ⇒ fail with "invalid Merkle proof" + otherwise ⇒ + set result = f_type(data) + push(incoming_B, R{tail(incoming_B), (B, connection, channel), (A, connection, channel), result}) + success +``` ### 3.6 Handling a receipt { todo: cleanup logic } -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` (escrowing the assets in question), 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 execution result returned in the IBC receipt. 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` (escrowing the assets in question), 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 provably 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 `receive`, 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 provably send a packet from chain `A` to chain `B`, but provably return the result of executing that packet (the receipt `data`) from chain `B` to chain `A`. If a valid IBC packet was sent from `A` to `B`, then the result of executing it is stored in `incoming_B`. 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 `A` for a message previously sent to `B`. Receipts, like packets, are processed in order. -`handle_receipt(A, M_kvh)` +To handle an IBC receipt on blockchain `A` received from blockchain `B`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_B`: + +`handle_receipt(R{sequence, source, destination, data}, M_kvh)` ``` case - outgoing_A == nil => fail with "unregistered sender" - destination /= (A, connection, channel) => fail with "wrong destination" - sequence /= head(incoming_A) => fail with "out of order" - H_h not in T_B => fail with "must submit header for height h" - valid(H_h, M_kvh) == false => fail with "invalid Merkle proof" - * _v = (\_, error)_ ⇒ _(type, data) := pop(qS.send ); rollbacktype(data); Success_ - * _v = (res, success)_ ⇒ _(type, data) := pop(qS.send ); committype(data, res); Success_ + outgoing_A == nil ⇒ fail with "unregistered sender" + destination /= (A, connection, channel) ⇒ fail with "wrong destination" + sequence /= head(incoming_A) ⇒ fail with "out of order" + H_h not in T_B ⇒ fail with "must submit header for height h" + valid(H_h, M_kvh) == false ⇒ fail with "invalid Merkle proof" + otherwise ⇒ + set P{type, _, _, _, _} = outgoing_A[sequence] + f_type(result) + success ``` -This enforces that the receipts are processed in order, to allow 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 no need to store it. +This allows applications to reason about ordering and enforce application-level guarantees by committing or reverting state changes on chain `A` based on the result of packet execution on chain `B`: ![Successful Transaction](images/Receipts.png) @@ -225,28 +231,24 @@ This enforces that the receipts are processed in order, to allow applications to { todo: one relay process can relay all the things } -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 does not open any actual network connections. We define the concept of a *relay* process that connects two chains by querying one for all outgoing packets & proofs, then committing those packets & 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. Relayers may employ application-level methods to recoup these fees. Any number of *relay* processes may be safely run in parallel. 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 outgoing packets from `A` to `B` and incoming receipts from `B` back to `A`. All reads of variables belonging to a chain imply queries and all function calls imply submitting a transaction to the blockchain. ``` while true - pending := tail(A:qB.send) - received := tail(B:qA.receive) + set pending = tail(outgoing_A) + set received = tail(incoming_B) if pending > received - Uh := A:latestHeader - B:updateHeader(Uh) - for i :=received...pending - k := (B, send, i) - packet := A:Mk,v,h - B:IBCreceive(A, packet) - sleep(desiredLatency) + set U_h = A.latestHeader + if U_h /= B.knownHeaderA + B.updateHeader(U_h) + for i from received to pending + set P = outgoing_A[i] + set M_kvh = A.prove(U_h, P) + B.receive(P, M_kvh) ``` -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. - -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). +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. diff --git a/docs/spec/ibc/connections.md b/docs/spec/ibc/connections.md index c8bf728d25..0e5c17cfeb 100644 --- a/docs/spec/ibc/connections.md +++ b/docs/spec/ibc/connections.md @@ -28,17 +28,19 @@ To facilitate an IBC connection, the two blockchains must provide the following it is possible to prove `H_h'` where `C_h' /= C_h` and `dt(now, H_h) < P` 3. Given a trusted `H_h` and a Merkle proof `M_kvh` it is possible to prove `V_kh` -It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix E](). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages. +It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix F](appendices.md#appendix-f-tendermint-header-proofs). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages. The Merkle proof `M_kvh` 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_h`. 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_h, M_kvh) => true | false` +Blockchains supporting IBC must implement Merkle proof verification: + +`valid(H_h, M_kvh) ⇒ true | false` ### 2.3 Connection Lifecycle #### 2.3.1 Opening a connection -All proofs require an initial `H_h` and `C_h` for some `h`, where Δ_(now, Hh) < P_. +All proofs require an initial `H_h` and `C_h` for some `h`, where `dt(now, H_h) < P`. Establishing a bidirectional initial root-of-trust between the two blockchains (`A` to `B` and `B` to `A`) — `H_ah` and `C_ah` stored on chain `B`, and `H_bh` and `C_bh` stored on chain `A` — is necessary before any IBC packets can be sent. @@ -46,38 +48,38 @@ Any header may be from a malicious chain (e.g. shadowing a real chain state with #### 2.3.2 Following block headers -We define two messages `U_h` and `X_h`, which together allow us to securely advance our trust from some known `H_n` to some future `H_h` where `h > n`. Some implementations may require that `h == n + 1` (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that `delta vals(C_n, C_h) < ⅓` (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)]. +We define two messages `U_h` and `X_h`, which together allow us to securely advance our trust from some known `H_n` to some future `H_h` where `h > n`. Some implementations may require that `h == n + 1` (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that `delta-vals(C_n, C_h) < ⅓` (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)]. -Either requirement is compatible with IBC. 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 packet between chains `A` and `B`, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). +Either requirement is compatible with IBC. 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 packet between chains `A` and `B`, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed). Since these messages `U_h` and `X_h` 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 in headers posted to chain `B` can be submitted back to chain `A` for punishment, in the same manner that chain `A` would independently punish (slash) identified Byzantine actors. More formally, given existing set of trust `T` = `{(H_i, C_i), (H_j, C_j), …}`, we must provide: -`valid(T, X_h | U_h) => true | false | unknown` +`valid(T, X_h | U_h) ⇒ true | false | unknown` `valid` must fulfill the following properties: ``` if H_h-1 ∈ T then - valid(T, X_h | U_h) => true | false - ∃ (U_h | X_h) => valid(T, X_h | U_h) + valid(T, X_h | U_h) ⇒ true | false + ∃ (U_h | X_h) ⇒ valid(T, X_h | U_h) ``` ``` if C_h ∉ T then - valid(T, U_h) => false + valid(T, U_h) ⇒ false ``` We can then process update transactions as follows: -`update(T, X_h | U_h) => success | failure` +`update(T, X_h | U_h) ⇒ success | failure` ``` update(T, X_h | U_h) = match valid(T, X_h | U_h) with - false => fail with "invalid proof" - unknown => fail with "need a proof between current and h" - true => + false ⇒ fail with "invalid proof" + unknown ⇒ fail with "need a proof between current and h" + true ⇒ set T = T ∪ (H_h, C_h) ``` From dddf818e204b96db435c7be339716bd831458e89 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 21 Apr 2018 15:04:06 +0200 Subject: [PATCH 28/77] Move Byzantine failure section to Appendix D --- docs/spec/ibc/README.md | 1 - docs/spec/ibc/appendices.md | 14 ++++++++++++++ docs/spec/ibc/channels-and-packets.md | 6 ------ docs/spec/ibc/optimizations.md | 27 +++++++-------------------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index 332bf9141b..b6689a1768 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -37,7 +37,6 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. **[Optimizations](optimizations.md)** 1. [Timeouts](optimizations.md#41-timeouts) 1. [Cleanup](optimizations.md#42-cleanup) - 1. [Handling Byzantine failures](optimizations.md#43-handling-byzantine-failures) 1. **[Conclusion](conclusion.md)** 1. **[References](references.md)** 1. **[Appendices](appendices.md)** diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 14e4ebde68..1e57d3c5ad 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -54,6 +54,20 @@ See [binary format as protobuf specification](./protobuf/merkle.proto) - Plasma-like fraud proofs - Trusted entity - governance +### 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: failures of the consensus mechanism (reversing "final" blocks), or 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. + +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 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. + ## Appendix E: Universal IBC Packets { what is this } diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 353bf7d205..80cc9d4b90 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -196,8 +196,6 @@ case ### 3.6 Handling a receipt -{ todo: cleanup logic } - When we wish to create a transaction that atomically commits or rolls back across two chains, we must look at the execution result returned in the IBC receipt. 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` (escrowing the assets in question), 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 provably send a packet from chain `A` to chain `B`, but provably return the result of executing that packet (the receipt `data`) from chain `B` to chain `A`. If a valid IBC packet was sent from `A` to `B`, then the result of executing it is stored in `incoming_B`. 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 `A` for a message previously sent to `B`. Receipts, like packets, are processed in order. @@ -227,10 +225,6 @@ This allows applications to reason about ordering and enforce application-level ### 3.7 Packet relayer -{ todo: cleanup wording & terms } - -{ todo: one relay process can relay all the things } - The blockchain itself only records the *intention* to send the given message to the recipient chain - it does not open any actual network connections. We define the concept of a *relay* process that connects two chains by querying one for all outgoing packets & proofs, then committing those packets & 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. Relayers may employ application-level methods to recoup these fees. Any number of *relay* processes may be safely run in parallel. However, they will consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination is ideal. diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index 0c4568e0eb..81cee63dfa 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -2,15 +2,15 @@ ([Back to table of contents](README.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. All messages are processed exactly once and in order, and applications can guarantee invariants over their combined state on both chains. IBC can be further extended and optimized to provide additional guarantees and minimize costs on the underlying blockchains. We detail two extensions: packet timeouts, and packet cleanup. ### 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. +Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves. -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 is to include a timeout in the IBC packet itself. When sending a packet, one can specify a block height or timestamp on chain `B` after which the packet is no longer valid. If the packet is posted before the cutoff, it will be processed normally. If it is posted after the cutoff, it will be a guaranteed error. In order to provide the necessary guarantees, the timeout must be specified relative to a condition on the receiving chain, and the sending chain must have proof of this condition 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 an IBC packet `P={_, sequence, _, _, _}`, the base IBC protocol provides the following guarantees: _A:Mk,v,h =_ ∅ _if message i was not sent before height h_ @@ -28,9 +28,10 @@ _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 `receive`, so that chain `B` doesn't even call the handler function if the timeout was reached, but instead directly writes an error in the receipt queue: + +`receive` -_IBCreceive:_ * …. * _expired(latestHeader, v)_ ⇒ _push(qS.receipt , (None, TimeoutError)),_ * _v = (\_, \_, type, data)_ ⇒ _(result, err) := ftype(data); push(qS.receipt , (result, err));_ @@ -88,17 +89,3 @@ _S:IBCcleanup(A, Mk,v,h)_ ⇒ _match_ 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: failures of the consensus mechanism (reversing "final" blocks), or 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. - -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 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. From 25a146d43f5f5add111f43a778e04fac0ef0abb4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 21 Apr 2018 20:56:53 +0200 Subject: [PATCH 29/77] Update optimizations section --- docs/spec/ibc/channels-and-packets.md | 2 +- docs/spec/ibc/optimizations.md | 77 +++++++++++++++------------ 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 80cc9d4b90..50df1a1f82 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -212,7 +212,7 @@ case H_h not in T_B ⇒ fail with "must submit header for height h" valid(H_h, M_kvh) == false ⇒ fail with "invalid Merkle proof" otherwise ⇒ - set P{type, _, _, _, _} = outgoing_A[sequence] + set P{type, _, _, _, _} = pop(outgoing_A) f_type(result) success ``` diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index 81cee63dfa..d017499e59 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -10,70 +10,75 @@ Application semantics may require some timeout: an upper limit to how long the c One solution is to include a timeout in the IBC packet itself. When sending a packet, one can specify a block height or timestamp on chain `B` after which the packet is no longer valid. If the packet is posted before the cutoff, it will be processed normally. If it is posted after the cutoff, it will be a guaranteed error. In order to provide the necessary guarantees, the timeout must be specified relative to a condition on the receiving chain, and the sending chain must have proof of this condition after the cutoff. -For a sending chain `A` and a receiving chain `B`, with an IBC packet `P={_, sequence, _, _, _}`, the base IBC protocol provides the following guarantees: +For a sending chain `A` and a receiving chain `B`, with an IBC packet `P={_, i, _, _, _}` and some height `h` on chain `B`, the base IBC protocol provides the following guarantees: -_A:Mk,v,h =_ ∅ _if message i was not sent before height h_ +`A:M_kvh == ∅` 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:M_kvh == ∅` if message `i` was sent and the corresponding 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:M_kvh /= ∅` otherwise, if message `i` was sent but the receipt has not yet been processed -_B:Mk,v,h =_ ∅ _if message i was not received before height h_ +`B:M_kvh == ∅` 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:M_kvh /= ∅` if message `i` was received before height `h` -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_. +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_h) > maxTime`. -_Vsend = (maxHeight, maxTime, type, data)_ +`P = {type, sequence, source, destination, data, maxHeight, maxTime}` -_expired(Hh ,Vsend )_ ⇒ _[true|false]_ +`expired(H_h, P) ⇒ true | false` -We then update message handling in `receive`, so that chain `B` doesn't even call the handler function if the timeout was reached, but instead directly writes an error in the receipt queue: +We then update message handling in `receive`, so that chain `B` doesn't even call the handler function if the timeout was reached but instead directly writes an error in the receipt queue: `receive` - * …. - * _expired(latestHeader, v)_ ⇒ _push(qS.receipt , (None, TimeoutError)),_ - * _v = (\_, \_, type, data)_ ⇒ _(result, err) := ftype(data); push(qS.receipt , (result, err));_ +``` +case + ... + expired(latestHeader, v) ⇒ push(incoming_b, R{..., TimeoutError}) + otherwise ⇒ + set result = f_type(data) + push(incoming_B, R{tail(incoming_B), (B, connection, channel), (A, connection, channel), result}) +``` -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. +The `receipt_handler` function on chain `A` can now verify timeouts and pass valid timeout receipts to the application handler (which can revert state changes such as escrowing assets): +`receipt_handler` -_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 = (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_ - * _default_ ⇒ _Error("must be a tail proof")_ +``` +case + ... + result == TimeoutError ⇒ case + not expired(H_h, P) ⇒ fail with "message timeout not yet reached" + otherwise ⇒ f_type(R, TimeoutError) + ... +``` -which processes timeouts in order, and adds one more condition to the queues: +This adds one more guarantee: -_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:M_kvh == ∅` 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 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. +Now chain `A` can rollback all transactions that were blocked by this flood of unrelayed packets - since they can never confirm - 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, we can guarantee that if a packet is not processed by the upper limit of the timeout period, then all previous packets 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. +Additionally, if timestamp-based timeouts are used instead of height-based timeouts, the destination chain's consensus ruleset must enforce always-increasing timestamps (or the sending chain must use a more complex `expired` function). + ### 4.2 Cleanup -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 cost 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: +Additionally, with the above timeout implementation, 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 packet `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:M_kvh == ∅` if packet `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:M_kvh == ∅` if packet 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:M_kvh /= ∅` otherwise (if packet `i` was processed before height `h` but chain `A` has not handled the receipt) 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, we know that all packets `i < head` have been fully processed and all packets `head <= i < tail` are awaiting processing. By proving a much advanced `head` of `outgoing_A`, we can demonstrate that the sending chain already handled all messages. Thus, we can safely advance `incoming_B` to the new head of `outgoing_A`. _S:IBCcleanup(A, Mk,v,h)_ ⇒ _match_ * _qA.receipt =_ ∅ ⇒ _Error("unknown sender"),_ @@ -86,6 +91,8 @@ _S:IBCcleanup(A, Mk,v,h)_ ⇒ _match_ * _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 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. +`cleanup` can be invoked to resolve all outstanding packets up to and including `head` with one Merkle proof. This handles both recovering from timeouts and routine cleanup to recover storage. + +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 packets that have been processed by the receiving chain, but not yet posted to the sending chain, `tail(incoming_B) > head(outgoing_A)`. As such, `advance` must not modify any packets between the head and the tail. ![Cleaning up Packets](images/CleanUp.png) From 43f97496cf9226d398b4dedc4173ee23cf8c71c6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 11:57:40 +0200 Subject: [PATCH 30/77] Update optimizations section --- docs/spec/ibc/channels-and-packets.md | 2 +- docs/spec/ibc/optimizations.md | 29 ++++++++++++--------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/docs/spec/ibc/channels-and-packets.md b/docs/spec/ibc/channels-and-packets.md index 50df1a1f82..0951ca6572 100644 --- a/docs/spec/ibc/channels-and-packets.md +++ b/docs/spec/ibc/channels-and-packets.md @@ -225,7 +225,7 @@ This allows applications to reason about ordering and enforce application-level ### 3.7 Packet relayer -The blockchain itself only records the *intention* to send the given message to the recipient chain - it does not open any actual network connections. We define the concept of a *relay* process that connects two chains by querying one for all outgoing packets & proofs, then committing those packets & proofs to the recipient chain. +The blockchain itself only records the *intention* to send the given message to the recipient chain. Physical network packet relay must be performed by off-chain infrastructure. We define the concept of a *relay* process that connects two chains by querying one for all outgoing packets & proofs, then committing those packets & 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. Relayers may employ application-level methods to recoup these fees. Any number of *relay* processes may be safely run in parallel. However, they will consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination is ideal. diff --git a/docs/spec/ibc/optimizations.md b/docs/spec/ibc/optimizations.md index d017499e59..69776ab2ba 100644 --- a/docs/spec/ibc/optimizations.md +++ b/docs/spec/ibc/optimizations.md @@ -2,7 +2,7 @@ ([Back to table of contents](README.md#contents)) -The above sections describe a secure messaging protocol that can handle all normal situations between two blockchains. All messages are processed exactly once and in order, and applications can guarantee invariants over their combined state on both chains. IBC can be further extended and optimized to provide additional guarantees and minimize costs on the underlying blockchains. We detail two extensions: packet timeouts, and packet cleanup. +The above sections describe a secure messaging protocol that can handle all normal situations between two blockchains. All messages are processed exactly once and in order, and applications can guarantee invariants over their combined state on both chains. IBC can be further extended and optimized to provide additional guarantees and minimize costs on the underlying blockchains. We detail two extensions: packet timeouts and packet cleanup. ### 4.1 Timeouts @@ -24,7 +24,7 @@ For a sending chain `A` and a receiving chain `B`, with an IBC packet `P={_, i, 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_h) > maxTime`. -`P = {type, sequence, source, destination, data, maxHeight, maxTime}` +`P = (type, sequence, source, destination, data, maxHeight, maxTime)` `expired(H_h, P) ⇒ true | false` @@ -78,21 +78,18 @@ Additionally, with the above timeout implementation, when we perform the timeout 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, we know that all packets `i < head` have been fully processed and all packets `head <= i < tail` are awaiting processing. By proving a much advanced `head` of `outgoing_A`, we can demonstrate that the sending chain already handled all messages. Thus, we can safely advance `incoming_B` to the new head of `outgoing_A`. +Through the definition of the send queue, we know that all packets `i < head` have been fully processed and all packets `head <= i < tail` are awaiting processing. By proving a much advanced `head` of `outgoing_B`, we can demonstrate that the sending chain already handled all messages. Thus, we can safely advance `incoming_A` to the new head of `outgoing_B`. -_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"),_ - * _head := v_ ⇒ _match_ - * _head <= head(qA.receipt)_ ⇒ _Error("cleanup must go forward"),_ - * _default_ ⇒ _advance(qA.receipt , head); Success_ +``` +cleanup(A, M_kvh, head) = case + incoming_A == ∅ => fail with "unknown sender" + H_h ∉ T_B => fail with "must submit header for height h" + not valid(H_h, M_kvh, head) => fail with "invalid Merkle proof of outgoing_B queue height" + head >= head(incoming_A) => fail with "cleanup must go forward" + otherwise => + advance(incoming_A, head) +``` -`cleanup` can be invoked to resolve all outstanding packets up to and including `head` with one Merkle proof. This handles both recovering from timeouts and routine cleanup to recover storage. - -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 packets that have been processed by the receiving chain, but not yet posted to the sending chain, `tail(incoming_B) > head(outgoing_A)`. As such, `advance` must not modify any packets between the head and the tail. +This allows us to invoke the `cleanup` function to resolve all outstanding messages up to and including `index` 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 packets that have been processed by the receiving chain, but not yet posted to the sending chain, `tail(incoming_B) > head(outgoing_A)`. As such, `advance` must not modify any packets between the head and the tail. ![Cleaning up Packets](images/CleanUp.png) From 9eeffaa06dca5b945c978c230452f3e5e992f7ea Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 12:09:00 +0200 Subject: [PATCH 31/77] Fix vector clock reference --- docs/spec/ibc/references.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/ibc/references.md b/docs/spec/ibc/references.md index 9262ff99cf..ba9fbee697 100644 --- a/docs/spec/ibc/references.md +++ b/docs/spec/ibc/references.md @@ -6,7 +6,7 @@ [https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc) ##### 2: -{ vector clock } +[https://en.wikipedia.org/wiki/Vector_clock](https://en.wikipedia.org/wiki/Vector_clock) ##### 3: [https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d) From cb2491bbb87f056e9be396c23e2aa1e60f98840f Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Tue, 1 May 2018 11:31:56 -0700 Subject: [PATCH 32/77] Use GoAmino 0.9.9 and change impl of Rational.MarshalAmino --- Gopkg.lock | 6 +++--- Gopkg.toml | 2 +- types/rational.go | 21 +++++---------------- types/rational_test.go | 13 ++++++++++++- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d773b4d7ad..f474e77564 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -277,8 +277,8 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "3668c02a8feace009f80754a5e5a8541e5d7b996" - version = "0.9.8" + revision = "ed62928576cfcaf887209dc96142cd79cdfff389" + version = "0.9.9" [[projects]] name = "github.com/tendermint/go-crypto" @@ -460,6 +460,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "48f831a7ccc2c0fd3b790c16ca99ec864b89253d087ff4190d821c01c13e635a" + inputs-digest = "fad966346d3b6042faf2bf793168b6ce9a8ff59ae207b7ad57008ead0f3ff7d4" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 1e71e68e26..d2d4ee2ff3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -62,7 +62,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "~0.9.8" + version = "~0.9.9" [[constraint]] name = "github.com/tendermint/iavl" diff --git a/types/rational.go b/types/rational.go index 550c96825a..7e0a091075 100644 --- a/types/rational.go +++ b/types/rational.go @@ -1,7 +1,6 @@ package types import ( - "bytes" "fmt" "math/big" "strconv" @@ -153,26 +152,16 @@ func (r Rat) ToLeftPadded(totalDigits int8) string { //___________________________________________________________________________________ -//Wraps r.MarshalText() in quotes to make it a valid JSON string. -func (r Rat) MarshalJSON() ([]byte, error) { +//Wraps r.MarshalText(). +func (r Rat) MarshalAmino() (string, error) { bz, err := (&(r.Rat)).MarshalText() - if err != nil { - return nil, err - } - return []byte(fmt.Sprintf("\"%s\"", bz)), nil + return string(bz), err } // Requires a valid JSON string - strings quotes and calls UnmarshalText -func (r *Rat) UnmarshalJSON(data []byte) (err error) { - quote := []byte(`"`) - if len(data) < 2 || - !bytes.HasPrefix(data, quote) || - !bytes.HasSuffix(data, quote) { - return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string, have %v", string(data)) - } - data = bytes.Trim(data, `"`) +func (r *Rat) UnmarshalAmino(text string) (err error) { tempRat := big.NewRat(0, 1) - err = tempRat.UnmarshalText(data) + err = tempRat.UnmarshalText([]byte(text)) if err != nil { return err } diff --git a/types/rational_test.go b/types/rational_test.go index 39207034cb..a69c941957 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -235,7 +235,7 @@ func TestSerializationText(t *testing.T) { assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } -func TestSerializationGoWire(t *testing.T) { +func TestSerializationGoWireJSON(t *testing.T) { r := NewRat(1, 3) bz, err := cdc.MarshalJSON(r) require.NoError(t, err) @@ -246,6 +246,17 @@ func TestSerializationGoWire(t *testing.T) { assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } +func TestSerializationGoWireBinary(t *testing.T) { + r := NewRat(1, 3) + bz, err := cdc.MarshalBinary(r) + require.NoError(t, err) + + r2 := NewRat(0, 1) + err = cdc.UnmarshalBinary(bz, &r2) + require.NoError(t, err) + assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) +} + type testEmbedStruct struct { Field1 string `json:"f1"` Field2 int `json:"f2"` From dd9b5e78f709ead717353c61d5e1694cdf6583a5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 1 May 2018 16:41:04 -0400 Subject: [PATCH 33/77] cleanup stake marshalJson -> marshalBinary --- cmd/gaia/app/app_test.go | 3 ++- types/rational_test.go | 6 ++--- x/stake/client/cli/query.go | 4 ++-- x/stake/client/rest/query.go | 2 +- x/stake/keeper.go | 44 ++++++++++++++++++------------------ 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 25c6b2d97b..3bca2654b8 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" @@ -107,7 +108,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { StakeData: stake.GetDefaultGenesisState(), } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") + stateBytes, err := wire.MarshalJSONIndent(gapp.cdc, genesisState) if err != nil { return err } diff --git a/types/rational_test.go b/types/rational_test.go index a69c941957..e59545bfc7 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) { bz, err := r.MarshalText() require.NoError(t, err) - r2 := NewRat(0, 1) + var r2 Rat err = r2.UnmarshalText(bz) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) @@ -240,7 +240,7 @@ func TestSerializationGoWireJSON(t *testing.T) { bz, err := cdc.MarshalJSON(r) require.NoError(t, err) - r2 := NewRat(0, 1) + var r2 Rat err = cdc.UnmarshalJSON(bz, &r2) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) @@ -251,7 +251,7 @@ func TestSerializationGoWireBinary(t *testing.T) { bz, err := cdc.MarshalBinary(r) require.NoError(t, err) - r2 := NewRat(0, 1) + var r2 Rat err = cdc.UnmarshalBinary(bz, &r2) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index bb2a2b8db4..145333e486 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -74,7 +74,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the candidate candidate := new(stake.Candidate) - err = cdc.UnmarshalJSON(res, candidate) + err = cdc.UnmarshalBinary(res, candidate) if err != nil { return err } @@ -122,7 +122,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command // parse out the bond bond := new(stake.DelegatorBond) - err = cdc.UnmarshalJSON(res, bond) + err = cdc.UnmarshalBinary(res, bond) if err != nil { return err } diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 22175d75a7..8eb0e03ceb 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -59,7 +59,7 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, } var bond stake.DelegatorBond - err = cdc.UnmarshalJSON(res, &bond) + err = cdc.UnmarshalBinary(res, &bond) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode bond. Error: %s", err.Error()))) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index d9fa293f5f..605f675db0 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -41,7 +41,7 @@ func (k Keeper) getCounter(ctx sdk.Context) int16 { return 0 } var counter int16 - err := k.cdc.UnmarshalJSON(b, &counter) + err := k.cdc.UnmarshalBinary(b, &counter) if err != nil { panic(err) } @@ -51,7 +51,7 @@ func (k Keeper) getCounter(ctx sdk.Context) int16 { // set the current in-block validator operation counter func (k Keeper) setCounter(ctx sdk.Context, counter int16) { store := ctx.KVStore(k.storeKey) - bz, err := k.cdc.MarshalJSON(counter) + bz, err := k.cdc.MarshalBinary(counter) if err != nil { panic(err) } @@ -67,7 +67,7 @@ func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi if b == nil { return candidate, false } - err := k.cdc.UnmarshalJSON(b, &candidate) + err := k.cdc.UnmarshalBinary(b, &candidate) if err != nil { panic(err) } @@ -88,7 +88,7 @@ func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Ca } bz := iterator.Value() var candidate Candidate - err := k.cdc.UnmarshalJSON(bz, &candidate) + err := k.cdc.UnmarshalBinary(bz, &candidate) if err != nil { panic(err) } @@ -112,7 +112,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { } // marshal the candidate record and add to the state - bz, err := k.cdc.MarshalJSON(candidate) + bz, err := k.cdc.MarshalBinary(candidate) if err != nil { panic(err) } @@ -145,7 +145,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { } // update the candidate record - bz, err = k.cdc.MarshalJSON(candidate) + bz, err = k.cdc.MarshalBinary(candidate) if err != nil { panic(err) } @@ -153,7 +153,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { // marshal the new validator record validator := candidate.validator() - bz, err = k.cdc.MarshalJSON(validator) + bz, err = k.cdc.MarshalBinary(validator) if err != nil { panic(err) } @@ -171,7 +171,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { setAcc = true } if setAcc { - bz, err = k.cdc.MarshalJSON(validator.abciValidator(k.cdc)) + bz, err = k.cdc.MarshalBinary(validator.abciValidator(k.cdc)) if err != nil { panic(err) } @@ -200,7 +200,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { if store.Get(GetRecentValidatorKey(address)) == nil { return } - bz, err := k.cdc.MarshalJSON(candidate.validator().abciValidatorZero(k.cdc)) + bz, err := k.cdc.MarshalBinary(candidate.validator().abciValidatorZero(k.cdc)) if err != nil { panic(err) } @@ -242,7 +242,7 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { } bz := iterator.Value() var validator Validator - err := k.cdc.UnmarshalJSON(bz, &validator) + err := k.cdc.UnmarshalBinary(bz, &validator) if err != nil { panic(err) } @@ -266,11 +266,11 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { // get the zero abci validator from the ToKickOut iterator value bz := iterator.Value() var validator Validator - err := k.cdc.UnmarshalJSON(bz, &validator) + err := k.cdc.UnmarshalBinary(bz, &validator) if err != nil { panic(err) } - bz, err = k.cdc.MarshalJSON(validator.abciValidatorZero(k.cdc)) + bz, err = k.cdc.MarshalBinary(validator.abciValidatorZero(k.cdc)) if err != nil { panic(err) } @@ -297,7 +297,7 @@ func (k Keeper) isNewValidator(ctx sdk.Context, store sdk.KVStore, address sdk.A } bz := iterator.Value() var val Validator - err := k.cdc.UnmarshalJSON(bz, &val) + err := k.cdc.UnmarshalBinary(bz, &val) if err != nil { panic(err) } @@ -330,7 +330,7 @@ func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []abci.Validato for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val abci.Validator - err := k.cdc.UnmarshalJSON(valBytes, &val) + err := k.cdc.UnmarshalBinary(valBytes, &val) if err != nil { panic(err) } @@ -364,7 +364,7 @@ func (k Keeper) GetDelegatorBond(ctx sdk.Context, return bond, false } - err := k.cdc.UnmarshalJSON(delegatorBytes, &bond) + err := k.cdc.UnmarshalBinary(delegatorBytes, &bond) if err != nil { panic(err) } @@ -385,7 +385,7 @@ func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorB } bondBytes := iterator.Value() var bond DelegatorBond - err := k.cdc.UnmarshalJSON(bondBytes, &bond) + err := k.cdc.UnmarshalBinary(bondBytes, &bond) if err != nil { panic(err) } @@ -410,7 +410,7 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet } bondBytes := iterator.Value() var bond DelegatorBond - err := k.cdc.UnmarshalJSON(bondBytes, &bond) + err := k.cdc.UnmarshalBinary(bondBytes, &bond) if err != nil { panic(err) } @@ -422,7 +422,7 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(bond) + b, err := k.cdc.MarshalBinary(bond) if err != nil { panic(err) } @@ -448,7 +448,7 @@ func (k Keeper) GetParams(ctx sdk.Context) (params Params) { panic("Stored params should not have been nil") } - err := k.cdc.UnmarshalJSON(b, ¶ms) + err := k.cdc.UnmarshalBinary(b, ¶ms) if err != nil { panic(err) } @@ -456,7 +456,7 @@ func (k Keeper) GetParams(ctx sdk.Context) (params Params) { } func (k Keeper) setParams(ctx sdk.Context, params Params) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(params) + b, err := k.cdc.MarshalBinary(params) if err != nil { panic(err) } @@ -477,7 +477,7 @@ func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { if b == nil { panic("Stored pool should not have been nil") } - err := k.cdc.UnmarshalJSON(b, &pool) + err := k.cdc.UnmarshalBinary(b, &pool) if err != nil { panic(err) // This error should never occur big problem if does } @@ -486,7 +486,7 @@ func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { func (k Keeper) setPool(ctx sdk.Context, p Pool) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(p) + b, err := k.cdc.MarshalBinary(p) if err != nil { panic(err) } From 9ccee0770fe366b6664d080272259a752c679b16 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 1 May 2018 23:33:23 -0400 Subject: [PATCH 34/77] switched test to test_nopcli --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index acbfba9511..d6d1fa2891 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" -all: check_tools get_vendor_deps build build_examples install install_examples test +all: check_tools get_vendor_deps build build_examples install install_examples test_nocli ######################################## ### CI From 7dc29c078537a65f364252c16606ca7d4a57cbc7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 2 May 2018 18:01:17 +0200 Subject: [PATCH 35/77] Update appendices --- docs/spec/ibc/README.md | 3 +- docs/spec/ibc/appendices.md | 57 +++++++++++------------------------- docs/spec/ibc/connections.md | 2 +- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/docs/spec/ibc/README.md b/docs/spec/ibc/README.md index b6689a1768..e9448bca82 100644 --- a/docs/spec/ibc/README.md +++ b/docs/spec/ibc/README.md @@ -44,5 +44,4 @@ IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosm 1. [Appendix B: IBC Queue Format](appendices.md#appendix-b-ibc-queue-format) 1. [Appendix C: Merkle Proof Format](appendices.md#appendix-c-merkle-proof-formats) 1. [Appendix D: Byzantine Recovery Strategies](appendices.md#appendix-d-byzantine-recovery-strategies) - 1. [Appendix E: Universal IBC Packets](appendices.md#appendix-e-universal-ibc-packets) - 1. [Appendix F: Tendermint Header Proofs](appendices.md#appendix-f-tendermint-header-proofs) + 1. [Appendix E: Tendermint Header Proofs](appendices.md#appendix-e-tendermint-header-proofs) diff --git a/docs/spec/ibc/appendices.md b/docs/spec/ibc/appendices.md index 1e57d3c5ad..751924f469 100644 --- a/docs/spec/ibc/appendices.md +++ b/docs/spec/ibc/appendices.md @@ -4,11 +4,9 @@ ## Appendix A: Encoding Libraries -{ figure out what encoding IBC actually uses } - The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _Hh , Uh , _and _Xh_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined. -In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: ethereum's rlp[[6](./references.md#6)] and google's protobuf[[7](./references.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. +In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: Ethereum's RLP[[6](./references.md#6)] and Google's Protobuf[[7](./references.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures. The tendermint-specific data structures are encoded with go-wire[[8](./references.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner. @@ -16,9 +14,7 @@ For the following appendixes, the data structure specifications will be in proto ## Appendix B: IBC Queue Format -{ include queue details here instead of in the other section } - -The foundational data structure of the IBC protocol are the message queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: +The foundational data structure of the IBC protocol are the packet queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above: _key = _(_remote id, [send|receipt], [head|tail|index])_ @@ -26,59 +22,42 @@ _Vsend = (maxHeight, maxTime, type, data)_ _Vreceipt = (result, [success|error code])_ -Keys and values are binary encoded and stored as bytes in the merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the merkle proofs for deterministically generating the sequence of hashes, and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the merkle proof and header, the bytes can be deserialized and the contents interpreted by the protocol. +Keys and values are binary encoded and stored as bytes in the Merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the Merkle proofs for deterministically generating the sequence of hashes and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the Merkle proof and header, the payload bytes can be deserialized and interpreted by the protocol. See [binary format as protobuf specification](./protobuf/queue.proto) ## Appendix C: Merkle Proof Formats -{ link to the implementation } +A Merkle tree (or a trie) generates a single hash that can be used to prove any element of the tree. In order to generate this hash, we first hash the leaf nodes, then hash multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree), and repeat for each level of the tree until we end up with one root hash. +With a known root hash (which is included in the block headers), the existence of a particular key/value in the tree can be proven by tracing the path to the value and revealing the (k-1) hashes for the paths not taken on each level ([[10](./references.md#10)]). -A merkle tree (or a trie) generates one hash that can prove every element of the tree. Generating this hash starts with hashing the leaf nodes. Then hashing multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree). And continue hashing together the inner nodes at each level of the tree, until it reaches a root hash. Once you have a known root hash, you can prove key/value belongs to this tree by tracing the path to the value and revealing the (k-1) hashes for all the paths we did not take on each level. If this is new to you, you can read a basic introduction[[10](./references.md#10)]. +There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent Merkle proofs from a variety of data stores, and provide for chaining proofs to allow for subtrees. While searching for a solution, we did find the chainpoint proof format[[11](./references.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. -There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent merkle proofs from a variety of data stores, and provide for chaining proofs to allow for sub-trees. While searching for a solution, we did find the chainpoint proof format[[11](./references.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed. +We generalize the left/right idiom to the concatenation a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility with arbitrary data stores. -We generalize the left/right idiom to concatenating a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility for arbitrary data stores. +The proof format also allows for chaining of trees, combining multiple Merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express Merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces its own Merkle root, and these are then hashed together to produce the root hash that is stored in the block header. -The proof format also allows for chaining of trees, combining multiple merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces their own merkle root, and these are then hashed together to produce the root hash that is stored in the block header. +A valid Merkle proof for IBC must either consist of a proof of one tree, and prepend `ibc` to all key names as defined above, or use a subtree named `ibc` in the first section, and store the key names as above in the second tree. -A valid merkle proof for IBC must either consist of a proof of one tree, and prepend "ibc" to all key names as defined above, or use a subtree named "ibc" in the first section, and store the key names as above in the second tree. - -For those who wish to minimize the size of their merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./references.md#12)], which is designed for optimal proof size, and freely available for use. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a quite large state. +In order to minimize the size of their Merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./references.md#12)], which is designed for optimal proof size and released under a permissive license. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log2(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a large state. See [binary format as protobuf specification](./protobuf/merkle.proto) ## Appendix D: Byzantine Recovery Strategies -- Goal: keep application invariants -- Plasma-like fraud proofs -- Trusted entity - governance +IBC guarantees reliable, ordered packet delivery in the face of malicious nodes or relays, on top of which application invariants can be ensured. However, all guarantees break down when the blockchain on the other end of the connection exhibits Byzantine behavior. This can take two forms: a failure of the consensus mechanism (reverting previously finalized blocks), or a failure at the application level (not correctly performing the application-level functions on the packet). -### 4.3 Handling Byzantine failures +The IBC protocol can detect a limited class of Byzantine faults at the consensus level by identifying duplicate headers -- if an IBC module ever sees two different headers for the same height (or any evidence that headers belong to different forks), then it can freeze the connection immediately. State reconciliation (e.g. restoring token balances to owners of vouchers on the other chain) must be handled by blockchain governance. -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). +If there is a big divide in the remote chain and the validation set splits (e.g. 60-40 weighted) as to the direction of the chain, then the light-client header update 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 the connection(s) will need to be reopened manually (by governance on the local chain) and set to the new header set(s). 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. -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. +Another kind of Byzantine action is at the application level. Let us assume packets 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 successfully, it promises to credit `x` token vouchers to the account indicated in the packet. If chain `A` does not remove tokens from supply, or chain `B` does not generate vouchers, the application invariants (conservation of supply & fungibility) break down. -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 does not handle these kinds of errors. They must be handled individually by each application. Applications could use Plasma-like fraud proofs to allow state recovery on one chain if fraud can be proved on the other chain. Although complex to implement, a correct implementation would allow applications to guarantee their invariants as long as *either* blockchain's consensus algorithm behaves correctly (and this could be extended to `n` chains). Economic incentives can additionally be used to disincentivize any kind of provable fraud. -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). +## Appendix E: Tendermint Header Proofs -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. - -## Appendix E: Universal IBC Packets - -{ what is this } - -The structures above can be used to define standard encodings for the basic IBC transactions that must be exposed by a blockchain: _IBCreceive_, _IBCreceipt_,_ IBCtimeout_, and _IBCcleanup_. As mentioned above, these are not complete transactions to be posted as is to a blockchain, but rather the "data" content of a transaction, which must also contain fees, nonce, and signatures. The other IBC transaction types _IBCregisterChain_, _IBCupdateHeader_, and _IBCchangeValidators_ are specific to the consensus engine and use unique encodings. We define the tendermint-specific format in the next section. - -See [binary format as protobuf specification](./protobuf/messages.proto) - -## Appendix F: Tendermint Header Proofs - -{ is this finalized? } +{ Ensure this is correct. } **TODO: clean this all up** @@ -123,5 +102,3 @@ A validator change in Tendermint can be securely verified with the following che * Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof). - - diff --git a/docs/spec/ibc/connections.md b/docs/spec/ibc/connections.md index 0e5c17cfeb..a5d5b939f0 100644 --- a/docs/spec/ibc/connections.md +++ b/docs/spec/ibc/connections.md @@ -28,7 +28,7 @@ To facilitate an IBC connection, the two blockchains must provide the following it is possible to prove `H_h'` where `C_h' /= C_h` and `dt(now, H_h) < P` 3. Given a trusted `H_h` and a Merkle proof `M_kvh` it is possible to prove `V_kh` -It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix F](appendices.md#appendix-f-tendermint-header-proofs). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages. +It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix E](appendices.md#appendix-e-tendermint-header-proofs). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages. The Merkle proof `M_kvh` 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_h`. 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. From e970dce841f5fc4130cc9363ab5ddeb83aab0cc8 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 2 May 2018 23:07:05 -0400 Subject: [PATCH 36/77] circle/makefile upgraaade --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++++++++ Makefile | 22 +++++++++------------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95f879392a..122827afd6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,6 +67,38 @@ jobs: command: | export PATH="$GOBIN:$PATH" gometalinter --disable-all --enable='golint' --vendor ./... + + test_unit: + <<: *defaults + parallelism: 4 + steps: + - attach_workspace: + at: /tmp/workspace + - restore_cache: + key: v1-pkg-cache + - restore_cache: + key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: + name: Test unit + command: | + export PATH="$GOBIN:$PATH" + make test_unit + + test_cli: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - restore_cache: + key: v1-pkg-cache + - restore_cache: + key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: + name: Test cli + command: | + export PATH="$GOBIN:$PATH" + make test_cli test_cover: <<: *defaults @@ -121,6 +153,12 @@ workflows: - lint: requires: - setup_dependencies + - test_unit: + requires: + - setup_dependencies + - test_cli: + requires: + - setup_dependencies - test_cover: requires: - setup_dependencies diff --git a/Makefile b/Makefile index d6d1fa2891..abcdc57569 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') +PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" -all: check_tools get_vendor_deps build build_examples install install_examples test_nocli +all: check_tools get_vendor_deps install install_examples test_lint test ######################################## ### CI -ci: get_tools get_vendor_deps install test_cover +ci: get_tools get_vendor_deps install test_cover test_lint test ######################################## ### Build @@ -83,18 +84,13 @@ godocs: ######################################## ### Testing -test: test_unit # test_cli +test: test_unit test_cli -test_nocli: - go test `go list ./... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` - -# Must be run in each package seperately for the visualization -# Added here for easy reference -# coverage: -# go test -coverprofile=c.out && go tool cover -html=c.out +test_cli: + @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` test_unit: - @go test $(PACKAGES) + @go test $(PACKAGES_NOCLITEST) test_cover: @bash tests/test_cover.sh @@ -103,7 +99,7 @@ test_lint: gometalinter --disable-all --enable='golint' --vendor ./... benchmark: - @go test -bench=. $(PACKAGES) + @go test -bench=. $(PACKAGES_NOCLITEST) ######################################## @@ -133,4 +129,4 @@ devdoc_update: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_nocli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update +.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update From 4911db850dd8232e7b8f701f81535d37c24a9b8d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 3 May 2018 18:35:12 +0200 Subject: [PATCH 37/77] Fix auto-sequencing (closes #950) --- client/context/viper.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/context/viper.go b/client/context/viper.go index 7e82f7ad9d..4b3007f12a 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -36,7 +36,7 @@ func NewCoreContextFromViper() CoreContext { Sequence: viper.GetInt64(client.FlagSequence), Client: rpc, Decoder: nil, - AccountStore: "main", + AccountStore: "acc", } } @@ -55,7 +55,8 @@ func defaultChainID() (string, error) { // EnsureSequence - automatically set sequence number if none provided func EnsureSequence(ctx CoreContext) (CoreContext, error) { - if viper.IsSet(client.FlagSequence) { + // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 + if viper.GetInt64(client.FlagSequence) != 0 { return ctx, nil } from, err := ctx.GetFromAddress() From 05988623af728301aec667b8ee3f955d8c2e1a6d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 3 May 2018 18:36:37 +0200 Subject: [PATCH 38/77] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6fd489592..0b1e67bee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ FEATURES: BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia +* Auto-sequencing now works correctly ## 0.15.1 (April 29, 2018) From 10540e38db9ee7e155d370284c98c61b13875edd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 3 May 2018 23:19:56 +0200 Subject: [PATCH 39/77] Handle case of empty (new) account --- client/context/helpers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/context/helpers.go b/client/context/helpers.go index ded70cb5c6..233dbcc2b8 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -158,6 +158,11 @@ func (ctx CoreContext) NextSequence(address []byte) (int64, error) { return 0, err } + if len(res) == 0 { + fmt.Printf("No account found, defaulting to sequence 0\n") + return 0, err + } + account, err := ctx.Decoder(res) if err != nil { panic(err) From 23c9e2fb6f04a26f1b7bb4a46b946fa18e25acdd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 4 May 2018 05:43:29 +0200 Subject: [PATCH 40/77] Update CLI tests to test auto-sequencing --- cmd/gaia/cli_test/cli_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 102c4ada98..b4529efd89 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -48,6 +48,15 @@ func TestGaiaCLISend(t *testing.T) { assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak")) fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooAddr, flags)) assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak")) + + // test autosequencing + executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barAddr), pass) + time.Sleep(time.Second * 3) // waiting for some blocks to pass + + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barAddr, flags)) + assert.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak")) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooAddr, flags)) + assert.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak")) } func TestGaiaCLIDeclareCandidacy(t *testing.T) { From 12ebad49e2b329dae1f0ef23e5afd82e01ff0fed Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 7 May 2018 01:01:01 +0200 Subject: [PATCH 41/77] Export all genesis information (closes #946) --- server/export.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/export.go b/server/export.go index 3d0e49a126..dad2df9cc9 100644 --- a/server/export.go +++ b/server/export.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/wire" + tmtypes "github.com/tendermint/tendermint/types" ) // ExportCmd dumps app state to JSON @@ -21,7 +22,16 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co if err != nil { return errors.Errorf("Error exporting state: %v\n", err) } - fmt.Println(string(appState)) + doc, err := tmtypes.GenesisDocFromFile(ctx.Config.GenesisFile()) + if err != nil { + return err + } + doc.AppStateJSON = appState + encoded, err := wire.MarshalJSONIndent(cdc, doc) + if err != nil { + return err + } + fmt.Println(string(encoded)) return nil }, } From 677a5f110806606aa475536ae7b8b1f69afb4268 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 7 May 2018 01:05:32 +0200 Subject: [PATCH 42/77] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b1e67bee6..77b4ffba89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ BREAKING CHANGES FEATURES: +* Added `gaiad export` command, which exports genesis information & current state * Gaia stake commands include, DeclareCandidacy, EditCandidacy, Delegate, Unbond * MountStoreWithDB without providing a custom store works. * Repo is now lint compliant / GoMetaLinter with tendermint-lint integrated into CI From bf178ba041d40c503fd2c5e0693b8ec7f96397ff Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 7 May 2018 09:47:17 -0400 Subject: [PATCH 43/77] spec/ibc -> spec/ibc/mvp --- docs/spec/ibc/{ => mvp}/ibc.md | 0 docs/spec/ibc/{ => mvp}/mvp1.md | 0 docs/spec/ibc/{ => mvp}/mvp2.md | 0 docs/spec/ibc/{ => mvp}/mvp3.md | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename docs/spec/ibc/{ => mvp}/ibc.md (100%) rename docs/spec/ibc/{ => mvp}/mvp1.md (100%) rename docs/spec/ibc/{ => mvp}/mvp2.md (100%) rename docs/spec/ibc/{ => mvp}/mvp3.md (100%) diff --git a/docs/spec/ibc/ibc.md b/docs/spec/ibc/mvp/ibc.md similarity index 100% rename from docs/spec/ibc/ibc.md rename to docs/spec/ibc/mvp/ibc.md diff --git a/docs/spec/ibc/mvp1.md b/docs/spec/ibc/mvp/mvp1.md similarity index 100% rename from docs/spec/ibc/mvp1.md rename to docs/spec/ibc/mvp/mvp1.md diff --git a/docs/spec/ibc/mvp2.md b/docs/spec/ibc/mvp/mvp2.md similarity index 100% rename from docs/spec/ibc/mvp2.md rename to docs/spec/ibc/mvp/mvp2.md diff --git a/docs/spec/ibc/mvp3.md b/docs/spec/ibc/mvp/mvp3.md similarity index 100% rename from docs/spec/ibc/mvp3.md rename to docs/spec/ibc/mvp/mvp3.md From cd689ce2c17397310c4e90b87ca8468e363ca449 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 7 May 2018 18:44:03 -0400 Subject: [PATCH 44/77] remove experimental bash tests from ci int Fix typo ... --- .circleci/config.yml | 21 +-------------------- Makefile | 2 +- tests/test_cover.sh | 2 +- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 122827afd6..2accbf1790 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,22 +84,6 @@ jobs: export PATH="$GOBIN:$PATH" make test_unit - test_cli: - <<: *defaults - parallelism: 1 - steps: - - attach_workspace: - at: /tmp/workspace - - restore_cache: - key: v1-pkg-cache - - restore_cache: - key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} - - run: - name: Test cli - command: | - export PATH="$GOBIN:$PATH" - make test_cli - test_cover: <<: *defaults parallelism: 4 @@ -115,7 +99,7 @@ jobs: command: | export PATH="$GOBIN:$PATH" make install - for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | circleci tests split --split-by=timings); do + for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do id=$(basename "$pkg") go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" @@ -156,9 +140,6 @@ workflows: - test_unit: requires: - setup_dependencies - - test_cli: - requires: - - setup_dependencies - test_cover: requires: - setup_dependencies diff --git a/Makefile b/Makefile index abcdc57569..043645c670 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ godocs: ######################################## ### Testing -test: test_unit test_cli +test: test_unit test_cli: @go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test` diff --git a/tests/test_cover.sh b/tests/test_cover.sh index 4f8aa55f1c..be6215b5a1 100644 --- a/tests/test_cover.sh +++ b/tests/test_cover.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -PKGS=$(go list ./... | grep -v /vendor/) +PKGS=$(go list ./... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) set -e echo "mode: atomic" > coverage.txt From 1ad820f67ce234e24e71d152b99c0b4c761dfa0f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 4 May 2018 06:13:23 +0200 Subject: [PATCH 45/77] Update Dockerfile for gometalinter requirement --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a788aa6416..80bbae85f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ ADD . $REPO_PATH # Install minimum necessary dependencies, build Cosmos SDK, remove packages RUN apk add --no-cache $PACKAGES && \ - cd $REPO_PATH && make get_tools && make get_vendor_deps && make all && make install && \ + cd $REPO_PATH && make get_tools && make get_vendor_deps && make build && make install && \ apk del $PACKAGES # Set entrypoint From b452859f6ab3b33a12e60bd34e09403c7c968528 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 10:50:02 -0400 Subject: [PATCH 46/77] staking spec: minor cleanup --- docs/spec/staking/spec-technical.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index 8d9baa7967..c556159369 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -36,24 +36,22 @@ provisions and transaction fees. ## State The staking module persists the following information to the store: -* `GlobalState`, describing the global pools and the inflation related fields -* validator candidates (including current validators), indexed by public key and shares in the global pool -(bonded or unbonded depending on candidate status) -* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate +* `GlobalState`, a struct describing the global pools, inflation, and + fees +* `ValidatorCandidates: => `, a map of all candidates (including current validators) in the store, +indexed by their public key and shares in the global pool. +* `DelegatorBonds: < delegator-address | candidate-pubkey > => `. a map of all delegations by a delegator to a candidate, +indexed by delegator address and candidate pubkey. public key -* the queue of unbonding delegations -* the queue of re-delegations +* `UnbondQueue`, the queue of unbonding delegations +* `RedelegateQueue`, the queue of re-delegations ### Global State -The GlobalState data structure contains total Atom supply, amount of Atoms in -the bonded pool, sum of all shares distributed for the bonded pool, amount of -Atoms in the unbonded pool, sum of all shares distributed for the unbonded -pool, a timestamp of the last processing of inflation, the current annual -inflation rate, a timestamp for the last comission accounting reset, the global -fee pool, a pool of reserve taxes collected for the governance use and an -adjustment factor for calculating global fee accum. `Params` is global data -structure that stores system parameters and defines overall functioning of the +The GlobalState contains information about the total amount of Atoms, the +global bonded/unbonded position, the Atom inflation rate, and the fees. + +`Params` is global data structure that stores system parameters and defines overall functioning of the module. ``` go @@ -95,7 +93,7 @@ type Params struct { ### Candidate -The `Candidate` data structure holds the current state and some historical +The `Candidate` holds the current state and some historical actions of validators or candidate-validators. ``` go From 8a6ec9a25749f4b8ede0ea368694feee7d176412 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 10:50:09 -0400 Subject: [PATCH 47/77] slashing wip --- docs/spec/staking/slashing.md | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/spec/staking/slashing.md diff --git a/docs/spec/staking/slashing.md b/docs/spec/staking/slashing.md new file mode 100644 index 0000000000..c64aa9d797 --- /dev/null +++ b/docs/spec/staking/slashing.md @@ -0,0 +1,79 @@ + +# Slashing + +A validator bond is an economic commitment made by a validator signing key to both the safety and liveness of +the consensus. Validator keys must not sign invalid messages which could +violate consensus safety, and their signed precommit messages must be regularly included in +block commits. + +The incentivization of these two goals are treated separately. + +## Safety + +Messges which may compromise the safety of the underlying consensus protocol ("equivocations") +result in some amount of the offending validator's shares being removed ("slashed"). + +Currently, such messages include only the following: + +- prevotes by the same validator for more than one BlockID at the same + Height and Round +- precommits by the same validator for more than one BlockID at the same + Height and Round + +We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the +detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending +validators punished. + +For some `evidence` to be valid, it must satisfy: + +`evidence.Height >= block.Height - MAX_EVIDENCE_AGE` + +If valid evidence is included in a block, the offending validator loses +a constant `SLASH_PROPORTION` of their current stake: + +``` +oldShares = validator.shares +validator.shares = oldShares * (1 - SLASH_PROPORTION) +``` + +This ensures that offending validators are punished the same amount whether they +act as a single validator with X stake or as N validators with collectively X +stake. + + + +## Liveness + +Every block includes a set of precommits by the validators for the previous block, +known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. + +Proposers are incentivized to include precommits from all +validators in the LastCommit by receiving additional fees +proportional to the difference between the voting power included in the +LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). + +Validators are penalized for failing to be included in the LastCommit for some +number of blocks by being automatically unbonded. + + +TODO: do we do this by trying to track absence directly in the state, using +something like the below, or do we let users notify the app when a validator has +been absent using the +[TxLivenessCheck](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/staking/spec-technical.md#txlivelinesscheck). + + +A list, `ValidatorAbsenceInfos`, is stored in the state and used to track how often +validators were included in a LastCommit. + +```go +// Ordered by ValidatorAddress. +// One entry for each validator. +type ValidatorAbsenceInfos []ValidatorAbsenceInfo + +type ValidatorAbsenceInfo struct { + ValidatorAddress []byte // address of the validator + FirstHeight int64 // first height the validator was absent + Count int64 // number of heights validator was absent since (and including) first +} +``` + From ac8597e49db2be2a845c082cf8eff9adc60834cb Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 15:31:34 -0400 Subject: [PATCH 48/77] spec/governance: update state.md --- docs/spec/governance/state.md | 116 +++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index b6f0d3a61c..e533ec6fe1 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -10,11 +10,29 @@ procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive. ```go + +type VoteType byte + +const ( + VoteTypeYes = 0x1 + VoteTypeNo = 0x2 + VoteTypeNoWithVeto = 0x3 + VoteTypeAbstain = 0x4 +) + +type ProposalType byte + +const ( + ProposalTypePlainText = 0x1 + ProposalTypeSoftwareUpgrade = 0x2 + +) + type Procedure struct { VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks MinDeposit int64 // Minimum deposit for a proposal to enter voting period. - OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} - ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} + VoteTypes []VoteType // Vote types available to voters. + ProposalTypes []ProposalType // Proposal types available to submitters. Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months @@ -30,7 +48,7 @@ The current active procedure is stored in a global `params` KVStore. ```go type Deposit struct { - Amount sdk.Coins // sAmount of coins deposited by depositer + Amount sdk.Coins // Amount of coins deposited by depositer Depositer crypto.address // Address of depositer } ``` @@ -39,27 +57,27 @@ The current active procedure is stored in a global `params` KVStore. ```go type Votes struct { - YesVotes int64 - NoVote int64 - NoWithVetoVotes int64 - AbstainVotes int64 + Yes int64 + No int64 + NoWithVeto int64 + Abstain int64 } ``` ### Proposals -`Proposals` are item to be voted on. +`Proposals` are an item to be voted on. ```go type Proposal struct { Title string // Title of the proposal Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit Deposits []Deposit // List of deposits on the proposal SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included - Submitter crypto.address // Address of the submitter + Submitter crypto.Address // Address of the submitter VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0) @@ -83,14 +101,12 @@ type ValidatorGovInfo struct { *Stores are KVStores in the multistore. The key to find the store is the first parameter in the list* -* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their +* `Proposals: int64 => Proposal` maps `proposalID` to the `Proposal` `proposalID` -* `Options`: A mapping `map[[]byte]string` of options indexed by - `::` as `[]byte`. Given a - `proposalID`, an `address` and a validator's `address`, returns option chosen by this `address` for this validator (`nil` if `address` has not voted under this validator) -* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's - governance infos indexed by `:`. Returns - `nil` if proposal has not entered voting period or if `address` was not the +* `Options: => VoteType`: maps to the vote of the `voterAddress` for `proposalID` re its delegation to `validatorAddress`. + Returns 0x0 If `voterAddress` has not voted under this validator. +* `ValidatorGovInfos: => ValidatorGovInfo`: maps to the gov info for the `validatorAddress` and `proposalID`. + Returns `nil` if proposal has not entered voting period or if `address` was not the address of a validator when proposal entered voting period. For pseudocode purposes, here are the two function we will use to read or write in stores: @@ -121,62 +137,62 @@ And the pseudocode for the `ProposalProcessingQueue`: // Recursive function. First call in BeginBlock func checkProposal() - if (ProposalProcessingQueue.Peek() == nil) + proposalID = ProposalProcessingQueue.Peek() + if (proposalID == nil) return - else - proposalID = ProposalProcessingQueue.Peek() - proposal = load(Proposals, proposalID) + proposal = load(Proposals, proposalID) - if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) + if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower > 2/3) - // proposal was urgent and accepted under the special condition - // no punishment - // refund deposits + // proposal accepted early by super-majority + // no punishments; refund deposits - ProposalProcessingQueue.pop() + ProposalProcessingQueue.pop() - newDeposits = new []Deposits + var newDeposits []Deposits - for each (amount, depositer) in proposal.Deposits - newDeposits.append[{0, depositer}] - depositer.AtomBalance += amount + // XXX: why do we need to reset deposits? cant we just clear it ? + for each (amount, depositer) in proposal.Deposits + newDeposits.append[{0, depositer}] + depositer.AtomBalance += amount - proposal.Deposits = newDeposits - store(Proposals, , proposal) + proposal.Deposits = newDeposits + store(Proposals, proposalID, proposal) - checkProposal() + checkProposal() - else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod) + else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod) - ProposalProcessingQueue.pop() - activeProcedure = load(params, 'ActiveProcedure') + ProposalProcessingQueue.pop() + activeProcedure = load(params, 'ActiveProcedure') - for each validator in CurrentBondedValidators - validatorGovInfo = load(ValidatorGovInfos, :) - - if (validatorGovInfo.InitVotingPower != nil) - // validator was bonded when vote started + for each validator in CurrentBondedValidators + validatorGovInfo = load(ValidatorGovInfos, ) + + if (validatorGovInfo.InitVotingPower != nil) + // validator was bonded when vote started - validatorOption = load(Options, :) - if (validatorOption == nil) - // validator did not vote - slash validator by activeProcedure.GovernancePenalty + validatorOption = load(Options, ) + if (validatorOption == nil) + // validator did not vote + slash validator by activeProcedure.GovernancePenalty - if((proposal.Votes.YesVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes)) > 0.5 AND (proposal.Votes.NoWithVetoVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes) < 1/3)) + totalNonAbstain = proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes + if( proposal.Votes.YesVotes/totalNonAbstain > 0.5 AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < 1/3) // proposal was accepted at the end of the voting period - // refund deposits + // refund deposits (non-voters already punished) - newDeposits = new []Deposits + var newDeposits []Deposits for each (amount, depositer) in proposal.Deposits newDeposits.append[{0, depositer}] depositer.AtomBalance += amount proposal.Deposits = newDeposits - store(Proposals, , proposal) + store(Proposals, proposalID, proposal) checkProposal() -``` \ No newline at end of file +``` From b3421a884d62be9653637aaa881671e5ddded229 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 15:53:23 -0400 Subject: [PATCH 49/77] spec/governance: update transactions.md --- docs/spec/governance/transactions.md | 347 +++++++++++++-------------- 1 file changed, 162 insertions(+), 185 deletions(-) diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 3e0af9ed66..5f5401de87 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -11,7 +11,7 @@ transaction. type TxGovSubmitProposal struct { Title string // Title of the proposal Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Type ProposalType // Type of proposal InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. } ``` @@ -36,61 +36,58 @@ upon receiving txGovSubmitProposal from sender do if !correctlyFormatted(txGovSubmitProposal) then // check if proposal is correctly formatted. Includes fee payment. - throw - else - if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then - // InitialDeposit is negative or null OR sender has insufficient funds - - throw + initialDeposit = txGovSubmitProposal.InitialDeposit + if (initialDeposit <= 0) OR (sender.AtomBalance < initialDeposit) then + // InitialDeposit is negative or null OR sender has insufficient funds + throw + + sender.AtomBalance -= initialDeposit + + proposalID = generate new proposalID + proposal = NewProposal() + + proposal.Title = txGovSubmitProposal.Title + proposal.Description = txGovSubmitProposal.Description + proposal.Type = txGovSubmitProposal.Type + proposal.TotalDeposit = initialDeposit + proposal.SubmitBlock = CurrentBlock + proposal.Deposits.append({initialDeposit, sender}) + proposal.Submitter = sender + proposal.Votes.Yes = 0 + proposal.Votes.No = 0 + proposal.Votes.NoWithVeto = 0 + proposal.Votes.Abstain = 0 + + activeProcedure = load(params, 'ActiveProcedure') + + if (initialDeposit < activeProcedure.MinDeposit) then + // MinDeposit is not reached - else - sender.AtomBalance -= txGovSubmitProposal.InitialDeposit - - proposalID = generate new proposalID - proposal = NewProposal() - - proposal.Title = txGovSubmitProposal.Title - proposal.Description = txGovSubmitProposal.Description - proposal.Type = txGovSubmitProposal.Type - proposal.TotalDeposit = txGovSubmitProposal.InitialDeposit - proposal.SubmitBlock = CurrentBlock - proposal.Deposits.append({InitialDeposit, sender}) - proposal.Submitter = sender - proposal.Votes.YesVotes = 0 - proposal.Votes.NoVotes = 0 - proposal.Votes.NoWithVetoVotes = 0 - proposal.Votes.AbstainVotes = 0 - - activeProcedure = load(params, 'ActiveProcedure') + proposal.VotingStartBlock = -1 + proposal.InitTotalVotingPower = 0 - if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then - // MinDeposit is not reached - - proposal.VotingStartBlock = -1 - proposal.InitTotalVotingPower = 0 - - else - // MinDeposit is reached - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedure = activeProcedure - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator + else + // MinDeposit is reached + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedure = activeProcedure + + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator - validatorGovInfo = new ValidatorGovInfo - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 + validatorGovInfo = new ValidatorGovInfo + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 - store(ValidatorGovInfos, :, validatorGovInfo) - - ProposalProcessingQueue.push(proposalID) + store(ValidatorGovInfos, , validatorGovInfo) + + ProposalProcessingQueue.push(proposalID) - store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping - return proposalID + store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping + return proposalID ``` ### Deposit @@ -127,61 +124,54 @@ upon receiving txGovDeposit from sender do if !correctlyFormatted(txGovDeposit) then throw - else - proposal = load(Proposals, txGovDeposit.ProposalID) + proposal = load(Proposals, txGovDeposit.ProposalID) - if (proposal == nil) then - // There is no proposal for this proposalID - + if (proposal == nil) then + // There is no proposal for this proposalID + throw + + if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) + // deposit is negative or null OR sender has insufficient funds + throw + + activeProcedure = load(params, 'ActiveProcedure') + + if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then + // MinDeposit was reached + // TODO: shouldnt we do something here ? + throw + + else + if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then + // Maximum deposit period reached throw - else - if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) - // deposit is negative or null OR sender has insufficient funds - - throw + // sender can deposit + + sender.AtomBalance -= txGovDeposit.Deposit + + proposal.Deposits.append({txGovVote.Deposit, sender}) + proposal.TotalDeposit += txGovDeposit.Deposit + + if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then + // MinDeposit is reached, vote opens - else - activeProcedure = load(params, 'ActiveProcedure') + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedure = activeProcedure + + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator - if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then - // MinDeposit was reached - - throw - - else - if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then - // Maximum deposit period reached - - throw - - else - // sender can deposit - - sender.AtomBalance -= txGovDeposit.Deposit + validatorGovInfo = NewValidatorGovInfo() + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 - proposal.Deposits.append({txGovVote.Deposit, sender}) - proposal.TotalDeposit += txGovDeposit.Deposit - - if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then - // MinDeposit is reached, vote opens - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedure = activeProcedure - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator + store(ValidatorGovInfos, , validatorGovInfo) + + ProposalProcessingQueue.push(txGovDeposit.ProposalID) - validatorGovInfo = NewValidatorGovInfo() - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 - - store(ValidatorGovInfos, :, validatorGovInfo) - - ProposalProcessingQueue.push(txGovDeposit.ProposalID) - - store(Proposals, txGovVote.ProposalID, proposal) + store(Proposals, txGovVote.ProposalID, proposal) ``` ### Vote @@ -227,101 +217,88 @@ handled: if !correctlyFormatted(txGovDeposit) then throw - else - proposal = load(Proposals, txGovDeposit.ProposalID) + proposal = load(Proposals, txGovDeposit.ProposalID) - if (proposal == nil) then - // There is no proposal for this proposalID - + if (proposal == nil) then + // There is no proposal for this proposalID + throw + + validator = load(CurrentValidators, txGovVote.ValidatorAddress) + + if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR + (validator == nil) then + + // Throws if + // Option is not in Option Set of procedure that was active when vote opened OR if + // ValidatorAddress is not the address of a current validator + + throw + + option = load(Options, ::) + + if (option != nil) + // sender has already voted with the Atoms bonded to ValidatorAddress + throw + + if (proposal.VotingStartBlock < 0) OR + (CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR + (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR + (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR + (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then + + // Throws if + // Vote has not started OR if + // Vote had ended OR if + // sender bonded Atoms to ValidatorAddress after start of vote OR if + // sender unbonded Atoms from ValidatorAddress after start of vote OR if + // special condition is met, i.e. proposal is accepted and closed + + throw + + validatorGovInfo = load(ValidatorGovInfos, :) + + if (validatorGovInfo == nil) + // validator became validator after proposal entered voting period + throw + + // sender can vote, check if sender == validator and store sender's option in Options + + store(Options, ::, txGovVote.Option) + + if (sender != validator.address) + // Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress + + if sender does not have bonded Atoms to txGovVote.ValidatorAddress then + // check in Staking module throw - + + validatorOption = load(Options, ::) + + if (validatorOption == nil) + // Validator has not voted already + + validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress) + store(ValidatorGovInfos, :, validatorGovInfo) + else - validator = load(CurrentValidators, txGovVote.ValidatorAddress) - - if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR - (validator == nil) then - - // Throws if - // Option is not in Option Set of procedure that was active when vote opened OR if - // ValidatorAddress is not the address of a current validator - - throw - - else - option = load(Options, ::) + // Validator has already voted + // Reduce votes of option chosen by validator by sender's bonded Amount - if (option != nil) - // sender has already voted with the Atoms bonded to ValidatorAddress + proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - throw + // increase votes of option chosen by sender by bonded Amount - else - if (proposal.VotingStartBlock < 0) OR - (CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR - (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR - (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR - (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then + senderOption = txGovVote.Option + propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - // Throws if - // Vote has not started OR if - // Vote had ended OR if - // sender bonded Atoms to ValidatorAddress after start of vote OR if - // sender unbonded Atoms from ValidatorAddress after start of vote OR if - // special condition is met, i.e. proposal is accepted and closed + store(Proposals, txGovVote.ProposalID, proposal) + - throw + else + // sender is the address of the validator whose main Address is txGovVote.ValidatorAddress + // i.e. sender == validator - else - validatorGovInfo = load(ValidatorGovInfos, :) + proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - if (validatorGovInfo == nil) - // validator became validator after proposal entered voting period - - throw - - else - // sender can vote, check if sender == validator and store sender's option in Options - - store(Options, ::, txGovVote.Option) - - if (sender != validator.address) - // Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress - - if sender does not have bonded Atoms to txGovVote.ValidatorAddress then - // check in Staking module - - throw - - else - validatorOption = load(Options, ::) - - if (validatorOption == nil) - // Validator has not voted already - - validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress) - store(ValidatorGovInfos, :, validatorGovInfo) - - else - // Validator has already voted - // Reduce votes of option chosen by validator by sender's bonded Amount - - proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - - // increase votes of option chosen by sender by bonded Amount - - senderOption = txGovVote.Option - propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - - store(Proposals, txGovVote.ProposalID, proposal) - - - else - // sender is the address of the validator whose main Address is txGovVote.ValidatorAddress - // i.e. sender == validator - - proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - - store(Proposals, txGovVote.ProposalID, proposal) - - -``` \ No newline at end of file + store(Proposals, txGovVote.ProposalID, proposal) +``` From d44c7afa30dd559c7959ed9b37ee546576335d03 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 8 May 2018 12:47:31 -0400 Subject: [PATCH 50/77] add range queries, add candidates query --- client/context/helpers.go | 17 +++++++- cmd/gaia/cmd/gaiacli/main.go | 2 +- store/iavlstore.go | 11 ++++- store/types.go | 1 + types/store.go | 7 +++ x/stake/client/cli/query.go | 83 ++++++++++++++++-------------------- x/stake/client/cli/tx.go | 4 +- x/stake/msg.go | 9 ++-- 8 files changed, 78 insertions(+), 56 deletions(-) diff --git a/client/context/helpers.go b/client/context/helpers.go index 233dbcc2b8..f71426f2e6 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -43,8 +43,23 @@ func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, // Query from Tendermint with the provided key and storename func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) { + return ctx.query(key, storeName, "key") +} - path := fmt.Sprintf("/%s/key", storeName) +// Query from Tendermint with the provided storename and subspace +func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KV, err error) { + resRaw, err := ctx.query(subspace, storeName, "iter") + if err != nil { + return res, err + } + cdc.MustUnmarshalBinary(resRaw, &res) + return +} + +// Query from Tendermint with the provided storename and path +func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { + + path := fmt.Sprintf("/%s/%s", storeName, endPath) node, err := ctx.GetNode() if err != nil { return res, err diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 93d6b57b91..8de2e3acc2 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -46,7 +46,7 @@ func main() { client.GetCommands( authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), stakecmd.GetCmdQueryCandidate("stake", cdc), - //stakecmd.GetCmdQueryCandidates("stake", cdc), + stakecmd.GetCmdQueryCandidates("stake", cdc), stakecmd.GetCmdQueryDelegatorBond("stake", cdc), //stakecmd.GetCmdQueryDelegatorBonds("stake", cdc), )...) diff --git a/store/iavlstore.go b/store/iavlstore.go index de32e27e1e..a3a85fe69e 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -176,7 +176,16 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } else { _, res.Value = tree.GetVersioned(key, height) } - + case "/iter": // Get by key + key := req.Data // Data holds the key bytes + res.Key = key + var KVs []KV + iterator := st.SubspaceIterator(key) + for ; iterator.Valid(); iterator.Next() { + KVs = append(KVs, KV{iterator.Key(), iterator.Value()}) + } + iterator.Close() + res.Value = cdc.MustMarshalBinary(KVs) default: msg := fmt.Sprintf("Unexpected Query path: %v", req.Path) return sdk.ErrUnknownRequest(msg).QueryResult() diff --git a/store/types.go b/store/types.go index ca43dab6bc..fc355a1b34 100644 --- a/store/types.go +++ b/store/types.go @@ -13,6 +13,7 @@ type MultiStore = types.MultiStore type CacheMultiStore = types.CacheMultiStore type CommitMultiStore = types.CommitMultiStore type KVStore = types.KVStore +type KV = types.KV type Iterator = types.Iterator type CacheKVStore = types.CacheKVStore type CommitKVStore = types.CommitKVStore diff --git a/types/store.go b/types/store.go index 7b73570cac..858f0e93ca 100644 --- a/types/store.go +++ b/types/store.go @@ -256,3 +256,10 @@ func PrefixEndBytes(prefix []byte) []byte { } return end } + +//---------------------------------------- + +// key-value result for iterator queries +type KV struct { + Key, Value []byte +} diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 145333e486..027a2a18fe 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -15,42 +15,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -//// create command to query for all candidates -//func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { -//cmd := &cobra.Command{ -//Use: "candidates", -//Short: "Query for the set of validator-candidates pubkeys", -//RunE: func(cmd *cobra.Command, args []string) error { - -//key := stake.CandidatesKey - -//ctx := context.NewCoreContextFromViper() -//res, err := ctx.Query(key, storeName) -//if err != nil { -//return err -//} - -//// parse out the candidates -//candidates := new(stake.Candidates) -//err = cdc.UnmarshalBinary(res, candidates) -//if err != nil { -//return err -//} -//output, err := wire.MarshalJSONIndent(cdc, candidates) -//if err != nil { -//return err -//} -//fmt.Println(string(output)) -//return nil - -//// TODO output with proofs / machine parseable etc. -//}, -//} - -//cmd.Flags().AddFlagSet(fsDelegator) -//return cmd -//} - // get the command to query a candidate func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -64,9 +28,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { } key := stake.GetCandidateKey(addr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) if err != nil { return err @@ -74,10 +36,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the candidate candidate := new(stake.Candidate) - err = cdc.UnmarshalBinary(res, candidate) - if err != nil { - return err - } + cdc.MustUnmarshalBinary(res, candidate) output, err := wire.MarshalJSONIndent(cdc, candidate) if err != nil { return err @@ -93,6 +52,41 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } +// get the command to query a candidate +func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "candidates", + Short: "Query for all validator-candidate accounts", + RunE: func(cmd *cobra.Command, args []string) error { + + key := stake.CandidatesKey + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the candidates + var candidates []stake.Candidate + for _, KV := range resKVs { + var candidate stake.Candidate + cdc.MustUnmarshalBinary(KV.Value, &candidate) + candidates = append(candidates, candidate) + } + + output, err := wire.MarshalJSONIndent(cdc, candidates) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} + // get the command to query a single delegator bond func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -122,10 +116,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command // parse out the bond bond := new(stake.DelegatorBond) - err = cdc.UnmarshalBinary(res, bond) - if err != nil { - return err - } + cdc.MustUnmarshalBinary(res, bond) output, err := wire.MarshalJSONIndent(cdc, bond) if err != nil { return err diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 091701a700..d4f97fd526 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -22,6 +22,8 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { Use: "declare-candidacy", Short: "create new validator-candidate account and delegate some coins to it", RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err @@ -56,8 +58,6 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err diff --git a/x/stake/msg.go b/x/stake/msg.go index dd8d3714fd..8367058c2a 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -4,6 +4,7 @@ import ( "encoding/json" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" crypto "github.com/tendermint/go-crypto" ) @@ -45,11 +46,9 @@ func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address // get the bytes for the message signer to sign on func (msg MsgDeclareCandidacy) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b + cdc := wire.NewCodec() + wire.RegisterCrypto(cdc) + return cdc.MustMarshalBinary(msg) } // quick validity check From d464779d3416eec8be37d8d7c64427bb1824ced7 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 8 May 2018 15:55:40 -0400 Subject: [PATCH 51/77] iter->substore, enable delegator bonds query --- client/context/helpers.go | 2 +- store/iavlstore.go | 8 ++-- x/stake/client/cli/query.go | 74 ++++++++++++++++++------------------- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/client/context/helpers.go b/client/context/helpers.go index f71426f2e6..5baf4742ec 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -48,7 +48,7 @@ func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, er // Query from Tendermint with the provided storename and subspace func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KV, err error) { - resRaw, err := ctx.query(subspace, storeName, "iter") + resRaw, err := ctx.query(subspace, storeName, "subspace") if err != nil { return res, err } diff --git a/store/iavlstore.go b/store/iavlstore.go index a3a85fe69e..6109d967f7 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -176,11 +176,11 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } else { _, res.Value = tree.GetVersioned(key, height) } - case "/iter": // Get by key - key := req.Data // Data holds the key bytes - res.Key = key + case "/subspace": + subspace := req.Data + res.Key = subspace var KVs []KV - iterator := st.SubspaceIterator(key) + iterator := st.SubspaceIterator(subspace) for ; iterator.Valid(); iterator.Next() { KVs = append(KVs, KV{iterator.Key(), iterator.Value()}) } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 027a2a18fe..8a5a06a709 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -106,9 +106,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command delegator := crypto.Address(bz) key := stake.GetDelegatorBondKey(delegator, addr, cdc) - ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) if err != nil { return err @@ -133,44 +131,42 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command return cmd } -//// get the command to query all the candidates bonded to a delegator -//func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command { -//cmd := &cobra.Command{ -//Use: "delegator-candidates", -//Short: "Query all delegators bond's candidate-addresses based on delegator-address", -//RunE: func(cmd *cobra.Command, args []string) error { +// get the command to query all the candidates bonded to a delegator +func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegator-candidates", + Short: "Query all delegators bonds based on delegator-address", + RunE: func(cmd *cobra.Command, args []string) error { -//bz, err := hex.DecodeString(viper.GetString(FlagAddressDelegator)) -//if err != nil { -//return err -//} -//delegator := crypto.Address(bz) + delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + key := stake.GetDelegatorBondsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } -//key := stake.GetDelegatorBondsKey(delegator, cdc) + // parse out the candidates + var delegators []stake.DelegatorBond + for _, KV := range resKVs { + var delegator stake.DelegatorBond + cdc.MustUnmarshalBinary(KV.Value, &delegator) + delegators = append(delegators, delegator) + } -//ctx := context.NewCoreContextFromViper() + output, err := wire.MarshalJSONIndent(cdc, delegators) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil -//res, err := ctx.Query(key, storeName) -//if err != nil { -//return err -//} - -//// parse out the candidates list -//var candidates []crypto.PubKey -//err = cdc.UnmarshalBinary(res, candidates) -//if err != nil { -//return err -//} -//output, err := wire.MarshalJSONIndent(cdc, candidates) -//if err != nil { -//return err -//} -//fmt.Println(string(output)) -//return nil - -//// TODO output with proofs / machine parseable etc. -//}, -//} -//cmd.Flags().AddFlagSet(fsDelegator) -//return cmd -//} + // TODO output with proofs / machine parseable etc. + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} From 1d82cdbbbcb6bb64c3624a17aed906e02b205e87 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 8 May 2018 16:15:35 -0400 Subject: [PATCH 52/77] subspace query tests, changelog --- CHANGELOG.md | 2 ++ store/iavlstore_test.go | 48 ++++++++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b4ffba89..1ca0189666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ FEATURES: * New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag) * Initialize with genesis txs using `--gen-txs` flag * Context now has access to the application-configured logger +* Add (non-proof) subspace query helper functions +* Add more staking query functions: candidates, delegator-bonds BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index 4557dea06f..ebfe36e335 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -263,43 +263,63 @@ func TestIAVLStoreQuery(t *testing.T) { tree := iavl.NewVersionedTree(db, cacheSize) iavlStore := newIAVLStore(tree, numHistory) - k, v := []byte("wind"), []byte("blows") - k2, v2 := []byte("water"), []byte("flows") - v3 := []byte("is cold") - // k3, v3 := []byte("earth"), []byte("soes") - // k4, v4 := []byte("fire"), []byte("woes") + k1, v1 := []byte("aaa"), []byte("val1") + k2, v2 := []byte("bbb"), []byte("val2") + v3 := []byte("val3") + + ksub = []byte("w") + KVs1, KVs2 := []KV{v1, v2}, []KV{v3, v2} + valExpSub1 := cdc.MustMarshalBinary(KVs1) + valExpSub2 := cdc.MustMarshalBinary(KVs2) cid := iavlStore.Commit() ver := cid.Version - query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} + query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver} + querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver} // set data without commit, doesn't show up - iavlStore.Set(k, v) + iavlStore.Set(k1, v1) qres := iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Nil(t, qres.Value) + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Nil(t, qres.Value) // commit it, but still don't see on old version cid = iavlStore.Commit() qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Nil(t, qres.Value) + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Nil(t, qres.Value) // but yes on the new version query.Height = cid.Version qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) + + // and for the subspace + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSub1, qres.Value) // modify iavlStore.Set(k2, v2) - iavlStore.Set(k, v3) + iavlStore.Set(k1, v3) cid = iavlStore.Commit() // query will return old values, as height is fixed qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) + + // and for the subspace + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSub1, qres.Value) // update to latest in the query and we are happy query.Height = cid.Version @@ -310,10 +330,14 @@ func TestIAVLStoreQuery(t *testing.T) { qres = iavlStore.Query(query2) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Equal(t, v2, qres.Value) + // and for the subspace + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 - query0 := abci.RequestQuery{Path: "/store", Data: k} + query0 := abci.RequestQuery{Path: "/store", Data: k1} qres = iavlStore.Query(query0) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) } From 8dd30520174f519e36337587284d31e385227f53 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 8 May 2018 16:32:41 -0400 Subject: [PATCH 53/77] fix subspace query tests --- store/iavlstore_test.go | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index ebfe36e335..bbccd8ef01 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -263,12 +263,21 @@ func TestIAVLStoreQuery(t *testing.T) { tree := iavl.NewVersionedTree(db, cacheSize) iavlStore := newIAVLStore(tree, numHistory) - k1, v1 := []byte("aaa"), []byte("val1") - k2, v2 := []byte("bbb"), []byte("val2") + k1, v1 := []byte("key1"), []byte("val1") + k2, v2 := []byte("key2"), []byte("val2") v3 := []byte("val3") - ksub = []byte("w") - KVs1, KVs2 := []KV{v1, v2}, []KV{v3, v2} + ksub := []byte("key") + KVs0 := []KV{} + KVs1 := []KV{ + {k1, v1}, + {k2, v2}, + } + KVs2 := []KV{ + {k1, v3}, + {k2, v2}, + } + valExpSubEmpty := cdc.MustMarshalBinary(KVs0) valExpSub1 := cdc.MustMarshalBinary(KVs1) valExpSub2 := cdc.MustMarshalBinary(KVs2) @@ -277,12 +286,17 @@ func TestIAVLStoreQuery(t *testing.T) { query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver} querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver} - // set data without commit, doesn't show up - iavlStore.Set(k1, v1) - qres := iavlStore.Query(query) + // query subspace before anything set + qres := iavlStore.Query(querySub) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Nil(t, qres.Value) - qres = iavlStore.Query(querySub) + assert.Equal(t, valExpSubEmpty, qres.Value) + + // set data + iavlStore.Set(k1, v1) + iavlStore.Set(k2, v2) + + // set data without commit, doesn't show up + qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Nil(t, qres.Value) @@ -291,9 +305,6 @@ func TestIAVLStoreQuery(t *testing.T) { qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Nil(t, qres.Value) - qres = iavlStore.Query(querySub) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Nil(t, qres.Value) // but yes on the new version query.Height = cid.Version @@ -307,7 +318,6 @@ func TestIAVLStoreQuery(t *testing.T) { assert.Equal(t, valExpSub1, qres.Value) // modify - iavlStore.Set(k2, v2) iavlStore.Set(k1, v3) cid = iavlStore.Commit() @@ -316,11 +326,6 @@ func TestIAVLStoreQuery(t *testing.T) { assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Equal(t, v1, qres.Value) - // and for the subspace - qres = iavlStore.Query(querySub) - assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, valExpSub1, qres.Value) - // update to latest in the query and we are happy query.Height = cid.Version qres = iavlStore.Query(query) From 2cad1aab3d0e5935b9cb9cccf5672b96940dc19a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 17:17:49 -0400 Subject: [PATCH 54/77] remove governance.md --- docs/spec/governance/governance.md | 659 ----------------------------- 1 file changed, 659 deletions(-) delete mode 100644 docs/spec/governance/governance.md diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md deleted file mode 100644 index 2e40e92dd4..0000000000 --- a/docs/spec/governance/governance.md +++ /dev/null @@ -1,659 +0,0 @@ -# Governance documentation - -*Disclaimer: This is work in progress. Mechanisms are susceptible to change.* - -This document describes the high-level architecture of the governance module. The governance module allows bonded Atom holders to vote on proposals on a 1 bonded Atom 1 vote basis. - -## Design overview - -The governance process is divided in a few steps that are outlined below: - -- **Proposal submission:** Proposal is submitted to the blockchain with a deposit -- **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal -- If the proposal involves a software upgrade: - - **Signal:** Validators start signaling that they are ready to switch to the new version - - **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version - -## Proposal submission - -### Right to submit a proposal - -Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. - -### Proposal filter (minimum deposit) - -To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`. - -When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period. - -### Deposit refund - -There are two instances where Atom holders that deposited can claim back their deposit: -- If the proposal is accepted -- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore. - -In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit. - -### Proposal types - -In the initial version of the governance module, there are two types of proposal: -- `PlainTextProposal`. All the proposals that do not involve a modification of the source code go under this type. For example, an opinion poll would use a proposal of type `PlainTextProposal` -- `SoftwareUpgradeProposal`. If accepted, validators are expected to update their software in accordance with the proposal. They must do so by following a 2-steps process described in the [Software Upgrade](#software-upgrade) section below. Software upgrade roadmap may be discussed and agreed on via `PlainTextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`. - -### Proposal categories - -There are two categories of proposal: -- `Regular` -- `Urgent` - -These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section. - -## Vote - -### Participants - -*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. - -Note that some *participants* can be forbidden to vote on a proposal under a certain validator if: -- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period -- *participant* became validator after proposal entered voting period - -This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden. - -### Voting period - -Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks. - -### Option set - -The option set of a proposal refers to the set of choices a participant can choose from when casting its vote. - -The initial option set includes the following options: -- `Yes` -- `No` -- `NoWithVeto` -- `Abstain` - -`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. - -*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.* - -### Quorum - -Quorum is defined as the minimum percentage of voting power that needs to be casted on a proposal for the result to be valid. - -In the initial version of the governance module, there will be no quorum enforced by the protocol. Participation is ensured via the combination of inheritance and validator's punishment for non-voting. - -### Threshold - -Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted. - -Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes). - -`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. - -### Inheritance - -If a delegator does not vote, it will inherit its validator vote. - -- If the delegator votes before its validator, it will not inherit from the validator's vote. -- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. - -### Validator’s punishment for non-voting - -Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty. - -If a validator’s address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`. - -*Note: Need to define values for `GovernancePenalty`* - -**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it. - -### Governance key and governance address - -Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. - - -## Software Upgrade - -If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade their software to the new version that was voted. This process is divided in two steps. - -### Signal - -After a `SoftwareUpgradeProposal` is accepted, validators are expected to download and install the new version of the software while continuing to run the previous version. Once a validator has downloaded and installed the upgrade, it will start signaling to the network that it is ready to switch by including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation that we want it in the precommit?*) - -Note: There is only one signal slot per *precommit*. If several `SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will form and they will be implemented one after the other in the order that they were accepted. - -### Switch - -Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgradeProposal` is signaled, all the nodes (including validator nodes, non-validating full nodes and light-nodes) are expected to switch to the new version of the software. - -*Note: Not clear how the flip is handled programatically* - - -## Implementation - -*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ* - -### State - -#### Procedures - -`Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive. - -```Go -type Procedure struct { - VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks - MinDeposit int64 // Minimum deposit for a proposal to enter voting period. - OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} - ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 - MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months - GovernancePenalty int64 // Penalty if validator does not vote - - IsActive bool // If true, procedure is active. Only one procedure can have isActive true. -} -``` - -**Store**: -- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber` -- `ActiveProcedureNumber`: returns current procedure number - -#### Proposals - -`Proposals` are item to be voted on. - -```Go -type Proposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit - SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included - - VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached - InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0) - InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) - Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) -} -``` - -We also introduce a type `ValidatorGovInfo` - -```Go -type ValidatorGovInfo struct { - InitVotingPower int64 // Voting power of validator when proposal enters voting period - Minus int64 // Minus of validator, used to compute validator's voting power -} -``` - -**Store:** - -- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` -- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal) -- `Options`: A mapping `map[[]byte]string` of options indexed by `::` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator) -- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `:`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period. - - -#### Proposal Processing Queue - -**Store:** -- `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. - -And the pseudocode for the `ProposalProcessingQueue`: - -``` - in BeginBlock do - - checkProposal() // First call of the recursive function - - - // Recursive function. First call in BeginBlock - func checkProposal() - if (ProposalProcessingQueue.Peek() == nil) - return - - else - proposalID = ProposalProcessingQueue.Peek() - proposal = load(store, Proposals, proposalID) - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) - - // proposal was urgent and accepted under the special condition - // no punishment - - ProposalProcessingQueue.pop() - checkProposal() - - else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - for each validator in CurrentBondedValidators - validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) - - if (validatorGovInfo.InitVotingPower != nil) - // validator was bonded when vote started - - validatorOption = load(store, Options, validator.GovPubKey) - if (validatorOption == nil) - // validator did not vote - slash validator by activeProcedure.GovernancePenalty - - ProposalProcessingQueue.pop() - checkProposal() -``` - - -### Transactions - -#### Proposal Submission - -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. - -```Go -type TxGovSubmitProposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. -} -``` - -**State modifications:** -- Generate new `proposalID` -- Create new `Proposal` -- Initialise `Proposals` attributes -- Store sender's deposit in `Deposits` -- Decrease balance of sender by `InitialDeposit` -- If `MinDeposit` is reached: - - Push `proposalID` in `ProposalProcessingQueueEnd` - - Store each validator's voting power in `ValidatorGovInfos` - -A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode - -``` -// PSEUDOCODE // -// Check if TxGovSubmitProposal is valid. If it is, create proposal // - -upon receiving txGovSubmitProposal from sender do - - if !correctlyFormatted(txGovSubmitProposal) then - // check if proposal is correctly formatted. Includes fee payment. - - throw - - else - if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then - // InitialDeposit is negative or null OR sender has insufficient funds - - throw - - else - sender.AtomBalance -= txGovSubmitProposal.InitialDeposit - - proposalID = generate new proposalID - proposal = NewProposal() - - proposal.Title = txGovSubmitProposal.Title - proposal.Description = txGovSubmitProposal.Description - proposal.Type = txGovSubmitProposal.Type - proposal.Category = txGovSubmitProposal.Category - proposal.Deposit = txGovSubmitProposal.InitialDeposit - proposal.SubmitBlock = CurrentBlock - - store(Deposits, :, txGovSubmitProposal.InitialDeposit) - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then - // MinDeposit is not reached - - proposal.VotingStartBlock = -1 - proposal.InitTotalVotingPower = 0 - proposal.InitProcedureNumber = -1 - - else - // MinDeposit is reached - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator - - validatorGovInfo = NewValidatorGovInfo() - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 - - store(ValidatorGovInfos, :, validatorGovInfo) - - ProposalProcessingQueue.push(proposalID) - - store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping - return proposalID -``` - - - -#### Deposit - -Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit. - -```Go -type TxGovDeposit struct { - ProposalID int64 // ID of the proposal - Deposit int64 // Number of Atoms to add to the proposal's deposit -} -``` - -**State modifications:** -- Decrease balance of sender by `deposit` -- Initialize or increase `deposit` of sender in `Deposits` -- Increase `proposal.Deposit` by sender's `deposit` -- If `MinDeposit` is reached: - - Push `proposalID` in `ProposalProcessingQueueEnd` - - Store each validator's voting power in `ValidatorGovInfos` - -A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode. - -``` -// PSEUDOCODE // -// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached - -upon receiving txGovDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) - // deposit is negative or null OR sender has insufficient funds - - throw - - else - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit was reached - - throw - - else - if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then - // Maximum deposit period reached - - throw - - else - // sender can deposit - - sender.AtomBalance -= txGovDeposit.Deposit - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has never deposited on this proposal - - store(Deposits, :, deposit) - - else - // sender has already deposited on this proposal - - newDeposit = deposit + txGovDeposit.Deposit - store(Deposits, :, newDeposit) - - proposal.Deposit += txGovDeposit.Deposit - - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit is reached, vote opens - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator - - validatorGovInfo = NewValidatorGovInfo() - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 - - store(ValidatorGovInfos, :, validatorGovInfo) - - ProposalProcessingQueue.push(txGovDeposit.ProposalID) -``` - -#### Claiming deposit - -Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits. - -```Go - type TxGovClaimDeposit struct { - ProposalID int64 - } -``` - -**State modifications:** -If conditions are met, reimburse the deposit, i.e. -- Increase `AtomBalance` of sender by `deposit` -- Set `deposit` of sender in `DepositorsList` to 0 - -And the associated pseudocode - -``` - // PSEUDOCODE // - /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ - - upon receiving txGovClaimDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovClaimDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has not deposited on this proposal - - throw - - else - if (deposit <= 0) - // deposit has already been claimed - - throw - - else - if (proposal.VotingStartBlock <= 0) - // Vote never started - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) - // MaxDepositPeriod is not reached - - throw - - else - // MaxDepositPeriod is reached - // Set sender's deposit to 0 and refund - - store(Deposits, :, 0) - sender.AtomBalance += deposit - - else - // Vote started - - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR - ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then - - // Proposal was accepted either because - // Proposal was urgent and special condition was met - // Voting period ended and vote satisfies threshold - - store(Deposits, :, 0) - sender.AtomBalance += deposit - -``` - - -#### Vote - -Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal. - -```Go - type TxGovVote struct { - ProposalID int64 // proposalID of the proposal - Option string // option from OptionSet chosen by the voter - ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to - } -``` - -**State modifications:** -- If sender is not a validator and validator has not voted, initialize or increase minus of validator by sender's `voting power` -- If sender is not a validator and validator has voted, decrease `proposal.Votes['validatorOption']` by sender's `voting power` -- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power` -- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0) - -Votes need to be tied to a validator in order to compute validator's voting power. If a delegator is bonded to multiple validators, it will have to send one transaction per validator (the UI should facilitate this so that multiple transactions can be sent in one "vote flow"). -If the sender is the validator itself, then it will input its own GovernancePubKey as `ValidatorPubKey` - - - -Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled: - -``` - // PSEUDOCODE // - // Check if TxGovVote is valid. If it is, count vote// - - upon receiving txGovVote from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened - validator = load(store, Validators, txGovVote.ValidatorPubKey) - - if !initProcedure.OptionSet.includes(txGovVote.Option) OR - (validator == nil) then - - // Throws if - // Option is not in Option Set of procedure that was active when vote opened OR if - // ValidatorPubKey is not the GovPubKey of a current validator - - throw - - else - option = load(store, Options, ::) - - if (option != nil) - // sender has already voted with the Atoms bonded to ValidatorPubKey - - throw - - else - if (proposal.VotingStartBlock < 0) OR - (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR - (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then - - // Throws if - // Vote has not started OR if - // Vote had ended OR if - // sender bonded Atoms to ValidatorPubKey after start of vote OR if - // sender unbonded Atoms from ValidatorPubKey after start of vote OR if - // proposal is urgent and special condition is met, i.e. proposal is accepted and closed - - throw - - else - validatorGovInfo = load(store, ValidatorGovInfos, :) - - if (validatorGovInfo == nil) - // validator became validator after proposal entered voting period - - throw - - else - // sender can vote, check if sender == validator and store sender's option in Options - - store(Options, ::, txGovVote.Option) - - if (sender != validator.GovPubKey) - // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey - - if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then - // check in Staking module - - throw - - else - validatorOption = load(store, Options, :::, validatorGovInfo) - - else - // Validator has already voted - // Reduce votes of option chosen by validator by sender's bonded Amount - - proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - // increase votes of option chosen by sender by bonded Amount - proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - else - // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey - // i.e. sender == validator - - proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - - -``` - - -## Future improvements (not in scope for MVP) - -The current documentation only describes the minimum viable product for the governance module. Future improvements may include: - -- **`BountyProposals`:** If accepted, a `BountyProposal` creates an open bounty. The `BountyProposal` specifies how many Atoms will be given upon completion. These Atoms will be taken from the `reserve pool`. After a `BountyProposal` is accepted by governance, anybody can submit a `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a `BountyProposal` is accepted, the corresponding funds in the `reserve pool` are locked so that payment can always be honored. In order to link a `SoftwareUpgradeProposal` to an open bounty, the submitter of the `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter. -- **Complex delegation:** Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote. -- **`ParameterProposals` and `WhitelistProposals`:** These proposals would automatically change pre-defined parameters and whitelists. Upon acceptance, these proposals would not require validators to do the signal and switch process. -- **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to reward third party auditors. From 111e7ecd5263022e499ef28c1f6f199e5835aadd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 17:35:24 -0400 Subject: [PATCH 55/77] spec: bust up staking into files --- docs/spec/staking/README.md | 37 ++ ...efinitions and examples.md => overview.md} | 37 +- docs/spec/staking/spec-technical.md | 622 ------------------ docs/spec/staking/state.md | 196 ++++++ docs/spec/staking/time.md | 90 +++ docs/spec/staking/transactions.md | 304 +++++++++ 6 files changed, 660 insertions(+), 626 deletions(-) create mode 100644 docs/spec/staking/README.md rename docs/spec/staking/{definitions and examples.md => overview.md} (83%) delete mode 100644 docs/spec/staking/spec-technical.md create mode 100644 docs/spec/staking/state.md create mode 100644 docs/spec/staking/time.md create mode 100644 docs/spec/staking/transactions.md diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md new file mode 100644 index 0000000000..37e07b70bb --- /dev/null +++ b/docs/spec/staking/README.md @@ -0,0 +1,37 @@ +# Staking module specification + +## Abstract + +This paper specifies the Staking module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016. + +The module enables Cosmos-SDK based blockchain to support an advanced Proof-of-Stake system. In this system, holders of the native staking token of the chain can become candidate validators and can delegate tokens to candidate validators, ultimately determining the effective validator set for the system. + +The module currently supports the following features: + +- TODO +- **Declare Candidacy:** +- **Edit Candidacy:** +- **Delegate:** +- **Unbond:** + +This module will be used in the Cosmos Hub, the first Hub in the Cosmos network. + +## Contents + +The following specification uses *Atom* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *Atom* with the native staking token of the chain. + +1. **[Design overview](overview.md)** +2. **Implementation** + 1. **[State](state.md)** + 1. Global State + 2. Validator Candidates + 3. Delegator Bonds + 4. Unbond and Rebond Queue + 2. **[Transactions](transactions.md)** + 1. Declare Candidacy + 2. Edit Candidacy + 3. Delegate + 4. Unbond + 5. Redelegate + 6. ProveLive +3. **[Future improvements](future_improvements.md)** diff --git a/docs/spec/staking/definitions and examples.md b/docs/spec/staking/overview.md similarity index 83% rename from docs/spec/staking/definitions and examples.md rename to docs/spec/staking/overview.md index ba4c1563e2..a202fbc119 100644 --- a/docs/spec/staking/definitions and examples.md +++ b/docs/spec/staking/overview.md @@ -1,5 +1,38 @@ # Staking Module +## Overview + +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that +serves as a backbone of the Cosmos ecosystem. It is operated and secured by an +open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in +the process of exchanging protocol messages in the production of each block. To +avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the +validator's private key, and this is a basis for Tendermint strict +accountability that allows punishing misbehaving validators by slashing +(burning) their bonded Atoms. On the other hand, validators are rewarded for +their service of securing blockchain network by the inflationary provisions and +transactions fees. This incentives correct behavior of the validators and +provides the economic security of the network. + +The native token of the Cosmos Hub is called Atom; becoming a validator of the +Cosmos Hub requires holding Atoms. However, not all Atom holders are validators +of the Cosmos Hub. More precisely, there is a selection process that determines +the validator set as a subset of all validator candidates (Atom holders that +wants to become a validator). The other option for Atom holder is to delegate +their atoms to validators, i.e., being a delegator. A delegator is an Atom +holder that has bonded its Atoms by delegating it to a validator (or validator +candidate). By bonding Atoms to secure the network (and taking a risk of being +slashed in case of misbehaviour), a user is rewarded with inflationary +provisions and transaction fees proportional to the amount of its bonded Atoms. +The Cosmos Hub is designed to efficiently facilitate a small numbers of +validators (hundreds), and large numbers of delegators (tens of thousands). +More precisely, it is the role of the Staking module of the Cosmos Hub to +support various staking functionality including validator set selection, +delegating, bonding and withdrawing Atoms, and the distribution of inflationary +provisions and transaction fees. + ## Basic Terms and Definitions * Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system @@ -179,7 +212,3 @@ provisions cycle: ```go GlobalState.BondedPool += provisionTokensHourly ``` - - - - diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md deleted file mode 100644 index c556159369..0000000000 --- a/docs/spec/staking/spec-technical.md +++ /dev/null @@ -1,622 +0,0 @@ -# Staking Module - -## Overview - -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that -serves as a backbone of the Cosmos ecosystem. It is operated and secured by an -open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in -the process of exchanging protocol messages in the production of each block. To -avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the -validator's private key, and this is a basis for Tendermint strict -accountability that allows punishing misbehaving validators by slashing -(burning) their bonded Atoms. On the other hand, validators are rewarded for -their service of securing blockchain network by the inflationary provisions and -transactions fees. This incentives correct behavior of the validators and -provides the economic security of the network. - -The native token of the Cosmos Hub is called Atom; becoming a validator of the -Cosmos Hub requires holding Atoms. However, not all Atom holders are validators -of the Cosmos Hub. More precisely, there is a selection process that determines -the validator set as a subset of all validator candidates (Atom holders that -wants to become a validator). The other option for Atom holder is to delegate -their atoms to validators, i.e., being a delegator. A delegator is an Atom -holder that has bonded its Atoms by delegating it to a validator (or validator -candidate). By bonding Atoms to secure the network (and taking a risk of being -slashed in case of misbehaviour), a user is rewarded with inflationary -provisions and transaction fees proportional to the amount of its bonded Atoms. -The Cosmos Hub is designed to efficiently facilitate a small numbers of -validators (hundreds), and large numbers of delegators (tens of thousands). -More precisely, it is the role of the Staking module of the Cosmos Hub to -support various staking functionality including validator set selection, -delegating, bonding and withdrawing Atoms, and the distribution of inflationary -provisions and transaction fees. - -## State - -The staking module persists the following information to the store: -* `GlobalState`, a struct describing the global pools, inflation, and - fees -* `ValidatorCandidates: => `, a map of all candidates (including current validators) in the store, -indexed by their public key and shares in the global pool. -* `DelegatorBonds: < delegator-address | candidate-pubkey > => `. a map of all delegations by a delegator to a candidate, -indexed by delegator address and candidate pubkey. - public key -* `UnbondQueue`, the queue of unbonding delegations -* `RedelegateQueue`, the queue of re-delegations - -### Global State - -The GlobalState contains information about the total amount of Atoms, the -global bonded/unbonded position, the Atom inflation rate, and the fees. - -`Params` is global data structure that stores system parameters and defines overall functioning of the -module. - -``` go -type GlobalState struct { - TotalSupply int64 // total supply of Atoms - BondedPool int64 // reserve of bonded tokens - BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonding tokens held with candidates - UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool - InflationLastTime int64 // timestamp of last processing of inflation - Inflation rational.Rat // current annual inflation rate - DateLastCommissionReset int64 // unix timestamp for last commission accounting reset - FeePool coin.Coins // fee pool for all the fee shares which have already been distributed - ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use - Adjustment rational.Rat // Adjustment factor for calculating global fee accum -} - -type Params struct { - HoldBonded Address // account where all bonded coins are held - HoldUnbonding Address // account where all delegated but unbonding coins are held - - InflationRateChange rational.Rational // maximum annual change in inflation rate - InflationMax rational.Rational // maximum inflation rate - InflationMin rational.Rational // minimum inflation rate - GoalBonded rational.Rational // Goal of percent bonded atoms - ReserveTax rational.Rational // Tax collected on all fees - - MaxVals uint16 // maximum number of validators - AllowedBondDenom string // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 - GasEditCandidacy int64 - GasDelegate int64 - GasRedelegate int64 - GasUnbond int64 -} -``` - -### Candidate - -The `Candidate` holds the current state and some historical -actions of validators or candidate-validators. - -``` go -type Candidate struct { - Status CandidateStatus - ConsensusPubKey crypto.PubKey - GovernancePubKey crypto.PubKey - Owner crypto.Address - GlobalStakeShares rational.Rat - IssuedDelegatorShares rational.Rat - RedelegatingShares rational.Rat - VotingPower rational.Rat - Commission rational.Rat - CommissionMax rational.Rat - CommissionChangeRate rational.Rat - CommissionChangeToday rational.Rat - ProposerRewardPool coin.Coins - Adjustment rational.Rat - Description Description -} - -type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string -} -``` - -Candidate parameters are described: -* Status: it can be Bonded (active validator), Unbonding (validator candidate) - or Revoked -* ConsensusPubKey: candidate public key that is used strictly for participating in - consensus -* GovernancePubKey: public key used by the validator for governance voting -* Owner: Address that is allowed to unbond coins. -* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if - `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` - otherwise -* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators - (which includes the candidate's self-bond); a delegator share represents - their stake in the Candidate's `GlobalStakeShares` -* RedelegatingShares: The portion of `IssuedDelegatorShares` which are - currently re-delegating to a new validator -* VotingPower: Proportional to the amount of bonded tokens which the validator - has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` -* Commission: The commission rate of fees charged to any delegators -* CommissionMax: The maximum commission rate this candidate can charge each - day from the date `GlobalState.DateLastCommissionReset` -* CommissionChangeRate: The maximum daily increase of the candidate commission -* CommissionChangeToday: Counter for the amount of change to commission rate - which has occurred today, reset on the first block of each day (UTC time) -* ProposerRewardPool: reward pool for extra fees collected when this candidate - is the proposer of a block -* Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` -* Description - * Name: moniker - * DateBonded: date determined which the validator was bonded - * Identity: optional field to provide a signature which verifies the - validators identity (ex. UPort or Keybase) - * Website: optional website link - * Details: optional details - -### DelegatorBond - -Atom holders may delegate coins to candidates; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one -delegator, and is associated with the shares for one candidate. The sender of -the transaction is the owner of the bond. - -``` go -type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat - AdjustmentFeePool coin.Coins - AdjustmentRewardPool coin.Coins -} -``` - -Description: -* Candidate: the public key of the validator candidate: bonding too -* Shares: the number of delegator shares received from the validator candidate -* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` -* AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Candidate.ProposerRewardPool` - - -### QueueElem - -The Unbonding and re-delegation process is implemented using the ordered queue -data structure. All queue elements share a common structure: - -```golang -type QueueElem struct { - Candidate crypto.PubKey - InitTime int64 // when the element was added to the queue -} -``` - -The queue is ordered so the next element to unbond/re-delegate is at the head. -Every tick the head of the queue is checked and if the unbonding period has -passed since `InitTime`, the final settlement of the unbonding is started or -re-delegation is executed, and the element is popped from the queue. Each -`QueueElem` is persisted in the store until it is popped from the queue. - -### QueueElemUnbondDelegation - -QueueElemUnbondDelegation structure is used in the unbonding queue. - -```golang -type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio -} -``` - -### QueueElemReDelegate - -QueueElemReDelegate structure is used in the re-delegation queue. - -```golang -type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to - Shares rational.Rat // amount of shares which are unbonding - NewCandidate crypto.PubKey // validator to bond to after unbond -} -``` - -### Transaction Overview - -Available Transactions: -* TxDeclareCandidacy -* TxEditCandidacy -* TxDelegate -* TxUnbond -* TxRedelegate -* TxLivelinessCheck -* TxProveLive - -## Transaction processing - -In this section we describe the processing of the transactions and the -corresponding updates to the global state. In the following text we will use -`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a -reference to the queue of unbond delegations, `reDelegationQueue` is the -reference for the queue of redelegations. We use `tx` to denote a -reference to a transaction that is being processed, and `sender` to denote the -address of the sender of the transaction. We use function -`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, -and `saveCandidate(store, candidate)` to save it. Similarly, we use -`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the -key (sender and PubKey) from the store, and -`saveDelegatorBond(store, sender, bond)` to save it. -`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the -store. - -### TxDeclareCandidacy - -A validator candidacy is declared using the `TxDeclareCandidacy` transaction. - -```golang -type TxDeclareCandidacy struct { - ConsensusPubKey crypto.PubKey - Amount coin.Coin - GovernancePubKey crypto.PubKey - Commission rational.Rat - CommissionMax int64 - CommissionMaxChange int64 - Description Description -} - -declareCandidacy(tx TxDeclareCandidacy): - candidate = loadCandidate(store, tx.PubKey) - if candidate != nil return // candidate with that public key already exists - - candidate = NewCandidate(tx.PubKey) - candidate.Status = Unbonded - candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero - init commision related fields based on the values from tx - candidate.ProposerRewardPool = Coin(0) - candidate.Description = tx.Description - - saveCandidate(store, candidate) - - txDelegate = TxDelegate(tx.PubKey, tx.Amount) - return delegateWithCandidate(txDelegate, candidate) - -// see delegateWithCandidate function in [TxDelegate](TxDelegate) -``` - -### TxEditCandidacy - -If either the `Description` (excluding `DateBonded` which is constant), -`Commission`, or the `GovernancePubKey` need to be updated, the -`TxEditCandidacy` transaction should be sent from the owner account: - -```golang -type TxEditCandidacy struct { - GovernancePubKey crypto.PubKey - Commission int64 - Description Description -} - -editCandidacy(tx TxEditCandidacy): - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Revoked return - - if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 candidate.Commission = tx.Commission - if tx.Description != nil candidate.Description = tx.Description - - saveCandidate(store, candidate) - return -``` - -### TxDelegate - -Delegator bonds are created using the `TxDelegate` transaction. Within this -transaction the delegator provides an amount of coins, and in return receives -some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. - -```golang -type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin -} - -delegate(tx TxDelegate): - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return - return delegateWithCandidate(tx, candidate) - -delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked return - - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded - - err = transfer(sender, poolAccount, tx.Amount) - if err != nil return - - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) - - issuedDelegatorShares = addTokens(tx.Amount, candidate) - bond.Shares += issuedDelegatorShares - - saveCandidate(store, candidate) - saveDelegatorBond(store, sender, bond) - saveGlobalState(store, gs) - return - -addTokens(amount coin.Coin, candidate Candidate): - if candidate.Status == Bonded - gs.BondedPool += amount - issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - else - gs.UnbondedPool += amount - issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares += issuedShares - - if candidate.IssuedDelegatorShares.IsZero() - exRate = rational.One - else - exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - - issuedDelegatorShares = issuedShares / exRate - candidate.IssuedDelegatorShares += issuedDelegatorShares - return issuedDelegatorShares - -exchangeRate(shares rational.Rat, tokenAmount int64): - if shares.IsZero() then return rational.One - return tokenAmount / shares - -``` - -### TxUnbond - -Delegator unbonding is defined with the following transaction: - -```golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat -} - -unbond(tx TxUnbond): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil return - if bond.Shares < tx.Shares return - - bond.Shares -= tx.Shares - - candidate = loadCandidate(store, tx.PubKey) - - revokeCandidacy = false - if bond.Shares.IsZero() - if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) - else - saveDelegatorBond(store, sender, bond) - - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded - - returnedCoins = removeShares(candidate, shares) - - unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) - unbondDelegationQueue.add(unbondDelegationElem) - - transfer(poolAccount, unbondingPoolAddress, returnCoins) - - if revokeCandidacy - if candidate.Status == Bonded then bondedToUnbondedPool(candidate) - candidate.Status = Revoked - - if candidate.IssuedDelegatorShares.IsZero() - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) - - saveGlobalState(store, gs) - return - -removeShares(candidate Candidate, shares rational.Rat): - globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares - - if candidate.Status == Bonded - gs.BondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove - gs.BondedPool -= removedTokens - else - gs.UnbondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove - gs.UnbondedPool -= removedTokens - - candidate.GlobalStakeShares -= removedTokens - candidate.IssuedDelegatorShares -= shares - return returnedCoins - -delegatorShareExRate(candidate Candidate): - if candidate.IssuedDelegatorShares.IsZero() then return rational.One - return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - -bondedToUnbondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares - gs.BondedShares -= candidate.GlobalStakeShares - gs.BondedPool -= removedTokens - - gs.UnbondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Unbonded - - return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) -``` - -### TxRedelegate - -The re-delegation command allows delegators to switch validators while still -receiving equal reward to as if they had never unbonded. - -```golang -type TxRedelegate struct { - PubKeyFrom crypto.PubKey - PubKeyTo crypto.PubKey - Shares rational.Rat -} - -redelegate(tx TxRedelegate): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return - - if bond.Shares < tx.Shares return - candidate = loadCandidate(store, tx.PubKeyFrom) - if candidate == nil return - - candidate.RedelegatingShares += tx.Shares - reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) - redelegationQueue.add(reDelegationElem) - return -``` - -### TxLivelinessCheck - -Liveliness issues are calculated by keeping track of the block precommits in -the block header. A queue is persisted which contains the block headers from -all recent blocks for the duration of the unbonding period. A validator is -defined as having livliness issues if they have not been included in more than -33% of the blocks over: -* The most recent 24 Hours if they have >= 20% of global stake -* The most recent week if they have = 0% of global stake -* Linear interpolation of the above two scenarios - -Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is -submitted. - -```golang -type TxLivelinessCheck struct { - PubKey crypto.PubKey - RewardAccount Addresss -} -``` - -If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the -liveliness punishment is provided as a reward to `RewardAccount`. - -### TxProveLive - -If the validator was kicked for liveliness issues and is able to regain -liveliness then all delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. Regaining liveliness is demonstrated -by sending in a `TxProveLive` transaction: - -```golang -type TxProveLive struct { - PubKey crypto.PubKey -} -``` - -### End of block handling - -```golang -tick(ctx Context): - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - - time = ctx.Time() - if time > gs.InflationLastTime + ProvisionTimeout - gs.InflationLastTime = time - gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) - - provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) - - gs.BondedPool += provisions - gs.TotalSupply += provisions - - saveGlobalState(store, gs) - - if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) - unbondDelegationQueue.remove(elem) - - if time > reDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - candidate = getCandidate(store, elem.PubKey) - returnedCoins = removeShares(candidate, elem.Shares) - candidate.RedelegatingShares -= elem.Shares - delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) - reDelegationQueue.remove(elem) - - return UpdateValidatorSet() - -nextInflation(hrsPerYr rational.Rat): - if gs.TotalSupply > 0 - bondedRatio = gs.BondedPool / gs.TotalSupply - else - bondedRation = 0 - - inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange - inflationRateChange = inflationRateChangePerYear / hrsPerYr - - inflation = gs.Inflation + inflationRateChange - if inflation > params.InflationMax then inflation = params.InflationMax - - if inflation < params.InflationMin then inflation = params.InflationMin - - return inflation - -UpdateValidatorSet(): - candidates = loadCandidates(store) - - v1 = candidates.Validators() - v2 = updateVotingPower(candidates).Validators() - - change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets - return change - -updateVotingPower(candidates Candidates): - foreach candidate in candidates do - candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) - - candidates.Sort() - - foreach candidate in candidates do - if candidate is not in the first params.MaxVals - candidate.VotingPower = rational.Zero - if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) - - else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) - - saveCandidate(store, c) - - return candidates - -unbondedToBondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares - gs.UnbondedShares -= candidate.GlobalStakeShares - gs.UnbondedPool -= removedTokens - - gs.BondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Bonded - - return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) -``` diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md new file mode 100644 index 0000000000..73ce1dde3a --- /dev/null +++ b/docs/spec/staking/state.md @@ -0,0 +1,196 @@ + +## State + +The staking module persists the following information to the store: +* `GlobalState`, a struct describing the global pools, inflation, and + fees +* `ValidatorCandidates: => `, a map of all candidates (including current validators) in the store, +indexed by their public key and shares in the global pool. +* `DelegatorBonds: < delegator-address | candidate-pubkey > => `. a map of all delegations by a delegator to a candidate, +indexed by delegator address and candidate pubkey. + public key +* `UnbondQueue`, the queue of unbonding delegations +* `RedelegateQueue`, the queue of re-delegations + +### Global State + +The GlobalState contains information about the total amount of Atoms, the +global bonded/unbonded position, the Atom inflation rate, and the fees. + +`Params` is global data structure that stores system parameters and defines overall functioning of the +module. + +``` go +type GlobalState struct { + TotalSupply int64 // total supply of Atoms + BondedPool int64 // reserve of bonded tokens + BondedShares rational.Rat // sum of all shares distributed for the BondedPool + UnbondedPool int64 // reserve of unbonding tokens held with candidates + UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool + InflationLastTime int64 // timestamp of last processing of inflation + Inflation rational.Rat // current annual inflation rate + DateLastCommissionReset int64 // unix timestamp for last commission accounting reset + FeePool coin.Coins // fee pool for all the fee shares which have already been distributed + ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use + Adjustment rational.Rat // Adjustment factor for calculating global fee accum +} + +type Params struct { + HoldBonded Address // account where all bonded coins are held + HoldUnbonding Address // account where all delegated but unbonding coins are held + + InflationRateChange rational.Rational // maximum annual change in inflation rate + InflationMax rational.Rational // maximum inflation rate + InflationMin rational.Rational // minimum inflation rate + GoalBonded rational.Rational // Goal of percent bonded atoms + ReserveTax rational.Rational // Tax collected on all fees + + MaxVals uint16 // maximum number of validators + AllowedBondDenom string // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 + GasEditCandidacy int64 + GasDelegate int64 + GasRedelegate int64 + GasUnbond int64 +} +``` + +### Candidate + +The `Candidate` holds the current state and some historical +actions of validators or candidate-validators. + +``` go +type Candidate struct { + Status CandidateStatus + ConsensusPubKey crypto.PubKey + GovernancePubKey crypto.PubKey + Owner crypto.Address + GlobalStakeShares rational.Rat + IssuedDelegatorShares rational.Rat + RedelegatingShares rational.Rat + VotingPower rational.Rat + Commission rational.Rat + CommissionMax rational.Rat + CommissionChangeRate rational.Rat + CommissionChangeToday rational.Rat + ProposerRewardPool coin.Coins + Adjustment rational.Rat + Description Description +} + +type Description struct { + Name string + DateBonded string + Identity string + Website string + Details string +} +``` + +Candidate parameters are described: +* Status: it can be Bonded (active validator), Unbonding (validator candidate) + or Revoked +* ConsensusPubKey: candidate public key that is used strictly for participating in + consensus +* GovernancePubKey: public key used by the validator for governance voting +* Owner: Address that is allowed to unbond coins. +* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` + otherwise +* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators + (which includes the candidate's self-bond); a delegator share represents + their stake in the Candidate's `GlobalStakeShares` +* RedelegatingShares: The portion of `IssuedDelegatorShares` which are + currently re-delegating to a new validator +* VotingPower: Proportional to the amount of bonded tokens which the validator + has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` +* Commission: The commission rate of fees charged to any delegators +* CommissionMax: The maximum commission rate this candidate can charge each + day from the date `GlobalState.DateLastCommissionReset` +* CommissionChangeRate: The maximum daily increase of the candidate commission +* CommissionChangeToday: Counter for the amount of change to commission rate + which has occurred today, reset on the first block of each day (UTC time) +* ProposerRewardPool: reward pool for extra fees collected when this candidate + is the proposer of a block +* Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` +* Description + * Name: moniker + * DateBonded: date determined which the validator was bonded + * Identity: optional field to provide a signature which verifies the + validators identity (ex. UPort or Keybase) + * Website: optional website link + * Details: optional details + +### DelegatorBond + +Atom holders may delegate coins to candidates; under this circumstance their +funds are held in a `DelegatorBond` data structure. It is owned by one +delegator, and is associated with the shares for one candidate. The sender of +the transaction is the owner of the bond. + +``` go +type DelegatorBond struct { + Candidate crypto.PubKey + Shares rational.Rat + AdjustmentFeePool coin.Coins + AdjustmentRewardPool coin.Coins +} +``` + +Description: +* Candidate: the public key of the validator candidate: bonding too +* Shares: the number of delegator shares received from the validator candidate +* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` +* AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Candidate.ProposerRewardPool` + + +### QueueElem + +The Unbonding and re-delegation process is implemented using the ordered queue +data structure. All queue elements share a common structure: + +```golang +type QueueElem struct { + Candidate crypto.PubKey + InitTime int64 // when the element was added to the queue +} +``` + +The queue is ordered so the next element to unbond/re-delegate is at the head. +Every tick the head of the queue is checked and if the unbonding period has +passed since `InitTime`, the final settlement of the unbonding is started or +re-delegation is executed, and the element is popped from the queue. Each +`QueueElem` is persisted in the store until it is popped from the queue. + +### QueueElemUnbondDelegation + +QueueElemUnbondDelegation structure is used in the unbonding queue. + +```golang +type QueueElemUnbondDelegation struct { + QueueElem + Payout Address // account to pay out to + Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio +} +``` + +### QueueElemReDelegate + +QueueElemReDelegate structure is used in the re-delegation queue. + +```golang +type QueueElemReDelegate struct { + QueueElem + Payout Address // account to pay out to + Shares rational.Rat // amount of shares which are unbonding + NewCandidate crypto.PubKey // validator to bond to after unbond +} +``` + diff --git a/docs/spec/staking/time.md b/docs/spec/staking/time.md new file mode 100644 index 0000000000..45f40a0c3b --- /dev/null +++ b/docs/spec/staking/time.md @@ -0,0 +1,90 @@ + +### End of block handling + +```golang +tick(ctx Context): + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + + time = ctx.Time() + if time > gs.InflationLastTime + ProvisionTimeout + gs.InflationLastTime = time + gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) + + provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) + + gs.BondedPool += provisions + gs.TotalSupply += provisions + + saveGlobalState(store, gs) + + if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) + unbondDelegationQueue.remove(elem) + + if time > reDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + candidate = getCandidate(store, elem.PubKey) + returnedCoins = removeShares(candidate, elem.Shares) + candidate.RedelegatingShares -= elem.Shares + delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) + reDelegationQueue.remove(elem) + + return UpdateValidatorSet() + +nextInflation(hrsPerYr rational.Rat): + if gs.TotalSupply > 0 + bondedRatio = gs.BondedPool / gs.TotalSupply + else + bondedRation = 0 + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr + + inflation = gs.Inflation + inflationRateChange + if inflation > params.InflationMax then inflation = params.InflationMax + + if inflation < params.InflationMin then inflation = params.InflationMin + + return inflation + +UpdateValidatorSet(): + candidates = loadCandidates(store) + + v1 = candidates.Validators() + v2 = updateVotingPower(candidates).Validators() + + change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets + return change + +updateVotingPower(candidates Candidates): + foreach candidate in candidates do + candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) + + candidates.Sort() + + foreach candidate in candidates do + if candidate is not in the first params.MaxVals + candidate.VotingPower = rational.Zero + if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) + + else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) + + saveCandidate(store, c) + + return candidates + +unbondedToBondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares + gs.UnbondedShares -= candidate.GlobalStakeShares + gs.UnbondedPool -= removedTokens + + gs.BondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Bonded + + return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) +``` diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md new file mode 100644 index 0000000000..ceb5e3be8e --- /dev/null +++ b/docs/spec/staking/transactions.md @@ -0,0 +1,304 @@ + +### Transaction Overview + +Available Transactions: +* TxDeclareCandidacy +* TxEditCandidacy +* TxDelegate +* TxUnbond +* TxRedelegate +* TxLivelinessCheck +* TxProveLive + +## Transaction processing + +In this section we describe the processing of the transactions and the +corresponding updates to the global state. In the following text we will use +`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a +reference to the queue of unbond delegations, `reDelegationQueue` is the +reference for the queue of redelegations. We use `tx` to denote a +reference to a transaction that is being processed, and `sender` to denote the +address of the sender of the transaction. We use function +`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, +and `saveCandidate(store, candidate)` to save it. Similarly, we use +`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the +key (sender and PubKey) from the store, and +`saveDelegatorBond(store, sender, bond)` to save it. +`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the +store. + +### TxDeclareCandidacy + +A validator candidacy is declared using the `TxDeclareCandidacy` transaction. + +```golang +type TxDeclareCandidacy struct { + ConsensusPubKey crypto.PubKey + Amount coin.Coin + GovernancePubKey crypto.PubKey + Commission rational.Rat + CommissionMax int64 + CommissionMaxChange int64 + Description Description +} + +declareCandidacy(tx TxDeclareCandidacy): + candidate = loadCandidate(store, tx.PubKey) + if candidate != nil return // candidate with that public key already exists + + candidate = NewCandidate(tx.PubKey) + candidate.Status = Unbonded + candidate.Owner = sender + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero + init commision related fields based on the values from tx + candidate.ProposerRewardPool = Coin(0) + candidate.Description = tx.Description + + saveCandidate(store, candidate) + + txDelegate = TxDelegate(tx.PubKey, tx.Amount) + return delegateWithCandidate(txDelegate, candidate) + +// see delegateWithCandidate function in [TxDelegate](TxDelegate) +``` + +### TxEditCandidacy + +If either the `Description` (excluding `DateBonded` which is constant), +`Commission`, or the `GovernancePubKey` need to be updated, the +`TxEditCandidacy` transaction should be sent from the owner account: + +```golang +type TxEditCandidacy struct { + GovernancePubKey crypto.PubKey + Commission int64 + Description Description +} + +editCandidacy(tx TxEditCandidacy): + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil or candidate.Status == Revoked return + + if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 candidate.Commission = tx.Commission + if tx.Description != nil candidate.Description = tx.Description + + saveCandidate(store, candidate) + return +``` + +### TxDelegate + +Delegator bonds are created using the `TxDelegate` transaction. Within this +transaction the delegator provides an amount of coins, and in return receives +some amount of candidate's delegator shares that are assigned to +`DelegatorBond.Shares`. + +```golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin +} + +delegate(tx TxDelegate): + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil return + return delegateWithCandidate(tx, candidate) + +delegateWithCandidate(tx TxDelegate, candidate Candidate): + if candidate.Status == Revoked return + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + err = transfer(sender, poolAccount, tx.Amount) + if err != nil return + + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) + + issuedDelegatorShares = addTokens(tx.Amount, candidate) + bond.Shares += issuedDelegatorShares + + saveCandidate(store, candidate) + saveDelegatorBond(store, sender, bond) + saveGlobalState(store, gs) + return + +addTokens(amount coin.Coin, candidate Candidate): + if candidate.Status == Bonded + gs.BondedPool += amount + issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares + else + gs.UnbondedPool += amount + issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares += issuedShares + + if candidate.IssuedDelegatorShares.IsZero() + exRate = rational.One + else + exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + + issuedDelegatorShares = issuedShares / exRate + candidate.IssuedDelegatorShares += issuedDelegatorShares + return issuedDelegatorShares + +exchangeRate(shares rational.Rat, tokenAmount int64): + if shares.IsZero() then return rational.One + return tokenAmount / shares + +``` + +### TxUnbond + +Delegator unbonding is defined with the following transaction: + +```golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat +} + +unbond(tx TxUnbond): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil return + if bond.Shares < tx.Shares return + + bond.Shares -= tx.Shares + + candidate = loadCandidate(store, tx.PubKey) + + revokeCandidacy = false + if bond.Shares.IsZero() + if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) + else + saveDelegatorBond(store, sender, bond) + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + returnedCoins = removeShares(candidate, shares) + + unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) + unbondDelegationQueue.add(unbondDelegationElem) + + transfer(poolAccount, unbondingPoolAddress, returnCoins) + + if revokeCandidacy + if candidate.Status == Bonded then bondedToUnbondedPool(candidate) + candidate.Status = Revoked + + if candidate.IssuedDelegatorShares.IsZero() + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) + + saveGlobalState(store, gs) + return + +removeShares(candidate Candidate, shares rational.Rat): + globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + + if candidate.Status == Bonded + gs.BondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove + gs.BondedPool -= removedTokens + else + gs.UnbondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove + gs.UnbondedPool -= removedTokens + + candidate.GlobalStakeShares -= removedTokens + candidate.IssuedDelegatorShares -= shares + return returnedCoins + +delegatorShareExRate(candidate Candidate): + if candidate.IssuedDelegatorShares.IsZero() then return rational.One + return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + +bondedToUnbondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares + gs.BondedShares -= candidate.GlobalStakeShares + gs.BondedPool -= removedTokens + + gs.UnbondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Unbonded + + return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) +``` + +### TxRedelegate + +The re-delegation command allows delegators to switch validators while still +receiving equal reward to as if they had never unbonded. + +```golang +type TxRedelegate struct { + PubKeyFrom crypto.PubKey + PubKeyTo crypto.PubKey + Shares rational.Rat +} + +redelegate(tx TxRedelegate): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then return + + if bond.Shares < tx.Shares return + candidate = loadCandidate(store, tx.PubKeyFrom) + if candidate == nil return + + candidate.RedelegatingShares += tx.Shares + reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) + redelegationQueue.add(reDelegationElem) + return +``` + +### TxLivelinessCheck + +Liveliness issues are calculated by keeping track of the block precommits in +the block header. A queue is persisted which contains the block headers from +all recent blocks for the duration of the unbonding period. A validator is +defined as having livliness issues if they have not been included in more than +33% of the blocks over: +* The most recent 24 Hours if they have >= 20% of global stake +* The most recent week if they have = 0% of global stake +* Linear interpolation of the above two scenarios + +Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is +submitted. + +```golang +type TxLivelinessCheck struct { + PubKey crypto.PubKey + RewardAccount Addresss +} +``` + +If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the +liveliness punishment is provided as a reward to `RewardAccount`. + +### TxProveLive + +If the validator was kicked for liveliness issues and is able to regain +liveliness then all delegators in the temporary unbonding pool which have not +transacted to move will be bonded back to the now-live validator and begin to +once again collect provisions and rewards. Regaining liveliness is demonstrated +by sending in a `TxProveLive` transaction: + +```golang +type TxProveLive struct { + PubKey crypto.PubKey +} +``` + From b8b200ac34e31b58b35e6ab4675ea4d6f0cd968b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 17:45:05 -0400 Subject: [PATCH 56/77] consolidate into valset-changes.md --- docs/spec/staking/README.md | 4 + docs/spec/staking/slashing.md | 79 ----------------- docs/spec/staking/transactions.md | 39 ++------- .../staking/{time.md => valset-changes.md} | 86 ++++++++++++++++++- 4 files changed, 98 insertions(+), 110 deletions(-) delete mode 100644 docs/spec/staking/slashing.md rename docs/spec/staking/{time.md => valset-changes.md} (51%) diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md index 37e07b70bb..8ec7b41cfe 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/README.md @@ -34,4 +34,8 @@ The following specification uses *Atom* as the native staking token. The module 4. Unbond 5. Redelegate 6. ProveLive + 3. **[Validator Set Changes](valset-changes.md)** + 1. Validator set updates + 2. Slashing + 3. Automatic Unbonding 3. **[Future improvements](future_improvements.md)** diff --git a/docs/spec/staking/slashing.md b/docs/spec/staking/slashing.md deleted file mode 100644 index c64aa9d797..0000000000 --- a/docs/spec/staking/slashing.md +++ /dev/null @@ -1,79 +0,0 @@ - -# Slashing - -A validator bond is an economic commitment made by a validator signing key to both the safety and liveness of -the consensus. Validator keys must not sign invalid messages which could -violate consensus safety, and their signed precommit messages must be regularly included in -block commits. - -The incentivization of these two goals are treated separately. - -## Safety - -Messges which may compromise the safety of the underlying consensus protocol ("equivocations") -result in some amount of the offending validator's shares being removed ("slashed"). - -Currently, such messages include only the following: - -- prevotes by the same validator for more than one BlockID at the same - Height and Round -- precommits by the same validator for more than one BlockID at the same - Height and Round - -We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the -detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending -validators punished. - -For some `evidence` to be valid, it must satisfy: - -`evidence.Height >= block.Height - MAX_EVIDENCE_AGE` - -If valid evidence is included in a block, the offending validator loses -a constant `SLASH_PROPORTION` of their current stake: - -``` -oldShares = validator.shares -validator.shares = oldShares * (1 - SLASH_PROPORTION) -``` - -This ensures that offending validators are punished the same amount whether they -act as a single validator with X stake or as N validators with collectively X -stake. - - - -## Liveness - -Every block includes a set of precommits by the validators for the previous block, -known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. - -Proposers are incentivized to include precommits from all -validators in the LastCommit by receiving additional fees -proportional to the difference between the voting power included in the -LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). - -Validators are penalized for failing to be included in the LastCommit for some -number of blocks by being automatically unbonded. - - -TODO: do we do this by trying to track absence directly in the state, using -something like the below, or do we let users notify the app when a validator has -been absent using the -[TxLivenessCheck](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/staking/spec-technical.md#txlivelinesscheck). - - -A list, `ValidatorAbsenceInfos`, is stored in the state and used to track how often -validators were included in a LastCommit. - -```go -// Ordered by ValidatorAddress. -// One entry for each validator. -type ValidatorAbsenceInfos []ValidatorAbsenceInfo - -type ValidatorAbsenceInfo struct { - ValidatorAddress []byte // address of the validator - FirstHeight int64 // first height the validator was absent - Count int64 // number of heights validator was absent since (and including) first -} -``` - diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index ceb5e3be8e..52f324b0f7 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -7,7 +7,6 @@ Available Transactions: * TxDelegate * TxUnbond * TxRedelegate -* TxLivelinessCheck * TxProveLive ## Transaction processing @@ -264,37 +263,10 @@ redelegate(tx TxRedelegate): return ``` -### TxLivelinessCheck - -Liveliness issues are calculated by keeping track of the block precommits in -the block header. A queue is persisted which contains the block headers from -all recent blocks for the duration of the unbonding period. A validator is -defined as having livliness issues if they have not been included in more than -33% of the blocks over: -* The most recent 24 Hours if they have >= 20% of global stake -* The most recent week if they have = 0% of global stake -* Linear interpolation of the above two scenarios - -Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is -submitted. - -```golang -type TxLivelinessCheck struct { - PubKey crypto.PubKey - RewardAccount Addresss -} -``` - -If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the -liveliness punishment is provided as a reward to `RewardAccount`. - ### TxProveLive -If the validator was kicked for liveliness issues and is able to regain -liveliness then all delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. Regaining liveliness is demonstrated -by sending in a `TxProveLive` transaction: +If a validator was automatically unbonded due to liveness issues and wishes to +assert it is still online, it can send `TxProveLive`: ```golang type TxProveLive struct { @@ -302,3 +274,10 @@ type TxProveLive struct { } ``` +All delegators in the temporary unbonding pool which have not +transacted to move will be bonded back to the now-live validator and begin to +once again collect provisions and rewards. + +``` +TODO: pseudo-code +``` diff --git a/docs/spec/staking/time.md b/docs/spec/staking/valset-changes.md similarity index 51% rename from docs/spec/staking/time.md rename to docs/spec/staking/valset-changes.md index 45f40a0c3b..db2a5f8122 100644 --- a/docs/spec/staking/time.md +++ b/docs/spec/staking/valset-changes.md @@ -1,5 +1,89 @@ +# Slashing -### End of block handling +A validator bond is an economic commitment made by a validator signing key to both the safety and liveness of +the consensus. Validator keys must not sign invalid messages which could +violate consensus safety, and their signed precommit messages must be regularly included in +block commits. + +The incentivization of these two goals are treated separately. + +## Safety + +Messges which may compromise the safety of the underlying consensus protocol ("equivocations") +result in some amount of the offending validator's shares being removed ("slashed"). + +Currently, such messages include only the following: + +- prevotes by the same validator for more than one BlockID at the same + Height and Round +- precommits by the same validator for more than one BlockID at the same + Height and Round + +We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the +detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending +validators punished. + +For some `evidence` to be valid, it must satisfy: + +`evidence.Height >= block.Height - MAX_EVIDENCE_AGE` + +If valid evidence is included in a block, the offending validator loses +a constant `SLASH_PROPORTION` of their current stake: + +``` +oldShares = validator.shares +validator.shares = oldShares * (1 - SLASH_PROPORTION) +``` + +This ensures that offending validators are punished the same amount whether they +act as a single validator with X stake or as N validators with collectively X +stake. + + + +## Liveness + +Every block includes a set of precommits by the validators for the previous block, +known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. + +Proposers are incentivized to include precommits from all +validators in the LastCommit by receiving additional fees +proportional to the difference between the voting power included in the +LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). + +Validators are penalized for failing to be included in the LastCommit for some +number of blocks by being automatically unbonded. + + +TODO: do we do this by trying to track absence directly in the state, using +something like the below, or do we let users notify the app when a validator has +been absent using the +[TxLivenessCheck](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/staking/spec-technical.md#txlivelinesscheck). + + +A list, `ValidatorAbsenceInfos`, is stored in the state and used to track how often +validators were included in a LastCommit. + +```go +// Ordered by ValidatorAddress. +// One entry for each validator. +type ValidatorAbsenceInfos []ValidatorAbsenceInfo + +type ValidatorAbsenceInfo struct { + ValidatorAddress []byte // address of the validator + FirstHeight int64 // first height the validator was absent + Count int64 // number of heights validator was absent since (and including) first +} +``` + + +### BeginBlock Handling + + + +### EndBlock Handling + +This is where we inflate the Atoms and deal with validator set changes. ```golang tick(ctx Context): From f4b2750b4f4721f6a8032ae5c8c29b0b45e48ad7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 8 May 2018 17:46:08 -0400 Subject: [PATCH 57/77] update readme --- docs/spec/staking/README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/spec/staking/README.md b/docs/spec/staking/README.md index 8ec7b41cfe..82604e2de2 100644 --- a/docs/spec/staking/README.md +++ b/docs/spec/staking/README.md @@ -6,14 +6,6 @@ This paper specifies the Staking module of the Cosmos-SDK, which was first descr The module enables Cosmos-SDK based blockchain to support an advanced Proof-of-Stake system. In this system, holders of the native staking token of the chain can become candidate validators and can delegate tokens to candidate validators, ultimately determining the effective validator set for the system. -The module currently supports the following features: - -- TODO -- **Declare Candidacy:** -- **Edit Candidacy:** -- **Delegate:** -- **Unbond:** - This module will be used in the Cosmos Hub, the first Hub in the Cosmos network. ## Contents From 077ffeb706c63a5103465d602d769a14b4332248 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 9 May 2018 09:55:19 -0400 Subject: [PATCH 58/77] spec: explicit CandidateStatus enum --- docs/spec/staking/state.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 73ce1dde3a..2bcf13dea0 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -63,6 +63,14 @@ The `Candidate` holds the current state and some historical actions of validators or candidate-validators. ``` go +type CandidateStatus byte + +const ( + Bonded CandidateStatus = 0x01 + Unbonded CandidateStatus = 0x02 + Revoked CandidateStatus = 0x03 +) + type Candidate struct { Status CandidateStatus ConsensusPubKey crypto.PubKey From 0cf51da799eb6f6f385e00244036ba1c5c8a4646 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 9 May 2018 09:55:39 -0400 Subject: [PATCH 59/77] update valset-changes.md --- docs/spec/staking/valset-changes.md | 178 +++++++++++++++------------- 1 file changed, 97 insertions(+), 81 deletions(-) diff --git a/docs/spec/staking/valset-changes.md b/docs/spec/staking/valset-changes.md index db2a5f8122..bc52b89980 100644 --- a/docs/spec/staking/valset-changes.md +++ b/docs/spec/staking/valset-changes.md @@ -1,89 +1,17 @@ -# Slashing +# Validator Set Changes -A validator bond is an economic commitment made by a validator signing key to both the safety and liveness of -the consensus. Validator keys must not sign invalid messages which could -violate consensus safety, and their signed precommit messages must be regularly included in -block commits. +The validator set may be updated by state transitions that run at the beginning and +end of every block. This can happen one of three ways: -The incentivization of these two goals are treated separately. +- voting power of a validator changes due to bonding and unbonding +- voting power of validator is "slashed" due to conflicting signed messages +- validator is automatically unbonded due to inactivity -## Safety +## Voting Power Changes -Messges which may compromise the safety of the underlying consensus protocol ("equivocations") -result in some amount of the offending validator's shares being removed ("slashed"). +At the end of every block, we run the following: -Currently, such messages include only the following: - -- prevotes by the same validator for more than one BlockID at the same - Height and Round -- precommits by the same validator for more than one BlockID at the same - Height and Round - -We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the -detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending -validators punished. - -For some `evidence` to be valid, it must satisfy: - -`evidence.Height >= block.Height - MAX_EVIDENCE_AGE` - -If valid evidence is included in a block, the offending validator loses -a constant `SLASH_PROPORTION` of their current stake: - -``` -oldShares = validator.shares -validator.shares = oldShares * (1 - SLASH_PROPORTION) -``` - -This ensures that offending validators are punished the same amount whether they -act as a single validator with X stake or as N validators with collectively X -stake. - - - -## Liveness - -Every block includes a set of precommits by the validators for the previous block, -known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. - -Proposers are incentivized to include precommits from all -validators in the LastCommit by receiving additional fees -proportional to the difference between the voting power included in the -LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). - -Validators are penalized for failing to be included in the LastCommit for some -number of blocks by being automatically unbonded. - - -TODO: do we do this by trying to track absence directly in the state, using -something like the below, or do we let users notify the app when a validator has -been absent using the -[TxLivenessCheck](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/spec/staking/spec-technical.md#txlivelinesscheck). - - -A list, `ValidatorAbsenceInfos`, is stored in the state and used to track how often -validators were included in a LastCommit. - -```go -// Ordered by ValidatorAddress. -// One entry for each validator. -type ValidatorAbsenceInfos []ValidatorAbsenceInfo - -type ValidatorAbsenceInfo struct { - ValidatorAddress []byte // address of the validator - FirstHeight int64 // first height the validator was absent - Count int64 // number of heights validator was absent since (and including) first -} -``` - - -### BeginBlock Handling - - - -### EndBlock Handling - -This is where we inflate the Atoms and deal with validator set changes. +(TODO remove inflation from here) ```golang tick(ctx Context): @@ -172,3 +100,91 @@ unbondedToBondedPool(candidate Candidate): return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) ``` + + +## Slashing + +Messges which may compromise the safety of the underlying consensus protocol ("equivocations") +result in some amount of the offending validator's shares being removed ("slashed"). + +Currently, such messages include only the following: + +- prevotes by the same validator for more than one BlockID at the same + Height and Round +- precommits by the same validator for more than one BlockID at the same + Height and Round + +We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the +detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending +validators punished. + +For some `evidence` to be valid, it must satisfy: + +`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE` + +where `evidence.Timestamp` is the timestamp in the block at height +`evidence.Height` and `block.Timestamp` is the current block timestamp. + +If valid evidence is included in a block, the offending validator loses +a constant `SLASH_PROPORTION` of their current stake at the beginning of the block: + +``` +oldShares = validator.shares +validator.shares = oldShares * (1 - SLASH_PROPORTION) +``` + +This ensures that offending validators are punished the same amount whether they +act as a single validator with X stake or as N validators with collectively X +stake. + + +## Automatic Unbonding + +Every block includes a set of precommits by the validators for the previous block, +known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power. + +Proposers are incentivized to include precommits from all +validators in the LastCommit by receiving additional fees +proportional to the difference between the voting power included in the +LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)). + +Validators are penalized for failing to be included in the LastCommit for some +number of blocks by being automatically unbonded. + +The following information is stored with each validator candidate, and is only non-zero if the candidate becomes an active validator: + +```go +type ValidatorSigningInfo struct { + StartHeight int64 + SignedBlocksBitArray BitArray +} +``` + +Where: +* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). +* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks, +whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not. +Note it is initialized with all 0s. + +At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: + +``` +h = block.Height +index = h % SIGNED_BLOCKS_WINDOW + +for val in block.Validators: + signInfo = val.SignInfo + if val in block.LastCommit: + signInfo.SignedBlocksBitArray.Set(index, 0) + else + signInfo.SignedBlocksBitArray.Set(index, 1) + + // validator must be active for at least SIGNED_BLOCKS_WINDOW + // before they can be automatically unbonded for failing to be + // included in 50% of the recent LastCommits + minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW + minSigned = SIGNED_BLOCKS_WINDOW / 2 + blocksSigned = signInfo.SignedBlocksBitArray.Sum() + if h > minHeight AND blocksSigned < minSigned: + unbond the validator +``` From 677559bf6c13556e2087d7ba26a4b7659bf37ddd Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 9 May 2018 18:24:51 -0400 Subject: [PATCH 60/77] cwgoes comments --- client/context/helpers.go | 2 +- store/iavlstore.go | 4 ++-- store/iavlstore_test.go | 6 +++--- store/types.go | 2 +- types/store.go | 5 ++--- x/stake/msg.go | 10 +++++++--- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client/context/helpers.go b/client/context/helpers.go index 5baf4742ec..c3dc0a4abb 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -47,7 +47,7 @@ func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, er } // Query from Tendermint with the provided storename and subspace -func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KV, err error) { +func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) { resRaw, err := ctx.query(subspace, storeName, "subspace") if err != nil { return res, err diff --git a/store/iavlstore.go b/store/iavlstore.go index 6109d967f7..5399b3d5c4 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -179,10 +179,10 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { case "/subspace": subspace := req.Data res.Key = subspace - var KVs []KV + var KVs []KVPair iterator := st.SubspaceIterator(subspace) for ; iterator.Valid(); iterator.Next() { - KVs = append(KVs, KV{iterator.Key(), iterator.Value()}) + KVs = append(KVs, KVPair{iterator.Key(), iterator.Value()}) } iterator.Close() res.Value = cdc.MustMarshalBinary(KVs) diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index bbccd8ef01..32bc1ebe0b 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -268,12 +268,12 @@ func TestIAVLStoreQuery(t *testing.T) { v3 := []byte("val3") ksub := []byte("key") - KVs0 := []KV{} - KVs1 := []KV{ + KVs0 := []KVPair{} + KVs1 := []KVPair{ {k1, v1}, {k2, v2}, } - KVs2 := []KV{ + KVs2 := []KVPair{ {k1, v3}, {k2, v2}, } diff --git a/store/types.go b/store/types.go index fc355a1b34..e232e6ec71 100644 --- a/store/types.go +++ b/store/types.go @@ -13,7 +13,7 @@ type MultiStore = types.MultiStore type CacheMultiStore = types.CacheMultiStore type CommitMultiStore = types.CommitMultiStore type KVStore = types.KVStore -type KV = types.KV +type KVPair = types.KVPair type Iterator = types.Iterator type CacheKVStore = types.CacheKVStore type CommitKVStore = types.CommitKVStore diff --git a/types/store.go b/types/store.go index 858f0e93ca..f8367a1260 100644 --- a/types/store.go +++ b/types/store.go @@ -4,6 +4,7 @@ import ( "fmt" abci "github.com/tendermint/abci/types" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" ) @@ -260,6 +261,4 @@ func PrefixEndBytes(prefix []byte) []byte { //---------------------------------------- // key-value result for iterator queries -type KV struct { - Key, Value []byte -} +type KVPair cmn.KVPair diff --git a/x/stake/msg.go b/x/stake/msg.go index 8367058c2a..4e322e6402 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -20,6 +20,12 @@ const StakingToken = "steak" //Verify interface at compile time var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} +var msgCdc = wire.NewCodec() + +func init() { + wire.RegisterCrypto(msgCdc) +} + //______________________________________________________________________ // MsgDeclareCandidacy - struct for unbonding transactions @@ -46,9 +52,7 @@ func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address // get the bytes for the message signer to sign on func (msg MsgDeclareCandidacy) GetSignBytes() []byte { - cdc := wire.NewCodec() - wire.RegisterCrypto(cdc) - return cdc.MustMarshalBinary(msg) + return msgCdc.MustMarshalBinary(msg) } // quick validity check From bef7e44f6dcb2b1c41aa33d291c072567c00454e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 26 Apr 2018 16:14:51 +0200 Subject: [PATCH 61/77] Example tag implementation for CoinKeeper --- examples/democoin/x/cool/handler.go | 2 +- examples/democoin/x/pow/keeper.go | 2 +- examples/democoin/x/simplestake/keeper.go | 4 +- types/result.go | 3 +- types/tags.go | 31 +++++++++++++ types/tags_test.go | 30 +++++++++++++ x/bank/handler.go | 7 +-- x/bank/keeper.go | 54 +++++++++++++---------- x/bank/keeper_test.go | 6 +-- x/ibc/handler.go | 4 +- x/ibc/ibc_test.go | 5 ++- x/stake/handler.go | 2 +- 12 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 types/tags.go create mode 100644 types/tags_test.go diff --git a/examples/democoin/x/cool/handler.go b/examples/democoin/x/cool/handler.go index c82fa4ae43..b4375c5dcd 100644 --- a/examples/democoin/x/cool/handler.go +++ b/examples/democoin/x/cool/handler.go @@ -53,7 +53,7 @@ func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result { bonusCoins := sdk.Coins{{msg.CoolAnswer, 69}} - _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) + _, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) if err != nil { return err.Result() } diff --git a/examples/democoin/x/pow/keeper.go b/examples/democoin/x/pow/keeper.go index 931e41a320..35fccf7424 100644 --- a/examples/democoin/x/pow/keeper.go +++ b/examples/democoin/x/pow/keeper.go @@ -125,7 +125,7 @@ func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (ui // Add some coins for a POW well done func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.Address, newDifficulty uint64, newCount uint64) sdk.Error { - _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}}) + _, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}}) if ckErr != nil { return ckErr } diff --git a/examples/democoin/x/simplestake/keeper.go b/examples/democoin/x/simplestake/keeper.go index 7b61c36236..5bd2639610 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/examples/democoin/x/simplestake/keeper.go @@ -66,7 +66,7 @@ func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, st return 0, ErrIncorrectStakingToken(k.codespace) } - _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake}) + _, _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake}) if err != nil { return 0, err } @@ -95,7 +95,7 @@ func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, returnedBond := sdk.Coin{stakingToken, bi.Power} - _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) + _, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) if err != nil { return bi.PubKey, bi.Power, err } diff --git a/types/result.go b/types/result.go index f4f7454e2c..65f87400d2 100644 --- a/types/result.go +++ b/types/result.go @@ -2,7 +2,6 @@ package types import ( abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" ) // Result is the union of ResponseDeliverTx and ResponseCheckTx. @@ -31,7 +30,7 @@ type Result struct { ValidatorUpdates []abci.Validator // Tags are used for transaction indexing and pubsub. - Tags []cmn.KVPair + Tags Tags } // TODO: In the future, more codes may be OK. diff --git a/types/tags.go b/types/tags.go new file mode 100644 index 0000000000..82e9bc9963 --- /dev/null +++ b/types/tags.go @@ -0,0 +1,31 @@ +package types + +import ( + cmn "github.com/tendermint/tmlibs/common" +) + +type Tag = cmn.KVPair + +type Tags = cmn.KVPairs + +// Append two lists of tags +func AppendTags(a, b Tags) Tags { + return append(a, b...) +} + +// New empty tags +func EmptyTags() Tags { + return make(Tags, 0) +} + +// Single tag to tags +func SingleTag(t Tag) Tags { + return append(EmptyTags(), t) +} + +// Make a tag from a key and a value +func MakeTag(k string, v []byte) Tag { + return Tag{Key: []byte(k), Value: v} +} + +// TODO: Deduplication? diff --git a/types/tags_test.go b/types/tags_test.go new file mode 100644 index 0000000000..ae00ffdd91 --- /dev/null +++ b/types/tags_test.go @@ -0,0 +1,30 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendTags(t *testing.T) { + a := SingleTag(MakeTag("a", []byte("1"))) + b := SingleTag(MakeTag("b", []byte("2"))) + c := AppendTags(a, b) + require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))}) +} + +func TestEmptyTags(t *testing.T) { + a := EmptyTags() + require.Equal(t, a, Tags{}) +} + +func TestSingleTag(t *testing.T) { + a := MakeTag("a", []byte("1")) + b := SingleTag(a) + require.Equal(t, b, Tags{MakeTag("a", []byte("1"))}) +} + +func TestMakeTag(t *testing.T) { + a := MakeTag("a", []byte("1")) + require.Equal(t, a, Tag{[]byte("a"), []byte("1")}) +} diff --git a/x/bank/handler.go b/x/bank/handler.go index a50b0afcf3..ec56d05b42 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -25,13 +25,14 @@ func NewHandler(k Keeper) sdk.Handler { func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { // NOTE: totalIn == totalOut should already have been checked - err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) + tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) if err != nil { return err.Result() } - // TODO: add some tags so we can search it! - return sdk.Result{} // TODO + return sdk.Result{ + Tags: tags, + } } // Handle MsgIssue. diff --git a/x/bank/keeper.go b/x/bank/keeper.go index d1fdeaea06..869caeb259 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -32,22 +32,22 @@ func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) } // SubtractCoins subtracts amt from the coins at the addr. -func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return subtractCoins(ctx, keeper.am, addr, amt) } // AddCoins adds amt to the coins at the addr. -func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return addCoins(ctx, keeper.am, addr, amt) } // SendCoins moves coins from one account to another -func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { +func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } // InputOutputCoins handles a list of inputs and outputs -func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error { +func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { return inputOutputCoins(ctx, keeper.am, inputs, outputs) } @@ -74,12 +74,12 @@ func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coi } // SendCoins moves coins from one account to another -func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { +func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } // InputOutputCoins handles a list of inputs and outputs -func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error { +func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { return inputOutputCoins(ctx, keeper.am, inputs, outputs) } @@ -131,59 +131,65 @@ func hasCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.C } // SubtractCoins subtracts amt from the coins at the addr. -func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Minus(amt) if !newCoins.IsNotNegative() { - return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - return newCoins, err + tags := sdk.SingleTag(sdk.MakeTag("sender", addr.Bytes())) + return newCoins, tags, err } // AddCoins adds amt to the coins at the addr. -func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Plus(amt) if !newCoins.IsNotNegative() { - return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - return newCoins, err + tags := sdk.SingleTag(sdk.MakeTag("recipient", addr.Bytes())) + return newCoins, tags, err } // SendCoins moves coins from one account to another // NOTE: Make sure to revert state changes from tx on error -func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { - _, err := subtractCoins(ctx, am, fromAddr, amt) +func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { + _, subTags, err := subtractCoins(ctx, am, fromAddr, amt) if err != nil { - return err + return nil, err } - _, err = addCoins(ctx, am, toAddr, amt) + _, addTags, err := addCoins(ctx, am, toAddr, amt) if err != nil { - return err + return nil, err } - return nil + return sdk.AppendTags(subTags, addTags), nil } // InputOutputCoins handles a list of inputs and outputs // NOTE: Make sure to revert state changes from tx on error -func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) sdk.Error { +func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { + allTags := sdk.EmptyTags() + for _, in := range inputs { - _, err := subtractCoins(ctx, am, in.Address, in.Coins) + _, tags, err := subtractCoins(ctx, am, in.Address, in.Coins) if err != nil { - return err + return nil, err } + allTags = sdk.AppendTags(allTags, tags) } for _, out := range outputs { - _, err := addCoins(ctx, am, out.Address, out.Coins) + _, tags, err := addCoins(ctx, am, out.Address, out.Coins) if err != nil { - return err + return nil, err } + allTags = sdk.AppendTags(allTags, tags) } - return nil + return allTags, nil } diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 4394392dde..3db16c5f92 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -65,7 +65,7 @@ func TestKeeper(t *testing.T) { coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}}) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) - _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}}) + _, _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}}) assert.Implements(t, (*sdk.Error)(nil), err) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) @@ -78,7 +78,7 @@ func TestKeeper(t *testing.T) { assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) - err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) assert.Implements(t, (*sdk.Error)(nil), err2) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) @@ -147,7 +147,7 @@ func TestSendKeeper(t *testing.T) { assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) - err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) assert.Implements(t, (*sdk.Error)(nil), err2) assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) diff --git a/x/ibc/handler.go b/x/ibc/handler.go index 46cbf1e309..1f334166bf 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -25,7 +25,7 @@ func NewHandler(ibcm Mapper, ck bank.Keeper) sdk.Handler { func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCTransferMsg) sdk.Result { packet := msg.IBCPacket - _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) + _, _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) if err != nil { return err.Result() } @@ -47,7 +47,7 @@ func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCRe return ErrInvalidSequence(ibcm.codespace).Result() } - _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) + _, _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) if err != nil { return err.Result() } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index d0019002fd..60cc59bad9 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -34,7 +34,8 @@ func newAddress() crypto.Address { func getCoins(ck bank.Keeper, ctx sdk.Context, addr crypto.Address) (sdk.Coins, sdk.Error) { zero := sdk.Coins(nil) - return ck.AddCoins(ctx, addr, zero) + coins, _, err := ck.AddCoins(ctx, addr, zero) + return coins, err } func makeCodec() *wire.Codec { @@ -70,7 +71,7 @@ func TestIBC(t *testing.T) { zero := sdk.Coins(nil) mycoins := sdk.Coins{sdk.Coin{"mycoin", 10}} - coins, err := ck.AddCoins(ctx, src, mycoins) + coins, _, err := ck.AddCoins(ctx, src, mycoins) assert.Nil(t, err) assert.Equal(t, mycoins, coins) diff --git a/x/stake/handler.go b/x/stake/handler.go index 1408c5bb13..5eff822e6e 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -171,7 +171,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, // Account new shares, save pool := k.GetPool(ctx) - _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) + _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) if err != nil { return err } From f103cd412d45b55e22a2cb308a802c0f8955a5dc Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 26 Apr 2018 16:18:01 +0200 Subject: [PATCH 62/77] Linter fix --- types/tags.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/tags.go b/types/tags.go index 82e9bc9963..0811ef8702 100644 --- a/types/tags.go +++ b/types/tags.go @@ -4,8 +4,10 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) +// Type synonym for convenience type Tag = cmn.KVPair +// Type synonym for convenience type Tags = cmn.KVPairs // Append two lists of tags From 60b56f9b1c5c5de76ba8e0a01a367b9cb8ccd15d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 00:47:28 +0200 Subject: [PATCH 63/77] Slight tags API changes (Jae comments) --- types/tags.go | 37 ++++++++++++++++++++++++++----------- types/tags_test.go | 14 ++++---------- x/bank/keeper.go | 4 ++-- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/types/tags.go b/types/tags.go index 0811ef8702..5a94e9bde8 100644 --- a/types/tags.go +++ b/types/tags.go @@ -8,26 +8,41 @@ import ( type Tag = cmn.KVPair // Type synonym for convenience -type Tags = cmn.KVPairs - -// Append two lists of tags -func AppendTags(a, b Tags) Tags { - return append(a, b...) -} +type Tags cmn.KVPairs // New empty tags func EmptyTags() Tags { return make(Tags, 0) } -// Single tag to tags -func SingleTag(t Tag) Tags { - return append(EmptyTags(), t) +// Append a single tag +func (t Tags) AppendTag(k string, v []byte) Tags { + return append(t, MakeTag(k, v)) +} + +// Append two lists of tags +func AppendTags(a, b Tags) Tags { + return append(a, b...) +} + +// New variadic tags, must be k string, v []byte repeating +func NewTags(tags ...interface{}) Tags { + var ret Tags + if len(tags)%2 != 0 { + panic("must specify key-value pairs as varargs") + } + i := 0 + for { + if i == len(tags) { + break + } + ret = append(ret, Tag{Key: []byte(tags[i].(string)), Value: tags[i+1].([]byte)}) + i += 2 + } + return ret } // Make a tag from a key and a value func MakeTag(k string, v []byte) Tag { return Tag{Key: []byte(k), Value: v} } - -// TODO: Deduplication? diff --git a/types/tags_test.go b/types/tags_test.go index ae00ffdd91..84dc10b33d 100644 --- a/types/tags_test.go +++ b/types/tags_test.go @@ -7,8 +7,8 @@ import ( ) func TestAppendTags(t *testing.T) { - a := SingleTag(MakeTag("a", []byte("1"))) - b := SingleTag(MakeTag("b", []byte("2"))) + a := NewTags("a", []byte("1")) + b := NewTags("b", []byte("2")) c := AppendTags(a, b) require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))}) } @@ -18,13 +18,7 @@ func TestEmptyTags(t *testing.T) { require.Equal(t, a, Tags{}) } -func TestSingleTag(t *testing.T) { - a := MakeTag("a", []byte("1")) - b := SingleTag(a) +func TestNewTags(t *testing.T) { + b := NewTags("a", []byte("1")) require.Equal(t, b, Tags{MakeTag("a", []byte("1"))}) } - -func TestMakeTag(t *testing.T) { - a := MakeTag("a", []byte("1")) - require.Equal(t, a, Tag{[]byte("a"), []byte("1")}) -} diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 869caeb259..8a73b964df 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -138,7 +138,7 @@ func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - tags := sdk.SingleTag(sdk.MakeTag("sender", addr.Bytes())) + tags := sdk.NewTags("sender", addr.Bytes()) return newCoins, tags, err } @@ -150,7 +150,7 @@ func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.C return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - tags := sdk.SingleTag(sdk.MakeTag("recipient", addr.Bytes())) + tags := sdk.NewTags("recipient", addr.Bytes()) return newCoins, tags, err } From c0eb66b1333436dd1e351b474f5280b30aa26ddd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 00:53:24 +0200 Subject: [PATCH 64/77] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca0189666..16b76aade2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ FEATURES: * Context now has access to the application-configured logger * Add (non-proof) subspace query helper functions * Add more staking query functions: candidates, delegator-bonds +* Bank module now tags transactions with sender/recipient for indexing & later retrieval BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia From 2b707f6b0a0d4b0b87d3ba4e6d97a3dcd5b07bb2 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 17:14:46 +0200 Subject: [PATCH 65/77] AppendTags a function of Tags --- types/tags.go | 4 ++-- types/tags_test.go | 2 +- x/bank/keeper.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/types/tags.go b/types/tags.go index 5a94e9bde8..95a826fd78 100644 --- a/types/tags.go +++ b/types/tags.go @@ -21,8 +21,8 @@ func (t Tags) AppendTag(k string, v []byte) Tags { } // Append two lists of tags -func AppendTags(a, b Tags) Tags { - return append(a, b...) +func (t Tags) AppendTags(a Tags) Tags { + return append(t, a...) } // New variadic tags, must be k string, v []byte repeating diff --git a/types/tags_test.go b/types/tags_test.go index 84dc10b33d..4ef5561240 100644 --- a/types/tags_test.go +++ b/types/tags_test.go @@ -9,7 +9,7 @@ import ( func TestAppendTags(t *testing.T) { a := NewTags("a", []byte("1")) b := NewTags("b", []byte("2")) - c := AppendTags(a, b) + c := a.AppendTags(b) require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))}) } diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 8a73b964df..d23167c3c5 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -167,7 +167,7 @@ func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAd return nil, err } - return sdk.AppendTags(subTags, addTags), nil + return subTags.AppendTags(addTags), nil } // InputOutputCoins handles a list of inputs and outputs @@ -180,7 +180,7 @@ func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, out if err != nil { return nil, err } - allTags = sdk.AppendTags(allTags, tags) + allTags = allTags.AppendTags(tags) } for _, out := range outputs { @@ -188,7 +188,7 @@ func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, out if err != nil { return nil, err } - allTags = sdk.AppendTags(allTags, tags) + allTags = allTags.AppendTags(tags) } return allTags, nil From e4e1068390118a37af08ed575c11b0d1f24d5197 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 17:19:06 +0200 Subject: [PATCH 66/77] Add delegate() tags --- x/stake/handler.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index 5eff822e6e..926d109114 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -100,11 +100,13 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) + tags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) if err != nil { return err.Result() } - return sdk.Result{} + return sdk.Result{ + Tags: tags, + } } func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result { @@ -148,16 +150,18 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { GasUsed: GasDelegate, } } - err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate) + tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate) if err != nil { return err.Result() } - return sdk.Result{} + return sdk.Result{ + Tags: tags, + } } // common functionality between handlers func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, candidate Candidate) sdk.Error { + bondAmt sdk.Coin, candidate Candidate) (sdk.Tags, sdk.Error) { // Get or create the delegator bond bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address) @@ -173,7 +177,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, pool := k.GetPool(ctx) _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) if err != nil { - return err + return nil, err } pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount) bond.Shares = bond.Shares.Add(newShares) @@ -184,7 +188,8 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, k.setDelegatorBond(ctx, bond) k.setCandidate(ctx, candidate) k.setPool(ctx, pool) - return nil + tags := sdk.NewTags("delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) + return tags, nil } func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { From 580ac5f57b098a5570dd14f8911ad9893af50dfb Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 17:23:46 +0200 Subject: [PATCH 67/77] Add tagging for MsgUnbond --- x/stake/handler.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index 926d109114..c3e76888ef 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -188,7 +188,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, k.setDelegatorBond(ctx, bond) k.setCandidate(ctx, candidate) k.setPool(ctx, pool) - tags := sdk.NewTags("delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) + tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) return tags, nil } @@ -286,5 +286,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { k.setCandidate(ctx, candidate) } k.setPool(ctx, p) - return sdk.Result{} + tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "candidate", msg.CandidateAddr.Bytes()) + return sdk.Result{ + Tags: tags, + } } From 0ec21e4e27793f841f95588dedcfbbd54754ee27 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 17:31:22 +0200 Subject: [PATCH 68/77] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b76aade2..a04650813d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ FEATURES: * Add (non-proof) subspace query helper functions * Add more staking query functions: candidates, delegator-bonds * Bank module now tags transactions with sender/recipient for indexing & later retrieval +* Stake module now tags transactions with delegator/candidate for delegation & unbonding BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia From a2f5855d8e04a7f0bee5e070717c454ac730a034 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 10 May 2018 21:55:51 +0200 Subject: [PATCH 69/77] Add tags for declare & edit candidacy txs --- CHANGELOG.md | 2 +- x/stake/handler.go | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a04650813d..f574667110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ FEATURES: * Add (non-proof) subspace query helper functions * Add more staking query functions: candidates, delegator-bonds * Bank module now tags transactions with sender/recipient for indexing & later retrieval -* Stake module now tags transactions with delegator/candidate for delegation & unbonding +* Stake module now tags transactions with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia diff --git a/x/stake/handler.go b/x/stake/handler.go index c3e76888ef..8d3bbb8b80 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -97,13 +97,15 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description) k.setCandidate(ctx, candidate) + tags := sdk.NewTags("action", []byte("declareCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - tags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) + delegateTags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) if err != nil { return err.Result() } + tags = tags.AppendTags(delegateTags) return sdk.Result{ Tags: tags, } @@ -130,7 +132,10 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk candidate.Description.Details = msg.Description.Details k.setCandidate(ctx, candidate) - return sdk.Result{} + tags := sdk.NewTags("action", []byte("editCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) + return sdk.Result{ + Tags: tags, + } } func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { From 5da41a6c88edb834848da38296b3cc0ab2120553 Mon Sep 17 00:00:00 2001 From: LLLeon Date: Sun, 13 May 2018 17:34:56 +0800 Subject: [PATCH 70/77] types: modify bad syntax for struct tag value --- types/tx_msg.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/tx_msg.go b/types/tx_msg.go index 25d35512db..d4c568e515 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -80,8 +80,8 @@ func FeePayer(tx Tx) Address { // gas to be used by the transaction. The ratio yields an effective "gasprice", // which must be above some miminum to be accepted into the mempool. type StdFee struct { - Amount Coins `json"amount"` - Gas int64 `json"gas"` + Amount Coins `json:"amount"` + Gas int64 `json:"gas"` } func NewStdFee(gas int64, amount ...Coin) StdFee { From f1e441346a05df46617a153a609b0c7727515a8f Mon Sep 17 00:00:00 2001 From: LLLeon Date: Sun, 13 May 2018 20:47:26 +0800 Subject: [PATCH 71/77] x/bank: fix typo --- x/bank/msgs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 15822eed75..c8fcb59255 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -138,7 +138,7 @@ func (msg IssueMsg) GetSigners() []sdk.Address { //---------------------------------------- // Input -// Transaction Output +// Transaction Input type Input struct { Address sdk.Address `json:"address"` Coins sdk.Coins `json:"coins"` From cd24244308a0e2afc9511c3f2a767e21c1d05140 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Sun, 13 May 2018 18:19:42 -0400 Subject: [PATCH 72/77] Sort all genesis transaction by node id This ensures that users can rename the genesis transactions and they will still be in the same order. --- cmd/gaia/app/genesis.go | 5 +++-- server/init.go | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 9d88953cf6..7cb7564dd3 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -66,8 +66,9 @@ func GaiaAppInit() server.AppInit { fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) fsAppGenTx.String(flagName, "", "validator moniker, if left blank, do not add validator") - fsAppGenTx.String(flagClientHome, DefaultCLIHome, "home directory for the client, used for key generation") - fsAppGenTx.Bool(flagOWK, false, "overwrite the for the accounts created") + fsAppGenTx.String(flagClientHome, DefaultCLIHome, + "home directory for the client, used for key generation") + fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created") return server.AppInit{ FlagsAppGenState: fsAppGenState, diff --git a/server/init.go b/server/init.go index 2d8be85f8d..ffb84f27c4 100644 --- a/server/init.go +++ b/server/init.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "sort" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -210,12 +211,14 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command { func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { - // XXX sort the files by contents just incase people renamed their files var fos []os.FileInfo fos, err = ioutil.ReadDir(genTxsDir) if err != nil { return } + + genTxs := make(map[string]GenesisTx) + var nodeIDs []string for _, fo := range fos { filename := path.Join(genTxsDir, fo.Name()) if !fo.IsDir() && (path.Ext(filename) != ".json") { @@ -234,16 +237,24 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( return } + genTxs[genTx.NodeID] = genTx + nodeIDs = append(nodeIDs, genTx.NodeID) + } + + sort.Strings(nodeIDs) + + for _, nodeID := range nodeIDs { // combine some stuff - validators = append(validators, genTx.Validator) - appGenTxs = append(appGenTxs, genTx.AppGenTx) + validators = append(validators, genTxs[nodeID].Validator) + appGenTxs = append(appGenTxs, genTxs[nodeID].AppGenTx) // Add a persistent peer comma := "," if len(persistentPeers) == 0 { comma = "" } - persistentPeers += fmt.Sprintf("%s%s@%s:46656", comma, genTx.NodeID, genTx.IP) + persistentPeers += fmt.Sprintf("%s%s@%s:46656", comma, genTxs[nodeID].NodeID, + genTxs[nodeID].IP) } return From 878a53bf0def7c4aa1d76145383dfda204514b5b Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Sun, 13 May 2018 18:24:48 -0400 Subject: [PATCH 73/77] Prevent --gen-txs from failing silently Instead of just failing silently if there is a non .json file or folder in the gentx/ directory it now just skips that file or directory. ref #940 --- server/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/init.go b/server/init.go index ffb84f27c4..8912d2a6f7 100644 --- a/server/init.go +++ b/server/init.go @@ -222,7 +222,7 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( for _, fo := range fos { filename := path.Join(genTxsDir, fo.Name()) if !fo.IsDir() && (path.Ext(filename) != ".json") { - return + continue } // get the genTx From d2163017ceed3a6b8b5ef5e0c907f9f9957991df Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 14 May 2018 09:39:29 -0400 Subject: [PATCH 74/77] small efficiency increase --- server/init.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/init.go b/server/init.go index 8912d2a6f7..68a1709bc8 100644 --- a/server/init.go +++ b/server/init.go @@ -244,17 +244,18 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( sort.Strings(nodeIDs) for _, nodeID := range nodeIDs { + genTx := genTxs[nodeID] + // combine some stuff - validators = append(validators, genTxs[nodeID].Validator) - appGenTxs = append(appGenTxs, genTxs[nodeID].AppGenTx) + validators = append(validators, genTx.Validator) + appGenTxs = append(appGenTxs, genTx.AppGenTx) // Add a persistent peer comma := "," if len(persistentPeers) == 0 { comma = "" } - persistentPeers += fmt.Sprintf("%s%s@%s:46656", comma, genTxs[nodeID].NodeID, - genTxs[nodeID].IP) + persistentPeers += fmt.Sprintf("%s%s@%s:46656", comma, genTx.NodeID, genTx.IP) } return From 69b6522410845ce065a01b21bfd0f373e6a73b38 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 14 May 2018 17:20:00 -0400 Subject: [PATCH 75/77] update for tendermint v0.19.3 --- Gopkg.lock | 35 ++++++++++++++++------------------- Gopkg.toml | 9 +++++++-- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index f474e77564..9aff8fe567 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -87,14 +87,14 @@ [[projects]] name = "github.com/gorilla/context" packages = ["."] - revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" - version = "v1.1" + revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" + version = "v1.1.1" [[projects]] name = "github.com/gorilla/mux" packages = ["."] - revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" - version = "v1.6.1" + revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" + version = "v1.6.2" [[projects]] name = "github.com/gorilla/websocket" @@ -159,7 +159,7 @@ branch = "master" name = "github.com/mitchellh/mapstructure" packages = ["."] - revision = "00c29f56e2386353d58c599509e8dc3801b0d716" + revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b" [[projects]] name = "github.com/pelletier/go-toml" @@ -183,7 +183,7 @@ branch = "master" name = "github.com/rcrowley/go-metrics" packages = ["."] - revision = "d932a24a8ccb8fcadc993e5c6c58f93dac168294" + revision = "e2704e165165ec55d062f5919b4b29494e9fa790" [[projects]] name = "github.com/spf13/afero" @@ -250,7 +250,7 @@ "leveldb/table", "leveldb/util" ] - revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" + revision = "9637fa0b2f0db13c99d899b91007edb7df4610b7" [[projects]] name = "github.com/tendermint/abci" @@ -323,7 +323,6 @@ "p2p", "p2p/conn", "p2p/pex", - "p2p/trust", "p2p/upnp", "proxy", "rpc/client", @@ -342,8 +341,8 @@ "types/priv_validator", "version" ] - revision = "26f633ed48441f72895b710f0e87b7b6c6791066" - version = "v0.19.1" + revision = "03f6a29a64fc4d26322a90839b892014d2cb90a5" + version = "v0.19.3-rc0" [[projects]] name = "github.com/tendermint/tmlibs" @@ -360,8 +359,8 @@ "pubsub", "pubsub/query" ] - revision = "d94e312673e16a11ea55d742cefb3e331228f898" - version = "v0.8.2" + revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd" + version = "v0.8.3-rc0" [[projects]] branch = "master" @@ -377,7 +376,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "b49d69b5da943f7ef3c9cf91c8777c1f78a0cc3c" + revision = "2fc4c88bf43f0ea5ea305eae2b7af24b2cc93287" [[projects]] branch = "master" @@ -389,16 +388,15 @@ "http2/hpack", "idna", "internal/timeseries", - "lex/httplex", "trace" ] - revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23" + revision = "2491c5de3490fced2f6cff376127c667efeed857" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "cbbc999da32df943dac6cd71eb3ee39e1d7838b9" + revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b" [[projects]] name = "golang.org/x/text" @@ -422,10 +420,9 @@ version = "v0.3.0" [[projects]] - branch = "master" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] - revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2" + revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" [[projects]] name = "google.golang.org/grpc" @@ -460,6 +457,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "fad966346d3b6042faf2bf793168b6ce9a8ff59ae207b7ad57008ead0f3ff7d4" + inputs-digest = "30cd9c49b9e87f62f40a02fd0ba35a2d67a519ef781e06316246b307e6be638d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index d2d4ee2ff3..5fe068df6f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -70,11 +70,16 @@ [[constraint]] name = "github.com/tendermint/tendermint" - version = "0.19.1" + version = "0.19.3-rc0" [[override]] name = "github.com/tendermint/tmlibs" - version = "~0.8.2-rc1" + version = "~0.8.3-rc0" + +# this got updated and broke, so locked to an old working commit ... +[[override]] + name = "google.golang.org/genproto" + revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" [prune] go-tests = true From a2ba50718e8156a73740a56bf53a150addf516f5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 15 May 2018 12:43:13 -0400 Subject: [PATCH 76/77] changelog and version --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++------ version/version.go | 4 ++-- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a88e09c46..01180ade98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 0.17.0 (TBD) + +BREAKING CHANGES + +* [stake] MarshalJSON -> MarshalBinary + +FEATURES + +* [gaiacli] Support queries for candidates, delegator-bonds +* [gaiad] Added `gaiad export` command to export current state to JSON +* [x/bank] Tx tags with sender/recipient for indexing & later retrieval +* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy + +IMPROVEMENTS + +* [gaiad] Update for Tendermint v0.19.3 (improve `/dump_consensus_state` and add + `/consensus_state`) +* [spec/ibc] Added spec! +* [spec/stake] Cleanup structure, include details about slashing and + auto-unbonding +* [spec/governance] Fixup some names and pseudocode +* NOTE: specs are still a work-in-progress ... + +BUG FIXES + +* Auto-sequencing now works correctly + ## 0.16.0 (May 14th, 2018) BREAKING CHANGES @@ -18,7 +45,6 @@ BREAKING CHANGES FEATURES: -* Added `gaiad export` command, which exports genesis information & current state * Gaia stake commands include, DeclareCandidacy, EditCandidacy, Delegate, Unbond * MountStoreWithDB without providing a custom store works. * Repo is now lint compliant / GoMetaLinter with tendermint-lint integrated into CI @@ -28,14 +54,10 @@ FEATURES: * New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag) * Initialize with genesis txs using `--gen-txs` flag * Context now has access to the application-configured logger -* Add (non-proof) subspace query helper functions -* Add more staking query functions: candidates, delegator-bonds -* Bank module now tags transactions with sender/recipient for indexing & later retrieval -* Stake module now tags transactions with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy + BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia -* Auto-sequencing now works correctly ## 0.15.1 (April 29, 2018) diff --git a/version/version.go b/version/version.go index b19a560180..ec2b70f3aa 100644 --- a/version/version.go +++ b/version/version.go @@ -6,10 +6,10 @@ package version // TODO improve const Maj = "0" -const Min = "16" +const Min = "17" const Fix = "0" -const Version = "0.16.0" +const Version = "0.17.0-dev" // GitCommit set by build flags var GitCommit = "" From 20abeb3dcf5d7d692f5dd93263bc785fdba7a9ec Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 15 May 2018 12:45:36 -0400 Subject: [PATCH 77/77] version and changelog --- CHANGELOG.md | 2 +- version/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01180ade98..486bd34763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.17.0 (TBD) +## 0.17.0 (May 15, 2018) BREAKING CHANGES diff --git a/version/version.go b/version/version.go index ec2b70f3aa..b8b59827db 100644 --- a/version/version.go +++ b/version/version.go @@ -9,7 +9,7 @@ const Maj = "0" const Min = "17" const Fix = "0" -const Version = "0.17.0-dev" +const Version = "0.17.0" // GitCommit set by build flags var GitCommit = ""

>>>>> gd2md-html alert: equation: use MathJax/LaTeX if your publishing platform supports it.
(
Back to top)(Next alert)
>>>>>