diff --git a/spec.pdf b/spec.pdf index 3a37ab978..983f16463 100644 Binary files a/spec.pdf and b/spec.pdf differ diff --git a/spec/ics-003-connection-semantics/README.md b/spec/ics-003-connection-semantics/README.md index 58a14b677..50d84ee8f 100644 --- a/spec/ics-003-connection-semantics/README.md +++ b/spec/ics-003-connection-semantics/README.md @@ -281,6 +281,13 @@ At the end of an opening handshake between two chains implementing the sub-proto This sub-protocol need not be permissioned, modulo anti-spam measures. +In `connOpenInit`, a sentinel empty-string identifier can be used to allow the recipient chain to choose its own connection identifier. Chains may implement a function `desiredIdentifier` which chooses an identifier, e.g. by incrementing a counter: + +```typescript +type desiredIdentifier = (provedIdentifier: Identifier) -> Identifier +``` + +A specific version can optionally be passed as `version` to ensure that the handshake will either complete with that version or fail. *ConnOpenInit* initialises a connection attempt on chain A. @@ -290,12 +297,20 @@ function connOpenInit( desiredCounterpartyConnectionIdentifier: Identifier, counterpartyPrefix: CommitmentPrefix, clientIdentifier: Identifier, - counterpartyClientIdentifier: Identifier) { + counterpartyClientIdentifier: Identifier, + version: string) { abortTransactionUnless(validateConnectionIdentifier(identifier)) abortTransactionUnless(provableStore.get(connectionPath(identifier)) == null) state = INIT + if version != "" { + // manually selected version must be one we can support + abortTransactionUnless(getCompatibleVersions().indexOf(version) > -1) + versions = [version] + } else { + versions = getCompatibleVersions() + } connection = ConnectionEnd{state, desiredCounterpartyConnectionIdentifier, counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, getCompatibleVersions()} + clientIdentifier, counterpartyClientIdentifier, versions} provableStore.set(connectionPath(identifier), connection) addConnectionToClient(clientIdentifier, identifier) } @@ -306,6 +321,7 @@ function connOpenInit( ```typescript function connOpenTry( desiredIdentifier: Identifier, + provedIdentifier: Identifier, counterpartyConnectionIdentifier: Identifier, counterpartyPrefix: CommitmentPrefix, counterpartyClientIdentifier: Identifier, @@ -318,15 +334,12 @@ function connOpenTry( abortTransactionUnless(validateConnectionIdentifier(desiredIdentifier)) abortTransactionUnless(consensusHeight < getCurrentHeight()) expectedConsensusState = getConsensusState(consensusHeight) - expected = ConnectionEnd{INIT, desiredIdentifier, getCommitmentPrefix(), counterpartyClientIdentifier, + abortTransationUnless( + provedIdentifier === desiredIdentifier || + provedIdentifier === "" + ) + expected = ConnectionEnd{INIT, provedIdentifier, getCommitmentPrefix(), counterpartyClientIdentifier, clientIdentifier, counterpartyVersions} - versionsIntersection = intersection(counterpartyVersions, getCompatibleVersions()) - version = pickVersion(versionsIntersection) - connection = ConnectionEnd{TRYOPEN, counterpartyConnectionIdentifier, counterpartyPrefix, - clientIdentifier, counterpartyClientIdentifier, version} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected)) - abortTransactionUnless(connection.verifyClientConsensusState( - proofHeight, proofConsensus, counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) previous = provableStore.get(connectionPath(desiredIdentifier)) abortTransactionUnless( (previous === null) || @@ -334,8 +347,14 @@ function connOpenTry( previous.counterpartyConnectionIdentifier === counterpartyConnectionIdentifier && previous.counterpartyPrefix === counterpartyPrefix && previous.clientIdentifier === clientIdentifier && - previous.counterpartyClientIdentifier === counterpartyClientIdentifier && - previous.version === getCompatibleVersions())) + previous.counterpartyClientIdentifier === counterpartyClientIdentifier)) + versionsIntersection = intersection(counterpartyVersions, previous !== null ? previous.version : getCompatibleVersions()) + version = pickVersion(versionsIntersection) // throws if there is no intersection + connection = ConnectionEnd{TRYOPEN, counterpartyConnectionIdentifier, counterpartyPrefix, + clientIdentifier, counterpartyClientIdentifier, version} + abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected)) + abortTransactionUnless(connection.verifyClientConsensusState( + proofHeight, proofConsensus, counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) identifier = desiredIdentifier provableStore.set(connectionPath(identifier), connection) addConnectionToClient(clientIdentifier, identifier) @@ -348,12 +367,17 @@ function connOpenTry( function connOpenAck( identifier: Identifier, version: string, + counterpartyIdentifier: Identifier, proofTry: CommitmentProof, proofConsensus: CommitmentProof, proofHeight: Height, consensusHeight: Height) { abortTransactionUnless(consensusHeight < getCurrentHeight()) connection = provableStore.get(connectionPath(identifier)) + abortTransactionUnless( + counterpartyIdentifier === connection.counterpartyConnectionIdentifier || + connection.counterpartyConnectionIdentifier === "" + ) abortTransactionUnless( (connection.state === INIT && connection.version.indexOf(version) !== -1) || (connection.state === TRYOPEN && connection.version === version)) @@ -361,11 +385,12 @@ function connOpenAck( expected = ConnectionEnd{TRYOPEN, identifier, getCommitmentPrefix(), connection.counterpartyClientIdentifier, connection.clientIdentifier, version} - abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, connection.counterpartyConnectionIdentifier, expected)) + abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, counterpartyIdentifier, expected)) abortTransactionUnless(connection.verifyClientConsensusState( proofHeight, proofConsensus, connection.counterpartyClientIdentifier, consensusHeight, expectedConsensusState)) connection.state = OPEN connection.version = version + connection.counterpartyConnectionIdentifier = counterpartyIdentifier provableStore.set(connectionPath(identifier), connection) } ``` diff --git a/spec/ics-004-channel-and-packet-semantics/README.md b/spec/ics-004-channel-and-packet-semantics/README.md index 560d0fd6b..d4b66eff7 100644 --- a/spec/ics-004-channel-and-packet-semantics/README.md +++ b/spec/ics-004-channel-and-packet-semantics/README.md @@ -273,6 +273,8 @@ When the opening handshake is complete, the module which initiates the handshake it specifies will own the other end of the created channel on the counterparty chain. Once a channel is created, ownership cannot be changed (although higher-level abstractions could be implemented to provide this). +A sentinel empty-string identifier can be used to allow the recipient chain to choose its own channel identifier. + ```typescript function chanOpenInit( order: ChannelOrder, @@ -311,6 +313,7 @@ function chanOpenTry( connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, + provedIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string, @@ -319,6 +322,11 @@ function chanOpenTry( proofHeight: Height): CapabilityKey { abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) abortTransactionUnless(connectionHops.length === 1) // for v1 of the IBC protocol + // empty-string is a sentinel value for "allow any identifier" + abortTransationUnless( + provedIdentifier === channelIdentifier || + provedIdentifier === "" + ) previous = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless( (previous === null) || @@ -334,7 +342,7 @@ function chanOpenTry( abortTransactionUnless(connection !== null) abortTransactionUnless(connection.state === OPEN) expected = ChannelEnd{INIT, order, portIdentifier, - channelIdentifier, [connection.counterpartyConnectionIdentifier], counterpartyVersion} + provedIdentifier, [connection.counterpartyConnectionIdentifier], counterpartyVersion} abortTransactionUnless(connection.verifyChannelState( proofHeight, proofInit, @@ -361,11 +369,17 @@ function chanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyVersion: string, + counterpartyChannelIdentifier: string, proofTry: CommitmentProof, proofHeight: Height) { channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel.state === INIT || channel.state === TRYOPEN) abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability)) + // empty-string is a sentinel value for "allow any identifier" + abortTransactionUnless( + counterpartyChannelIdentifier === channel.counterpartyChannelIdentifier || + channel.counterpartyChannelIdentifier === "" + ) connection = provableStore.get(connectionPath(channel.connectionHops[0])) abortTransactionUnless(connection !== null) abortTransactionUnless(connection.state === OPEN) @@ -375,11 +389,12 @@ function chanOpenAck( proofHeight, proofTry, channel.counterpartyPortIdentifier, - channel.counterpartyChannelIdentifier, + counterpartyChannelIdentifier, expected )) channel.state = OPEN channel.version = counterpartyVersion + channel.counterpartyChannelIdentifier = counterpartyChannelIdentifier provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) } ```