Skip to content

Commit

Permalink
Merge PR cosmos#307: Specify detailed example relayer algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
cwgoes authored Nov 26, 2019
1 parent cc55eaf commit acffa86
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 19 deletions.
3 changes: 2 additions & 1 deletion misc/aspell_dict
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 483
personal_ws-1.1 en 484
ABCI
ABI
Agoric
Expand Down Expand Up @@ -403,6 +403,7 @@ removeConnectionFromClient
renderer
repudiability
repudiable
rivalrous
rootKey
routingModule
runtime
Expand Down
6 changes: 4 additions & 2 deletions spec/ics-003-connection-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ function connOpenTry(
clientIdentifier: Identifier,
counterpartyVersions: string[],
proofInit: CommitmentProof,
proofConsensus: CommitmentProof,
proofHeight: uint64,
consensusHeight: uint64) {
abortTransactionUnless(validateConnectionIdentifier(desiredIdentifier))
Expand All @@ -322,7 +323,7 @@ function connOpenTry(
connection = ConnectionEnd{state, counterpartyConnectionIdentifier, counterpartyPrefix,
clientIdentifier, counterpartyClientIdentifier, version}
abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofInit, counterpartyClientIdentifier, expectedConsensusState))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofConsensus, counterpartyClientIdentifier, expectedConsensusState))
abortTransactionUnless(provableStore.get(connectionPath(desiredIdentifier)) === null)
identifier = desiredIdentifier
state = TRYOPEN
Expand All @@ -338,6 +339,7 @@ function connOpenAck(
identifier: Identifier,
version: string,
proofTry: CommitmentProof,
proofConsensus: CommitmentProof,
proofHeight: uint64,
consensusHeight: uint64) {
abortTransactionUnless(consensusHeight <= getCurrentHeight())
Expand All @@ -348,7 +350,7 @@ function connOpenAck(
connection.counterpartyClientIdentifier, connection.clientIdentifier,
version}
abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, connection.counterpartyConnectionIdentifier, expected))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofTry, connection.counterpartyClientIdentifier, expectedConsensusState))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofConsensus, connection.counterpartyClientIdentifier, expectedConsensusState))
connection.state = OPEN
abortTransactionUnless(getCompatibleVersions().indexOf(version) !== -1)
connection.version = version
Expand Down
3 changes: 0 additions & 3 deletions spec/ics-003-connection-semantics/state.png

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions spec/ics-004-channel-and-packet-semantics/dataflow.png

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions spec/ics-004-channel-and-packet-semantics/packet-transit.png

This file was deleted.

137 changes: 136 additions & 1 deletion spec/ics-018-relayer-algorithms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A *relayer* is an off-chain process with the ability to read the state of and su

The relayer algorithm is defined over a set `C` of chains implementing the IBC protocol. Each relayer may not necessarily have access to read state from and write datagrams to all chains in the interchain network (especially in the case of permissioned or private chains) — different relayers may relay between different subsets.

`pendingDatagrams` calculates the set of all valid datagrams to be relayed from one chain to another based on the state of both chains. Sub-components of this function are defined in individual ICSs. The relayer must possess prior knowledge of what subset of the IBC protocol is implemented by the blockchains in the set for which they are relaying (e.g. by reading the source code).
`pendingDatagrams` calculates the set of all valid datagrams to be relayed from one chain to another based on the state of both chains. The relayer must possess prior knowledge of what subset of the IBC protocol is implemented by the blockchains in the set for which they are relaying (e.g. by reading the source code). An example is defined below.

`submitDatagram` is a procedure defined per-chain (submitting a transaction of some sort).

Expand All @@ -51,6 +51,141 @@ function relay(C: Set<Chain>) {
}
```

### Pending datagrams

`pendingDatagrams` collates datagrams to be sent from one machine to another. The implementation of this function will depend on the subset of the IBC protocol supported by both machines & the state layout of the source machine. Particular relayers will likely also want to implement their own filter functions in order to relay only a subset of the datagrams which could possibly be relayed (e.g. the subset for which they have been paid to relay in some off-chain manner).

An example implementation which performs bidirectional relay between two chains:

```typescript
function pendingDatagrams(chain: Chain, counterparty: Chain): List<Set<Datagram>> {
const localDatagrams = []
const counterpartyDatagrams = []

// ICS2 : Clients
// - Determine if light client needs to be updated (local & counterparty)
height = chain.latestHeight()
client = counterparty.queryClientConsensusState(chain)
if client.height < height {
header = chain.latestHeader()
counterpartyDatagrams.push(ClientUpdate{chain, header})
}
counterpartyHeight = counterparty.latestHeight()
client = chain.queryClientConsensusState(counterparty)
if client.height < counterpartyHeight {
header = counterparty.latestHeader()
localDatagrams.push(ClientUpdate{counterparty, header})
}

// ICS3 : Connections
// - Determine if any connection handshakes are in progress
connections = chain.getConnectionsUsingClient(counterparty)
for (const localEnd of connections) {
remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier)
if (localEnd.state === INIT && remoteEnd === null)
// Handshake has started locally (1 step done), relay `connOpenTry` to the remote end
counterpartyDatagrams.push(ConnOpenTry{
desiredIdentifier: localEnd.counterpartyConnectionIdentifier,
counterpartyConnectionIdentifier: localEnd.identifier,
counterpartyClientIdentifier: localEnd.clientIdentifier,
clientIdentifier: localEnd.counterpartyClientIdentifier,
version: localEnd.version,
counterpartyVersion: localEnd.version,
proofInit: localEnd.proof(),
proofConsensus: localEnd.client.consensusState.proof(),
proofHeight: height,
consensusHeight: localEnd.client.height,
})
else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN)
// Handshake has started on the other end (2 steps done), relay `connOpenAck` to the local end
localDatagrams.push(ConnOpenAck{
identifier: localEnd.identifier,
version: remoteEnd.version,
proofTry: remoteEnd.proof(),
proofConsensus: remoteEnd.client.consensusState.proof(),
proofHeight: remoteEnd.client.height,
consensusHeight: remoteEnd.client.height,
})
else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN)
// Handshake has confirmed locally (3 steps done), relay `connOpenConfirm` to the remote end
counterpartyDatagrams.push(ConnOpenConfirm{
identifier: remoteEnd.identifier,
proofAck: localEnd.proof(),
proofHeight: height,
})
}

// ICS4 : Channels & Packets
// - Determine if any channel handshakes are in progress
// - Determine if any packets, acknowledgements, or timeouts need to be relayed
channels = chain.getChannelsUsingConnections(connections)
for (const localEnd of channels) {
remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier)
// Deal with handshakes in progress
if (localEnd.state === INIT && remoteEnd === null)
// Handshake has started locally (1 step done), relay `chanOpenTry` to the remote end
counterpartyDatagrams.push(ChanOpenTry{
order: localEnd.order,
connectionHops: localEnd.connectionHops.reverse(),
portIdentifier: localEnd.counterpartyPortIdentifier,
channelIdentifier: localEnd.counterpartyChannelIdentifier,
counterpartyPortIdentifier: localEnd.portIdentifier,
counterpartyChannelIdentifier: localEnd.channelIdentifier,
version: localEnd.version,
counterpartyVersion: localEnd.version,
proofInit: localEnd.proof(),
proofHeight: height,
})
else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN)
// Handshake has started on the other end (2 steps done), relay `chanOpenAck` to the local end
localDatagrams.push(ChanOpenAck{
portIdentifier: localEnd.portIdentifier,
channelIdentifier: localEnd.channelIdentifier,
version: remoteEnd.version,
proofTry: remoteEnd.proof(),
proofHeight: localEnd.client.height,
})
else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN)
// Handshake has confirmed locally (3 steps done), relay `chanOpenConfirm` to the remote end
counterpartyDatagrams.push(ChanOpenConfirm{
portIdentifier: remoteEnd.portIdentifier,
channelIdentifier: remoteEnd.channelIdentifier,
proofAck: localEnd.proof(),
proofHeight: height
})
// Deal with packets
// - For ordered channels, check local sequence & remote sequence
// - For unordered channels, check presence or absence of acknowledgement
if (localEnd.order === ORDERED) {
const sequenceSend = localEnd.nextSequenceSend
const sequenceRecv = remoteEnd.nextSequenceRecv
let sequence = 0
for (sequence = sequenceRecv; sequence <= sequenceSend - 1; sequence++) {
// relay packet with this sequence number
// TODO: need log access for commitment and timeout height!
packetData = Packet{sequence, timeoutHeight, localEnd.portIdentifier, localEnd.channelIdentifier,
remoteEnd.portIdentifier, remoteEnd.channelIdentifier, data}
counterpartyDatagrams.push(PacketRecv{
packet: packetData,
proof: packet.proof(),
proofHeight: height,
})
}
} else if (localEnd.order === UNORDERED) {
// todo: should just read the logs, not scan the state (scanning state is super inefficient)
packetData = Packet{data}
counterpartyDatagrams.push(PacketRecv{
packet: packetData,
proof: packet.proof(),
proofHeight: height,
})
}
}

return [localDatagrams, counterpartyDatagrams]
}
```

### Ordering constraints

There are implicit ordering constraints imposed on the relayer process determining which datagrams must be submitted in what order. For example, a header must be submitted to finalise the stored consensus state & commitment root for a particular height in a light client before a packet can be relayed. The relayer process is responsible for frequently querying the state of the chains between which they are relaying in order to determine what must be relayed when.
Expand Down
4 changes: 4 additions & 0 deletions spec/ics-026-routing-module/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ interface ConnOpenTry {
version: string
counterpartyVersion: string
proofInit: CommitmentProof
proofConsensus: CommitmentProof
proofHeight: uint64
consensusHeight: uint64
}
Expand All @@ -308,6 +309,7 @@ function handleConnOpenTry(datagram: ConnOpenTry) {
datagram.version,
datagram.counterpartyVersion,
datagram.proofInit,
datagram.proofConsensus,
datagram.proofHeight,
datagram.consensusHeight
)
Expand All @@ -321,6 +323,7 @@ interface ConnOpenAck {
identifier: Identifier
version: string
proofTry: CommitmentProof
proofConsensus: CommitmentProof
proofHeight: uint64
consensusHeight: uint64
}
Expand All @@ -332,6 +335,7 @@ function handleConnOpenAck(datagram: ConnOpenAck) {
datagram.identifier,
datagram.version,
datagram.proofTry,
datagram.proofConsensus,
datagram.proofHeight,
datagram.consensusHeight
)
Expand Down

0 comments on commit acffa86

Please sign in to comment.