Skip to content

Commit

Permalink
feat(evm): introduce v2 of CometblsClient
Browse files Browse the repository at this point in the history
  • Loading branch information
hussein-aitlahcen committed Nov 2, 2023
1 parent 56f2cb7 commit d87fce0
Show file tree
Hide file tree
Showing 5 changed files with 474 additions and 50 deletions.
393 changes: 393 additions & 0 deletions evm/contracts/clients/CometblsClientV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
pragma solidity ^0.8.18;

import "../core/02-client/ILightClient.sol";
import "../core/02-client/IBCHeight.sol";
import "../proto/ibc/core/client/v1/client.sol";
import "../proto/ibc/lightclients/tendermint/v1/tendermint.sol";
import "../proto/cosmos/ics23/v1/proofs.sol";
import "../proto/tendermint/types/types.sol";
import "../proto/tendermint/types/canonical.sol";
import "../proto/union/ibc/lightclients/cometbls/v1/cometbls.sol";
import "../proto/ibc/lightclients/wasm/v1/wasm.sol";
import {GoogleProtobufAny as Any} from "../proto/GoogleProtobufAny.sol";
import "solidity-bytes-utils/BytesLib.sol";
import "../lib/CometblsHelp.sol";
import "../lib/ICS23.sol";
import "../core/IZKVerifierV2.sol";
import "../core/IMembershipVerifier.sol";

contract CometblsClient is ILightClient {
using BytesLib for bytes;
using IBCHeight for IbcCoreClientV1Height.Data;
using CometblsHelp for TendermintTypesHeader.Data;
using CometblsHelp for TendermintTypesCommit.Data;
using CometblsHelp for UnionIbcLightclientsCometblsV1ConsensusState.Data;
using CometblsHelp for UnionIbcLightclientsCometblsV1ClientState.Data;
using CometblsHelp for OptimizedConsensusState;
using CometblsHelp for bytes;
using CometblsHelp for IZKVerifierV2;

// OptimizedConsensusState
mapping(string => IbcCoreClientV1Height.Data) internal latestHeights;
mapping(string => bytes) internal codeIds;
mapping(string => UnionIbcLightclientsCometblsV1ClientState.Data)
internal clientStates;
mapping(bytes32 => OptimizedConsensusState) internal consensusStates;
mapping(bytes32 => ProcessedMoment) internal processedMoments;

address internal ibcHandler;
IZKVerifierV2 internal zkVerifier;
IMembershipVerifier internal membershipVerifier;

constructor(
address ibcHandler_,
IZKVerifierV2 zkVerifier_,
IMembershipVerifier membershipVerifier_
) {
ibcHandler = ibcHandler_;
zkVerifier = zkVerifier_;
membershipVerifier = membershipVerifier_;
}

function stateIndex(
string calldata clientId,
uint128 height
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(clientId, height));
}

function createClient(
string calldata clientId,
bytes calldata clientStateBytes,
bytes calldata consensusStateBytes
)
external
override
onlyIBC
returns (
bytes32 clientStateCommitment,
ConsensusStateUpdate memory update,
bool ok
)
{
(
UnionIbcLightclientsCometblsV1ClientState.Data memory clientState,
IbcCoreClientV1Height.Data memory latestHeight,
bytes memory codeId
) = clientStateBytes.unmarshalClientStateFromProto();
(
UnionIbcLightclientsCometblsV1ConsensusState.Data
memory consensusState,
uint64 timestamp
) = consensusStateBytes.unmarshalConsensusStateFromProto();

if (latestHeight.revision_height == 0 || timestamp == 0) {
return (clientStateCommitment, update, false);
}

clientStates[clientId] = clientState;
latestHeights[clientId] = latestHeight;
codeIds[clientId] = codeId;
OptimizedConsensusState memory optimizedConsensusState = consensusState
.toOptimizedConsensusState(timestamp);
consensusStates[
stateIndex(clientId, latestHeight.toUint128())
] = optimizedConsensusState;
return (
clientState.marshalToCommitment(latestHeight, codeId),
ConsensusStateUpdate({
consensusStateCommitment: optimizedConsensusState
.marshalToCommitment(),
height: latestHeight
}),
true
);
}

function getTimestampAtHeight(
string calldata clientId,
IbcCoreClientV1Height.Data calldata height
) external view override returns (uint64, bool) {
OptimizedConsensusState memory consensusState = consensusStates[
stateIndex(clientId, height.toUint128())
];
return (consensusState.timestamp, true);
}

function getLatestHeight(
string calldata clientId
) external view override returns (IbcCoreClientV1Height.Data memory, bool) {
return (latestHeights[clientId], true);
}

function updateClient(
string calldata clientId,
bytes calldata clientMessageBytes
)
external
override
onlyIBC
returns (bytes32, ConsensusStateUpdate[] memory, bool)
{
UnionIbcLightclientsCometblsV1Header.Data
memory header = clientMessageBytes.unmarshalHeaderEthABI();
UnionIbcLightclientsCometblsV1ClientState.Data
storage clientState = clientStates[clientId];
OptimizedConsensusState storage consensusState = consensusStates[
stateIndex(clientId, header.trusted_height.toUint128())
];

require(
consensusState.timestamp != 0,
"LC: trusted height does not exists"
);

uint64 untrustedHeightNumber = uint64(
header.signed_header.commit.height
);
uint64 trustedHeightNumber = header.trusted_height.revision_height;
require(
untrustedHeightNumber > trustedHeightNumber,
"LC: header height <= consensus state height"
);

uint64 trustedTimestamp = consensusState.timestamp;
uint64 untrustedTimestamp = uint64(
header.signed_header.header.time.secs
);
require(
untrustedTimestamp > trustedTimestamp,
"LC: header time <= consensus state time"
);

GoogleProtobufDuration.Data memory currentTime = GoogleProtobufDuration
.Data({Seconds: int64(uint64(block.timestamp)), nanos: 0});
require(
!CometblsHelp.isExpired(
header.signed_header.header.time,
clientState.trusting_period,
currentTime
),
"LC: header expired"
);

uint64 maxClockDrift = uint64(
currentTime.Seconds + clientState.max_clock_drift.Seconds
);
require(
untrustedTimestamp < maxClockDrift,
"LC: header back to the future"
);

/*
We want to verify that 1/3 of trusted valset & 2/3 of untrusted valset signed.
In adjacent verification, trusted vals = untrusted vals.
In non adjacent verification, untrusted vals are coming from the untrusted header.
*/
bytes32 trustedValidatorsHash = consensusState.nextValidatorsHash;
bytes32 untrustedValidatorsHash;
bool adjacent = untrustedHeightNumber == trustedHeightNumber + 1;
if (adjacent) {
untrustedValidatorsHash = trustedValidatorsHash;
} else {
untrustedValidatorsHash = header
.signed_header
.header
.validators_hash
.toBytes32(0);
}

bytes32 expectedBlockHash = header.signed_header.header.merkleRoot();

require(
header.signed_header.commit.block_id.hash.toBytes32(0) ==
expectedBlockHash,
"LC: commit.block_id.hash != header.root()"
);

TendermintTypesCanonicalVote.Data memory vote = header
.signed_header
.commit
.toCanonicalVote(clientState.chain_id, expectedBlockHash);
bytes memory signedVote = Encoder.encodeDelim(
TendermintTypesCanonicalVote.encode(vote)
);

bool ok = zkVerifier.verifyZKP(
trustedValidatorsHash,
untrustedValidatorsHash,
signedVote,
header.zero_knowledge_proof
);
require(ok, "LC: invalid ZKP");

IbcCoreClientV1Height.Data
memory untrustedHeight = IbcCoreClientV1Height.Data({
revision_number: header.trusted_height.revision_number,
revision_height: untrustedHeightNumber
});

// Update states
IbcCoreClientV1Height.Data storage latestHeight = latestHeights[
clientId
];
if (untrustedHeightNumber > latestHeight.revision_height) {
latestHeight.revision_height = untrustedHeightNumber;
}

uint128 newHeightIdx = untrustedHeight.toUint128();

consensusState = consensusStates[stateIndex(clientId, newHeightIdx)];
consensusState.timestamp = uint64(
header.signed_header.header.time.secs
);
consensusState.root = header.signed_header.header.app_hash.toBytes32(0);
consensusState.nextValidatorsHash = untrustedValidatorsHash;

ConsensusStateUpdate[] memory updates = new ConsensusStateUpdate[](1);
updates[0] = ConsensusStateUpdate({
consensusStateCommitment: consensusState.marshalToCommitment(),
height: untrustedHeight
});

processedMoments[stateIndex(clientId, newHeightIdx)] = ProcessedMoment({
timestamp: uint128(block.timestamp),
height: uint128(block.number)
});

return (
clientState.marshalToCommitment(latestHeight, codeIds[clientId]),
updates,
true
);
}

function verifyMembership(
string calldata clientId,
IbcCoreClientV1Height.Data calldata height,
uint64 delayTimePeriod,
uint64 delayBlockPeriod,
bytes calldata proof,
bytes memory prefix,
bytes calldata path,
bytes calldata value
) external view override returns (bool) {
OptimizedConsensusState memory consensusState = consensusStates[
stateIndex(clientId, height.toUint128())
];
require(
consensusState.timestamp != 0,
"LC: verifyMembership: consensusState does not exist"
);
if (
!validateDelayPeriod(
clientId,
height,
delayTimePeriod,
delayBlockPeriod
)
) {
revert("LC: delayPeriod expired");
}
return
membershipVerifier.verifyMembership(
abi.encodePacked(consensusState.root),
proof,
prefix,
path,
value
);
}

function verifyNonMembership(
string calldata clientId,
IbcCoreClientV1Height.Data calldata height,
uint64 delayTimePeriod,
uint64 delayBlockPeriod,
bytes calldata proof,
bytes calldata prefix,
bytes calldata path
) external returns (bool) {
OptimizedConsensusState memory consensusState = consensusStates[
stateIndex(clientId, height.toUint128())
];
require(
consensusState.timestamp != 0,
"LC: verifyNonMembership: consensusState does not exist"
);
if (
!validateDelayPeriod(
clientId,
height,
delayTimePeriod,
delayBlockPeriod
)
) {
revert("LC: delayPeriod expired");
}
return
membershipVerifier.verifyNonMembership(
abi.encodePacked(consensusState.root),
proof,
prefix,
path
);
}

function validateDelayPeriod(
string calldata clientId,
IbcCoreClientV1Height.Data calldata height,
uint64 delayPeriodTime,
uint64 delayPeriodBlocks
) public view returns (bool) {
uint128 heightU128 = height.toUint128();
uint64 currentTime = uint64(block.timestamp);
ProcessedMoment memory moment = processedMoments[
stateIndex(clientId, heightU128)
];
uint64 validTime = uint64(moment.timestamp) + delayPeriodTime;
if (delayPeriodTime != 0 && currentTime < validTime) {
return false;
}
uint64 currentHeight = uint64(block.number);
uint64 validHeight = uint64(moment.height) + delayPeriodBlocks;
if (delayPeriodBlocks != 0 && currentHeight < validHeight) {
return false;
}
return true;
}

function getClientState(
string calldata clientId
) external view returns (bytes memory, bool) {
bytes memory codeId = codeIds[clientId];
if (codeId.length == 0) {
return (bytes(""), false);
}
return (
clientStates[clientId].marshalToProto(
latestHeights[clientId],
codeId
),
true
);
}

function getConsensusState(
string calldata clientId,
IbcCoreClientV1Height.Data calldata height
) external view returns (bytes memory, bool) {
OptimizedConsensusState memory consensusState = consensusStates[
stateIndex(clientId, height.toUint128())
];
if (consensusState.timestamp == 0) {
return (bytes(""), false);
}
return (consensusState.marshalToProto(), true);
}

modifier onlyIBC() {
require(msg.sender == ibcHandler);
_;
}
}
Loading

0 comments on commit d87fce0

Please sign in to comment.