Skip to content

Commit

Permalink
Merge pull request #244 from hyperledger-labs/adr-001
Browse files Browse the repository at this point in the history
ADR-001

Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele authored Jan 22, 2024
2 parents 53e6d75 + 01cf56f commit 2c339fe
Show file tree
Hide file tree
Showing 17 changed files with 635 additions and 255 deletions.
61 changes: 31 additions & 30 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
IBCTest:testBenchmarkCreateMockClient() (gas: 211733)
IBCTest:testBenchmarkRecvPacket() (gas: 137185)
IBCTest:testBenchmarkSendPacket() (gas: 85217)
IBCTest:testBenchmarkUpdateMockClient() (gas: 126635)
IBCTest:testToUint128((uint64,uint64)) (runs: 256, μ: 917, ~: 917)
TestICS02:testCreateClient() (gas: 23452628)
TestICS02:testInvalidCreateClient() (gas: 23300373)
TestICS02:testInvalidUpdateClient() (gas: 23304140)
TestICS02:testRegisterClient() (gas: 22924315)
TestICS02:testRegisterClientDuplicatedClientType() (gas: 22906365)
TestICS02:testRegisterClientInvalidClientType() (gas: 22891147)
TestICS02:testUpdateClient() (gas: 23447360)
TestICS03Handshake:testConnOpenAck() (gas: 1637131)
TestICS03Handshake:testConnOpenConfirm() (gas: 1770226)
TestICS03Handshake:testConnOpenInit() (gas: 1285023)
TestICS03Handshake:testConnOpenTry() (gas: 2130608)
TestICS03Handshake:testInvalidConnOpenAck() (gas: 2007129)
TestICS03Handshake:testInvalidConnOpenConfirm() (gas: 2013130)
TestICS03Handshake:testInvalidConnOpenInit() (gas: 671953)
TestICS03Handshake:testInvalidConnOpenTry() (gas: 2116111)
IBCTest:testBenchmarkCreateMockClient() (gas: 209734)
IBCTest:testBenchmarkLCUpdateMockClient() (gas: 39892)
IBCTest:testBenchmarkRecvPacket() (gas: 135110)
IBCTest:testBenchmarkSendPacket() (gas: 85184)
IBCTest:testBenchmarkUpdateMockClient() (gas: 137351)
IBCTest:testToUint128((uint64,uint64)) (runs: 256, μ: 947, ~: 947)
TestICS02:testCreateClient() (gas: 24292608)
TestICS02:testInvalidCreateClient() (gas: 24140061)
TestICS02:testInvalidUpdateClient() (gas: 24142376)
TestICS02:testRegisterClient() (gas: 23767439)
TestICS02:testRegisterClientDuplicatedClientType() (gas: 23749489)
TestICS02:testRegisterClientInvalidClientType() (gas: 23734361)
TestICS02:testUpdateClient() (gas: 24308717)
TestICS03Handshake:testConnOpenAck() (gas: 1632607)
TestICS03Handshake:testConnOpenConfirm() (gas: 1765682)
TestICS03Handshake:testConnOpenInit() (gas: 1280926)
TestICS03Handshake:testConnOpenTry() (gas: 2126039)
TestICS03Handshake:testInvalidConnOpenAck() (gas: 2002819)
TestICS03Handshake:testInvalidConnOpenConfirm() (gas: 2008592)
TestICS03Handshake:testInvalidConnOpenInit() (gas: 667744)
TestICS03Handshake:testInvalidConnOpenTry() (gas: 2111830)
TestICS03Version:testCopyVersions() (gas: 558658)
TestICS03Version:testFindSupportedVersion() (gas: 19400)
TestICS03Version:testIsSupportedVersion() (gas: 7864)
TestICS03Version:testPickVersion() (gas: 25399)
TestICS03Version:testVerifyProposedVersion() (gas: 11777)
TestICS03Version:testVerifySupportedFeature() (gas: 4153)
TestICS04Handshake:testBindPort() (gas: 40333)
TestICS04Handshake:testChanOpenAck() (gas: 2886503)
TestICS04Handshake:testChanOpenConfirm() (gas: 3066353)
TestICS04Handshake:testChanOpenInit() (gas: 2203010)
TestICS04Handshake:testChanOpenTry() (gas: 2660533)
TestICS04Handshake:testInvalidChanOpenAck() (gas: 2064324)
TestICS04Handshake:testInvalidChanOpenConfirm() (gas: 2122414)
TestICS04Handshake:testInvalidChanOpenInit() (gas: 1279070)
TestICS04Handshake:testInvalidChanOpenTry() (gas: 1352890)
TestICS04Packet:testInvalidSendPacket() (gas: 2209143)
TestICS04Packet:testSendPacket() (gas: 2268362)
TestICS04Handshake:testChanOpenAck() (gas: 2882434)
TestICS04Handshake:testChanOpenConfirm() (gas: 3062240)
TestICS04Handshake:testChanOpenInit() (gas: 2198987)
TestICS04Handshake:testChanOpenTry() (gas: 2656420)
TestICS04Handshake:testInvalidChanOpenAck() (gas: 2060199)
TestICS04Handshake:testInvalidChanOpenConfirm() (gas: 2118288)
TestICS04Handshake:testInvalidChanOpenInit() (gas: 1274861)
TestICS04Handshake:testInvalidChanOpenTry() (gas: 1348509)
TestICS04Packet:testInvalidSendPacket() (gas: 2205317)
TestICS04Packet:testSendPacket() (gas: 2264488)
TestICS20:testAddressToHex(address) (runs: 256, μ: 22673, ~: 22824)
TestICS20:testHexToAddress(string) (runs: 256, μ: 4776, ~: 4734)
TestICS20:testIsEscapedString() (gas: 48979)
Expand Down
84 changes: 35 additions & 49 deletions contracts/clients/IBFT2Client.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

import {ILightClient, ConsensusStateUpdate, ClientStatus} from "../core/02-client/ILightClient.sol";
import {ILightClient} from "../core/02-client/ILightClient.sol";
import {IBCHeight} from "../core/02-client/IBCHeight.sol";
import {IIBCHandler} from "../core/25-handler/IIBCHandler.sol";
import {Height} from "../proto/Client.sol";
Expand Down Expand Up @@ -63,7 +63,7 @@ contract IBFT2Client is ILightClient {
external
override
onlyIBC
returns (bytes32 clientStateCommitment, ConsensusStateUpdate memory update, bool ok)
returns (bytes32 clientStateCommitment, ILightClient.ConsensusStateUpdate memory update, bool ok)
{
ClientState.Data memory clientState;
ConsensusState.Data memory consensusState;
Expand All @@ -82,14 +82,30 @@ contract IBFT2Client is ILightClient {
consensusStates[clientId][clientState.latest_height.toUint128()] = consensusState;
return (
keccak256(clientStateBytes),
ConsensusStateUpdate({
ILightClient.ConsensusStateUpdate({
consensusStateCommitment: keccak256(consensusStateBytes),
height: clientState.latest_height
}),
true
);
}

/**
* @dev routeUpdateClient returns the calldata to the receiving function of the client message.
* The light client encodes a client message as ethereum ABI.
*/
function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage)
external
pure
virtual
override
returns (bytes4, bytes memory)
{
(Header.Data memory header, bool ok) = unmarshalHeader(protoClientMessage);
require(ok, "header is invalid");
return (this.updateClient.selector, abi.encode(clientId, header));
}

/**
* @dev getTimestampAtHeight returns the timestamp of the consensus state at the given height.
* The timestamp is nanoseconds since unix epoch.
Expand All @@ -116,56 +132,31 @@ contract IBFT2Client is ILightClient {
/**
* @dev getStatus returns the status of the client corresponding to `clientId`.
*/
function getStatus(string calldata) external pure override returns (ClientStatus) {
return ClientStatus.Active;
function getStatus(string calldata) external pure override returns (ILightClient.ClientStatus) {
return ILightClient.ClientStatus.Active;
}

/**
* @dev updateClient is intended to perform the followings:
* 1. verify a given client message(e.g. header)
* 2. check misbehaviour such like duplicate block height
* 3. if misbehaviour is found, update state accordingly and return
* 4. update state(s) with the client message
* 5. persist the state(s) on the host
*/
function updateClient(string calldata clientId, bytes calldata clientMessageBytes)
external
override
onlyIBC
returns (bytes32 clientStateCommitment, ConsensusStateUpdate[] memory updates, bool ok)
function updateClient(string calldata clientId, Header.Data calldata header)
public
returns (Height.Data[] memory heights)
{
Header.Data memory header;
bytes[] memory validators;
uint128 newHeight;

/* Validation */

ClientState.Data storage clientState = clientStates[clientId];
assert(clientState.ibc_store_address.length != 0);

// TODO add misbehaviour check support
(header, ok) = unmarshalHeader(clientMessageBytes);
require(ok, "header is invalid");
// check if the provided client message is valid
ParsedBesuHeader memory parsedHeader = parseBesuHeader(header);
require(parsedHeader.height.gt(header.trusted_height), "header height <= consensus state height");
newHeight = parsedHeader.height.toUint128();
require(parsedHeader.height.gt(clientState.latest_height), "header height <= consensus state height");
uint128 newHeight = parsedHeader.height.toUint128();

/* State verification */
ConsensusState.Data storage trustedConsensusState = consensusStates[clientId][header.trusted_height.toUint128()];
require(trustedConsensusState.timestamp != 0);

ConsensusState.Data storage consensusState = consensusStates[clientId][header.trusted_height.toUint128()];
assert(consensusState.timestamp != 0);
(validators, ok) = verify(consensusState.validators, parsedHeader);
(bytes[] memory validators, bool ok) = verify(trustedConsensusState.validators, parsedHeader);
require(ok, "failed to verify the header");

/* Update states */

if (parsedHeader.height.gt(clientState.latest_height)) {
clientState.latest_height = parsedHeader.height;
clientStateCommitment = keccak256(marshalClientState(clientState));
}
// if client message is verified and there is no misbehaviour, update state
consensusState = consensusStates[clientId][newHeight];
ConsensusState.Data storage consensusState = consensusStates[clientId][newHeight];
consensusState.timestamp = parsedHeader.time;
consensusState.root = abi.encodePacked(
verifyStorageProof(
Expand All @@ -174,17 +165,12 @@ contract IBFT2Client is ILightClient {
);
consensusState.validators = validators;

/* Make updates message */
updates = new ConsensusStateUpdate[](1);
updates[0] = ConsensusStateUpdate({
consensusStateCommitment: keccak256(marshalConsensusState(consensusState)),
height: parsedHeader.height
});

processedTimes[clientId][newHeight] = block.timestamp;
processedHeights[clientId][newHeight] = block.number;

return (clientStateCommitment, updates, true);
heights = new Height.Data[](1);
heights[0] = parsedHeader.height;
return heights;
}

/**
Expand All @@ -205,7 +191,7 @@ contract IBFT2Client is ILightClient {
return false;
}
ConsensusState.Data storage consensusState = consensusStates[clientId][height.toUint128()];
assert(consensusState.timestamp != 0);
require(consensusState.timestamp != 0);
return verifyMembership(
proof,
bytes32(consensusState.root),
Expand All @@ -231,7 +217,7 @@ contract IBFT2Client is ILightClient {
return false;
}
ConsensusState.Data storage consensusState = consensusStates[clientId][height.toUint128()];
assert(consensusState.timestamp != 0);
require(consensusState.timestamp != 0);
return verifyNonMembership(
proof, bytes32(consensusState.root), keccak256(abi.encodePacked(keccak256(path), COMMITMENT_SLOT))
);
Expand Down
88 changes: 43 additions & 45 deletions contracts/clients/MockClient.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

import {ILightClient, ConsensusStateUpdate, ClientStatus} from "../core/02-client/ILightClient.sol";
import {ILightClient} from "../core/02-client/ILightClient.sol";
import {IBCHeight} from "../core/02-client/IBCHeight.sol";
import {IIBCHandler} from "../core/25-handler/IIBCHandler.sol";
import {Height} from "../proto/Client.sol";
Expand All @@ -25,7 +25,7 @@ contract MockClient is ILightClient {
bytes32 private constant CLIENT_STATE_TYPE_URL_HASH = keccak256(abi.encodePacked(CLIENT_STATE_TYPE_URL));
bytes32 private constant CONSENSUS_STATE_TYPE_URL_HASH = keccak256(abi.encodePacked(CONSENSUS_STATE_TYPE_URL));

address internal ibcHandler;
address internal immutable ibcHandler;
mapping(string => ClientState.Data) internal clientStates;
mapping(string => mapping(uint128 => ConsensusState.Data)) internal consensusStates;
mapping(string => ClientStatus) internal statuses;
Expand All @@ -37,21 +37,21 @@ contract MockClient is ILightClient {
/**
* @dev createClient creates a new client with the given state
*/
function createClient(string calldata clientId, bytes calldata clientStateBytes, bytes calldata consensusStateBytes)
function createClient(string calldata clientId, bytes calldata protoClientState, bytes calldata protoConsensusState)
external
virtual
override
onlyIBC
returns (bytes32 clientStateCommitment, ConsensusStateUpdate memory update, bool ok)
returns (bytes32 clientStateCommitment, ILightClient.ConsensusStateUpdate memory update, bool ok)
{
ClientState.Data memory clientState;
ConsensusState.Data memory consensusState;

(clientState, ok) = unmarshalClientState(clientStateBytes);
(clientState, ok) = unmarshalClientState(protoClientState);
if (!ok) {
return (clientStateCommitment, update, false);
}
(consensusState, ok) = unmarshalConsensusState(consensusStateBytes);
(consensusState, ok) = unmarshalConsensusState(protoConsensusState);
if (!ok) {
return (clientStateCommitment, update, false);
}
Expand All @@ -65,15 +65,36 @@ contract MockClient is ILightClient {
consensusStates[clientId][clientState.latest_height.toUint128()] = consensusState;
statuses[clientId] = ClientStatus.Active;
return (
keccak256(clientStateBytes),
ConsensusStateUpdate({
consensusStateCommitment: keccak256(consensusStateBytes),
keccak256(protoClientState),
ILightClient.ConsensusStateUpdate({
consensusStateCommitment: keccak256(protoConsensusState),
height: clientState.latest_height
}),
true
);
}

/**
* @dev routeUpdateClient returns the calldata to the receiving function of the client message.
* The light client encodes a client message as ethereum ABI.
*/
function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage)
external
pure
virtual
override
returns (bytes4, bytes memory)
{
Any.Data memory any = Any.decode(protoClientMessage);
require(keccak256(abi.encodePacked(any.type_url)) == HEADER_TYPE_URL_HASH, "invalid header type");
Header.Data memory header = Header.decode(any.value);
require(
header.height.revision_number == 0 && header.height.revision_height != 0 && header.timestamp != 0,
"invalid header"
);
return (this.updateClient.selector, abi.encode(clientId, header));
}

/**
* @dev getTimestampAtHeight returns the timestamp of the consensus state at the given height.
* The timestamp is nanoseconds since unix epoch.
Expand Down Expand Up @@ -110,44 +131,21 @@ contract MockClient is ILightClient {
return statuses[clientId];
}

/**
* @dev updateClient is intended to perform the followings:
* 1. verify a given client message(e.g. header)
* 2. check misbehaviour such like duplicate block height
* 3. if misbehaviour is found, update state accordingly and return
* 4. update state(s) with the client message
* 5. persist the state(s) on the host
*/
function updateClient(string calldata clientId, bytes calldata clientMessageBytes)
external
virtual
override
onlyIBC
returns (bytes32 clientStateCommitment, ConsensusStateUpdate[] memory updates, bool ok)
function updateClient(string calldata clientId, Header.Data calldata header)
public
returns (Height.Data[] memory heights)
{
Height.Data memory height;
uint64 timestamp;
Any.Data memory anyConsensusState;

(height, timestamp) = parseHeader(clientMessageBytes);
if (height.gt(clientStates[clientId].latest_height)) {
Any.Data memory anyClientState;
clientStates[clientId].latest_height = height;
anyClientState.type_url = CLIENT_STATE_TYPE_URL;
anyClientState.value = ClientState.encode(clientStates[clientId]);
clientStateCommitment = keccak256(Any.encode(anyClientState));
require(statuses[clientId] == ClientStatus.Active, "client not active");
require(header.height.revision_number == 0, "invalid revision number");
require(header.height.revision_height != 0, "invalid revision height");
require(header.timestamp != 0, "invalid timestamp");
if (header.height.gt(clientStates[clientId].latest_height)) {
clientStates[clientId].latest_height = header.height;
}

ConsensusState.Data storage consensusState = consensusStates[clientId][height.toUint128()];
consensusState.timestamp = timestamp;

anyConsensusState.type_url = CONSENSUS_STATE_TYPE_URL;
anyConsensusState.value = ConsensusState.encode(consensusState);

updates = new ConsensusStateUpdate[](1);
updates[0] =
ConsensusStateUpdate({consensusStateCommitment: keccak256(Any.encode(anyConsensusState)), height: height});
return (clientStateCommitment, updates, true);
consensusStates[clientId][header.height.toUint128()].timestamp = header.timestamp;
heights = new Height.Data[](1);
heights[0] = header.height;
return heights;
}

/**
Expand Down
Loading

0 comments on commit 2c339fe

Please sign in to comment.