ics | title | stage | category | kind | author | created | modified | requires | implements |
---|---|---|---|---|---|---|---|---|---|
9 |
Loopback Client |
draft |
IBC/TAO |
instantiation |
Christopher Goes <[email protected]> |
2020-01-17 |
2023-03-02 |
2 |
2 |
This specification describes a loopback client, designed to be used for interaction over the IBC interface with modules present on the same ledger.
Loopback clients may be useful in cases where the calling module does not have prior knowledge of where precisely the destination module lives and would like to use the uniform IBC message-passing interface (similar to 127.0.0.1
in TCP/IP).
Functions & terms are as defined in ICS 2.
ConnectionEnd
and generateIdentifier
are as defined in ICS 3
getCommitmentPrefix
is as defined in ICS 24.
removePrefix
is as defined in ICS 23.
Intended client semantics should be preserved, and loopback abstractions should be negligible cost.
No consensus state, headers, or evidence data structures are required for a loopback client. The loopback client does not need to store the consensus state of a remote chain, since state verification does not require to check a Merkle proof against a previously validated commitment root.
type ConsensusState object
type Header object
type Misbehaviour object
The loopback client state tracks the latest height of the local ledger.
interface ClientState {
latestHeight: Height
}
The height of a loopback client state consists of two uint64
s: the revision number, and the height in the revision.
interface Height {
revisionNumber: uint64
revisionHeight: uint64
}
Similarly as in TCP/IP, where there exists a single loopback address, the protocol defines the existence of a single sentinel ClientState
instance with the client identifier 09-localhost
. Creation of other loopback clients MUST be forbidden.
Additionally, implementations will reserve a special connection identifier connection-localhost
to be used by a single sentinel ConnectionEnd
stored by default (i.e. at genesis or upgrade). The clientIdentifier
and counterpartyClientIdentifier
of the connection end both reference the sentinel 09-localhost
client identifier. The counterpartyConnectionIdentifier
references the special connection identifier connection-localhost
. The existence of a sentinel loopback connection end enables IBC applications to establish channels directly on top of the sentinel connection. Channel handshakes can then be initiated by supplying the loopback connection identifier (connection-localhost
) in the connectionHops
parameter of the ChanOpenInit
datagram.
Implementations may also allow the creation of more connections associated with the loopback client. These connections would then have a connection identifier as generated by generateIdentifier
.
Relayers supporting localhost packet flow must be adapted to submit messages from sending applications back to the originating chain.
This would require first checking the underlying connection identifier on any channel-level messages. If the underlying connection identifier is connection-localhost
, then the relayer must construct the message and send it back to the originating chain. The message MUST be constructed with a sentinel byte for the proof ([]byte{0x01}
), since the loopback client does not need Merkle proofs of the state of a remote ledger; the proof height in the message may be zero, since it is ignored by the loopback client.
Implementations may choose to implement loopback such that the next message in the handshake or packet flow is automatically called without relayer-driven transactions. However, implementors must take care to ensure that automatic message execution does not cause gas consumption issues.
Loopback client initialisation requires the latest height of the local ledger.
function initialise(identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) {
assert(clientState.latestHeight > 0)
assert(consensusState === nil)
provableStore.set("clients/{identifier}/clientState", clientState)
}
No validity checking is necessary in a loopback client; the function should never be called.
function verifyClientMessage(clientMsg: ClientMessage) {
assert(false)
}
No misbehaviour checking is necessary in a loopback client; the function should never be called.
function checkForMisbehaviour(clientMsg: clientMessage) => bool {
return false
}
Function updateState
will perform a regular update for the loopback client. The clientState
will be updated with the latest height of the local ledger. This function should be called automatically at every height.
function updateState(clientMsg: clientMessage) {
clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
// retrieve the latest height from the local ledger
height = getSelfHeight()
clientState.latestHeight = header.height
// save the client state
provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
}
Function updateStateOnMisbehaviour
is unsupported by the loopback client and performs a no-op.
function updateStateOnMisbehaviour(clientMsg: clientMessage) { }
State verification functions simply need to read state from the local ledger and compare with the bytes stored under the standardized key paths. The loopback client needs read-only access to the entire IBC store of the local ledger, and not only to its own client identifier-prefixed store.
function verifyMembership(
clientState: ClientState,
height: Height,
delayTimePeriod: uint64,
delayBlockPeriod: uint64,
proof: CommitmentProof,
path: CommitmentPath,
value: []byte
): Error {
// path is prefixed with the store prefix of the commitment proof
// e.g. in ibc-go implementation this is "ibc"
// since verification is done on the IBC store of the local ledger
// the prefix needs to be removed from the path to retrieve the
// correct key in the store
unprefixedPath = removePrefix(getCommitmentPrefix(), path)
// The complete (not only client identifier-prefixed) store is needed
// to verify that a path has been set to a particular value
if provableStore.get(unprefixedPath) !== value {
return error
}
return nil
}
function verifyNonMembership(
clientState: ClientState,
height: Height,
delayTimePeriod: uint64,
delayBlockPeriod: uint64,
proof: CommitmentProof,
path: CommitmentPath
): Error {
// path is prefixed with the store prefix of the commitment proof
// e.g. in ibc-go implementation this is "ibc"
// since verification is done on the IBC store of the local ledger
// the prefix needs to be removed from the path to retrieve the
// correct key in the store
unprefixedPath = removePrefix(getCommitmentPrefix(), path)
// The complete (not only client identifier-prefixed) store is needed
// to verifiy that a path has not been set to a particular value
if provableStore.get(unprefixedPath) !== nil {
return error
}
return nil
}
Semantics are as if this were a remote client of the local ledger.
Not applicable.
Not applicable. Alterations to the client algorithm will require a new client standard.
- Implementation of ICS 09 in Go can be found in ibc-go repository.
January 17, 2020 - Initial version
March 2, 2023 - Update after ICS 02 refactor and addition of sentinel objects.
All content herein is licensed under Apache 2.0.