Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

English spec for SendPacketEvent handling #230

Merged
merged 5 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions docs/spec/relayer/Packets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# IBC packet handling

This document specifies datagram creation logic for packets. It is used by the relayer.

## Data Types

```go
type Packet {
sequence uint64
timeoutHeight Height
timeoutTimestamp uint64
sourcePort Identifier
sourceChannel Identifier
destPort Identifier
destChannel Identifier
data bytes
}
```

```go
type PacketRecv {
packet Packet
proof CommitmentProof
proofHeight Height
}
```

```go
type SendPacketEvent {
height Height
sequence uint64
timeoutHeight Height
timeoutTimestamp uint64
sourcePort Identifier
sourceChannel Identifier
destPort Identifier
destChannel Identifier
data bytes
}
```

```go
type ChannelEnd {
state ChannelState
ordering ChannelOrder
counterpartyPortIdentifier Identifier
counterpartyChannelIdentifier Identifier
connectionHops [Identifier]
version string
}

enum ChannelState {
INIT,
TRYOPEN,
OPEN,
CLOSED,
}

enum ChannelOrder {
ORDERED,
UNORDERED,
}
```

```go
type ConnectionEnd {
state ConnectionState
counterpartyConnectionIdentifier Identifier
counterpartyPrefix CommitmentPrefix
clientIdentifier Identifier
counterpartyClientIdentifier Identifier
version []string
}

enum ConnectionState {
INIT,
TRYOPEN,
OPEN,
}
```

```go
type ClientState {
chainID string
validatorSet List<Pair<Address, uint64>>
trustLevel Rational
trustingPeriod uint64
unbondingPeriod uint64
latestHeight Height
latestTimestamp uint64
frozenHeight Maybe<uint64>
upgradeCommitmentPrefix CommitmentPrefix
upgradeKey []byte
maxClockDrift uint64
proofSpecs []ProofSpec
}
```
## Helper functions

We assume the existence of the following helper functions:

milosevic marked this conversation as resolved.
Show resolved Hide resolved
```go
// Returns channel end with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.
// Channel end is queried at the given chain at the height proofHeight. If LATEST_HEIGHT is passed as a parameter,
// the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetChannel(chain Chain,
portId Identifier,
channelId Identifier,
proofHeight Height) (ChannelEnd, CommitmentProof)

// Returns connection end with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.
// Connection end is queried at the given chain at the height proofHeight. If LATEST_HEIGHT is passed as a parameter,
// the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetConnection(chain Chain,
connectionId Identifier,
proofHeight Height) (ConnectionEnd, CommitmentProof)


// Returns client connection with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Returns client connection with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.
// Returns client state with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.

// Client state is queried at the given chain at the height proofHeight. If LATEST_HEIGHT is passed as a parameter,
// the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetClientState(chain Chain,
clientId Identifier,
proofHeight Height) (ClientState, CommitmentProof)

// Returns packet commitment with a commitment proof. If proof != nil, then it is being verified with the corresponding light client.
// Packet commitment is queried at the given chain at the height proofHeight. If LATEST_HEIGHT is passed as a parameter,
// the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetPacketCommitment(chain Chain,
portId Identifier,
channelId Identifier,
sequence uint64,
proofHeight Height) (bytes, CommitmentProof)

// Returns next recv sequence number a commitment proof. If proof != nil, then it is being verified with the corresponding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Returns next recv sequence number a commitment proof. If proof != nil, then it is being verified with the corresponding
// Returns next recv sequence number with a commitment proof. If proof != nil, then it is being verified with the corresponding

// light client. It is queried at the given chain at the height proofHeight. If LATEST_HEIGHT is passed as a parameter,
// the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetNextSequenceRecv(chain Chain,
portId Identifier,
channelId Identifier,
proofHeight Height) (uint64, CommitmentProof)

// Returns packet acknowledgment with a commitment proof. If proof != nil, then it is being verified with the
// corresponding light client. Packet acknowledgment is queried at the given chain at the height proofHeight.
// If LATEST_HEIGHT is passed as a parameter, the query should be for latest height for which proof exists (MAX_HEIGHT - 1).
GetPacketAcknowledgement(chain Chain,
portId Identifier,
channelId Identifier,
sequence uint64,
proofHeight Height) (bytes, CommitmentProof)

// Returns estimate of the consensus height on the given chain.
GetConsensusHeight(chain Chain) Height

// Returns estimate of the current time on the given chain.
GetCurrentTimestamp(chainB) uint64

```

## Computing destination chain

```golang
func GetDestinationInfo(ev IBCEvent, chainA Chain) Chain {
switch ev.type {
case SendPacketEvent:
channel, proof = GetChannel(chainA, ev.sourcePort, ev.sourceChannel, ev.Height)
if proof == nil return nil

connectionId = channel.connectionHops[0]
connection, proof = GetConnection(chainA, connectionId, ev.Height)
if proof == nil return nil

clientState = GetClientState(chainA, connection.clientIdentifier, ev.Height)
return getHostInfo(clientStateA.chainID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return getHostInfo(clientStateA.chainID)
return getHostInfo(clientState.chainID)

...
}
}
```

## Datagram creation logic

### PacketRecv datagram creation

```golang
func createDatagram(ev SendPacketEvent, chainA Chain, chainB Chain, installedHeight Height) PacketRecv {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this function should be called createPacketRecvDatagram.


// Stage 1
// Verify if packet is committed to chain A and it is still pending (commitment exists)

proofHeight = installedHeight - 1
packetCommitment, packetCommitmentProof =
GetPacketCommitment(chainA, ev.sourcePort, ev.sourceChannel, ev.sequence, proofHeight)
if packetCommitmentProof != nil { return nil }
milosevic marked this conversation as resolved.
Show resolved Hide resolved

if packetCommitment == null OR
milosevic marked this conversation as resolved.
Show resolved Hide resolved
packetCommitment != hash(concat(ev.data, ev.timeoutHeight, ev.timeoutTimestamp)) { return nil }

// Stage 2
// Execute checks IBC handler on chainB will execute

channel, proof = GetChannel(chainB, ev.destPort, ev.destChannel, LATEST_HEIGHT)
if proof != nil { return nil }

if channel == null OR
channel.state != OPEN OR
milosevic marked this conversation as resolved.
Show resolved Hide resolved
ev.sourcePort != channel.counterpartyPortIdentifier OR
ev.sourceChannel != channel.counterpartyChannelIdentifier { return nil }

connectionId = channel.connectionHops[0]
connection, proof = GetConnection(chainB, connectionId, LATEST_HEIGHT)
milosevic marked this conversation as resolved.
Show resolved Hide resolved
if proof != nil { return nil }

if connectionB == null OR connectionB.state != OPEN { return nil }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if connectionB == null OR connectionB.state != OPEN { return nil }
if connection == null OR connection.state != OPEN { return nil }


if ev.timeoutHeight != 0 AND GetConsensusHeight(chainB) >= ev.timeoutHeight { return nil }
if ev.timeoutTimestamp != 0 AND GetCurrentTimestamp(chainB) >= ev.timeoutTimestamp { return nil }

// we now check if this packet is already received by the destination chain
if (channel.ordering === ORDERED) {
nextSequenceRecv, proof = GetNextSequenceRecv(chainB, ev.destPort, ev.destChannel, LATEST_HEIGHT)
if proof != nil { return nil }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this check below the sequence matching check and change it to:

Suggested change
if proof != nil { return nil }
if proof == nil { return nil }

i.e. first we check if the chain expects exactly the ev.sequence. Not sure what to do if it doesn't though. Maybe the packet was delivered or maybe a full rpc node is lying?
Then if we ask for a proof and we don't get one something is wrong -> change the rpc full node?
Then do the proof verification, if it fails -> change the rpc full node?

I don't know the safest sequence here...and I think we trust the full node here? So maybe ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We trust light client, so everything checked by the light client (via header's app hash) we also trust. I will add more on error handling in the next version, but in general we should probably switch to different rpc node within GetX functions, but handler ideally don't need to care about that. But this need to be clarified.


if ev.sequence != nextSequenceRecv { return nil } // packet has already been delivered by another relayer

} else {
packetAcknowledgement, proof =
GetPacketAcknowledgement(chainB, ev.destPort, ev.destChannel, ev.sequence, LATEST_HEIGHT)
if proof != nil { return nil }
milosevic marked this conversation as resolved.
Show resolved Hide resolved

if packetAcknowledgement != nil { return nil }
milosevic marked this conversation as resolved.
Show resolved Hide resolved
}

// Stage 3
// Build datagram as all checks has passed
packet = Packet {
sequence: ev.sequence,
timeoutHeight: ev.timeoutHeight,
timeoutTimestamp: ev.timeoutTimestamp,
sourcePort: ev.sourcePort,
sourceChannel: ev.sourceChannel,
destPort: ev.destPort,
destChannel: ev.destChannel,
data: ev.data
}

return PacketRecv { packet, packetCommitmentProof, proofHeight }
}
```


27 changes: 12 additions & 15 deletions docs/spec/relayer/Relayer.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ succeed. The interface between stage 2 and stage 3 is a set of datagrams.
We assume that the corresponding light client is correctly installed on each chain.

```golang
func handleEvent(ev, chainA, chainB) {
func handleEvent(ev, chainA) {
// NOTE: we don't verify if event data are valid at this point. We trust full node we are connected to
// until some verification fails. Otherwise, we can have Stage 2 (datagram creation being done first).

// Stage 1.
// Determine destination chain
chainB = GetDestinationInfo(ev, chainA)

// Stage 2.
// Update on `chainB` the IBC client for `chainA` to height `>= targetHeight`.
targetHeight = ev.height + 1
// See the code for `updateIBCClient` below.
Expand All @@ -87,22 +91,15 @@ func handleEvent(ev, chainA, chainB) {
return error
}

// Stage 2.
// Stage 3.
// Create the IBC datagrams including `ev` & verify them.
datagram = createDatagram(ev, chainA, chainB, installedHeight)
milosevic marked this conversation as resolved.
Show resolved Hide resolved

sh = chainA.lc.get_header(installedHeight)
while (true) {
datagrams = pendingDatagrams(installedHeight - 1, chainA, chainB)
if verifyProof(datagrams, sh.appHash) {
break;
}
// Full node for `chainA` is faulty. Connect to different node of `chainA` and retry.
replaceFullNode(src)
}

// Stage 3.
// Stage 4.
// Submit datagrams.
chainB.submit(datagrams)
if datagram != nil {
chainB.submit(datagram)
}
}


Expand Down