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

Add timeout timestamp to IBC packets #398

Merged
merged 4 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 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 564
personal_ws-1.1 en 567
ABCI
ABI
Agoric
Expand Down Expand Up @@ -302,6 +302,8 @@ getHeight
getMerkleRoot
getRoot
getStoredRecentConsensusStateCount
getTimestamp
getTimestampAtHeight
getValue
getVerifiedRoot
handleChanCloseConfirm
Expand Down Expand Up @@ -506,6 +508,7 @@ timeoutOnClose
timeoutPacket
timeoutPacketClose
timeoutPropose
timeoutTimestamp
tokenising
tokenize
tokenized
Expand Down
Binary file modified spec.pdf
Binary file not shown.
6 changes: 6 additions & 0 deletions spec/ics-002-client-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ type ConsensusState = bytes

The `ConsensusState` MUST be stored under a particular key, defined below, so that other chains can verify that a particular consensus state has been stored.

The `ConsensusState` MUST define a `getTimestamp()` method which returns the timestamp associated with that consensus state:

```typescript
type getTimestamp = ConsensusState => uint64
```

#### Header

A `Header` is an opaque data structure defined by a client type which provides information to update a `ConsensusState`.
Expand Down
7 changes: 7 additions & 0 deletions spec/ics-003-connection-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ function verifyNextSequenceRecv(
client = queryClient(connection.clientIdentifier)
return client.verifyNextSequenceRecv(connection, height, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, nextSequenceRecv)
}

function getTimestampAtHeight(
connection: ConnectionEnd,
height: uint64) {
client = queryClient(connection.clientIdentifier)
return client.queryConsensusState(height).getTimestamp()
}
```

### Sub-protocols
Expand Down
35 changes: 21 additions & 14 deletions spec/ics-004-channel-and-packet-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ A `Packet`, in the interblockchain communication protocol, is a particular inter
interface Packet {
sequence: uint64
timeoutHeight: uint64
timeoutTimestamp: uint64
sourcePort: Identifier
sourceChannel: Identifier
destPort: Identifier
Expand All @@ -113,6 +114,7 @@ interface Packet {

- The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number must be sent and received before a packet with a later sequence number.
- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out.
- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out.
- The `sourcePort` identifies the port on the sending chain.
- The `sourceChannel` identifies the channel end on the sending chain.
- The `destPort` identifies the port on the receiving chain.
Expand Down Expand Up @@ -521,7 +523,7 @@ function sendPacket(packet: Packet) {

// sanity-check that the timeout height hasn't already passed in our local client tracking the receiving chain
latestClientHeight = provableStore.get(clientPath(connection.clientIdentifier)).latestClientHeight()
abortTransactionUnless(latestClientHeight < packet.timeoutHeight)
abortTransactionUnless(packet.timeoutHeight === 0 || latestClientHeight < packet.timeoutHeight)

nextSequenceSend = provableStore.get(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel))
abortTransactionUnless(packet.sequence === nextSequenceSend)
Expand All @@ -530,10 +532,11 @@ function sendPacket(packet: Packet) {

nextSequenceSend = nextSequenceSend + 1
provableStore.set(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel), nextSequenceSend)
provableStore.set(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence), hash(packet.data, packet.timeoutHeight))
provableStore.set(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence),
hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))

// log that a packet has been sent
emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, timeout: packet.timeoutHeight})
emitLogEntry("sendPacket", {sequence: packet.sequence, data: packet.data, timeoutHeight: packet.timeoutHeight, timeoutTimestamp: packet.timeoutTimestamp})
}
```

Expand Down Expand Up @@ -572,15 +575,16 @@ function recvPacket(
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)

abortTransactionUnless(getConsensusHeight() < packet.timeoutHeight)
abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight)
abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp)

abortTransactionUnless(connection.verifyPacketData(
proofHeight,
proof,
packet.sourcePort,
packet.sourceChannel,
packet.sequence,
concat(packet.data, packet.timeoutHeight)
concat(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)
))

// all assertions passed (except sequence check), we can alter state
Expand All @@ -599,7 +603,8 @@ function recvPacket(
}

// log that a packet has been received & acknowledged
emitLogEntry("recvPacket", {sequence: packet.sequence, timeout: packet.timeoutHeight, data: packet.data, acknowledgement})
emitLogEntry("recvPacket", {sequence: packet.sequence, timeoutHeight: packet.timeoutHeight,
timeoutTimestamp: packet.timeoutTimestamp, data: packet.data, acknowledgement})

// return transparent packet
return packet
Expand Down Expand Up @@ -635,7 +640,7 @@ function acknowledgePacket(

// verify we sent the packet and haven't cleared it out yet
abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
=== hash(packet.data, packet.timeoutHeight))
=== hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))

// abort transaction unless correct acknowledgement on counterparty chain
abortTransactionUnless(connection.verifyPacketAcknowledgement(
Expand Down Expand Up @@ -666,7 +671,7 @@ Note that in order to avoid any possible "double-spend" attacks, the timeout alg
##### Sending end

The `timeoutPacket` function is called by a module which originally attempted to send a packet to a counterparty module,
where the timeout height has passed on the counterparty chain without the packet being committed, to prove that the packet
where the timeout height or timeout timestamp has passed on the counterparty chain without the packet being committed, to prove that the packet
can no longer be executed and to allow the calling module to safely perform appropriate state transitions.

Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`.
Expand Down Expand Up @@ -695,15 +700,17 @@ function timeoutPacket(
// note: the connection may have been closed
abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier)

// check that timeout height has passed on the other end
abortTransactionUnless(proofHeight >= packet.timeoutHeight)
// check that timeout height or timeout timestamp has passed on the other end
abortTransactionUnless(
(packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) ||
(packet.timeoutTimestamp > 0 && connection.getTimestampAtHeight(proofHeight) > packet.timeoutTimestamp))

// check that packet has not been received
abortTransactionUnless(nextSequenceRecv < packet.sequence)

// verify we actually sent this packet, check the store
abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
=== hash(packet.data, packet.timeoutHeight))
=== hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))

if channel.order === ORDERED
// ordered channel: check that the recv sequence is as claimed
Expand Down Expand Up @@ -744,7 +751,7 @@ function timeoutPacket(

The `timeoutOnClose` function is called by a module in order to prove that the channel
to which an unreceived packet was addressed has been closed, so the packet will never be received
(even if the `timeoutHeight` has not yet been reached).
(even if the `timeoutHeight` or `timeoutTimestamp` has not yet been reached).

```typescript
function timeoutOnClose(
Expand All @@ -765,7 +772,7 @@ function timeoutOnClose(

// verify we actually sent this packet, check the store
abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
=== hash(packet.data, packet.timeoutHeight))
=== hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))

// check that the opposing channel end has closed
expected = ChannelEnd{CLOSED, channel.order, channel.portIdentifier,
Expand Down Expand Up @@ -837,7 +844,7 @@ function cleanupPacket(

// verify we actually sent the packet, check the store
abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
=== hash(packet.data, packet.timeoutHeight))
=== hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))

if channel.order === ORDERED
// check that the recv sequence is as claimed
Expand Down
6 changes: 4 additions & 2 deletions spec/ics-018-relayer-algorithms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ function pendingDatagrams(chain: Chain, counterparty: Chain): List<Set<Datagram>
sentPacketLogs = queryByTopic(height, "sendPacket")
for (const logEntry of sentPacketLogs) {
// relay packet with this sequence number
packetData = Packet{logEntry.sequence, logEntry.timeout, localEnd.portIdentifier, localEnd.channelIdentifier,
packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp,
localEnd.portIdentifier, localEnd.channelIdentifier,
remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data}
counterpartyDatagrams.push(PacketRecv{
packet: packetData,
Expand All @@ -180,7 +181,8 @@ function pendingDatagrams(chain: Chain, counterparty: Chain): List<Set<Datagram>
recvPacketLogs = queryByTopic(height, "recvPacket")
for (const logEntry of recvPacketLogs) {
// relay packet acknowledgement with this sequence number
packetData = Packet{logEntry.sequence, logEntry.timeout, localEnd.portIdentifier, localEnd.channelIdentifier,
packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp,
localEnd.portIdentifier, localEnd.channelIdentifier,
remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data}
counterpartyDatagrams.push(PacketAcknowledgement{
packet: packetData,
Expand Down
2 changes: 2 additions & 0 deletions spec/ics-024-host-requirements/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ Host chains MUST provide a current Unix timestamp, accessible with `currentTimes
type currentTimestamp = () => uint64
```

In order for timestamps to be used safely in timeouts, timestamps in subsequent headers MUST be non-decreasing.

### Port system

Host state machines MUST implement a port system, where the IBC handler can allow different modules in the host state machine to bind to uniquely named ports. Ports are identified by an `Identifier`.
Expand Down