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

Update paths for consistency; revise ICS 2 for client type sub-specs #348

Merged
merged 14 commits into from
Jan 22, 2020
Merged
3 changes: 2 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 544
personal_ws-1.1 en 545
ABCI
ABI
Agoric
Expand Down Expand Up @@ -214,6 +214,7 @@ connectionPath
conns
consensusHeight
consensusState
consensusStateHeight
consensusStateKey
consensusStatePath
consensusStates
Expand Down
Binary file modified spec.pdf
Binary file not shown.
73 changes: 22 additions & 51 deletions spec/ics-002-client-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ requires: 23, 24
required-by: 3
author: Juwoon Yun <joon@tendermint.com>, Christopher Goes <cwgoes@tendermint.com>
created: 2019-02-25
modified: 2019-08-25
modified: 2020-01-13
---

## Synopsis
Expand Down Expand Up @@ -253,10 +253,18 @@ but they must expose this common set of query functions to the IBC handler.
type ClientState = bytes
```

Client types must also define a method to initialize a client state with a provided consensus state:
Client types MUST define a method to initialize a client state with a provided consensus state, writing to state as appropriate.

```typescript
type initialize = (state: ConsensusState) => ClientState
type initialize = (state: ConsensusState) => ()
```

Client types MUST define a method to fetch the current height (height of the most recent validated header).

```typescript
type latestHeight = (

Choose a reason for hiding this comment

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

this was confusing for me at least in the beginning. The naming of the third-party client consensus state vs the self consensus state was not clear. So whenever it said getConsensusState(height) it meant self consensus state (i.e block header and validator info), while the getConsensusState(clientID, height) meant the counterparty client.

I'd suggest adding the client or self prefix to the ConsensusState to clarify this item.

Choose a reason for hiding this comment

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

Suggested change
type latestHeight = (
type latestClientHeight = (

Choose a reason for hiding this comment

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

or, alternatively clientState.latestHeight().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aye, that makes sense, I'll change the client function name to getClientConsensusState.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I can't find any uses of getConsensusState now which take a clientID and height. Can you point to a specific location which is confusing?

clientState: ClientState)
=> uint64
```

#### CommitmentProof
Expand All @@ -280,6 +288,7 @@ type verifyClientConsensusState = (
height: uint64,
proof: CommitmentProof,
clientIdentifier: Identifier,
consensusStateHeight: uint64,
consensusState: ConsensusState)
=> boolean
```
Expand Down Expand Up @@ -420,34 +429,6 @@ type validateClientIdentifier = (id: Identifier) => boolean

If not provided, the default `validateClientIdentifier` will always return `true`.

#### Path-space

`clientStatePath` takes an `Identifier` and returns a `Path` under which to store a particular client state.

```typescript
function clientStatePath(id: Identifier): Path {
return "clients/{id}/state"
}
```

`clientTypePath` takes an `Identifier` and returns `Path` under which to store the type of a particular client.

```typescript
function clientTypePath(id: Identifier): Path {
return "clients/{id}/type"
}
```

Consensus states MUST be stored separately so that they can be independently verified.

`consensusStatePath` takes an `Identifier` and returns a `Path` under which to store the consensus state of a client.

```typescript
function consensusStatePath(id: Identifier): Path {
return "clients/{id}/consensusState"
}
```

##### Utilising past roots

To avoid race conditions between client updates (which change the state root) and proof-carrying
Expand All @@ -468,28 +449,15 @@ function createClient(
abortTransactionUnless(validateClientIdentifier(id))
abortTransactionUnless(privateStore.get(clientStatePath(id)) === null)
abortSystemUnless(provableStore.get(clientTypePath(id)) === null)
clientState = clientType.initialize(consensusState)
privateStore.set(clientStatePath(id), clientState)
clientType.initialise(consensusState)
provableStore.set(clientTypePath(id), clientType)
}
```

#### Query

Client consensus state and client internal state can be queried by identifier. The returned
client state must fulfil an interface allowing membership / non-membership verification.

```typescript
function queryClientConsensusState(id: Identifier): ConsensusState {
return provableStore.get(consensusStatePath(id))
}
```

```typescript
function queryClient(id: Identifier): ClientState {
return privateStore.get(clientStatePath(id))
}
```
Client consensus state and client internal state can be queried by identifier, but
the specific paths which must be queried are defined by each client type.

Choose a reason for hiding this comment

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

the specific paths which must be queried are defined by each client type.

why is this now defined per client? All clients need to still be able to query the consensus and client states

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 client state, yes, but the consensus state, not necessarily, e.g. for a solo machine there is not a consensus state stored for each height. Client types can be required to expose queryConsensusState I suppose - is this required? The relayer should use a client-type-agnostic method.

Choose a reason for hiding this comment

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

The client state, yes, but the consensus state, not necessarily, e.g. for a solo machine there is not a consensus state stored for each height. Client types can be required to expose queryConsensusState I suppose...

👍 It'd be awesome if we could define a standard interface for all clients with the functions that they need to implement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aye, agreed. I'll add that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With regards to a standard interface, I completely agree, but the only parts of that I can reliably guess are those which will be required by relayers (querying clients), so if you want additional functions for convenience or CLI UX, let me know.

Choose a reason for hiding this comment

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

I defined a few interfaces for clients in cosmos/cosmos-sdk#5485

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. I think I'll split that out to a separate issue & we can discuss first. #356


#### Update

Expand Down Expand Up @@ -576,12 +544,13 @@ function commit(
}

// initialisation function defined by the client type
function initialize(consensusState: ConsensusState): ClientState {
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
return {
function initialize(consensusState: ConsensusState): () {
clientState = {
frozen: false,
pastPublicKeys: Set.singleton(consensusState.publicKey),
verifiedRoots: Map.empty()
}
privateStore.set(identifier, clientState)
}

// validity predicate function defined by the client type
Expand All @@ -605,7 +574,7 @@ function verifyClientConsensusState(
proof: CommitmentProof,
clientIdentifier: Identifier,
consensusState: ConsensusState) {
path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState")
path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusStates/{height}")
abortTransactionUnless(!clientState.frozen)
return clientState.verifiedRoots[sequence].verifyMembership(path, consensusState, proof)
}
Expand All @@ -617,7 +586,7 @@ function verifyConnectionState(
proof: CommitmentProof,
connectionIdentifier: Identifier,
connectionEnd: ConnectionEnd) {
path = applyPrefix(prefix, "connection/{connectionIdentifier}")
path = applyPrefix(prefix, "connections/{connectionIdentifier}")
abortTransactionUnless(!clientState.frozen)
return clientState.verifiedRoots[sequence].verifyMembership(path, connectionEnd, proof)
}
Expand Down Expand Up @@ -733,6 +702,8 @@ May 29, 2019 - Various revisions, notably multiple commitment-roots

Aug 15, 2019 - Major rework for clarity around client interface

Jan 13, 2020 - Revisions for client type separation & path alterations

## Copyright

All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
9 changes: 6 additions & 3 deletions spec/ics-003-connection-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,10 @@ function verifyClientConsensusState(
height: uint64,
proof: CommitmentProof,
clientIdentifier: Identifier,
consensusStateHeight: uint64,
consensusState: ConsensusState) {
client = queryClient(connection.clientIdentifier)
return client.verifyClientConsensusState(connection, height, connection.counterpartyPrefix, proof, clientIdentifier, consensusState)
return client.verifyClientConsensusState(connection, height, connection.counterpartyPrefix, proof, clientIdentifier, consensusStateHeight, consensusState)
}

function verifyConnectionState(
Expand Down Expand Up @@ -324,7 +325,8 @@ function connOpenTry(
connection = ConnectionEnd{state, counterpartyConnectionIdentifier, counterpartyPrefix,
clientIdentifier, counterpartyClientIdentifier, version}
abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expected))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofConsensus, counterpartyClientIdentifier, expectedConsensusState))
abortTransactionUnless(connection.verifyClientConsensusState(
proofHeight, proofConsensus, counterpartyClientIdentifier, consensusHeight, expectedConsensusState))
previous = provableStore.get(connectionPath(desiredIdentifier))
abortTransactionUnless(
(previous === null) ||
Expand Down Expand Up @@ -359,7 +361,8 @@ function connOpenAck(
connection.counterpartyClientIdentifier, connection.clientIdentifier,
version}
abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, connection.counterpartyConnectionIdentifier, expected))
abortTransactionUnless(connection.verifyClientConsensusState(proofHeight, proofConsensus, connection.counterpartyClientIdentifier, expectedConsensusState))
abortTransactionUnless(connection.verifyClientConsensusState(
proofHeight, proofConsensus, connection.counterpartyClientIdentifier, consensusHeight, expectedConsensusState))
connection.state = OPEN
abortTransactionUnless(getCompatibleVersions().indexOf(version) !== -1)
connection.version = version
Expand Down
5 changes: 3 additions & 2 deletions spec/ics-004-channel-and-packet-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,9 @@ function sendPacket(packet: Packet) {
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state !== CLOSED)

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

nextSequenceSend = provableStore.get(nextSequenceSendPath(packet.sourcePort, packet.sourceChannel))
abortTransactionUnless(packet.sequence === nextSequenceSend)
Expand Down
11 changes: 10 additions & 1 deletion spec/ics-006-solo-machine-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ function initialise(consensusState: ConsensusState): ClientState {
}
```

The solo machine client `latestHeight` function returns the latest sequence.

```typescript
function latestHeight(clientState: ClientState): uint64 {
return clientState.consensusState.sequence
}
```

### Validity predicate

The solo machine client `checkValidityAndUpdateState` function checks that the currently registered public key has signed over the new public key with the correct sequence.
Expand Down Expand Up @@ -134,8 +142,9 @@ function verifyClientConsensusState(
prefix: CommitmentPrefix,
proof: CommitmentProof,
clientIdentifier: Identifier,
consensusStateHeight: uint64,
consensusState: ConsensusState) {
path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState")
path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}")
abortTransactionUnless(!clientState.frozen)
value = clientState.consensusState.sequence + path + consensusState
assert(checkSignature(clientState.consensusState.pubKey, value, proof))
Expand Down
35 changes: 22 additions & 13 deletions spec/ics-007-tendermint-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,23 @@ interface Evidence {
Tendermint client initialisation requires a (subjectively chosen) latest consensus state, including the full validator set.

```typescript
function initialize(consensusState: ConsensusState, validatorSet: List<Pair<Address, uint64>>, latestHeight: uint64): ClientState {
function initialize(consensusState: ConsensusState, validatorSet: List<Pair<Address, uint64>>, height: uint64): ClientState {
return ClientState{
validatorSet,
latestHeight,
latestHeight: height,
pastHeaders: Map.singleton(latestHeight, consensusState)
}
}
```

The Tendermint client `latestHeight` function returns the latest stored height, which is updated every time a new (more recent) header is validated.

```typescript
function latestHeight(clientState: ClientState): uint64 {
return clientState.latestHeight
}
```

### Validity predicate

Tendermint client validity checking uses the bisection algorithm described in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). If the provided header is valid, the client state is updated & the newly verified commitment written to the store.
Expand All @@ -112,7 +120,7 @@ function checkValidityAndUpdateState(
clientState.latestHeight = header.height
// create recorded consensus state, save it
consensusState = ConsensusState{validatorSet.hash(), header.commitmentRoot}
set("consensusStates/{identifier}/{header.height}", consensusState)
set("clients/{identifier}/consensusStates/{header.height}", consensusState)
// save the client
set("clients/{identifier}", clientState)
}
Expand All @@ -131,7 +139,7 @@ function checkMisbehaviourAndUpdateState(
// assert that the commitments are different
assert(h1.commitmentRoot !== h2.commitmentRoot)
// fetch the previously verified commitment root & validator set hash
consensusState = get("consensusStates/{identifier}/{evidence.fromHeight}")
consensusState = get("clients/{identifier}/consensusStates/{evidence.fromHeight}")
// check that the validator set matches
assert(consensusState.validatorSetHash === evidence.fromValidatorSet.hash())
// check if the light client "would have been fooled"
Expand All @@ -157,14 +165,15 @@ function verifyClientConsensusState(
prefix: CommitmentPrefix,
proof: CommitmentProof,
clientIdentifier: Identifier,
consensusStateHeight: uint64,
consensusState: ConsensusState) {
path = applyPrefix(prefix, "consensusStates/{clientIdentifier}")
path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}")
// check that the client is at a sufficient height
assert(clientState.latestHeight >= height)
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the provided consensus state has been stored
assert(root.verifyMembership(path, consensusState, proof))
}
Expand All @@ -176,13 +185,13 @@ function verifyConnectionState(
proof: CommitmentProof,
connectionIdentifier: Identifier,
connectionEnd: ConnectionEnd) {
path = applyPrefix(prefix, "connection/{connectionIdentifier}")
path = applyPrefix(prefix, "connections/{connectionIdentifier}")
// check that the client is at a sufficient height
assert(clientState.latestHeight >= height)
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the provided connection end has been stored
assert(root.verifyMembership(path, connectionEnd, proof))
}
Expand All @@ -201,7 +210,7 @@ function verifyChannelState(
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the provided channel end has been stored
assert(root.verifyMembership(path, channelEnd, proof))
}
Expand All @@ -221,7 +230,7 @@ function verifyPacketCommitment(
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the provided commitment has been stored
assert(root.verifyMembership(path, commitment, proof))
}
Expand All @@ -241,7 +250,7 @@ function verifyPacketAcknowledgement(
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the provided acknowledgement has been stored
assert(root.verifyMembership(path, acknowledgement, proof))
}
Expand All @@ -260,7 +269,7 @@ function verifyPacketAcknowledgementAbsence(
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that no acknowledgement has been stored
assert(root.verifyNonMembership(path, proof))
}
Expand All @@ -279,7 +288,7 @@ function verifyNextSequenceRecv(
// check that the client is unfrozen or frozen at a higher height
assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
// fetch the previously verified commitment root & verify membership
root = get("consensusStates/{identifier}/{height}")
root = get("clients/{identifier}/consensusStates/{height}")
// verify that the nextSequenceRecv is as claimed
assert(root.verifyMembership(path, nextSequenceRecv, proof))
}
Expand Down
7 changes: 5 additions & 2 deletions spec/ics-024-host-requirements/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,14 @@ Parts of the private store MAY safely be used for other purposes as long as the
Keys used in the private store MAY safely vary as long as there exists a bipartite mapping between the key formats defined herein and the ones
actually used in the private store implementation.

Note that the client-related paths listed below reflect the Tendermint client as defined in [ICS 7](../ics-007-tendermint-client) and may vary for other client types.

| Store | Path format | Value type | Defined in |
| -------------- | ------------------------------------------------------------------------------ | ----------------- | ---------------------- |
| privateStore | "clients/{identifier}" | ClientState | [ICS 2](../ics-002-client-semantics) |
| provableStore | "clients/{identifier}/consensusState" | ConsensusState | [ICS 2](../ics-002-client-semantics) |
| provableStore | "clients/{identifier}/type" | ClientType | [ICS 2](../ics-002-client-semantics) |
| privateStore | "clients/{identifier}" | ClientState | [ICS 2](../ics-007-tendermint-client) |
| provableStore | "clients/{identifier}/consensusStates/{height}" | ConsensusState | [ICS 7](../ics-007-tendermint-client) |
| privateStore | "clients/{identifier}/connections | []Identifier | [ICS 3](../ics-003-connection-semantics) |

Choose a reason for hiding this comment

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

what is this path for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used for reverse lookup of what connections are using a client.

| provableStore | "connections/{identifier}" | ConnectionEnd | [ICS 3](../ics-003-connection-semantics) |
| privateStore | "ports/{identifier}" | CapabilityKey | [ICS 5](../ics-005-port-allocation) |
| provableStore | "ports/{identifier}/channels/{identifier}" | ChannelEnd | [ICS 4](../ics-004-channel-and-packet-semantics) |
Expand Down