ics | title | stage | requires | required-by | category | kind | version compatibility | author | created | modified |
---|---|---|---|---|---|---|---|---|---|---|
5 |
Port Allocation |
Draft |
24 |
4 |
IBC/TAO |
interface |
ibc-go v7.0.0 |
Christopher Goes <cwgoes@tendermint.com> |
2019-06-20 |
2019-08-25 |
This standard specifies the port allocation system by which modules can bind to uniquely named ports allocated by the IBC handler. Ports can then be used to open channels and can be transferred or later released by the module which originally bound to them.
The interblockchain communication protocol is designed to facilitate module-to-module traffic, where modules are independent, possibly mutually distrusted, self-contained elements of code executing on sovereign ledgers. In order to provide the desired end-to-end semantics, the IBC handler must permission channels to particular modules. This specification defines the port allocation and ownership system which realises that model.
Conventions may emerge as to what kind of module logic is bound to a particular port name, such as "bank" for fungible token handling or "staking" for interchain collateralisation. This is analogous to port 80's common use for HTTP servers — the protocol cannot enforce that particular module logic is actually bound to conventional ports, so users must check that themselves. Ephemeral ports with pseudorandom identifiers may be created for temporary protocol handling.
Modules may bind to multiple ports and connect to multiple ports bound to by another module on a separate machine. Any number of (uniquely identified) channels can utilise a single port simultaneously. Channels are end-to-end between two ports, each of which must have been previously bound to by a module, which will then control that end of the channel.
Optionally, the host state machine can elect to expose port binding only to a specially-permissioned module manager, by generating a capability key specifically for the ability to bind ports. The module manager can then control which ports modules can bind to with a custom rule-set, and transfer ports to modules only when it has validated the port name & module. This role can be played by the routing module (see ICS 26).
Identifier
, get
, set
, and delete
are defined as in ICS 24.
A port is a particular kind of identifier which is used to permission channel opening and usage to modules.
A module is a sub-component of the host state machine independent of the IBC handler. Examples include Ethereum smart contracts and Cosmos SDK & Substrate modules. The IBC specification makes no assumptions of module functionality other than the ability of the host state machine to use object-capability or source authentication to permission ports to modules.
- Once a module has bound to a port, no other modules can use that port until the module releases it
- A module can, on its option, release a port or transfer it to another module
- A single module can bind to multiple ports at once
- Ports are allocated first-come first-serve, and "reserved" ports for known modules can be bound when the chain is first started
As a helpful comparison, the following analogies to TCP are roughly accurate:
IBC Concept | TCP/IP Concept | Differences |
---|---|---|
IBC | TCP | Many, see the architecture documents describing IBC |
Port (e.g. "bank") | Port (e.g. 80) | No low-number reserved ports, ports are strings |
Module (e.g. "bank") | Application (e.g. Nginx) | Application-specific |
Client | - | No direct analogy, a bit like L2 routing and a bit like TLS |
Connection | - | No direct analogy, folded into connections in TCP |
Channel | Connection | Any number of channels can be opened to or from a port simultaneously |
The host state machine MUST support either object-capability reference or source authentication for modules.
In the former object-capability case, the IBC handler must have the ability to generate object-capabilities, unique, opaque references which can be passed to a module and will not be duplicable by other modules. Two examples are store keys as used in the Cosmos SDK (reference) and object references as used in Agoric's Javascript runtime (reference).
type CapabilityKey object
newCapability
must take a name and generate a unique capability key, such that the name is locally mapped to the capability key and can be used with getCapability
later.
function newCapability(name: string): CapabilityKey {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
authenticateCapability
must take a name & a capability and check whether the name is locally mapped to the provided capability. The name can be untrusted user input.
function authenticateCapability(name: string, capability: CapabilityKey): bool {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
claimCapability
must take a name & a capability (provided by another module) and locally map the name to the capability, "claiming" it for future usage.
function claimCapability(name: string, capability: CapabilityKey) {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
getCapability
must allow a module to lookup a capability which it has previously created or claimed by name.
function getCapability(name: string): CapabilityKey {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
releaseCapability
must allow a module to release a capability which it owns.
function releaseCapability(capability: CapabilityKey) {
// provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
}
In the latter source authentication case, the IBC handler must have the ability to securely read the source identifier of the calling module, a unique string for each module in the host state machine, which cannot be altered by the module or faked by another module. An example is smart contract addresses as used by Ethereum (reference).
type SourceIdentifier string
function callingModuleIdentifier(): SourceIdentifier {
// provided by host state machine, e.g. contract address in Ethereum
}
newCapability
, authenticateCapability
, claimCapability
, getCapability
, and releaseCapability
are then implemented as follows:
function newCapability(name: string): CapabilityKey {
return callingModuleIdentifier()
}
function authenticateCapability(name: string, capability: CapabilityKey) {
return callingModuleIdentifier() === name
}
function claimCapability(name: string, capability: CapabilityKey) {
// no-op
}
function getCapability(name: string): CapabilityKey {
// not actually used
return nil
}
function releaseCapability(capability: CapabilityKey) {
// no-op
}
portPath
takes an Identifier
and returns the store path under which the object-capability reference or owner module identifier associated with a port should be stored.
function portPath(id: Identifier): Path {
return "ports/{id}"
}
Owner module identifier for ports are stored under a unique Identifier
prefix.
The validation function validatePortIdentifier
MAY be provided.
type validatePortIdentifier = (id: Identifier) => boolean
If not provided, the default validatePortIdentifier
function will always return true
.
The IBC handler MUST implement bindPort
. bindPort
binds to an unallocated port, failing if the port has already been allocated.
If the host state machine does not implement a special module manager to control port allocation, bindPort
SHOULD be available to all modules. If it does, bindPort
SHOULD only be callable by the module manager.
function bindPort(id: Identifier): CapabilityKey {
abortTransactionUnless(validatePortIdentifier(id))
abortTransactionUnless(getCapability(portPath(id)) === null)
capability = newCapability(portPath(id))
return capability
}
If the host state machine supports object-capabilities, no additional protocol is necessary, since the port reference is a bearer capability.
The IBC handler MUST implement the releasePort
function, which allows a module to release a port such that other modules may then bind to it.
releasePort
SHOULD be available to all modules.
Warning: releasing a port will allow other modules to bind to that port and possibly intercept incoming channel opening handshakes. Modules should release ports only when doing so is safe.
function releasePort(id: Identifier, capability: CapabilityKey) {
abortTransactionUnless(authenticateCapability(portPath(id), capability))
releaseCapability(capability)
}
- By default, port identifiers are first-come-first-serve: once a module has bound to a port, only that module can utilise the port until the module transfers or releases it. A module manager can implement custom logic which overrides this.
Not applicable.
Port binding is not a wire protocol, so interfaces can change independently on separate chains as long as the ownership semantics are unaffected.
- Implementation of ICS 05 in Go can be found in ibc-go repository.
- Implementation of ICS 05 in Rust can be found in ibc-rs repository.
Jun 29, 2019 - Initial draft
All content herein is licensed under Apache 2.0.