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

Draft broadcast channel abstraction #912

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
274 changes: 274 additions & 0 deletions spec/core/ics-004-channel-and-packet-semantics/README-pubsub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
---
ics: 4
title: Broadcast Channel Semantics
stage: draft
category: IBC/TAO
kind: instantiation
requires: 2, 3, 5, 24
author: Manuel Bravo <[email protected]>
created: 2023-01-16
modified: 2023-01-16
---

## Synopsis

This standard document specifies the state machine handling logic of the broadcast channel abstraction.

## Overview and Basic Concepts

### Motivation

TODO

### Definitions

`Connection` is as defined in [ICS 3](../ics-003-connection-semantics).

`Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation).

`hash` is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilising the channel. `hash` can be defined differently by different chains.

`Identifier`, `get`, `set` and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements).

## System Model and Properties

### Desired Properties

TODO

## Technical Specification

### General Design

TODO

During the handshake process, two ends of a channel come to agreement on a version bytestring associated
with that channel. The contents of this version bytestring are and will remain opaque to the IBC core protocol.
Host state machines MAY utilise the version data to indicate supported IBC/APP protocols, agree on packet
encoding formats, or negotiate other channel-related metadata related to custom logic on top of IBC.

Host state machines MAY also safely ignore the version data or specify an empty string.

### Sub-protocols

> Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter.

#### Opening a broadcast channel

The `broadcastChanOpen` function is called by a module to open a broadcast channel.

```typescript
function broadcastChanOpen(
order: ChannelOrder,
portIdentifier: Identifier,
version: string): CapabilityKey {
abortTransactionUnless(order !== ORDERED_ALLOW_TIMEOUT)
Copy link
Contributor

Choose a reason for hiding this comment

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

From reading the spec I infer that it will not be possible to time out packets? If that's the case, would it be possible to just use the regular ordered channel? Or maybe have a new broadcast channel type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thing here is that the broadcaster does not expect any other chain to receive it, so timeouts do not apply: it could actually open it and start broadcasting packets before any other chain subscribes.

A broadcast channel can be ordered or unordered, so we could not just use the regular ordered channel or a new type.


channelIdentifier = generateIdentifier()
abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))

abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null)

abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
channel = ChannelEnd{OPEN, order, "", "", "", version}
angbrav marked this conversation as resolved.
Show resolved Hide resolved
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
return channelCapability
}
```

#### Subscribing to broadcast channel

The `broadcastChanSubscribe` function is called by a module to subscribe to an already open broadcast channel.

```typescript
function broadcastChanSubscribe(
order: ChannelOrder,
connectionHops: [Identifier],
portIdentifier: Identifier,
counterpartyPortIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
version: string,
counterpartyVersion: string,
proofInit: CommitmentProof,
proofHeight: Height): CapabilityKey {
channelIdentifier = generateIdentifier()

abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))
abortTransactionUnless(connectionHops.length === 1) // for v1 of the IBC protocol
abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
connection = provableStore.get(connectionPath(connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
expected = ChannelEnd{OPEN, order, "", "", "", counterpartyVersion}
abortTransactionUnless(connection.verifyChannelState(
proofHeight,
proofInit,
counterpartyPortIdentifier,
counterpartyChannelIdentifier,
expected
))
channel = ChannelEnd{OPEN, order, counterpartyPortIdentifier,
counterpartyChannelIdentifier, connectionHops, version}
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))

// initialize channel sequences
provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)

return channelCapability
}
```

#### Closing a broadcast channel

The `broadcastChanClose` function is called by either the broadcaster or subscriber to close the broadcast channel or unsubscribe respectively.

```typescript
function broadcastChanClose(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)
channel.state = CLOSED
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
}
```

Once closed, broadcast channels cannot be reopened and identifiers cannot be reused.

#### Packet flow & handling

The `broadcastPacket` function is called by a module in order to send *data* (in the form of an IBC packet) on a channel end owned by the calling module.

The IBC handler performs the following steps in order:

- Checks that the broadcast channel is open.
- Checks that the calling module owns the sending port (see [ICS 5](../ics-005-port-allocation)).
- Increments the send sequence counter associated with the channel.
- Stores a constant-size commitment to the packet data.
- Emits a `broadcastPacket` event.

Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index.

```typescript
function broadcastPacket(
capability: CapabilityKey,
sourcePort: Identifier,
sourceChannel: Identifier,
data: bytes) {
channel = provableStore.get(channelPath(sourcePort, sourceChannel))

// check that the channel is not closed to send packets;
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)

// check if the calling module owns the sending port
abortTransactionUnless(authenticateCapability(channelCapabilityPath(sourcePort, sourceChannel), capability))

// increment the send sequence counter
sequence = provableStore.get(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel))
provableStore.set(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel), sequence+1)

// store commitment to the packet data & packet timeout
angbrav marked this conversation as resolved.
Show resolved Hide resolved
provableStore.set(
packetCommitmentPath(sourcePort, sourceChannel, sequence),
hash(data)
)

// log that a packet can be safely sent
emitLogEntry("broadcastPacket", {sequence: sequence, data: data})
}
```

#### Delivering packets

The `deliverPacket` function is called by a module in order to receive an IBC broadcast packet sent on the corresponding broadcast channel end on the counterparty chain.

The IBC handler performs the following steps in order:

- Checks that the broadcast channel & connection are open to receive packets
- Checks that the calling module owns the receiving port
- Checks that the packet metadata matches the channel & connection information
- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered)
- Checks the inclusion proof of packet data commitment in the outgoing chain's state
- Sets a store path to indicate that the packet has been received (for unordered channels only)
- Increments the packet receive sequence associated with the channel end (ordered)

We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard).

```typescript
function deliverPacket(
Copy link
Member

Choose a reason for hiding this comment

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

Why call this deliverPacket?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was just matching the standard API of broadcast primitives. In the literature, a broadcast service allows a process to send an application message m using a call broadcast(m). Messages are delivered using a notification deliver(m). We could use something else, for instance recvBroadcastPacket

Copy link
Member

Choose a reason for hiding this comment

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

Hmm I guess its a question of whether we stick with broadcast terminology or IBC terminology. I do prefer standardizing on IBC terminology

packet: OpaquePacket,
proof: CommitmentProof,
proofHeight: Height,
relayer: string): Packet {

channel = provableStore.get(channelPath(packet.destPort, packet.destChannel))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === OPEN)
abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability))
abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier)
abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier)

connection = provableStore.get(connectionPath(channel.connectionHops[0]))

abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)

abortTransactionUnless(connection.verifyPacketData(
proofHeight,
proof,
packet.sourcePort,
packet.sourceChannel,
packet.sequence,
hash(packet.data)
))

switch channel.order {
case ORDERED:
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be ordered allow timeout?

Suggested change
case ORDERED:
case ORDERED_ALLOW_TIMEOUT:

Copy link
Contributor Author

@angbrav angbrav Jan 20, 2023

Choose a reason for hiding this comment

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

I can see where the confusion comes from. I thought it is simpler to allow bcast channels to be either ORDERED or UNORDERED, as those are simpler terms than ORDERED_ALLOW_TIMEOUT. Nevertheless, I understand that semantically anORDERED bcast channel is closer to an ORDERED_ALLOW_TIMEOUT normal channel.

If it is up to me, I will leave as it is, but if you and @AdityaSripal think differently, I can change it.

Copy link
Member

Choose a reason for hiding this comment

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

Think the binary choice of ORDERED and UNORDERED makes more sense. Since timeouts aren't even relevant here.

But maybe we can treat both ORDERED types the same way

Copy link
Contributor

@crodriguezvega crodriguezvega Jan 24, 2023

Choose a reason for hiding this comment

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

Ok, I think I am confused by the fact that in broadcastChanOpen the order is checked to be ORDERED_ALLOW_TIMEOUT and the channel end in the subscriber will also be of order ORDERED_ALLOW_TIMEOUT, right? But then when checking the channel order in deliverPacket I see ORDERED and UNORDERED... If I understand it correctly now, is the idea that the channel can be created as ORDERED or UNORDERED?

nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel))
abortTransactionUnless(packet.sequence === nextSequenceRecv)

// all assertions passed, we can alter state
nextSequenceRecv = nextSequenceRecv + 1
provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)

case UNORDERED:
crodriguezvega marked this conversation as resolved.
Show resolved Hide resolved
packetReceipt = provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence))
abortTransactionUnless(packetReceipt === null)

// all assertions passed, we can alter state
provableStore.set(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), SUCCESSFUL_RECEIPT)
}
// return transparent packet
return packet
}
```

## Backwards Compatibility

Not applicable.

## Forwards Compatibility

Data structures & encoding can be versioned at the connection or channel level. Channel logic is completely agnostic to packet data formats, which can be changed by the modules any way they like at any time.

## Example Implementation

Coming soon.

## Other Implementations

Coming soon.

## History

Jan 16, 2023 - Draft submitted

## Copyright

All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).