From 25265b1d6784f1accfbf40971ab20d5dafa95663 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Wed, 11 Dec 2024 14:07:47 +0100 Subject: [PATCH 01/11] feat(evm): introduce `EvmInCosmosClient` state lens --- evm/contracts/clients/EvmInCosmosClient.sol | 311 ++++++++ evm/contracts/lib/MPTVerifier.sol | 775 ++++++++++++++++++++ evm/evm.nix | 64 +- evm/scripts/Deploy.s.sol | 1 + evm/tests/src/lib/MPTVerifier.t.sol | 90 +++ 5 files changed, 1209 insertions(+), 32 deletions(-) create mode 100644 evm/contracts/clients/EvmInCosmosClient.sol create mode 100644 evm/contracts/lib/MPTVerifier.sol create mode 100644 evm/tests/src/lib/MPTVerifier.t.sol diff --git a/evm/contracts/clients/EvmInCosmosClient.sol b/evm/contracts/clients/EvmInCosmosClient.sol new file mode 100644 index 0000000000..4f69878ad2 --- /dev/null +++ b/evm/contracts/clients/EvmInCosmosClient.sol @@ -0,0 +1,311 @@ +pragma solidity ^0.8.27; + +import "@openzeppelin-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol"; +import "solidity-bytes-utils/BytesLib.sol"; + +import "../core/02-client/ILightClient.sol"; +import "../core/24-host/IBCStore.sol"; +import "../core/24-host/IBCCommitment.sol"; +import "../lib/ICS23.sol"; +import "../lib/Common.sol"; +import "../lib/MPTVerifier.sol"; + +struct Header { + uint64 l1Height; + uint64 l2Height; + bytes l2InclusionProof; + bytes l2ConsensusState; +} + +struct ClientState { + uint32 l1ClientId; + uint32 l2ChainId; + uint32 l2ClientId; + uint64 latestHeight; + uint16 timestampOffset; + uint16 stateRootOffset; + uint16 storageRootOffset; +} + +struct ConsensusState { + uint64 timestamp; + bytes32 stateRoot; + bytes32 storageRoot; +} + +library EvmInCosmosLib { + uint256 public constant EVM_IBC_COMMITMENT_SLOT = 0; + + error ErrNotIBC(); + error ErrTrustedConsensusStateNotFound(); + error ErrClientFrozen(); + error ErrInvalidL1Proof(); + error ErrInvalidInitialConsensusState(); + error ErrInvalidMisbehaviour(); + + function encode( + ConsensusState memory consensusState + ) internal pure returns (bytes memory) { + return abi.encode(consensusState); + } + + function encode( + ClientState memory clientState + ) internal pure returns (bytes memory) { + return abi.encode(clientState); + } + + function commit( + ConsensusState memory consensusState + ) internal pure returns (bytes32) { + return keccak256(encode(consensusState)); + } + + function commit( + ClientState memory clientState + ) internal pure returns (bytes32) { + return keccak256(encode(clientState)); + } +} + +contract EvmInCosmosClient is + ILightClient, + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + PausableUpgradeable +{ + using EvmInCosmosLib for *; + + address private ibcHandler; + + mapping(uint32 => ClientState) private clientStates; + mapping(uint32 => mapping(uint64 => ConsensusState)) private consensusStates; + mapping(uint32 => mapping(uint64 => ProcessedMoment)) private + processedMoments; + + constructor() { + _disableInitializers(); + } + + function initialize( + address _ibcHandler, + address admin + ) public initializer { + __Ownable_init(admin); + ibcHandler = _ibcHandler; + } + + function createClient( + uint32 clientId, + bytes calldata clientStateBytes, + bytes calldata consensusStateBytes + ) external override onlyIBC returns (ConsensusStateUpdate memory update) { + ClientState calldata clientState; + assembly { + clientState := clientStateBytes.offset + } + ConsensusState calldata consensusState; + assembly { + consensusState := consensusStateBytes.offset + } + if (clientState.latestHeight == 0 || consensusState.timestamp == 0) { + revert EvmInCosmosLib.ErrInvalidInitialConsensusState(); + } + clientStates[clientId] = clientState; + consensusStates[clientId][clientState.latestHeight] = consensusState; + // Normalize to nanosecond because ibc-go recvPacket expects nanos... + processedMoments[clientId][clientState.latestHeight] = ProcessedMoment({ + timestamp: block.timestamp * 1e9, + height: block.number + }); + return ConsensusStateUpdate({ + clientStateCommitment: clientState.commit(), + consensusStateCommitment: consensusState.commit(), + height: clientState.latestHeight + }); + } + + /* + * We update the L₂ client through the L₁ client. + * Given an L₂ and L₁ heights (H₂, H₁), we prove that L₂[H₂] ∈ L₁[H₁]. + */ + function updateClient( + uint32 clientId, + bytes calldata clientMessageBytes + ) external override onlyIBC returns (ConsensusStateUpdate memory) { + Header calldata header; + assembly { + header := clientMessageBytes.offset + } + ClientState memory clientState = clientStates[clientId]; + ILightClient l1Client = + IBCStore(ibcHandler).getClient(clientState.l1ClientId); + // L₂[H₂] ∈ L₁[H₁] + if ( + !l1Client.verifyMembership( + clientState.l1ClientId, + header.l1Height, + header.l2InclusionProof, + abi.encodePacked( + IBCCommitment.consensusStateCommitmentKey( + clientState.l2ClientId, header.l2Height + ) + ), + abi.encodePacked(keccak256(header.l2ConsensusState)) + ) + ) { + revert EvmInCosmosLib.ErrInvalidL1Proof(); + } + + bytes calldata rawL2ConsensusState = header.l2ConsensusState; + uint64 timestampOffset = clientState.timestampOffset; + uint64 stateRootOffset = clientState.stateRootOffset; + uint64 storageRootOffset = clientState.storageRootOffset; + uint64 l2Timestamp; + bytes32 l2StateRoot; + bytes32 l2StorageRoot; + assembly { + l2Timestamp := + calldataload(add(rawL2ConsensusState.offset, timestampOffset)) + l2StateRoot := + calldataload(add(rawL2ConsensusState.offset, stateRootOffset)) + l2StorageRoot := + calldataload(add(rawL2ConsensusState.offset, storageRootOffset)) + } + + if (header.l2Height > clientState.latestHeight) { + clientState.latestHeight = header.l2Height; + } + + // L₂[H₂] = S₂ + // We use ethereum native encoding to make it more efficient. + ConsensusState storage consensusState = + consensusStates[clientId][header.l2Height]; + consensusState.timestamp = l2Timestamp; + consensusState.stateRoot = l2StateRoot; + consensusState.storageRoot = l2StorageRoot; + + // P[H₂] = now() + ProcessedMoment storage processed = + processedMoments[clientId][header.l2Height]; + processed.timestamp = block.timestamp * 1e9; + processed.height = block.number; + + // commit(S₂) + return ConsensusStateUpdate({ + clientStateCommitment: clientState.commit(), + consensusStateCommitment: consensusState.commit(), + height: header.l2Height + }); + } + + function misbehaviour( + uint32 clientId, + bytes calldata clientMessageBytes + ) external override onlyIBC { + revert EvmInCosmosLib.ErrInvalidMisbehaviour(); + } + + function verifyMembership( + uint32 clientId, + uint64 height, + bytes calldata proof, + bytes calldata path, + bytes calldata value + ) external virtual returns (bool) { + if (isFrozenImpl(clientId)) { + revert EvmInCosmosLib.ErrClientFrozen(); + } + bytes32 storageRoot = consensusStates[clientId][height].storageRoot; + bytes32 slot = keccak256( + abi.encodePacked( + keccak256(abi.encodePacked(path)), + EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT + ) + ); + (bool exists, bytes calldata provenValue) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot + ); + return exists && keccak256(value) == keccak256(provenValue); + } + + function verifyNonMembership( + uint32 clientId, + uint64 height, + bytes calldata proof, + bytes calldata path + ) external virtual returns (bool) { + if (isFrozenImpl(clientId)) { + revert EvmInCosmosLib.ErrClientFrozen(); + } + bytes32 storageRoot = consensusStates[clientId][height].storageRoot; + bytes32 slot = keccak256( + abi.encodePacked( + keccak256(abi.encodePacked(path)), + EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT + ) + ); + (bool exists, bytes calldata provenValue) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot + ); + return !exists; + } + + function getClientState( + uint32 clientId + ) external view returns (bytes memory) { + return clientStates[clientId].encode(); + } + + function getConsensusState( + uint32 clientId, + uint64 height + ) external view returns (bytes memory) { + return consensusStates[clientId][height].encode(); + } + + function getTimestampAtHeight( + uint32 clientId, + uint64 height + ) external view override returns (uint64) { + return consensusStates[clientId][height].timestamp; + } + + function getLatestHeight( + uint32 clientId + ) external view override returns (uint64) { + return clientStates[clientId].latestHeight; + } + + function isFrozen( + uint32 clientId + ) external view virtual returns (bool) { + return isFrozenImpl(clientId); + } + + function isFrozenImpl( + uint32 clientId + ) internal view returns (bool) { + uint32 l1ClientId = clientStates[clientId].l1ClientId; + return IBCStore(ibcHandler).getClient(l1ClientId).isFrozen(l1ClientId); + } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} + + function _onlyIBC() internal view { + if (msg.sender != ibcHandler) { + revert EvmInCosmosLib.ErrNotIBC(); + } + } + + modifier onlyIBC() { + _onlyIBC(); + _; + } +} diff --git a/evm/contracts/lib/MPTVerifier.sol b/evm/contracts/lib/MPTVerifier.sol new file mode 100644 index 0000000000..4d7199c391 --- /dev/null +++ b/evm/contracts/lib/MPTVerifier.sol @@ -0,0 +1,775 @@ +pragma solidity ^0.8.27; + +// custom bytes calldata pointer storing (length | offset) in one word, +// also allows calldata pointers to be stored in memory +type BytesCalldata is uint256; + +using BytesCalldataOps for BytesCalldata global; + +// can't introduce global using .. for non UDTs +// each consumer should add the following line: +using BytesCalldataOps for bytes; + +/** + * @author Theori, Inc + * @title BytesCalldataOps + * @notice Common operations for bytes calldata, implemented for both the builtin + * type and our BytesCalldata type. These operations are heavily optimized + * and omit safety checks, so this library should only be used when memory + * safety is not a security issue. + */ +library BytesCalldataOps { + function length( + BytesCalldata bc + ) internal pure returns (uint256 result) { + assembly { + result := shr(128, shl(128, bc)) + } + } + + function offset( + BytesCalldata bc + ) internal pure returns (uint256 result) { + assembly { + result := shr(128, bc) + } + } + + function convert( + BytesCalldata bc + ) internal pure returns (bytes calldata value) { + assembly { + value.offset := shr(128, bc) + value.length := shr(128, shl(128, bc)) + } + } + + function convert( + bytes calldata inp + ) internal pure returns (BytesCalldata bc) { + assembly { + bc := or(shl(128, inp.offset), inp.length) + } + } + + function slice( + BytesCalldata bc, + uint256 start, + uint256 len + ) internal pure returns (BytesCalldata result) { + assembly { + result := shl(128, add(shr(128, bc), start)) // add to the offset and clear the length + result := or(result, len) // set the new length + } + } + + function slice( + bytes calldata value, + uint256 start, + uint256 len + ) internal pure returns (bytes calldata result) { + assembly { + result.offset := add(value.offset, start) + result.length := len + } + } + + function prefix( + BytesCalldata bc, + uint256 len + ) internal pure returns (BytesCalldata result) { + assembly { + result := shl(128, shr(128, bc)) // clear out the length + result := or(result, len) // set it to the new length + } + } + + function prefix( + bytes calldata value, + uint256 len + ) internal pure returns (bytes calldata result) { + assembly { + result.offset := value.offset + result.length := len + } + } + + function suffix( + BytesCalldata bc, + uint256 start + ) internal pure returns (BytesCalldata result) { + assembly { + result := add(bc, shl(128, start)) // add to the offset + result := sub(result, start) // subtract from the length + } + } + + function suffix( + bytes calldata value, + uint256 start + ) internal pure returns (bytes calldata result) { + assembly { + result.offset := add(value.offset, start) + result.length := sub(value.length, start) + } + } + + function split( + BytesCalldata bc, + uint256 start + ) internal pure returns (BytesCalldata, BytesCalldata) { + return (prefix(bc, start), suffix(bc, start)); + } + + function split( + bytes calldata value, + uint256 start + ) internal pure returns (bytes calldata, bytes calldata) { + return (prefix(value, start), suffix(value, start)); + } +} + +/** + * @title RLP + * @author Theori, Inc. + * @notice Gas optimized RLP parsing code. Note that some parsing logic is + * duplicated because helper functions are oddly expensive. + */ +library RLP { + function parseUint( + bytes calldata buf + ) internal pure returns (uint256 result, uint256 size) { + assembly { + // check that we have at least one byte of input + if iszero(buf.length) { revert(0, 0) } + let first32 := calldataload(buf.offset) + let kind := shr(248, first32) + + // ensure it's a not a long string or list (> 0xB7) + // also ensure it's not a short string longer than 32 bytes (> 0xA0) + if gt(kind, 0xA0) { revert(0, 0) } + + switch lt(kind, 0x80) + case true { + // small single byte + result := kind + size := 1 + } + case false { + // short string + size := sub(kind, 0x80) + + // ensure it's not reading out of bounds + if lt(buf.length, size) { revert(0, 0) } + + switch eq(size, 32) + case true { + // if it's exactly 32 bytes, read it from calldata + result := calldataload(add(buf.offset, 1)) + } + case false { + // if it's < 32 bytes, we've already read it from calldata + result := shr(shl(3, sub(32, size)), shl(8, first32)) + } + size := add(size, 1) + } + } + } + + function nextSize( + bytes calldata buf + ) internal pure returns (uint256 size) { + assembly { + if iszero(buf.length) { revert(0, 0) } + let first32 := calldataload(buf.offset) + let kind := shr(248, first32) + + switch lt(kind, 0x80) + case true { + // small single byte + size := 1 + } + case false { + switch lt(kind, 0xB8) + case true { + // short string + size := add(1, sub(kind, 0x80)) + } + case false { + switch lt(kind, 0xC0) + case true { + // long string + let lengthSize := sub(kind, 0xB7) + + // ensure that we don't overflow + if gt(lengthSize, 31) { revert(0, 0) } + + // ensure that we don't read out of bounds + if lt(buf.length, lengthSize) { revert(0, 0) } + size := + shr(mul(8, sub(32, lengthSize)), shl(8, first32)) + size := add(size, add(1, lengthSize)) + } + case false { + switch lt(kind, 0xF8) + case true { + // short list + size := add(1, sub(kind, 0xC0)) + } + case false { + let lengthSize := sub(kind, 0xF7) + + // ensure that we don't overflow + if gt(lengthSize, 31) { revert(0, 0) } + // ensure that we don't read out of bounds + if lt(buf.length, lengthSize) { revert(0, 0) } + size := + shr(mul(8, sub(32, lengthSize)), shl(8, first32)) + size := add(size, add(1, lengthSize)) + } + } + } + } + } + } + + function skip( + bytes calldata buf + ) internal pure returns (bytes calldata) { + uint256 size = RLP.nextSize(buf); + assembly { + buf.offset := add(buf.offset, size) + buf.length := sub(buf.length, size) + } + return buf; + } + + function parseList( + bytes calldata buf + ) internal pure returns (uint256 listSize, uint256 offset) { + assembly { + // check that we have at least one byte of input + if iszero(buf.length) { revert(0, 0) } + let first32 := calldataload(buf.offset) + let kind := shr(248, first32) + + // ensure it's a list + if lt(kind, 0xC0) { revert(0, 0) } + + switch lt(kind, 0xF8) + case true { + // short list + listSize := sub(kind, 0xC0) + offset := 1 + } + case false { + // long list + let lengthSize := sub(kind, 0xF7) + + // ensure that we don't overflow + if gt(lengthSize, 31) { revert(0, 0) } + // ensure that we don't read out of bounds + if lt(buf.length, lengthSize) { revert(0, 0) } + listSize := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) + offset := add(lengthSize, 1) + } + } + } + + function splitBytes( + bytes calldata buf + ) internal pure returns (bytes calldata result, bytes calldata rest) { + uint256 offset; + uint256 size; + assembly { + // check that we have at least one byte of input + if iszero(buf.length) { revert(0, 0) } + let first32 := calldataload(buf.offset) + let kind := shr(248, first32) + + // ensure it's a not list + if gt(kind, 0xBF) { revert(0, 0) } + + switch lt(kind, 0x80) + case true { + // small single byte + offset := 0 + size := 1 + } + case false { + switch lt(kind, 0xB8) + case true { + // short string + offset := 1 + size := sub(kind, 0x80) + } + case false { + // long string + let lengthSize := sub(kind, 0xB7) + + // ensure that we don't overflow + if gt(lengthSize, 31) { revert(0, 0) } + // ensure we don't read out of bounds + if lt(buf.length, lengthSize) { revert(0, 0) } + size := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) + offset := add(lengthSize, 1) + } + } + + result.offset := add(buf.offset, offset) + result.length := size + + let end := add(offset, size) + rest.offset := add(buf.offset, end) + rest.length := sub(buf.length, end) + } + } + + function encodeUint( + uint256 value + ) internal pure returns (bytes memory) { + // allocate our result bytes + bytes memory result = new bytes(33); + + if (value == 0) { + // store length = 1, value = 0x80 + assembly { + mstore(add(result, 1), 0x180) + } + return result; + } + + if (value < 128) { + // store length = 1, value = value + assembly { + mstore(add(result, 1), or(0x100, value)) + } + return result; + } + + if ( + value + > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ) { + // length 33, prefix 0xa0 followed by value + assembly { + mstore(add(result, 1), 0x21a0) + mstore(add(result, 33), value) + } + return result; + } + + if ( + value + > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ) { + // length 32, prefix 0x9f followed by value + assembly { + mstore(add(result, 1), 0x209f) + mstore(add(result, 33), shl(8, value)) + } + return result; + } + + assembly { + let length := 1 + for { let min := 0x100 } lt(sub(min, 1), value) { + min := shl(8, min) + } { length := add(length, 1) } + + let bytesLength := add(length, 1) + + // bytes length field + let hi := shl(mul(bytesLength, 8), bytesLength) + + // rlp encoding of value + let lo := or(shl(mul(length, 8), add(length, 0x80)), value) + + mstore(add(result, bytesLength), or(hi, lo)) + } + return result; + } +} + +library MPTVerifier { + using BytesCalldataOps for bytes; + + struct Node { + BytesCalldata data; + bytes32 hash; + } + + // prefix constants + uint8 constant ODD_LENGTH = 1; + uint8 constant LEAF = 2; + uint8 constant MAX_PREFIX = 3; + + function parseHash( + bytes calldata buf + ) internal pure returns (bytes32 result, uint256 offset) { + uint256 value; + (value, offset) = RLP.parseUint(buf); + result = bytes32(value); + } + + /** + * @notice parses concatenated MPT nodes into processed Node structs + * @param input the concatenated MPT nodes + * @return result the parsed nodes array, containing a calldata slice and hash + * for each node + */ + function parseNodes( + bytes calldata input + ) internal pure returns (Node[] memory result) { + uint256 freePtr; + uint256 firstNode; + + // we'll use a dynamic amount of memory starting at the free pointer + // it is crucial that no other allocations happen during parsing + assembly { + freePtr := mload(0x40) + + // corrupt free pointer to cause out-of-gas if allocation occurs + mstore( + 0x40, + 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + ) + + firstNode := freePtr + } + + uint256 count; + while (input.length > 0) { + (uint256 listsize, uint256 offset) = RLP.parseList(input); + bytes calldata node = input.slice(offset, listsize); + BytesCalldata slice = node.convert(); + + uint256 len; + assembly { + len := add(listsize, offset) + + // compute node hash + calldatacopy(freePtr, input.offset, len) + let nodeHash := keccak256(freePtr, len) + + // store the Node struct (calldata slice and hash) + mstore(freePtr, slice) + mstore(add(freePtr, 0x20), nodeHash) + + // advance pointer + count := add(count, 1) + freePtr := add(freePtr, 0x40) + } + + input = input.suffix(len); + } + + assembly { + // allocate the result array and fill it with the node pointers + result := freePtr + mstore(result, count) + freePtr := add(freePtr, 0x20) + for { let i := 0 } lt(i, count) { i := add(i, 1) } { + mstore(freePtr, add(firstNode, mul(0x40, i))) + freePtr := add(freePtr, 0x20) + } + + // update the free pointer + mstore(0x40, freePtr) + } + } + + /** + * @notice parses a compressed MPT proof into arrays of Node structs + * @param nodes the set of nodes used in the compressed proofs + * @param compressed the compressed MPT proof + * @param count the number of proofs expected from the compressed proof + * @return result the array of proofs + */ + function parseCompressedProofs( + Node[] memory nodes, + bytes calldata compressed, + uint256 count + ) internal pure returns (Node[][] memory result) { + uint256 resultPtr; + uint256 freePtr; + + // we'll use a dynamic amount of memory starting at the free pointer + // it is crucial that no other allocations happen during parsing + assembly { + result := mload(0x40) + + // corrupt free pointer to cause out-of-gas if allocation occurs + mstore( + 0x40, + 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + ) + + mstore(result, count) + resultPtr := add(result, 0x20) + freePtr := add(resultPtr, mul(0x20, count)) + } + + (uint256 listSize, uint256 offset) = RLP.parseList(compressed); + compressed = compressed.slice(offset, listSize); + + // parse the indices and populate the proof list + for (; count > 0; count--) { + bytes calldata indices; + (listSize, offset) = RLP.parseList(compressed); + indices = compressed.slice(offset, listSize); + compressed = compressed.suffix(listSize + offset); + + // begin next proof array + uint256 arr; + assembly { + arr := freePtr + freePtr := add(freePtr, 0x20) + } + + // fill proof array + uint256 len; + for (len = 0; indices.length > 0; len++) { + uint256 idx; + (idx, offset) = RLP.parseUint(indices); + indices = indices.suffix(offset); + require( + idx < nodes.length, "invalid node index in compressed proof" + ); + assembly { + let node := mload(add(add(nodes, 0x20), mul(0x20, idx))) + mstore(freePtr, node) + freePtr := add(freePtr, 0x20) + } + } + + assembly { + // store the array length + mstore(arr, len) + + // store the array pointer in the result + mstore(resultPtr, arr) + resultPtr := add(resultPtr, 0x20) + } + } + + assembly { + // update the free pointer + mstore(0x40, freePtr) + } + } + + /** + * @notice Checks if the provided bytes match the key at a given offset + * @param key the MPT key to check against + * @param keyLen the length (in nibbles) of the key + * @param testBytes the subkey to check + */ + function subkeysEqual( + bytes32 key, + uint256 keyLen, + bytes calldata testBytes + ) private pure returns (bool result) { + // arithmetic cannot overflow because testBytes is from calldata + uint256 nibbleLength; + unchecked { + nibbleLength = 2 * testBytes.length; + require(nibbleLength <= keyLen); + } + + assembly { + let shiftAmount := sub(256, shl(2, nibbleLength)) + let testValue := shr(shiftAmount, calldataload(testBytes.offset)) + let subkey := shr(shiftAmount, key) + result := eq(testValue, subkey) + } + } + + /** + * @notice checks the MPT proof. Note: for certain optimizations, we assume + * that the rootHash belongs to a valid ethereum block. Correctness + * is only guaranteed in that case. + * Gas usage depends on both proof size and key nibble values. + * Gas usage for actual ethereum account proofs: ~ 30000 - 45000 + * @param nodes MPT proof nodes, parsed using parseNodes() + * @param key the MPT key, padded with trailing 0s if needed + * @param keyLen the byte length of the MPT key, must be <= 32 + * @param expectedHash the root hash of the MPT + */ + function verifyTrieValueWithNodes( + Node[] memory nodes, + bytes32 key, + uint256 keyLen, + bytes32 expectedHash + ) internal pure returns (bool exists, bytes calldata value) { + // handle completely empty trie case + if (nodes.length == 0) { + require(keccak256(hex"80") == expectedHash, "root hash incorrect"); + return (false, msg.data[:0]); + } + + // we will read the key nibble by nibble, so double the length + unchecked { + keyLen *= 2; + } + + // initialize return values to make solc happy; + // one will always be overwritten before returing + assembly { + value.offset := 0 + value.length := 0 + } + exists = true; + + // we'll use nodes as a pointer, advancing through each element + // end will point to the end of the array + uint256 end; + assembly { + end := add(nodes, add(0x20, mul(0x20, mload(nodes)))) + nodes := add(nodes, 0x20) + } + + while (true) { + bytes calldata node; + { + BytesCalldata slice; + bytes32 nodeHash; + + // load the element and advance the proof pointer + assembly { + // bounds checking + if iszero(lt(nodes, end)) { revert(0, 0) } + + let ptr := mload(nodes) + nodes := add(nodes, 0x20) + + slice := mload(ptr) + nodeHash := mload(add(ptr, 0x20)) + } + node = slice.convert(); + + require(nodeHash == expectedHash, "node hash incorrect"); + } + + // find the length of the first two elements + uint256 size = RLP.nextSize(node); + unchecked { + size += RLP.nextSize(node.suffix(size)); + } + + // we now know which type of node we're looking at: + // leaf + extension nodes have 2 list elements, branch nodes have 17 + if (size == node.length) { + // only two elements, leaf or extension node + bytes calldata encodedPath; + (encodedPath, node) = RLP.splitBytes(node); + + // keep track of whether the key nibbles match + bool keysMatch; + + // the first nibble of the encodedPath tells us the type of + // node and if it contains an even or odd number of nibbles + uint8 firstByte = uint8(encodedPath[0]); + uint8 prefix = firstByte >> 4; + require(prefix <= MAX_PREFIX); + if (prefix & ODD_LENGTH == 0) { + // second nibble is padding, must be 0 + require(firstByte & 0xf == 0); + keysMatch = true; + } else { + // second nibble is part of key + keysMatch = (firstByte & 0xf) == (uint8(bytes1(key)) >> 4); + unchecked { + key <<= 4; + keyLen--; + } + } + + // check the remainder of the encodedPath + encodedPath = encodedPath.suffix(1); + keysMatch = keysMatch && subkeysEqual(key, keyLen, encodedPath); + // cannot overflow because encodedPath is from calldata + unchecked { + key <<= 8 * encodedPath.length; + keyLen -= 2 * encodedPath.length; + } + + if (prefix & LEAF == 0) { + // extension can't prove nonexistence, subkeys must match + require(keysMatch); + + (expectedHash,) = parseHash(node); + } else { + // leaf node, must have used all of key + require(keyLen == 0); + + if (keysMatch) { + // if keys equal, we found the value + (value, node) = RLP.splitBytes(node); + break; + } else { + // if keys aren't equal, key doesn't exist + exists = false; + break; + } + } + } else { + // branch node, this is the hotspot for gas usage + + // there should be 17 elements (16 branch hashes + a value) + // we won't explicitly check this in order to save gas, since + // it's implied by inclusion in a valid ethereum block + + // also note, we never need the value element because we assume + // uniquely-prefixed keys, so branch nodes never hold values + + // fetch the branch for the next nibble of the key + uint256 keyNibble = uint256(key >> 252); + + // skip past the branches we don't need + // we already skipped past 2 elements; start there if we can + uint256 i = 0; + if (keyNibble >= 2) { + i = 2; + node = node.suffix(size); + } + while (i < keyNibble) { + node = RLP.skip(node); + unchecked { + i++; + } + } + + (expectedHash,) = parseHash(node); + // if we've reached an empty branch, key doesn't exist + if (expectedHash == 0) { + exists = false; + break; + } + unchecked { + key <<= 4; + keyLen -= 1; + } + } + } + } + + /** + * @notice checks the MPT proof. Note: for certain optimizations, we assume + * that the rootHash belongs to a valid ethereum block. Correctness + * is only guaranteed in that case. + * Gas usage depends on both proof size and key nibble values. + * Gas usage for actual ethereum account proofs: ~ 30000 - 45000 + * @param proof the encoded MPT proof noodes concatenated + * @param key the MPT key, padded with trailing 0s if needed + * @param rootHash the root hash of the MPT + */ + function verifyTrieValue( + bytes calldata proof, + bytes32 key, + bytes32 rootHash + ) internal pure returns (bool exists, bytes calldata value) { + Node[] memory nodes = parseNodes(proof); + return verifyTrieValueWithNodes(nodes, key, 32, rootHash); + } +} diff --git a/evm/evm.nix b/evm/evm.nix index 0af13da397..b747e9acc2 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -530,39 +530,39 @@ _: { } ); - # Stack too deep :) + # Stack too deep :), again # - solidity-coverage = - pkgs.runCommand "solidity-coverage" - { - buildInputs = [ - self'.packages.forge - pkgs.lcov - ]; - } - '' - cp --no-preserve=mode -r ${evmSources}/* . - FOUNDRY_PROFILE="test" forge coverage --ir-minimum --report lcov - lcov --remove ./lcov.info -o ./lcov.info.pruned \ - 'contracts/Multicall.sol' \ - 'contracts/clients/Verifier.sol' \ - 'contracts/apps/ucs/00-pingpong/*' \ - 'contracts/lib/*' \ - 'contracts/core/OwnableIBCHandler.sol' \ - 'contracts/core/24-host/IBCCommitment.sol' \ - 'contracts/core/25-handler/IBCHandler.sol' \ - 'contracts/clients/ICS23MembershipVerifier.sol' \ - 'tests/*' - genhtml lcov.info.pruned -o $out --branch-coverage - mv lcov.info.pruned $out/lcov.info - ''; - show-solidity-coverage = pkgs.writeShellApplication { - name = "show-solidity-coverage"; - runtimeInputs = [ ]; - text = '' - xdg-open ${self'.packages.solidity-coverage}/index.html - ''; - }; + # solidity-coverage = + # pkgs.runCommand "solidity-coverage" + # { + # buildInputs = [ + # self'.packages.forge + # pkgs.lcov + # ]; + # } + # '' + # cp --no-preserve=mode -r ${evmSources}/* . + # FOUNDRY_PROFILE="test" forge coverage --ir-minimum --report lcov + # lcov --remove ./lcov.info -o ./lcov.info.pruned \ + # 'contracts/Multicall.sol' \ + # 'contracts/clients/Verifier.sol' \ + # 'contracts/apps/ucs/00-pingpong/*' \ + # 'contracts/lib/*' \ + # 'contracts/core/OwnableIBCHandler.sol' \ + # 'contracts/core/24-host/IBCCommitment.sol' \ + # 'contracts/core/25-handler/IBCHandler.sol' \ + # 'contracts/clients/ICS23MembershipVerifier.sol' \ + # 'tests/*' + # genhtml lcov.info.pruned -o $out --branch-coverage + # mv lcov.info.pruned $out/lcov.info + # ''; + # show-solidity-coverage = pkgs.writeShellApplication { + # name = "show-solidity-coverage"; + # runtimeInputs = [ ]; + # text = '' + # xdg-open ${self'.packages.solidity-coverage}/index.html + # ''; + # }; hubble-abis = let diff --git a/evm/scripts/Deploy.s.sol b/evm/scripts/Deploy.s.sol index 08de9776ad..571ebfe0a3 100644 --- a/evm/scripts/Deploy.s.sol +++ b/evm/scripts/Deploy.s.sol @@ -16,6 +16,7 @@ import "../contracts/core/OwnableIBCHandler.sol"; import "../contracts/clients/CometblsClient.sol"; import {CosmosInCosmosClient} from "../contracts/clients/CosmosInCosmosClient.sol"; +import {EvmInCosmosClient} from "../contracts/clients/EvmInCosmosClient.sol"; import "../contracts/apps/ucs/00-pingpong/PingPong.sol"; import "../contracts/apps/ucs/01-relay/Relay.sol"; import "../contracts/apps/ucs/02-nft/NFT.sol"; diff --git a/evm/tests/src/lib/MPTVerifier.t.sol b/evm/tests/src/lib/MPTVerifier.t.sol new file mode 100644 index 0000000000..f185959db8 --- /dev/null +++ b/evm/tests/src/lib/MPTVerifier.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "../../../contracts/lib/MPTVerifier.sol"; + +contract MPTVerifierTests is Test { + /* Proof extracted from ethereum mainnet + * {"method":"eth_getProof","params":["0xd1d2eb1b1e90b638588728b4130137d262c87cae",["0x0"], "0x14655B2"],"id":1,"jsonrpc":"2.0"} + */ + function test_verify_ok() public { + vm.pauseGasMetering(); + bytes[] memory proof = new bytes[](7); + proof[0] = + hex"f90211a0b51ceda38c7c0d96cee1d651d8c9001299aae0a56dd4778366faccf8c89802f0a011e1adf2007c6afdc9300271c03ad104cf9ed625a3cca7050416449175f7ef21a0e4187606d7baba63b37fd6978f264374e8d7289da084c4a56170ce1e438ff0f0a061869b1b76c51cc75983fc4792b3fc9c1c5e366a76149979920143afd2899770a0ae2ffd634be69d00ca955e55ad4bb4c1065d40938f82f56d678a87180087d2aba0dcfab65101c9968d7891a91ffc1d6c8bcda2773458d802feca923a7d938f7695a0c62fdc1d9731b77b5310a9a9e1bc9edb79976637f6f29c13ce49459ef7cdb7d5a0fce12c4968e940f0f7dbe888d359b81425bde60f261761608465fd74fa390828a04f77e522f007df2b5c6090006e531d113647900ef01ce8ddad6b6b908e786ce9a04beb43119c19f9f2b94738830b8ca07ce2cb40a2fc60e51567810deda9719527a05085bfa24339e17ba1305a8d7c93468ab8414fde3b1b0ce77ea3f196e16217eaa0071e1a46d2a544b7cc24d3153619887ab88606501aea6f30f03e084dab9da01aa0a27d98ca7583cd6f303c41747e5109978c3399cb632283a9a6d5300366bfc97ca0c38268688069ddd9ec101532ea6f0253025f9df93c6d5e916968221232f8da00a0654ec1fadfb6c2d7849b96c26a1373e111cc6fd30c408ee833e0e2a89c4828f7a04a1eba1371dffabf57cd6f2a1774d2d464968546390a9f4dd78a76444cfce53580"; + proof[1] = + hex"f90211a0e06a0657d0607ed2e2c32e879f439169a7fc4af77b35d9932dbeb2dfebe695c7a0a36e146bac35dbfac21f392c4030f374d6a749fbb09a17f61763374b758850f7a06984b50415a207367532fa5a6191f819b7ac6ef29164bc545b55d49397e2651fa022cc8e966d7c342d94abbe77dbcfb0a52b123f8117d78aa50463b8355acaadaaa0fcd4ae819a2addc899ddc0dda500f51bf61e2f20b3835c9ebd1011d62f28c934a0db9ed7b2486bb67a7971ae8c29683266ad9add781a1825de4e36c890e0f3cdf6a03b12d6c5b2b7211fbe8be70283bfdb3a382f85ec3db7f4e40733037b0d74dd7fa0b47f4e1076af0e9fe906587dd314896bcf4e496660b1f48a7fffd23f82b2ab5da098b00edd42f648defdd793c58f1d7f62ffa20f0b2b49073901496b9735e70e39a03adf726421bddaf8624147ca2ec8abc017e40ad77eada3157da77078ea9adf22a0e9ae74e8967516c78db4ac8aa7de5d80f02cd78b63213c3ba3357410df0a2e04a0cf56383af5dbdf2f6a1faae0681f81d00235ed137d5ac60e9ec0ff6ec8c37617a03826b3b060923bb30be9247355a3a7f570798bd1bdffeaf8c9100a1148f2071ca0a26f8f831f9939d92f85544dd55113f789a2042c5bf6adbf2f1fa261ad0d1266a0d07681f008065226a1ca369667ca08e1b6f76e92e0943d8075a25cb44c000814a01bcb25ed256b6742464e7229c04009279be5064d99ff5beb73361c5e79e6a55480"; + proof[2] = + hex"f90211a09dd4cacf70185dbb577b5c33f042e6f9c549c52331097b52258f214247891619a022dbeaa0beca42f9149bed094e43e8917ae33fb12f4f0ef73918174726a51145a0dc8b421a2dba882b708f4f919a5d150d5ac23098758b51e6582d89e32749d25ba0791a4ba7cd6cbbbe430be28fb6274d96493387c57701f7e93032db5fd149f45ca04d7fb99c8c354be9261724a4fa71c5be06132c600179725e2e5c86c60251da64a042356ecfc50f7b34d72e0dd7317f259fbbbe3f34db86baa21dcf35020f033ae5a01baf1a9894e8a1d9421e89c5b35f9cb1a97d85de2eeaca8cb2887e8acb41339ea0e81d4a406243c4093b54586b4ff46013065260f9e5ef4c00f582a813fc403ae9a0299b27a27116a228dfa8c54475ff83b5f6cb0b220193d3e725d7384c2540f697a0bc67389552138a30be44b93506db12b3c744a49d0cd305284ade2147f1a6d24ba0b15bbf046f439753f42359ecd4c0a9b9bbfc7839db828a683d64d0f1e0eb2609a0b145b111161cfe54b14a06c0d9166303c3531ce1b28fabda37bb65716fc0f0b5a081da4b16a391ec0c44c1008368daeb742f023e953cdb4c22cb125cf67c16c3b8a002ce122b0ece08a93c64c24462663e5671c832091aea0a549b8d97cfe17f5c6aa03a507cd55ea9be74639d78ade7135a6816f677c85e29d9e575ee44b3b470bbe2a05049a431f1ce759fb5fe0053c1ceaa64fc3c3b69a4e936502946080a452e0bbc80"; + proof[3] = + hex"f90211a03600c8c217ed73076745dc695be6dd82dae247c6f8a1ab5a54c138a05430847ea0f1c001ada8d78660bd4e76f7e826d851c5cc8286f86d625be87b34b4f751be29a09a0cca8adf25996197bac969a6b51ff5c4277ef98d63a104c1d36a78c71f1e34a0ff0f8508adb6649051f05bee1918e4e4369ad69ddf75ee8bf85617cd5092ffdda01af98a2f12fb227c56da4fde86d2728154774385f9d4a3439c31b4c58e7fe69fa04303c4a3a0718a2d0802aba9c2f79d60b3da50ae58a66ca96d19d12ee1dad10fa066c67418b17b99c11716aa93dd2f37948f335f5028467deb592c5a760f406513a0b879342a5e6e0f0fcd9782bede64a229619485c1f00f7358fb4fb5a09277bc8ea0d3af6303bae36da3f7c9a77027b3cdbb752efe0ece4df34f12bc95f8b2a2c982a0685aea1196bd14056d1fe4d1ab6376239a2765865bec5e6a58a8ed3cf3687beba0f60c3da6c4bbc68bc02ae62ed14a53c6b456fb39f9a6a29192a3280eba80ca83a07eb4d7ee61fac4cd7d6b60ebd40e39ab67d149f92092fb4544886fcb2c129d98a089165c6c4ec484255323338862b5b4f84c1a425a0544a6197a44de209545163aa0969c546e88a081325e4e94790d16a42b737fcd4449f3ab5cf0357a369e50b1cfa0d4ef7c6ba9e64d60179b5a7cb84b678c9374b232d60edd14b42bcc856950726ca0d0a3de051337fee25685748612adde9bebc1acff896d2142eef25cd85e69b5c380"; + proof[4] = + hex"f8b180808080a0595f19b886413b654f6ec0fc17933985ec962dc71f526bbb58111ce8a6169f5d80808080a00803d929cf7dd0abafcc85912206b2f29b3c3d39b1042def65b0891c3891ef0d80a0a1793298225c34075209c538c84c18c39760df1b851cccb42b039a83e612469ca0b63459eca6ea6705c6b4067115a73202bba37f32db40d5df642138f46f98195980a08c14912091a7a7a1b45434498d91c48461dc50aff8895b0402f7c950b2c4f2178080"; + proof[5] = + hex"f8518080808080a03073e62a78a3f9f4d405308b3da3311763019d81f315ea983561f9cebd783b18808080808080a099d0f432e9ce1cb35f07c66e4936a06134dc48b4d43d4c058fe17dc0ecb281ff80808080"; + proof[6] = + hex"e09e20d9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56301"; + bytes memory proofChain = hex""; + for (uint256 i = 0; i < 7; i++) { + proofChain = abi.encodePacked(proofChain, proof[i]); + } + this.checkExistence( + 0x195170ca4e76873504de92ee3651ba91e339555d9d008c5995e51c2c3ada74eb, + proofChain, + hex"00", + hex"01" + ); + } + + function checkExistence( + bytes32 storageRoot, + bytes calldata proof, + bytes32 slot, + bytes calldata expectedValue + ) public { + vm.resumeGasMetering(); + (bool exists, bytes calldata value) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot + ); + assertEq(exists, true); + assertEq(value, expectedValue); + } + + function test_verify_absence_ok() public { + vm.pauseGasMetering(); + bytes[] memory proof = new bytes[](6); + proof[0] = + hex"f90211a0b51ceda38c7c0d96cee1d651d8c9001299aae0a56dd4778366faccf8c89802f0a011e1adf2007c6afdc9300271c03ad104cf9ed625a3cca7050416449175f7ef21a0e4187606d7baba63b37fd6978f264374e8d7289da084c4a56170ce1e438ff0f0a061869b1b76c51cc75983fc4792b3fc9c1c5e366a76149979920143afd2899770a0ae2ffd634be69d00ca955e55ad4bb4c1065d40938f82f56d678a87180087d2aba0dcfab65101c9968d7891a91ffc1d6c8bcda2773458d802feca923a7d938f7695a0c62fdc1d9731b77b5310a9a9e1bc9edb79976637f6f29c13ce49459ef7cdb7d5a0fce12c4968e940f0f7dbe888d359b81425bde60f261761608465fd74fa390828a04f77e522f007df2b5c6090006e531d113647900ef01ce8ddad6b6b908e786ce9a04beb43119c19f9f2b94738830b8ca07ce2cb40a2fc60e51567810deda9719527a05085bfa24339e17ba1305a8d7c93468ab8414fde3b1b0ce77ea3f196e16217eaa0071e1a46d2a544b7cc24d3153619887ab88606501aea6f30f03e084dab9da01aa0a27d98ca7583cd6f303c41747e5109978c3399cb632283a9a6d5300366bfc97ca0c38268688069ddd9ec101532ea6f0253025f9df93c6d5e916968221232f8da00a0654ec1fadfb6c2d7849b96c26a1373e111cc6fd30c408ee833e0e2a89c4828f7a04a1eba1371dffabf57cd6f2a1774d2d464968546390a9f4dd78a76444cfce53580"; + proof[1] = + hex"f90211a0d2ad4e2cda383901cb101634058b0c28584f168817c8447237daeb3c9faf6a57a02aded885b60f7182faa84f45d253edfab7a4038ee77c0175a8a20c9ecdd5f7c1a0438af8d7678818cc1e6f8bd83fc257651d70271cb471aa44b8ae9ca1aaa39786a0461f00dce5db7e9aa3ea7809371894a533df231c19a7c560bc7dd8ed2da011bba0c7a35d543c85562f030230c6da9e7b14009763aede970ced5a7aa386efcc62aaa09ed4100cc66c040897a2f0ea8aad36b9e40a0a2d4c60ecd7e6f2fa1d0e9fe707a007dadece03c60c19d74e6b837b77016ec48bb2c03ee427f87a12fa2302414a70a099ea06c8a1ac2f55e4d92ee67d611246e4a5decbfacfb401280d36fdd7ed72f0a04314144712573463dd20917ec938c54c19d033345f4eefaa8176d5cc0947cd1ba0b186d1d4416d902040cd48bf76d66b9c07178a295eedd0b27dcdb9a1c62eaf3ea0725af34d18fa40a2bceb40044cc53887f6142ce85a117594880a961d7338ff5da0a5e885ec57fe93a8ea19d0a7d59978504369d1d70cc6725be2c3ce8269e8e43da03a9da344b108b810acdbb908a5126884db431ab6f92c1ae4ec8588878c9755e0a060725a3a4909300cb466cead03f96631829328cad205178b209075f93e3fba2aa088932d16240dba2d1e610ba21f8ae39eec93c7ea2b7f24cfaf5382b9473a6df7a07c8ef77d3c8bf3eba9410f21bd181cd5e3ab748853591bcb4590b369b51f8b6c80"; + proof[2] = + hex"f90211a0c3f832d8e98835a14ea9a03c00a01cbf29276bfb67651eac13718367a4ad0e76a0db6816cae273414a9a5df1737a56881974aed8c1d9b04cf1f507046a61cc7b1ba04063f66644dda617fe33e8d5ea79a099060be0b795b1bcae23f3385368176e0fa08dbda17ff3ac3ac7119738929d5b3e7b3693a838fdc619dfaa1c3edf56d2d471a0cf3b6bca6c7f17ba1850a73298fd39ee664da3477fec632db21b4dcd606e000aa026cee290ba68be291699ccfdf9a36b9c71095823fdd7a2728c3c7c4de7a5a5a7a078edcc7bd5823abc0d061a6161f08b8570a0ea1e18c37bd45daa2c8f92e66c79a05aa01ed340d6660dfbba7d83ed8f3f7c67051eacb094539f4c6e66f5380ac811a0607e85089c3c93110100f72589316c5ee05a294170b9c7c237ba3e6d7b43fe24a08f62d04a9f3f53bbc0eedbd6977ab1b310f95dfac18c474e18276d10e841eaf8a016faef66b158b706e9ef82cbce0af7633a07a692b4efb0c190a7b512bdd60d53a0cf3b7eec574b244719ec6e6839d77ae6729b601f08ed95005604a8db1ab00a09a0cd3c3095df97c9704ca69e39f5447e025dddde3a88dd627d6b53498e752b1222a0633ba144a3457f62fee4d360651e9bf74b47e586a0ea728f007c5384782d40bba05fa650d9a8e984e22122a9e0f784c05eaaf715a6c47b4e25ef11f5c76eaa4a7da0ee4d4150dc5bcb72e56b064379612bf9f98f737669ddb2dee0fc37e106b6f07480"; + proof[3] = + hex"f90211a05c977fc66d9a243988617d8342a5b16e5c699dad0d0fb41b9e2338eeb50d3243a0a384c741370fc093dcfd685ef64e17cd7f60620e3c5667944875c711e866f04fa0f1b3c8885165550d9c6210b68c06f403deec85c80ce416622704f9c4f2fb0d67a046f967f97da2767a0441a3b6e781d50a2d4c0bcc8486712f3fb2fbedaa33b656a0bb65d399adb76fdcf6b65a191886fb1371bcf24508f75a7cce35bd89a35117a5a0464f1500e7d54aa4947a16d0f6853db12cca3fb0d3e75b05b44d64ea3af6fcfca02fd9eca689c827f6583dadca743f4fb215b2098fd1fceb0ce2641d25191058d3a03d3507f62fc570fb59a2a6af53a0902ab6c09f7ae8553f81ff408d03fef60258a0cf027ae79de4be99b553c4107000c7dbbe0f86a8feceb0e84cf54e47e00676c1a0759bc87c6eebad2fa9ce8d3ab17926bae7140dc63e92ac9eff66b3e7c0bd0688a02b3a89578a034492fd59e93d0db0112ca9a3291ddcc631b7786edb10859d8015a0ca725677841d29a9acc1a1b61232d2d8332649b33ddd2160294901e20dac5e96a08f494c65b6346ca006ffbc13fce5089beb3f3e60b85091e598baef14abccead1a0dec06af2f069a106a419338744178e5b240302c17db4dc61a88c78ac6763f1eba0713ef37453d2d3c1fbe0a897c9f0757a3d1fdb53a20504aee155da26df70c117a0c4c286a1d37c2bb0a776d0895391e5dd89b1d902ac1526dedead2859faf8137d80"; + proof[4] = + hex"f89180a0b0ed89cc0fc540cb9cf910b35e5a017a0f171ef740c046716e23c35af3e668b380a05fff94dc1b65c002a713803772dc0ea531377d4e35f723163b8ee85a9747dc47a0f818819b52ccd3f4617889ab8480540cf7f2d591dade8b04a843d5ab3bb3781980808080808080a0e826c12966bfaf8019ae0e008dd2a972b92255bb7bab9a9283d6803f1de1a19d80808080"; + proof[5] = hex""; + bytes memory proofChain = hex""; + for (uint256 i = 0; i < 6; i++) { + proofChain = abi.encodePacked(proofChain, proof[i]); + } + this.checkNonexistence( + 0x195170ca4e76873504de92ee3651ba91e339555d9d008c5995e51c2c3ada74eb, + proofChain, + bytes32(uint256(1)) + ); + } + + function checkNonexistence( + bytes32 storageRoot, + bytes calldata proof, + bytes32 slot + ) public { + vm.resumeGasMetering(); + (bool exists, bytes calldata value) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot + ); + assertEq(exists, false); + } +} From f6c23c579bfdd2b092e4053a4246284082244700 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 16 Dec 2024 13:42:31 +0100 Subject: [PATCH 02/11] chore(evm): rearrange libraries --- evm/contracts/clients/CometblsClient.sol | 11 +++++------ evm/contracts/clients/CosmosInCosmosClient.sol | 7 +++---- .../Verifier.sol => lib/CometblsZKVerifier.sol} | 2 +- .../ICS23Verifier.sol} | 2 +- evm/evm.nix | 2 -- evm/tests/src/Verifier.t.sol | 2 +- 6 files changed, 11 insertions(+), 15 deletions(-) rename evm/contracts/{clients/Verifier.sol => lib/CometblsZKVerifier.sol} (99%) rename evm/contracts/{clients/ICS23MembershipVerifier.sol => lib/ICS23Verifier.sol} (97%) diff --git a/evm/contracts/clients/CometblsClient.sol b/evm/contracts/clients/CometblsClient.sol index 564b7bb53a..556ec541e7 100644 --- a/evm/contracts/clients/CometblsClient.sol +++ b/evm/contracts/clients/CometblsClient.sol @@ -5,14 +5,13 @@ import "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol"; -import "./ICS23MembershipVerifier.sol"; -import "./Verifier.sol"; - import "../core/02-client/ILightClient.sol"; import "../core/24-host/IBCStore.sol"; import "../core/24-host/IBCCommitment.sol"; import "../lib/Common.sol"; import "../lib/ICS23.sol"; +import "../lib/CometblsZKVerifier.sol"; +import "../lib/ICS23Verifier.sol"; struct SignedHeader { uint64 height; @@ -392,7 +391,7 @@ contract CometblsClient is } bytes32 contractAddress = clientStates[clientId].contractAddress; bytes32 appHash = consensusStates[clientId][height].appHash; - return ICS23MembershipVerifier.verifyMembership( + return ICS23Verifier.verifyMembership( appHash, proof, abi.encodePacked(IBCStoreLib.COMMITMENT_PREFIX), @@ -414,7 +413,7 @@ contract CometblsClient is } bytes32 contractAddress = clientStates[clientId].contractAddress; bytes32 appHash = consensusStates[clientId][height].appHash; - return ICS23MembershipVerifier.verifyNonMembership( + return ICS23Verifier.verifyNonMembership( appHash, proof, abi.encodePacked(IBCStoreLib.COMMITMENT_PREFIX), @@ -540,7 +539,7 @@ contract CometblsClient is commitmentHash ]; - return Verifier.verifyProof( + return CometblsZKVerifier.verifyProof( zkp.proof, zkp.proofCommitment, zkp.proofCommitmentPOK, publicInputs ); } diff --git a/evm/contracts/clients/CosmosInCosmosClient.sol b/evm/contracts/clients/CosmosInCosmosClient.sol index 83d5897673..94322428e7 100644 --- a/evm/contracts/clients/CosmosInCosmosClient.sol +++ b/evm/contracts/clients/CosmosInCosmosClient.sol @@ -11,8 +11,7 @@ import "../core/24-host/IBCStore.sol"; import "../core/24-host/IBCCommitment.sol"; import "../lib/ICS23.sol"; import "../lib/Common.sol"; - -import "./ICS23MembershipVerifier.sol"; +import "../lib/ICS23Verifier.sol"; struct TendermintConsensusState { uint64 timestamp; @@ -216,7 +215,7 @@ contract CosmosInCosmosClient is revert CosmosInCosmosLib.ErrClientFrozen(); } bytes32 appHash = consensusStates[clientId][height].appHash; - return ICS23MembershipVerifier.verifyMembership( + return ICS23Verifier.verifyMembership( appHash, proof, abi.encodePacked(IBCStoreLib.COMMITMENT_PREFIX), @@ -235,7 +234,7 @@ contract CosmosInCosmosClient is revert CosmosInCosmosLib.ErrClientFrozen(); } bytes32 appHash = consensusStates[clientId][height].appHash; - return ICS23MembershipVerifier.verifyNonMembership( + return ICS23Verifier.verifyNonMembership( appHash, proof, abi.encodePacked(IBCStoreLib.COMMITMENT_PREFIX), diff --git a/evm/contracts/clients/Verifier.sol b/evm/contracts/lib/CometblsZKVerifier.sol similarity index 99% rename from evm/contracts/clients/Verifier.sol rename to evm/contracts/lib/CometblsZKVerifier.sol index 067a6b7c09..f41fb9933a 100644 --- a/evm/contracts/clients/Verifier.sol +++ b/evm/contracts/lib/CometblsZKVerifier.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; /// @author Remco Bloemen /// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed /// (256 bytes) and compressed (128 bytes) format. -library Verifier { +library CometblsZKVerifier { // Addresses of precompiles uint256 constant PRECOMPILE_MODEXP = 0x05; uint256 constant PRECOMPILE_ADD = 0x06; diff --git a/evm/contracts/clients/ICS23MembershipVerifier.sol b/evm/contracts/lib/ICS23Verifier.sol similarity index 97% rename from evm/contracts/clients/ICS23MembershipVerifier.sol rename to evm/contracts/lib/ICS23Verifier.sol index 9c138d9d79..f91241b891 100644 --- a/evm/contracts/clients/ICS23MembershipVerifier.sol +++ b/evm/contracts/lib/ICS23Verifier.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.27; import "../lib/ICS23.sol"; import "../lib/UnionICS23.sol"; -library ICS23MembershipVerifier { +library ICS23Verifier { function verifyMembership( bytes32 root, bytes calldata proof, diff --git a/evm/evm.nix b/evm/evm.nix index b747e9acc2..688c2c1fa9 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -545,13 +545,11 @@ _: { # FOUNDRY_PROFILE="test" forge coverage --ir-minimum --report lcov # lcov --remove ./lcov.info -o ./lcov.info.pruned \ # 'contracts/Multicall.sol' \ - # 'contracts/clients/Verifier.sol' \ # 'contracts/apps/ucs/00-pingpong/*' \ # 'contracts/lib/*' \ # 'contracts/core/OwnableIBCHandler.sol' \ # 'contracts/core/24-host/IBCCommitment.sol' \ # 'contracts/core/25-handler/IBCHandler.sol' \ - # 'contracts/clients/ICS23MembershipVerifier.sol' \ # 'tests/*' # genhtml lcov.info.pruned -o $out --branch-coverage # mv lcov.info.pruned $out/lcov.info diff --git a/evm/tests/src/Verifier.t.sol b/evm/tests/src/Verifier.t.sol index 5b8d2aa273..bd5bc4d3c4 100644 --- a/evm/tests/src/Verifier.t.sol +++ b/evm/tests/src/Verifier.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; -import "../../contracts/clients/Verifier.sol"; +import "../../contracts/lib/CometblsZKVerifier.sol"; import { CometblsClient, SignedHeader From 568e2006eccbaa70b09422c4d0748d7b3e1d5d32 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Fri, 27 Dec 2024 17:47:59 +0100 Subject: [PATCH 03/11] feat(voyager): evm state lens modules --- Cargo.toml | 6 + evm/contracts/clients/EvmInCosmosClient.sol | 80 ++--- .../src/02-client/CosmosInCosmosClient.t.sol | 1 - .../src/02-client/EvmInCosmosClient.t.sol | 29 ++ .../evm-light-client-types/Cargo.toml | 17 + .../src/client_state.rs | 21 ++ .../src/consensus_state.rs | 50 +++ .../evm-light-client-types/src/header.rs | 9 + .../evm-light-client-types/src/lib.rs | 5 + lib/voyager-core/src/lib.rs | 6 + lib/voyager-message/src/lib.rs | 41 +++ .../modules/client/state-lens/evm/Cargo.toml | 25 ++ .../modules/client/state-lens/evm/src/main.rs | 215 ++++++++++++ .../consensus/state-lens/evm/Cargo.toml | 28 ++ .../consensus/state-lens/evm/src/main.rs | 132 ++++++++ .../client-update/state-lens/evm/Cargo.toml | 29 ++ .../client-update/state-lens/evm/src/call.rs | 25 ++ .../state-lens/evm/src/callback.rs | 6 + .../client-update/state-lens/evm/src/data.rs | 4 + .../client-update/state-lens/evm/src/main.rs | 306 ++++++++++++++++++ 20 files changed, 989 insertions(+), 46 deletions(-) create mode 100644 evm/tests/src/02-client/EvmInCosmosClient.t.sol create mode 100644 lib/state-lens/evm-light-client-types/Cargo.toml create mode 100644 lib/state-lens/evm-light-client-types/src/client_state.rs create mode 100644 lib/state-lens/evm-light-client-types/src/consensus_state.rs create mode 100644 lib/state-lens/evm-light-client-types/src/header.rs create mode 100644 lib/state-lens/evm-light-client-types/src/lib.rs create mode 100644 voyager/modules/client/state-lens/evm/Cargo.toml create mode 100644 voyager/modules/client/state-lens/evm/src/main.rs create mode 100644 voyager/modules/consensus/state-lens/evm/Cargo.toml create mode 100644 voyager/modules/consensus/state-lens/evm/src/main.rs create mode 100644 voyager/plugins/client-update/state-lens/evm/Cargo.toml create mode 100644 voyager/plugins/client-update/state-lens/evm/src/call.rs create mode 100644 voyager/plugins/client-update/state-lens/evm/src/callback.rs create mode 100644 voyager/plugins/client-update/state-lens/evm/src/data.rs create mode 100644 voyager/plugins/client-update/state-lens/evm/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index f71a7e788c..e9ed31e9fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ members = [ "lib/tendermint-light-client-types", "lib/linea-light-client-types", "lib/movement-light-client-types", + "lib/state-lens/evm-light-client-types", # these will all be re enabled and updated once ethereum-light-client is updated @@ -114,6 +115,7 @@ members = [ "voyager/modules/client/ethereum", "voyager/modules/client/movement", "voyager/modules/client/tendermint", + "voyager/modules/client/state-lens/evm", "voyager/modules/client-bootstrap/cometbls", "voyager/modules/client-bootstrap/ethereum", @@ -125,12 +127,14 @@ members = [ "voyager/modules/consensus/ethereum", "voyager/modules/consensus/movement", "voyager/modules/consensus/tendermint", + "voyager/modules/consensus/state-lens/evm", "voyager/plugins/client-update/berachain", "voyager/plugins/client-update/cometbls", "voyager/plugins/client-update/ethereum", "voyager/plugins/client-update/movement", "voyager/plugins/client-update/tendermint", + "voyager/plugins/client-update/state-lens/evm", "voyager/plugins/periodic-client-update", @@ -208,6 +212,8 @@ scroll-api = { path = "lib/scroll-api", default-features = fal scroll-codec = { path = "lib/scroll-codec", default-features = false } scroll-rpc = { path = "lib/scroll-rpc", default-features = false } +evm-state-lens-light-client-types = { path = "lib/state-lens/evm-light-client-types", default-features = false } + tendermint-light-client = { path = "cosmwasm/union-ibc/light-clients/tendermint", default-features = false } tendermint-light-client-types = { path = "lib/tendermint-light-client-types", default-features = false } tendermint-verifier = { path = "lib/tendermint-verifier", default-features = false } diff --git a/evm/contracts/clients/EvmInCosmosClient.sol b/evm/contracts/clients/EvmInCosmosClient.sol index 4f69878ad2..7bbdbd8440 100644 --- a/evm/contracts/clients/EvmInCosmosClient.sol +++ b/evm/contracts/clients/EvmInCosmosClient.sol @@ -10,7 +10,6 @@ import "../core/02-client/ILightClient.sol"; import "../core/24-host/IBCStore.sol"; import "../core/24-host/IBCCommitment.sol"; import "../lib/ICS23.sol"; -import "../lib/Common.sol"; import "../lib/MPTVerifier.sol"; struct Header { @@ -22,9 +21,8 @@ struct Header { struct ClientState { uint32 l1ClientId; - uint32 l2ChainId; uint32 l2ClientId; - uint64 latestHeight; + uint64 l2LatestHeight; uint16 timestampOffset; uint16 stateRootOffset; uint16 storageRootOffset; @@ -44,7 +42,7 @@ library EvmInCosmosLib { error ErrClientFrozen(); error ErrInvalidL1Proof(); error ErrInvalidInitialConsensusState(); - error ErrInvalidMisbehaviour(); + error ErrUnsupported(); function encode( ConsensusState memory consensusState @@ -69,6 +67,15 @@ library EvmInCosmosLib { ) internal pure returns (bytes32) { return keccak256(encode(clientState)); } + + function extract( + bytes calldata input, + uint16 offset + ) internal pure returns (bytes32 val) { + assembly { + val := calldataload(add(input.offset, offset)) + } + } } contract EvmInCosmosClient is @@ -84,8 +91,6 @@ contract EvmInCosmosClient is mapping(uint32 => ClientState) private clientStates; mapping(uint32 => mapping(uint64 => ConsensusState)) private consensusStates; - mapping(uint32 => mapping(uint64 => ProcessedMoment)) private - processedMoments; constructor() { _disableInitializers(); @@ -112,20 +117,15 @@ contract EvmInCosmosClient is assembly { consensusState := consensusStateBytes.offset } - if (clientState.latestHeight == 0 || consensusState.timestamp == 0) { + if (clientState.l2LatestHeight == 0 || consensusState.timestamp == 0) { revert EvmInCosmosLib.ErrInvalidInitialConsensusState(); } clientStates[clientId] = clientState; - consensusStates[clientId][clientState.latestHeight] = consensusState; - // Normalize to nanosecond because ibc-go recvPacket expects nanos... - processedMoments[clientId][clientState.latestHeight] = ProcessedMoment({ - timestamp: block.timestamp * 1e9, - height: block.number - }); + consensusStates[clientId][clientState.l2LatestHeight] = consensusState; return ConsensusStateUpdate({ clientStateCommitment: clientState.commit(), consensusStateCommitment: consensusState.commit(), - height: clientState.latestHeight + height: clientState.l2LatestHeight }); } @@ -141,7 +141,7 @@ contract EvmInCosmosClient is assembly { header := clientMessageBytes.offset } - ClientState memory clientState = clientStates[clientId]; + ClientState storage clientState = clientStates[clientId]; ILightClient l1Client = IBCStore(ibcHandler).getClient(clientState.l1ClientId); // L₂[H₂] ∈ L₁[H₁] @@ -162,23 +162,22 @@ contract EvmInCosmosClient is } bytes calldata rawL2ConsensusState = header.l2ConsensusState; - uint64 timestampOffset = clientState.timestampOffset; - uint64 stateRootOffset = clientState.stateRootOffset; - uint64 storageRootOffset = clientState.storageRootOffset; - uint64 l2Timestamp; - bytes32 l2StateRoot; - bytes32 l2StorageRoot; - assembly { - l2Timestamp := - calldataload(add(rawL2ConsensusState.offset, timestampOffset)) - l2StateRoot := - calldataload(add(rawL2ConsensusState.offset, stateRootOffset)) - l2StorageRoot := - calldataload(add(rawL2ConsensusState.offset, storageRootOffset)) - } + uint64 l2Timestamp = uint64( + uint256( + EvmInCosmosLib.extract( + rawL2ConsensusState, clientState.timestampOffset + ) + ) + ); + bytes32 l2StateRoot = EvmInCosmosLib.extract( + rawL2ConsensusState, clientState.stateRootOffset + ); + bytes32 l2StorageRoot = EvmInCosmosLib.extract( + rawL2ConsensusState, clientState.storageRootOffset + ); - if (header.l2Height > clientState.latestHeight) { - clientState.latestHeight = header.l2Height; + if (header.l2Height > clientState.l2LatestHeight) { + clientState.l2LatestHeight = header.l2Height; } // L₂[H₂] = S₂ @@ -189,12 +188,6 @@ contract EvmInCosmosClient is consensusState.stateRoot = l2StateRoot; consensusState.storageRoot = l2StorageRoot; - // P[H₂] = now() - ProcessedMoment storage processed = - processedMoments[clientId][header.l2Height]; - processed.timestamp = block.timestamp * 1e9; - processed.height = block.number; - // commit(S₂) return ConsensusStateUpdate({ clientStateCommitment: clientState.commit(), @@ -207,7 +200,7 @@ contract EvmInCosmosClient is uint32 clientId, bytes calldata clientMessageBytes ) external override onlyIBC { - revert EvmInCosmosLib.ErrInvalidMisbehaviour(); + revert EvmInCosmosLib.ErrUnsupported(); } function verifyMembership( @@ -227,9 +220,8 @@ contract EvmInCosmosClient is EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT ) ); - (bool exists, bytes calldata provenValue) = MPTVerifier.verifyTrieValue( - proof, keccak256(abi.encodePacked(slot)), storageRoot - ); + (bool exists, bytes calldata provenValue) = + MPTVerifier.verifyTrieValue(proof, slot, storageRoot); return exists && keccak256(value) == keccak256(provenValue); } @@ -249,9 +241,7 @@ contract EvmInCosmosClient is EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT ) ); - (bool exists, bytes calldata provenValue) = MPTVerifier.verifyTrieValue( - proof, keccak256(abi.encodePacked(slot)), storageRoot - ); + (bool exists,) = MPTVerifier.verifyTrieValue(proof, slot, storageRoot); return !exists; } @@ -278,7 +268,7 @@ contract EvmInCosmosClient is function getLatestHeight( uint32 clientId ) external view override returns (uint64) { - return clientStates[clientId].latestHeight; + return clientStates[clientId].l2LatestHeight; } function isFrozen( diff --git a/evm/tests/src/02-client/CosmosInCosmosClient.t.sol b/evm/tests/src/02-client/CosmosInCosmosClient.t.sol index 767d81b347..275ced1c32 100644 --- a/evm/tests/src/02-client/CosmosInCosmosClient.t.sol +++ b/evm/tests/src/02-client/CosmosInCosmosClient.t.sol @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.27; import "forge-std/Test.sol"; diff --git a/evm/tests/src/02-client/EvmInCosmosClient.t.sol b/evm/tests/src/02-client/EvmInCosmosClient.t.sol new file mode 100644 index 0000000000..7355f98bfc --- /dev/null +++ b/evm/tests/src/02-client/EvmInCosmosClient.t.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "../core/IBCHandler.sol"; +import "../core/Relay.sol"; +import "../../../contracts/clients/EvmInCosmosClient.sol"; +import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; + +contract EvmInCosmosClientTest is Test { + EvmInCosmosClient client; + address admin = address(0xABCD); + address ibcHandler; + + function setUp() public { + ibcHandler = address(0xC0DE); + EvmInCosmosClient implementation = new EvmInCosmosClient(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(implementation), + abi.encodeWithSelector( + EvmInCosmosClient.initialize.selector, ibcHandler, admin + ) + ); + client = EvmInCosmosClient(address(proxy)); + } + + function test_initialize_ok() public { + assertEq(client.owner(), admin); + } +} diff --git a/lib/state-lens/evm-light-client-types/Cargo.toml b/lib/state-lens/evm-light-client-types/Cargo.toml new file mode 100644 index 0000000000..a568562e4e --- /dev/null +++ b/lib/state-lens/evm-light-client-types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +edition = "2021" +name = "evm-state-lens-light-client-types" +version = "0.1.0" + +[dependencies] +alloy = { workspace = true, features = ["sol-types"], optional = true } +protos = { workspace = true, optional = true, features = ["union+ibc+lightclients+berachain+v1"] } +serde = { workspace = true, optional = true, features = ["derive"] } +thiserror = { workspace = true } +unionlabs = { workspace = true } + +[features] +default = [] +ethabi = ["unionlabs/ethabi", "dep:alloy"] +proto = ["dep:protos"] +serde = ["dep:serde"] diff --git a/lib/state-lens/evm-light-client-types/src/client_state.rs b/lib/state-lens/evm-light-client-types/src/client_state.rs new file mode 100644 index 0000000000..998b8ccdb3 --- /dev/null +++ b/lib/state-lens/evm-light-client-types/src/client_state.rs @@ -0,0 +1,21 @@ +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ClientState { + /// l1 client id used to check the l2 inclusion proof against + pub l1_client_id: u32, + /// l2 chain id + pub l2_chain_id: String, + /// l2 client id + pub l2_client_id: u32, + /// l2 latest height + pub l2_latest_height: u64, + /// the offset at which we extract the u64 timestamp from the l2 consensus state + /// timestamp = consensus_state[timestamp_offset:timestamp_offset+8] + pub timestamp_offset: u16, + /// the offset at which we extract the bytes32 state root from the l2 consensus state + /// state_root = consensus_state[state_root_offset:state_root_offset+32] + pub state_root_offset: u16, + /// the offset at which we extract the bytes32 storage root (of the ibc contract on the l2) from the l2 consensus state + /// storage_root = consensus_state[storage_root_offset:storage_root_offset+32] + pub storage_root_offset: u16, +} diff --git a/lib/state-lens/evm-light-client-types/src/consensus_state.rs b/lib/state-lens/evm-light-client-types/src/consensus_state.rs new file mode 100644 index 0000000000..b55b86da8f --- /dev/null +++ b/lib/state-lens/evm-light-client-types/src/consensus_state.rs @@ -0,0 +1,50 @@ +use unionlabs::hash::H256; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConsensusState { + /// Timestamp of the execution layer. + pub timestamp: u64, + /// State root of the execution layer. + pub state_root: H256, + /// Storage root of the ibc contract extracted from the state root. + pub storage_root: H256, +} + +#[cfg(feature = "ethabi")] +pub mod ethabi { + use alloy::sol_types::SolValue; + use unionlabs::impl_ethabi_via_try_from_into; + + use super::*; + + impl_ethabi_via_try_from_into!(ConsensusState => SolConsensusState); + + alloy::sol! { + struct SolConsensusState { + uint64 timestamp; + bytes32 stateRoot; + bytes32 storageRoot; + } + } + + impl From for SolConsensusState { + fn from(value: ConsensusState) -> Self { + Self { + timestamp: value.timestamp, + stateRoot: value.state_root.get().into(), + storageRoot: value.storage_root.get().into(), + } + } + } + + impl From for ConsensusState { + fn from(value: SolConsensusState) -> Self { + Self { + timestamp: value.timestamp, + state_root: H256::new(value.stateRoot.0), + storage_root: H256::new(value.storageRoot.0), + } + } + } +} diff --git a/lib/state-lens/evm-light-client-types/src/header.rs b/lib/state-lens/evm-light-client-types/src/header.rs new file mode 100644 index 0000000000..0b3632238a --- /dev/null +++ b/lib/state-lens/evm-light-client-types/src/header.rs @@ -0,0 +1,9 @@ +use unionlabs::ibc::core::{client::height::Height, commitment::merkle_proof::MerkleProof}; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Header { + pub l1_height: Height, + pub l2_height: Height, + pub l2_consensus_state_proof: MerkleProof, +} diff --git a/lib/state-lens/evm-light-client-types/src/lib.rs b/lib/state-lens/evm-light-client-types/src/lib.rs new file mode 100644 index 0000000000..3d184a96ce --- /dev/null +++ b/lib/state-lens/evm-light-client-types/src/lib.rs @@ -0,0 +1,5 @@ +pub mod client_state; +pub mod consensus_state; +pub mod header; + +pub use crate::{client_state::ClientState, consensus_state::ConsensusState, header::Header}; diff --git a/lib/voyager-core/src/lib.rs b/lib/voyager-core/src/lib.rs index 158aac2702..9f88f2d0d0 100644 --- a/lib/voyager-core/src/lib.rs +++ b/lib/voyager-core/src/lib.rs @@ -113,6 +113,12 @@ impl ClientType { /// [Movement]: https://github.com/movementlabsxyz/movement pub const MOVEMENT: &'static str = "movement"; + /// A client tracking the Ethereum beacon chain consensus verified through the + /// [Ethereum Proof-of-Stake Consensus Specifications](spec). As an L2 extracted from [CometBLS]. + /// + /// [spec]: https://github.com/ethereum/consensus-specs + pub const STATE_LENS_EVM: &'static str = "state-lens/evm"; + // lots more to come - near, linea, polygon - stay tuned } diff --git a/lib/voyager-message/src/lib.rs b/lib/voyager-message/src/lib.rs index 6dab91a431..179c226f7f 100644 --- a/lib/voyager-message/src/lib.rs +++ b/lib/voyager-message/src/lib.rs @@ -22,6 +22,7 @@ use jsonrpsee::{ }; use macros::model; use reth_ipc::{client::IpcClientBuilder, server::RpcServiceBuilder}; +use rpc::{SelfClientState, SelfConsensusState}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; use tracing::{ @@ -564,6 +565,46 @@ impl ClientT for IdThreadClient { } impl VoyagerClient { + pub async fn query_latest_timestamp( + &self, + chain_id: ChainId, + finalized: bool, + ) -> RpcResult { + let latest_timestamp = self + .0 + .query_latest_timestamp(chain_id, finalized) + .await + .map_err(json_rpc_error_to_error_object)?; + + Ok(latest_timestamp) + } + + pub async fn self_client_state( + &self, + chain_id: ChainId, + height: QueryHeight, + ) -> RpcResult { + let client_state = self + .0 + .self_client_state(chain_id, height) + .await + .map_err(json_rpc_error_to_error_object)?; + Ok(client_state) + } + + pub async fn self_consensus_state( + &self, + chain_id: ChainId, + height: QueryHeight, + ) -> RpcResult { + let consensus_state = self + .0 + .self_consensus_state(chain_id, height) + .await + .map_err(json_rpc_error_to_error_object)?; + Ok(consensus_state) + } + pub async fn query_latest_height( &self, chain_id: ChainId, diff --git a/voyager/modules/client/state-lens/evm/Cargo.toml b/voyager/modules/client/state-lens/evm/Cargo.toml new file mode 100644 index 0000000000..50dfaf3b16 --- /dev/null +++ b/voyager/modules/client/state-lens/evm/Cargo.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "voyager-client-module-state-lens-evm" +version = "0.1.0" + +[dependencies] +beacon-api-types = { workspace = true, features = ["serde"] } +chain-utils = { workspace = true } +enumorph = { workspace = true } +ethereum-light-client-types = { workspace = true, features = ["serde", "ethabi"] } +evm-state-lens-light-client-types = { workspace = true, features = ["serde", "ethabi"] } +futures = { workspace = true } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +macros = { workspace = true } +prost = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde-utils = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +unionlabs = { workspace = true } +voyager-message = { workspace = true } +voyager-vm = { workspace = true } diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs new file mode 100644 index 0000000000..a8007247e2 --- /dev/null +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -0,0 +1,215 @@ +use ethereum_light_client_types::StorageProof; +use evm_state_lens_light_client_types::{ClientState, ConsensusState, Header}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + types::ErrorObject, + Extensions, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use tracing::instrument; +use unionlabs::{ + self, + bytes::Bytes, + encoding::{Bincode, DecodeAs, EncodeAs, EthAbi}, + ibc::core::client::height::Height, + ErrorReporter, +}; +use voyager_message::{ + core::{ChainId, ClientStateMeta, ClientType, ConsensusStateMeta}, + module::{ClientModuleInfo, ClientModuleServer}, + ClientModule, FATAL_JSONRPC_ERROR_CODE, +}; +use voyager_vm::BoxDynError; + +#[tokio::main(flavor = "multi_thread")] +async fn main() { + Module::run().await +} + +#[derive(Debug, Clone)] +pub struct Module {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config {} + +impl ClientModule for Module { + type Config = Config; + + async fn new(_: Self::Config, info: ClientModuleInfo) -> Result { + info.ensure_client_type(ClientType::STATE_LENS_EVM)?; + Ok(Self {}) + } +} + +type SelfConsensusState = ConsensusState; +type SelfClientState = ClientState; + +impl Module { + pub fn decode_consensus_state(consensus_state: &[u8]) -> RpcResult { + SelfConsensusState::decode_as::(consensus_state).map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to decode consensus state: {}", ErrorReporter(err)), + None::<()>, + ) + }) + } + + pub fn decode_client_state(client_state: &[u8]) -> RpcResult { + ::decode_as::(client_state).map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to decode client state: {}", ErrorReporter(err)), + None::<()>, + ) + }) + } + + pub fn make_height(revision_height: u64) -> Height { + Height::new(revision_height) + } +} + +#[async_trait] +impl ClientModuleServer for Module { + #[instrument] + async fn decode_client_state_meta( + &self, + _: &Extensions, + client_state: Bytes, + ) -> RpcResult { + let cs = Module::decode_client_state(&client_state)?; + + Ok(ClientStateMeta { + chain_id: ChainId::new(cs.l2_chain_id.to_string()), + height: Module::make_height(cs.l2_latest_height), + }) + } + + #[instrument] + async fn decode_consensus_state_meta( + &self, + _: &Extensions, + consensus_state: Bytes, + ) -> RpcResult { + let cs = Module::decode_consensus_state(&consensus_state)?; + + Ok(ConsensusStateMeta { + timestamp_nanos: cs.timestamp, + }) + } + + #[instrument] + async fn decode_client_state(&self, _: &Extensions, client_state: Bytes) -> RpcResult { + Ok(serde_json::to_value(Module::decode_client_state(&client_state)?).unwrap()) + } + + #[instrument] + async fn decode_consensus_state( + &self, + _: &Extensions, + consensus_state: Bytes, + ) -> RpcResult { + Ok(serde_json::to_value(Module::decode_consensus_state(&consensus_state)?).unwrap()) + } + + #[instrument] + async fn encode_client_state( + &self, + _: &Extensions, + client_state: Value, + metadata: Value, + ) -> RpcResult { + if !metadata.is_null() { + return Err(ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + "metadata was provided, but this client type does not require \ + metadata for client state encoding", + Some(json!({ + "provided_metadata": metadata, + })), + )); + } + + serde_json::from_value::(client_state) + .map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to deserialize client state: {}", ErrorReporter(err)), + None::<()>, + ) + }) + .map(|cs| cs.encode_as::()) + .map(Into::into) + } + + #[instrument] + async fn encode_consensus_state( + &self, + _: &Extensions, + consensus_state: Value, + ) -> RpcResult { + serde_json::from_value::(consensus_state) + .map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!( + "unable to deserialize consensus state: {}", + ErrorReporter(err) + ), + None::<()>, + ) + }) + .map(|cs| cs.encode_as::()) + .map(Into::into) + } + + #[instrument(skip_all)] + async fn reencode_counterparty_client_state( + &self, + _: &Extensions, + _client_state: Bytes, + _client_type: ClientType, + ) -> RpcResult { + todo!() + } + + #[instrument(skip_all)] + async fn reencode_counterparty_consensus_state( + &self, + _: &Extensions, + _consensus_state: Bytes, + _client_type: ClientType, + ) -> RpcResult { + todo!() + } + + #[instrument] + async fn encode_header(&self, _: &Extensions, header: Value) -> RpcResult { + serde_json::from_value::
(header) + .map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to deserialize header: {}", ErrorReporter(err)), + None::<()>, + ) + }) + .map(|header| header.encode_as::()) + .map(Into::into) + } + + #[instrument] + async fn encode_proof(&self, _: &Extensions, proof: Value) -> RpcResult { + serde_json::from_value::(proof) + .map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to deserialize proof: {}", ErrorReporter(err)), + None::<()>, + ) + }) + .map(|storage_proof| storage_proof.encode_as::()) + .map(Into::into) + } +} diff --git a/voyager/modules/consensus/state-lens/evm/Cargo.toml b/voyager/modules/consensus/state-lens/evm/Cargo.toml new file mode 100644 index 0000000000..c437992977 --- /dev/null +++ b/voyager/modules/consensus/state-lens/evm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +edition = "2021" +name = "voyager-consensus-module-state-lens-evm" +version = "0.1.0" + +[dependencies] +alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +cometbft-rpc = { workspace = true } +dashmap = { workspace = true } +enumorph = { workspace = true } +ethereum-light-client-types = { workspace = true, features = ["serde"] } +evm-state-lens-light-client-types = { workspace = true, features = ["proto", "serde"] } +futures = { workspace = true } +ics23 = { workspace = true } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +macros = { workspace = true } +num-bigint = { workspace = true } +prost = { workspace = true } +protos = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +unionlabs = { workspace = true } +voyager-message = { workspace = true } +voyager-vm = { workspace = true } diff --git a/voyager/modules/consensus/state-lens/evm/src/main.rs b/voyager/modules/consensus/state-lens/evm/src/main.rs new file mode 100644 index 0000000000..2883d39215 --- /dev/null +++ b/voyager/modules/consensus/state-lens/evm/src/main.rs @@ -0,0 +1,132 @@ +use std::fmt::Debug; + +use alloy::providers::{Provider, ProviderBuilder}; +use ethereum_light_client_types::ConsensusState as EthConsensusState; +use evm_state_lens_light_client_types::{ClientState, ConsensusState}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + Extensions, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tracing::instrument; +use unionlabs::ibc::core::client::height::Height; +use voyager_message::{ + core::{ChainId, ConsensusType, QueryHeight}, + into_value, + module::{ConsensusModuleInfo, ConsensusModuleServer}, + ConsensusModule, ExtensionsExt, VoyagerClient, +}; +use voyager_vm::BoxDynError; + +#[tokio::main(flavor = "multi_thread")] +async fn main() { + Module::run().await +} + +#[derive(Debug, Clone)] +pub struct Module { + pub l1_chain_id: ChainId, + pub l2_chain_id: ChainId, + pub l1_client_id: u32, + pub l2_client_id: u32, + pub timestamp_offset: u16, + pub state_root_offset: u16, + pub storage_root_offset: u16, + pub l1_tm_client: cometbft_rpc::Client, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub l1_client_id: u32, + pub l2_client_id: u32, + pub timestamp_offset: u16, + pub state_root_offset: u16, + pub storage_root_offset: u16, + pub l1_comet_ws_url: String, + pub l2_eth_rpc_url: String, +} + +impl ConsensusModule for Module { + type Config = Config; + + async fn new(config: Self::Config, info: ConsensusModuleInfo) -> Result { + let l1_tm_client = cometbft_rpc::Client::new(config.l1_comet_ws_url).await?; + + let l1_chain_id = l1_tm_client.status().await?.node_info.network.to_string(); + + info.ensure_chain_id(&l1_chain_id)?; + info.ensure_consensus_type(ConsensusType::COMETBLS)?; + + let provider = ProviderBuilder::new() + .on_builtin(&config.l2_eth_rpc_url) + .await?; + + let l2_chain_id = ChainId::new(provider.get_chain_id().await?.to_string()); + + info.ensure_chain_id(l2_chain_id.to_string())?; + info.ensure_consensus_type(ConsensusType::ETHEREUM)?; + + Ok(Self { + l1_tm_client, + l1_chain_id: ChainId::new(l1_chain_id), + l2_chain_id: ChainId::new(l2_chain_id.to_string()), + l1_client_id: config.l1_client_id, + l2_client_id: config.l2_client_id, + timestamp_offset: config.timestamp_offset, + state_root_offset: config.state_root_offset, + storage_root_offset: config.storage_root_offset, + }) + } +} + +#[async_trait] +impl ConsensusModuleServer for Module { + /// Query the latest finalized height of this chain. + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn query_latest_height(&self, ext: &Extensions, finalized: bool) -> RpcResult { + let voy_client = ext.try_get::()?; + voy_client + .query_latest_height(self.l2_chain_id.clone(), finalized) + .await + } + + /// Query the latest finalized timestamp of this chain. + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn query_latest_timestamp(&self, ext: &Extensions, finalized: bool) -> RpcResult { + let voy_client = ext.try_get::()?; + voy_client + .query_latest_timestamp(self.l2_chain_id.clone(), finalized) + .await + } + + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn self_client_state(&self, _: &Extensions, height: Height) -> RpcResult { + Ok(into_value(ClientState { + l1_client_id: self.l1_client_id, + l2_chain_id: self.l2_chain_id.to_string(), + l2_client_id: self.l2_client_id, + l2_latest_height: height.height(), + timestamp_offset: self.timestamp_offset, + state_root_offset: self.state_root_offset, + storage_root_offset: self.storage_root_offset, + })) + } + + /// The consensus state on this chain at the specified `Height`. + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn self_consensus_state(&self, ext: &Extensions, height: Height) -> RpcResult { + let voy_client = ext.try_get::()?; + let state = voy_client + .self_consensus_state(self.l2_chain_id.clone(), QueryHeight::Specific(height)) + .await? + .state; + let consensus_state = + serde_json::from_value::(state).expect("big trouble"); + Ok(into_value(&ConsensusState { + timestamp: consensus_state.timestamp, + state_root: consensus_state.state_root, + storage_root: consensus_state.storage_root, + })) + } +} diff --git a/voyager/plugins/client-update/state-lens/evm/Cargo.toml b/voyager/plugins/client-update/state-lens/evm/Cargo.toml new file mode 100644 index 0000000000..0231242804 --- /dev/null +++ b/voyager/plugins/client-update/state-lens/evm/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "voyager-client-update-plugin-state-lens-evm" +version = "0.1.0" + +[dependencies] +alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +cometbft-rpc = { workspace = true } +cometbft-types.workspace = true +dashmap = { workspace = true } +enumorph = { workspace = true } +evm-state-lens-light-client-types = { workspace = true, features = ["proto", "serde"] } +futures = { workspace = true } +ibc-union-spec.workspace = true +ics23 = { workspace = true } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +macros = { workspace = true } +num-bigint = { workspace = true } +prost = { workspace = true } +protos = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +unionlabs = { workspace = true } +voyager-message = { workspace = true } +voyager-vm = { workspace = true } diff --git a/voyager/plugins/client-update/state-lens/evm/src/call.rs b/voyager/plugins/client-update/state-lens/evm/src/call.rs new file mode 100644 index 0000000000..3d9897aafd --- /dev/null +++ b/voyager/plugins/client-update/state-lens/evm/src/call.rs @@ -0,0 +1,25 @@ +use enumorph::Enumorph; +use macros::model; +use unionlabs::ibc::core::client::height::Height; +use voyager_message::core::ChainId; + +#[model] +#[derive(Enumorph)] +pub enum ModuleCall { + FetchUpdate(FetchUpdate), + FetchUpdateAfterL1Update(FetchUpdateAfterL1Update), +} + +#[model] +pub struct FetchUpdate { + pub counterparty_chain_id: ChainId, + pub update_from: Height, + pub update_to: Height, +} + +#[model] +pub struct FetchUpdateAfterL1Update { + pub counterparty_chain_id: ChainId, + pub update_from: Height, + pub update_to: Height, +} diff --git a/voyager/plugins/client-update/state-lens/evm/src/callback.rs b/voyager/plugins/client-update/state-lens/evm/src/callback.rs new file mode 100644 index 0000000000..a332e95f9a --- /dev/null +++ b/voyager/plugins/client-update/state-lens/evm/src/callback.rs @@ -0,0 +1,6 @@ +use enumorph::Enumorph; +use macros::model; + +#[model] +#[derive(Enumorph)] +pub enum ModuleCallback {} diff --git a/voyager/plugins/client-update/state-lens/evm/src/data.rs b/voyager/plugins/client-update/state-lens/evm/src/data.rs new file mode 100644 index 0000000000..f52f66d6b6 --- /dev/null +++ b/voyager/plugins/client-update/state-lens/evm/src/data.rs @@ -0,0 +1,4 @@ +use macros::model; + +#[model] +pub enum ModuleData {} diff --git a/voyager/plugins/client-update/state-lens/evm/src/main.rs b/voyager/plugins/client-update/state-lens/evm/src/main.rs new file mode 100644 index 0000000000..70245bdf95 --- /dev/null +++ b/voyager/plugins/client-update/state-lens/evm/src/main.rs @@ -0,0 +1,306 @@ +use std::{collections::VecDeque, fmt::Debug}; + +use alloy::{ + providers::{Provider, ProviderBuilder, RootProvider}, + transports::BoxTransport, +}; +use call::FetchUpdateAfterL1Update; +use evm_state_lens_light_client_types::Header; +use ibc_union_spec::{ConsensusStatePath, IbcUnion}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + Extensions, +}; +use serde::{Deserialize, Serialize}; +use tracing::instrument; +use unionlabs::ibc::core::commitment::merkle_proof::MerkleProof; +use voyager_message::{ + call::{Call, FetchUpdateHeaders, WaitForTrustedHeight}, + callback::AggregateMsgUpdateClientsFromOrderedHeaders, + core::{ChainId, ClientType, IbcSpec, IbcSpecId, QueryHeight}, + data::{Data, DecodedHeaderMeta, OrderedHeaders}, + hook::UpdateHook, + into_value, + module::{PluginInfo, PluginServer}, + DefaultCmd, ExtensionsExt, Plugin, PluginMessage, RawClientId, VoyagerClient, VoyagerMessage, +}; +use voyager_vm::{call, conc, data, pass::PassResult, promise, seq, BoxDynError, Op, Visit}; + +use crate::{ + call::{FetchUpdate, ModuleCall}, + callback::ModuleCallback, +}; + +pub mod call; +pub mod callback; + +#[tokio::main(flavor = "multi_thread")] +async fn main() { + Module::run().await +} + +#[derive(Debug, Clone)] +pub struct Module { + pub l0_client_id: u32, + pub l1_client_id: u32, + pub l1_chain_id: ChainId, + pub l2_chain_id: ChainId, + + pub l2_eth_provider: RootProvider, + pub l1_tm_client: cometbft_rpc::Client, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub l0_client_id: u32, + pub l1_client_id: u32, + pub l1_chain_id: ChainId, + pub l2_chain_id: ChainId, + + pub l1_ws_url: String, + pub l2_rpc_url: String, +} + +impl Plugin for Module { + type Call = ModuleCall; + type Callback = ModuleCallback; + + type Config = Config; + type Cmd = DefaultCmd; + + async fn new(config: Self::Config) -> Result { + let l1_tm_client = cometbft_rpc::Client::new(config.l1_ws_url).await?; + + let l1_chain_id = l1_tm_client.status().await?.node_info.network.to_string(); + + if l1_chain_id != config.l1_chain_id.as_str() { + return Err(format!( + "incorrect chain id: expected `{}`, but found `{}`", + config.l1_chain_id, l1_chain_id + ) + .into()); + } + + let l2_eth_provider = ProviderBuilder::new() + .on_builtin(&config.l2_rpc_url) + .await?; + + let l2_chain_id = ChainId::new(l2_eth_provider.get_chain_id().await?.to_string()); + + Ok(Self { + l0_client_id: config.l0_client_id, + l1_client_id: config.l1_client_id, + l1_chain_id: ChainId::new(l1_chain_id), + l2_chain_id, + l1_tm_client, + l2_eth_provider, + }) + } + + fn info(config: Self::Config) -> PluginInfo { + PluginInfo { + name: plugin_name(&config.l2_chain_id), + interest_filter: UpdateHook::filter( + &config.l2_chain_id, + &ClientType::new(ClientType::STATE_LENS_EVM), + ), + } + } + + async fn cmd(_config: Self::Config, cmd: Self::Cmd) { + match cmd {} + } +} + +fn plugin_name(chain_id: &ChainId) -> String { + pub const PLUGIN_NAME: &str = env!("CARGO_PKG_NAME"); + + format!("{PLUGIN_NAME}/{}", chain_id) +} + +impl Module { + fn plugin_name(&self) -> String { + plugin_name(&self.l2_chain_id) + } +} + +#[async_trait] +impl PluginServer for Module { + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn run_pass( + &self, + _: &Extensions, + msgs: Vec>, + ) -> RpcResult> { + Ok(PassResult { + optimize_further: vec![], + ready: msgs + .into_iter() + .map(|mut op| { + UpdateHook::new( + &self.l2_chain_id, + &ClientType::new(ClientType::STATE_LENS_EVM), + |fetch| { + Call::Plugin(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchUpdate { + counterparty_chain_id: fetch.counterparty_chain_id.clone(), + update_from: fetch.update_from, + update_to: fetch.update_to, + }), + )) + }, + ) + .visit_op(&mut op); + + op + }) + .enumerate() + .map(|(i, op)| (vec![i], op)) + .collect(), + }) + } + + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn call(&self, ext: &Extensions, msg: ModuleCall) -> RpcResult> { + match msg { + ModuleCall::FetchUpdate(FetchUpdate { + counterparty_chain_id, + update_from, + update_to, + }) => { + let voy_client = ext.try_get::()?; + let l1_latest_height = voy_client + .query_latest_height(self.l1_chain_id.clone(), true) + .await?; + let l2_consensus_proof = voy_client + .query_ibc_proof( + self.l1_chain_id.clone(), + QueryHeight::Specific(l1_latest_height), + ConsensusStatePath { + client_id: self.l1_client_id, + height: update_to.height(), + }, + ) + .await; + let continuation = call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchUpdateAfterL1Update { + counterparty_chain_id, + update_from, + update_to, + }), + )); + // If the L2 consensus proof exists on the L1, we don't have to update the L2 on the L1. + match l2_consensus_proof { + Ok(_) => Ok(continuation), + Err(_) => Ok(conc([ + // Update the L2 (eth) client on L1 (union) and then dispatch the continuation + promise( + [call(FetchUpdateHeaders { + client_type: ClientType::new(ClientType::ETHEREUM), + chain_id: self.l2_chain_id.clone(), + counterparty_chain_id: self.l1_chain_id.clone(), + update_from, + update_to, + })], + [], + AggregateMsgUpdateClientsFromOrderedHeaders { + ibc_spec_id: IbcUnion::ID, + chain_id: self.l1_chain_id.clone(), + client_id: RawClientId::new(self.l1_client_id.clone()), + }, + ), + seq([ + call(WaitForTrustedHeight { + chain_id: self.l1_chain_id.clone(), + ibc_spec_id: IbcUnion::ID, + client_id: RawClientId::new(self.l1_client_id), + height: update_to, + }), + continuation, + ]), + ])), + } + } + ModuleCall::FetchUpdateAfterL1Update(FetchUpdateAfterL1Update { + counterparty_chain_id, + update_to, + .. + }) => { + let voy_client = ext.try_get::()?; + let l1_latest_height = voy_client + .query_latest_height(self.l1_chain_id.clone(), true) + .await?; + let l0_client_meta = voy_client + .client_meta::( + counterparty_chain_id.clone(), + QueryHeight::Latest, + self.l0_client_id, + ) + .await?; + let l2_consensus_state_proof = serde_json::from_value::( + voy_client + .query_ibc_proof( + self.l1_chain_id.clone(), + QueryHeight::Specific(l1_latest_height), + ConsensusStatePath { + client_id: self.l1_client_id, + height: update_to.height(), + }, + ) + .await + .expect("big trouble") + .proof, + ) + .expect("impossible"); + // Dispatch an update for the L1 on the destination, then dispatch the L2 update on the destination + Ok(conc([ + promise( + [call(FetchUpdateHeaders { + client_type: ClientType::new(ClientType::COMETBLS), + chain_id: self.l1_chain_id.clone(), + counterparty_chain_id: counterparty_chain_id.clone(), + update_from: l0_client_meta.height, + update_to: l1_latest_height, + })], + [], + AggregateMsgUpdateClientsFromOrderedHeaders { + ibc_spec_id: IbcUnion::ID, + chain_id: counterparty_chain_id.clone(), + client_id: RawClientId::new(self.l0_client_id.clone()), + }, + ), + seq([ + call(WaitForTrustedHeight { + chain_id: counterparty_chain_id, + ibc_spec_id: IbcSpecId::new(IbcSpecId::UNION), + client_id: RawClientId::new(self.l0_client_id), + height: l1_latest_height, + }), + data(OrderedHeaders { + headers: vec![( + DecodedHeaderMeta { height: update_to }, + into_value(Header { + l1_height: l1_latest_height, + l2_height: update_to, + l2_consensus_state_proof, + }), + )], + }), + ]), + ])) + } + } + } + + #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] + async fn callback( + &self, + _: &Extensions, + callback: ModuleCallback, + _data: VecDeque, + ) -> RpcResult> { + match callback {} + } +} From e8f74401fad49021fe51deb148f595d3b95d5c3d Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Sun, 29 Dec 2024 08:46:01 +0100 Subject: [PATCH 04/11] fix(voyager): modules are now individually buildable --- voyager/modules/proof/ethereum/Cargo.toml | 2 +- voyager/plugins/event-source/cosmos-sdk/Cargo.toml | 2 +- voyager/plugins/transaction-batch/Cargo.toml | 2 +- voyager/plugins/transaction/cosmos-sdk/src/main.rs | 2 ++ voyager/plugins/transaction/ethereum/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/voyager/modules/proof/ethereum/Cargo.toml b/voyager/modules/proof/ethereum/Cargo.toml index 362c7021e7..70c08a87ca 100644 --- a/voyager/modules/proof/ethereum/Cargo.toml +++ b/voyager/modules/proof/ethereum/Cargo.toml @@ -4,7 +4,7 @@ name = "voyager-proof-module-ethereum" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws"] } +alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } ethereum-light-client-types = { workspace = true } ibc-union-spec.workspace = true jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } diff --git a/voyager/plugins/event-source/cosmos-sdk/Cargo.toml b/voyager/plugins/event-source/cosmos-sdk/Cargo.toml index 6e108172a6..1738353dd3 100644 --- a/voyager/plugins/event-source/cosmos-sdk/Cargo.toml +++ b/voyager/plugins/event-source/cosmos-sdk/Cargo.toml @@ -11,7 +11,7 @@ dashmap = { workspace = true } enumorph = { workspace = true } ibc-classic-spec.workspace = true ibc-solidity = { workspace = true, features = ["serde"] } -ibc-union-spec.workspace = true +ibc-union-spec = { workspace = true, features = ["tracing"] } jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } macros = { workspace = true } prost = { workspace = true } diff --git a/voyager/plugins/transaction-batch/Cargo.toml b/voyager/plugins/transaction-batch/Cargo.toml index 9b9761becb..0876aa1f59 100644 --- a/voyager/plugins/transaction-batch/Cargo.toml +++ b/voyager/plugins/transaction-batch/Cargo.toml @@ -4,7 +4,7 @@ name = "voyager-plugin-transaction-batch" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["sol-types"] } +alloy = { workspace = true, features = ["sol-types", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } either = { workspace = true } enumorph = { workspace = true } futures = { workspace = true } diff --git a/voyager/plugins/transaction/cosmos-sdk/src/main.rs b/voyager/plugins/transaction/cosmos-sdk/src/main.rs index e942d1662f..714160fdf9 100644 --- a/voyager/plugins/transaction/cosmos-sdk/src/main.rs +++ b/voyager/plugins/transaction/cosmos-sdk/src/main.rs @@ -361,6 +361,7 @@ impl Module { .encode_as::(), ) .expect("signing failed") + .to_bytes() .to_vec(); let tx_raw_bytes = TxRaw { @@ -534,6 +535,7 @@ impl Module { .encode_as::(), ) .expect("signing failed") + .to_bytes() .to_vec(); let result = client diff --git a/voyager/plugins/transaction/ethereum/Cargo.toml b/voyager/plugins/transaction/ethereum/Cargo.toml index d3895e8319..d61f9c0d81 100644 --- a/voyager/plugins/transaction/ethereum/Cargo.toml +++ b/voyager/plugins/transaction/ethereum/Cargo.toml @@ -4,7 +4,7 @@ name = "voyager-transaction-plugin-ethereum" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["contract", "network", "providers", "signers", "signer-local"] } +alloy = { workspace = true, features = ["contract", "network", "providers", "signers", "signer-local", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } bip32 = { workspace = true } chain-utils = { workspace = true } enumorph = { workspace = true } From 1052e421541d94b2c9c0dcf23a59d8bbfd5a56ac Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Sun, 29 Dec 2024 08:47:17 +0100 Subject: [PATCH 05/11] feat(state-lens): include chain id and deployment script --- evm/contracts/clients/EvmInCosmosClient.sol | 1 + evm/evm.nix | 37 +++- evm/scripts/Deploy.s.sol | 162 +++++++++++++++++- .../evm-light-client-types/Cargo.toml | 7 +- .../src/client_state.rs | 39 ++++- .../evm-light-client-types/src/header.rs | 118 ++++++++++++- .../modules/client/state-lens/evm/src/main.rs | 23 ++- .../consensus/state-lens/evm/Cargo.toml | 2 +- .../client-update/state-lens/evm/Cargo.toml | 2 +- .../client-update/state-lens/evm/src/main.rs | 55 ++++-- 10 files changed, 396 insertions(+), 50 deletions(-) diff --git a/evm/contracts/clients/EvmInCosmosClient.sol b/evm/contracts/clients/EvmInCosmosClient.sol index 7bbdbd8440..76d22ed5ee 100644 --- a/evm/contracts/clients/EvmInCosmosClient.sol +++ b/evm/contracts/clients/EvmInCosmosClient.sol @@ -20,6 +20,7 @@ struct Header { } struct ClientState { + string l2ChainId; uint32 l1ClientId; uint32 l2ClientId; uint64 l2LatestHeight; diff --git a/evm/evm.nix b/evm/evm.nix index 688c2c1fa9..dfa5f2f619 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -333,7 +333,7 @@ _: { } ); - eth-deploy-multicall = + eth-deploy-single = { rpc-url, kind, @@ -342,7 +342,7 @@ _: { }: mkCi false ( pkgs.writeShellApplicationWithArgs { - name = "eth-deploy-multicall"; + name = "eth-deploy-single-${kind}"; runtimeInputs = [ self'.packages.forge ]; arguments = [ { @@ -355,6 +355,16 @@ _: { required = true; help = "The contract owner private key."; } + { + arg = "sender_pk"; + required = true; + help = "The sender address that created the contract through the deployer."; + } + { + arg = "etherscan_api_key"; + required = true; + help = "The sender address that created the contract through the deployer."; + } ]; text = '' ${ensureAtRepositoryRoot} @@ -364,12 +374,13 @@ _: { cp --no-preserve=mode -r ${evmSources}/* . DEPLOYER="$argc_deployer_pk" \ + SENDER="$argc_sender_pk" \ PRIVATE_KEY="$argc_private_key" \ FOUNDRY_PROFILE="script" \ forge script scripts/Deploy.s.sol:Deploy${kind} \ -vvvv \ --rpc-url "${rpc-url}" \ - --broadcast ${extra-args} + --broadcast popd rm -rf "$OUT" @@ -681,10 +692,16 @@ _: { value = eth-deploy args; }) networks ) + // builtins.listToAttrs ( + builtins.map (args: { + name = "eth-deploy-${args.network}-evm-lens"; + value = eth-deploy-single ({ kind = "EvmLens"; } // args); + }) networks + ) // builtins.listToAttrs ( builtins.map (args: { name = "eth-deploy-${args.network}-multicall"; - value = eth-deploy-multicall ({ kind = "Multicall"; } // args); + value = eth-deploy-single ({ kind = "Multicall"; } // args); }) networks ) // builtins.listToAttrs ( @@ -711,6 +728,18 @@ _: { ); }) networks ) + // builtins.listToAttrs ( + builtins.map (args: { + name = "eth-upgrade-${args.network}-evm-lens-client"; + value = eth-upgrade ( + { + dry = false; + protocol = "EvmInCosmosClient"; + } + // args + ); + }) networks + ) // builtins.listToAttrs ( builtins.map (args: { name = "eth-dryupgrade-${args.network}-ibc"; diff --git a/evm/scripts/Deploy.s.sol b/evm/scripts/Deploy.s.sol index 571ebfe0a3..ba58cb2117 100644 --- a/evm/scripts/Deploy.s.sol +++ b/evm/scripts/Deploy.s.sol @@ -42,6 +42,7 @@ library IBC { library LightClients { string constant NAMESPACE = "lightclients"; string constant COMETBLS = "cometbls"; + string constant STATE_LENS_EVM = "state-lens/evm"; function make( string memory lightClient @@ -105,6 +106,23 @@ abstract contract UnionScript is UnionBase { ); } + function deployEvmLens( + IBCHandler handler, + address owner + ) internal returns (EvmInCosmosClient) { + return EvmInCosmosClient( + deploy( + LightClients.make(LightClients.STATE_LENS_EVM), + abi.encode( + address(new EvmInCosmosClient()), + abi.encodeCall( + EvmInCosmosClient.initialize, (address(handler), owner) + ) + ) + ) + ); + } + function deployCometbls( IBCHandler handler, address owner @@ -177,6 +195,7 @@ abstract contract UnionScript is UnionBase { returns ( IBCHandler, CometblsClient, + EvmInCosmosClient, PingPong, UCS01Relay, UCS02NFT, @@ -184,12 +203,21 @@ abstract contract UnionScript is UnionBase { ) { IBCHandler handler = deployIBCHandler(owner); - CometblsClient client = deployCometbls(handler, owner); + CometblsClient cometblsClient = deployCometbls(handler, owner); + EvmInCosmosClient evmLensClient = deployEvmLens(handler, owner); PingPong pingpong = deployUCS00(handler, owner, 100000000000000); UCS01Relay relay = deployUCS01(handler, owner); UCS02NFT nft = deployUCS02(handler, owner); Multicall multicall = deployMulticall(); - return (handler, client, pingpong, relay, nft, multicall); + return ( + handler, + cometblsClient, + evmLensClient, + pingpong, + relay, + nft, + multicall + ); } } @@ -225,6 +253,48 @@ contract DeployMulticall is UnionScript { } } +contract DeployEvmLens is UnionScript { + using LibString for *; + + address immutable deployer; + address immutable sender; + + constructor() { + deployer = vm.envAddress("DEPLOYER"); + sender = vm.envAddress("SENDER"); + } + + function getDeployer() internal view override returns (Deployer) { + return Deployer(deployer); + } + + function getDeployed( + string memory salt + ) internal view returns (address) { + return CREATE3.predictDeterministicAddress( + keccak256(abi.encodePacked(sender.toHexString(), "/", salt)), + deployer + ); + } + + function run() public { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + + address owner = vm.addr(privateKey); + + address handler = getDeployed(IBC.BASED); + + vm.startBroadcast(privateKey); + + EvmInCosmosClient evmLensClient = + deployEvmLens(IBCHandler(handler), owner); + + vm.stopBroadcast(); + + console.log("EvmInCosmosClient: ", address(evmLensClient)); + } +} + contract DeployIBC is UnionScript { Deployer immutable deployer; @@ -242,20 +312,23 @@ contract DeployIBC is UnionScript { ( IBCHandler handler, - CometblsClient client, + CometblsClient cometblsClient, + EvmInCosmosClient evmLensClient, PingPong pingpong, UCS01Relay relay, UCS02NFT nft, Multicall multicall ) = deployIBC(vm.addr(privateKey)); - handler.registerClient(LightClients.COMETBLS, client); + handler.registerClient(LightClients.COMETBLS, cometblsClient); + handler.registerClient(LightClients.STATE_LENS_EVM, evmLensClient); vm.stopBroadcast(); console.log("Deployer: ", address(deployer)); console.log("Sender: ", vm.addr(privateKey)); console.log("IBCHandler: ", address(handler)); - console.log("CometblsClient: ", address(client)); + console.log("CometblsClient: ", address(cometblsClient)); + console.log("EvmInCosmosClient: ", address(evmLensClient)); console.log("UCS00: ", address(pingpong)); console.log("UCS01: ", address(relay)); console.log("UCS02: ", address(nft)); @@ -279,20 +352,23 @@ contract DeployDeployerAndIBC is UnionScript { ( IBCHandler handler, - CometblsClient client, + CometblsClient cometblsClient, + EvmInCosmosClient evmLensClient, PingPong pingpong, UCS01Relay relay, UCS02NFT nft, Multicall multicall ) = deployIBC(vm.addr(privateKey)); - handler.registerClient(LightClients.COMETBLS, client); + handler.registerClient(LightClients.COMETBLS, cometblsClient); + handler.registerClient(LightClients.STATE_LENS_EVM, evmLensClient); vm.stopBroadcast(); console.log("Deployer: ", address(deployer)); console.log("Sender: ", vm.addr(privateKey)); console.log("IBCHandler: ", address(handler)); - console.log("CometblsClient: ", address(client)); + console.log("CometblsClient: ", address(cometblsClient)); + console.log("EvmInCosmosClient: ", address(evmLensClient)); console.log("UCS00: ", address(pingpong)); console.log("UCS01: ", address(relay)); console.log("UCS02: ", address(nft)); @@ -333,6 +409,8 @@ contract GetDeployed is Script { address handler = getDeployed(IBC.BASED); address cometblsClient = getDeployed(LightClients.make(LightClients.COMETBLS)); + address evmLensClient = + getDeployed(LightClients.make(LightClients.STATE_LENS_EVM)); address ucs00 = getDeployed(Protocols.make(Protocols.UCS00)); address ucs01 = getDeployed(Protocols.make(Protocols.UCS01)); address ucs02 = getDeployed(Protocols.make(Protocols.UCS02)); @@ -350,6 +428,11 @@ contract GetDeployed is Script { ) ) ); + console.log( + string( + abi.encodePacked("EvmLensClient: ", evmLensClient.toHexString()) + ) + ); console.log(string(abi.encodePacked("UCS00: ", ucs00.toHexString()))); console.log(string(abi.encodePacked("UCS01: ", ucs01.toHexString()))); console.log(string(abi.encodePacked("UCS02: ", ucs02.toHexString()))); @@ -387,6 +470,22 @@ contract GetDeployed is Script { ); impls.serialize(cometblsClient.toHexString(), proxyComet); + string memory proxyEvmLens = "proxyEvmLens"; + proxyEvmLens.serialize( + "contract", + string( + "libs/@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy" + ) + ); + proxyEvmLens = proxyEvmLens.serialize( + "args", + abi.encode( + implOf(evmLensClient), + abi.encodeCall(EvmInCosmosClient.initialize, (handler, sender)) + ) + ); + impls.serialize(evmLensClient.toHexString(), proxyEvmLens); + string memory proxyUCS00 = "proxyUCS00"; proxyUCS00.serialize( "contract", @@ -465,6 +564,14 @@ contract GetDeployed is Script { implComet = implComet.serialize("args", bytes(hex"")); impls.serialize(implOf(cometblsClient).toHexString(), implComet); + string memory implEvmLens = "implEvmLens"; + implEvmLens.serialize( + "contract", + string("contracts/clients/EvmInCosmosClient.sol:EvmInCosmosClient") + ); + implEvmLens = implEvmLens.serialize("args", bytes(hex"")); + impls.serialize(implOf(evmLensClient).toHexString(), implEvmLens); + string memory implUCS00 = "implUCS00"; implUCS00.serialize( "contract", @@ -704,3 +811,42 @@ contract UpgradeCometblsClient is Script { vm.stopBroadcast(); } } + +contract UpgradeEvmInCosmosClient is Script { + using LibString for *; + + address immutable deployer; + address immutable sender; + uint256 immutable privateKey; + + constructor() { + deployer = vm.envAddress("DEPLOYER"); + sender = vm.envAddress("SENDER"); + privateKey = vm.envUint("PRIVATE_KEY"); + } + + function getDeployed( + string memory salt + ) internal view returns (address) { + return CREATE3.predictDeterministicAddress( + keccak256(abi.encodePacked(sender.toHexString(), "/", salt)), + deployer + ); + } + + function run() public { + address evmLensClient = + getDeployed(LightClients.make(LightClients.STATE_LENS_EVM)); + console.log( + string( + abi.encodePacked("EvmLensClient: ", evmLensClient.toHexString()) + ) + ); + vm.startBroadcast(privateKey); + address newImplementation = address(new EvmInCosmosClient()); + CometblsClient(evmLensClient).upgradeToAndCall( + newImplementation, new bytes(0) + ); + vm.stopBroadcast(); + } +} diff --git a/lib/state-lens/evm-light-client-types/Cargo.toml b/lib/state-lens/evm-light-client-types/Cargo.toml index a568562e4e..5a7de0de49 100644 --- a/lib/state-lens/evm-light-client-types/Cargo.toml +++ b/lib/state-lens/evm-light-client-types/Cargo.toml @@ -5,13 +5,12 @@ version = "0.1.0" [dependencies] alloy = { workspace = true, features = ["sol-types"], optional = true } -protos = { workspace = true, optional = true, features = ["union+ibc+lightclients+berachain+v1"] } +protos = { workspace = true, optional = true, features = ["proto_full", "serde"] } serde = { workspace = true, optional = true, features = ["derive"] } thiserror = { workspace = true } -unionlabs = { workspace = true } +unionlabs = { workspace = true, features = ["ethabi", "proto"] } [features] default = [] -ethabi = ["unionlabs/ethabi", "dep:alloy"] -proto = ["dep:protos"] +ethabi = ["unionlabs/ethabi", "dep:alloy", "dep:protos"] serde = ["dep:serde"] diff --git a/lib/state-lens/evm-light-client-types/src/client_state.rs b/lib/state-lens/evm-light-client-types/src/client_state.rs index 998b8ccdb3..f2c27bcddb 100644 --- a/lib/state-lens/evm-light-client-types/src/client_state.rs +++ b/lib/state-lens/evm-light-client-types/src/client_state.rs @@ -1,10 +1,10 @@ #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ClientState { - /// l1 client id used to check the l2 inclusion proof against - pub l1_client_id: u32, /// l2 chain id pub l2_chain_id: String, + /// l1 client id used to check the l2 inclusion proof against + pub l1_client_id: u32, /// l2 client id pub l2_client_id: u32, /// l2 latest height @@ -19,3 +19,38 @@ pub struct ClientState { /// storage_root = consensus_state[storage_root_offset:storage_root_offset+32] pub storage_root_offset: u16, } + +#[cfg(feature = "ethabi")] +pub mod ethabi { + use alloy::sol_types::SolValue; + use unionlabs::encoding::{Encode, EthAbi}; + + use crate::ClientState; + + alloy::sol! { + struct SolClientState { + string l2ChainId; + uint32 l1ClientId; + uint32 l2ClientId; + uint64 l2LatestHeight; + uint16 timestampOffset; + uint16 stateRootOffset; + uint16 storageRootOffset; + } + } + + impl Encode for ClientState { + fn encode(self) -> Vec { + SolClientState { + l2ChainId: self.l2_chain_id, + l1ClientId: self.l1_client_id, + l2ClientId: self.l2_client_id, + l2LatestHeight: self.l2_latest_height, + timestampOffset: self.timestamp_offset, + stateRootOffset: self.state_root_offset, + storageRootOffset: self.storage_root_offset, + } + .abi_encode_params() + } + } +} diff --git a/lib/state-lens/evm-light-client-types/src/header.rs b/lib/state-lens/evm-light-client-types/src/header.rs index 0b3632238a..751d06ec9a 100644 --- a/lib/state-lens/evm-light-client-types/src/header.rs +++ b/lib/state-lens/evm-light-client-types/src/header.rs @@ -1,4 +1,7 @@ -use unionlabs::ibc::core::{client::height::Height, commitment::merkle_proof::MerkleProof}; +use unionlabs::{ + bytes::Bytes, + ibc::core::{client::height::Height, commitment::merkle_proof::MerkleProof}, +}; #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -6,4 +9,117 @@ pub struct Header { pub l1_height: Height, pub l2_height: Height, pub l2_consensus_state_proof: MerkleProof, + pub l2_consensus_state: Bytes, +} + +#[cfg(feature = "ethabi")] +pub mod ethabi { + use alloy::sol_types::SolValue; + use unionlabs::{ + encoding::{Encode, EthAbi}, + union::ics23, + }; + + use crate::Header; + + impl Encode for Header { + fn encode(self) -> Vec { + Into::::into(self).abi_encode_params() + } + } + + alloy::sol! { + struct SolHeader { + uint64 l1Height; + uint64 l2Height; + bytes l2InclusionProof; + bytes l2ConsensusState; + } + } + + #[derive(Debug, Clone, PartialEq, thiserror::Error)] + pub enum Error {} + + impl From
for SolHeader { + fn from(value: Header) -> Self { + Self { + l1Height: value.l1_height.height(), + l2Height: value.l2_height.height(), + l2InclusionProof: encode_merkle_proof_for_evm(value.l2_consensus_state_proof) + .into(), + l2ConsensusState: value.l2_consensus_state.into(), + } + } + } + + // FIXME: deduplicate with voyager/module/client/cometbls, in unionlabs? + fn encode_merkle_proof_for_evm( + proof: unionlabs::ibc::core::commitment::merkle_proof::MerkleProof, + ) -> Vec { + alloy::sol! { + struct ExistenceProof { + bytes key; + bytes value; + bytes leafPrefix; + InnerOp[] path; + } + + struct NonExistenceProof { + bytes key; + ExistenceProof left; + ExistenceProof right; + } + + struct InnerOp { + bytes prefix; + bytes suffix; + } + + struct ProofSpec { + uint256 childSize; + uint256 minPrefixLength; + uint256 maxPrefixLength; + } + } + + let merkle_proof = ics23::merkle_proof::MerkleProof::try_from( + protos::ibc::core::commitment::v1::MerkleProof::from(proof), + ) + .unwrap(); + + let convert_inner_op = |i: unionlabs::union::ics23::inner_op::InnerOp| InnerOp { + prefix: i.prefix.into(), + suffix: i.suffix.into(), + }; + + let convert_existence_proof = + |e: unionlabs::union::ics23::existence_proof::ExistenceProof| ExistenceProof { + key: e.key.into(), + value: e.value.into(), + leafPrefix: e.leaf_prefix.into(), + path: e.path.into_iter().map(convert_inner_op).collect(), + }; + + let exist_default = || ics23::existence_proof::ExistenceProof { + key: vec![].into(), + value: vec![].into(), + leaf_prefix: vec![].into(), + path: vec![], + }; + + match merkle_proof { + ics23::merkle_proof::MerkleProof::Membership(a, b) => { + (convert_existence_proof(a), convert_existence_proof(b)).abi_encode_params() + } + ics23::merkle_proof::MerkleProof::NonMembership(a, b) => ( + NonExistenceProof { + key: a.key.into(), + left: convert_existence_proof(a.left.unwrap_or_else(exist_default)), + right: convert_existence_proof(a.right.unwrap_or_else(exist_default)), + }, + convert_existence_proof(b), + ) + .abi_encode_params(), + } + } } diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index a8007247e2..91ab194fa0 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -140,7 +140,7 @@ impl ClientModuleServer for Module { None::<()>, ) }) - .map(|cs| cs.encode_as::()) + .map(|cs| cs.encode_as::()) .map(Into::into) } @@ -195,21 +195,20 @@ impl ClientModuleServer for Module { None::<()>, ) }) - .map(|header| header.encode_as::()) + .map(|header| header.encode_as::()) .map(Into::into) } #[instrument] async fn encode_proof(&self, _: &Extensions, proof: Value) -> RpcResult { - serde_json::from_value::(proof) - .map_err(|err| { - ErrorObject::owned( - FATAL_JSONRPC_ERROR_CODE, - format!("unable to deserialize proof: {}", ErrorReporter(err)), - None::<()>, - ) - }) - .map(|storage_proof| storage_proof.encode_as::()) - .map(Into::into) + let proof = serde_json::from_value::(proof).map_err(|err| { + ErrorObject::owned( + FATAL_JSONRPC_ERROR_CODE, + format!("unable to deserialize proof: {}", ErrorReporter(err)), + None::<()>, + ) + })?; + // TODO: extract to unionlabs? this is MPT proofs encoding for EVM + Ok(proof.proof.concat().into()) } } diff --git a/voyager/modules/consensus/state-lens/evm/Cargo.toml b/voyager/modules/consensus/state-lens/evm/Cargo.toml index c437992977..3afc50c389 100644 --- a/voyager/modules/consensus/state-lens/evm/Cargo.toml +++ b/voyager/modules/consensus/state-lens/evm/Cargo.toml @@ -9,7 +9,7 @@ cometbft-rpc = { workspace = true } dashmap = { workspace = true } enumorph = { workspace = true } ethereum-light-client-types = { workspace = true, features = ["serde"] } -evm-state-lens-light-client-types = { workspace = true, features = ["proto", "serde"] } +evm-state-lens-light-client-types = { workspace = true, features = ["serde"] } futures = { workspace = true } ics23 = { workspace = true } jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } diff --git a/voyager/plugins/client-update/state-lens/evm/Cargo.toml b/voyager/plugins/client-update/state-lens/evm/Cargo.toml index 0231242804..7ec196b15c 100644 --- a/voyager/plugins/client-update/state-lens/evm/Cargo.toml +++ b/voyager/plugins/client-update/state-lens/evm/Cargo.toml @@ -9,7 +9,7 @@ cometbft-rpc = { workspace = true } cometbft-types.workspace = true dashmap = { workspace = true } enumorph = { workspace = true } -evm-state-lens-light-client-types = { workspace = true, features = ["proto", "serde"] } +evm-state-lens-light-client-types = { workspace = true, features = ["serde"] } futures = { workspace = true } ibc-union-spec.workspace = true ics23 = { workspace = true } diff --git a/voyager/plugins/client-update/state-lens/evm/src/main.rs b/voyager/plugins/client-update/state-lens/evm/src/main.rs index 70245bdf95..06f0099369 100644 --- a/voyager/plugins/client-update/state-lens/evm/src/main.rs +++ b/voyager/plugins/client-update/state-lens/evm/src/main.rs @@ -173,16 +173,25 @@ impl PluginServer for Module { let l1_latest_height = voy_client .query_latest_height(self.l1_chain_id.clone(), true) .await?; - let l2_consensus_proof = voy_client - .query_ibc_proof( - self.l1_chain_id.clone(), - QueryHeight::Specific(l1_latest_height), - ConsensusStatePath { - client_id: self.l1_client_id, - height: update_to.height(), - }, - ) - .await; + let l2_consensus_state_proof = serde_json::from_value::( + voy_client + .query_ibc_proof( + self.l1_chain_id.clone(), + QueryHeight::Specific(l1_latest_height), + ConsensusStatePath { + client_id: self.l1_client_id, + height: update_to.height(), + }, + ) + .await + .expect("big trouble") + .proof, + ) + .expect("impossible"); + let l2_merkle_proof = unionlabs::union::ics23::merkle_proof::MerkleProof::try_from( + protos::ibc::core::commitment::v1::MerkleProof::from(l2_consensus_state_proof), + ) + .expect("impossible"); let continuation = call(PluginMessage::new( self.plugin_name(), ModuleCall::from(FetchUpdateAfterL1Update { @@ -192,9 +201,11 @@ impl PluginServer for Module { }), )); // If the L2 consensus proof exists on the L1, we don't have to update the L2 on the L1. - match l2_consensus_proof { - Ok(_) => Ok(continuation), - Err(_) => Ok(conc([ + match l2_merkle_proof { + unionlabs::union::ics23::merkle_proof::MerkleProof::Membership(_, _) => { + Ok(continuation) + } + _ => Ok(conc([ // Update the L2 (eth) client on L1 (union) and then dispatch the continuation promise( [call(FetchUpdateHeaders { @@ -239,15 +250,24 @@ impl PluginServer for Module { self.l0_client_id, ) .await?; + let l2_consensus_state_path = ConsensusStatePath { + client_id: self.l1_client_id, + height: update_to.height(), + }; + let l2_consensus_state = voy_client + .query_ibc_state( + self.l1_chain_id.clone(), + QueryHeight::Specific(l1_latest_height), + l2_consensus_state_path.clone(), + ) + .await? + .state; let l2_consensus_state_proof = serde_json::from_value::( voy_client .query_ibc_proof( self.l1_chain_id.clone(), QueryHeight::Specific(l1_latest_height), - ConsensusStatePath { - client_id: self.l1_client_id, - height: update_to.height(), - }, + l2_consensus_state_path, ) .await .expect("big trouble") @@ -285,6 +305,7 @@ impl PluginServer for Module { l1_height: l1_latest_height, l2_height: update_to, l2_consensus_state_proof, + l2_consensus_state, }), )], }), From da8b8e1e17155ea733711f3af3c7ab9a6fa75f2d Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 30 Dec 2024 16:04:23 +0100 Subject: [PATCH 06/11] feat(voyager): state lens bootstrap and more --- Cargo.lock | 629 +++--------------- Cargo.toml | 8 +- .../src/client_state.rs | 33 +- lib/voyager-message/src/lib.rs | 6 +- .../state-lens/evm/Cargo.toml | 24 + .../state-lens/evm/src/main.rs | 65 +- .../modules/client/state-lens/evm/src/main.rs | 9 +- .../consensus/state-lens/evm/Cargo.toml | 28 - .../client-update/state-lens/evm/src/main.rs | 4 +- voyager/src/main.rs | 38 +- 10 files changed, 194 insertions(+), 650 deletions(-) create mode 100644 voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml rename voyager/modules/{consensus => client-bootstrap}/state-lens/evm/src/main.rs (55%) delete mode 100644 voyager/modules/consensus/state-lens/evm/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 13c9932d33..25a14a089e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,7 +892,7 @@ dependencies = [ "thiserror", "tiny-keccak", "typenum", - "x25519-dalek 1.2.0", + "x25519-dalek", ] [[package]] @@ -1400,18 +1400,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - [[package]] name = "arith" version = "0.2.3" @@ -2344,12 +2332,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - [[package]] name = "bitflags" version = "1.3.2" @@ -2485,25 +2467,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "blst" version = "0.3.13" @@ -2584,25 +2547,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "bstr" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "buffer-redux" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8acf87c5b9f5897cd3ebb9a327f420e0cae9dd4e5c1d2e36f2c84c571a58f1" -dependencies = [ - "memchr", -] - [[package]] name = "bulletproofs" version = "4.0.0" @@ -2682,16 +2626,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" -[[package]] -name = "camellia" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "camino" version = "1.1.6" @@ -2740,36 +2674,12 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cast5" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" -dependencies = [ - "cipher", -] - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.0.90" @@ -2782,15 +2692,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfb-mode" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" -dependencies = [ - "cipher", -] - [[package]] name = "cfg-expr" version = "0.15.7" @@ -2999,17 +2900,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cmac" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" -dependencies = [ - "cipher", - "dbl", - "digest 0.10.7", -] - [[package]] name = "codespan" version = "0.11.1" @@ -3167,19 +3057,6 @@ dependencies = [ "unionlabs", ] -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "console" version = "0.15.8" @@ -3523,12 +3400,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc24" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" - [[package]] name = "crc32fast" version = "1.4.0" @@ -3653,31 +3524,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.5.0", - "crossterm_winapi", - "libc", - "mio 0.8.11", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi 0.3.9", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -4205,15 +4051,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" -[[package]] -name = "dbl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "der" version = "0.7.9" @@ -4267,37 +4104,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "derive_builder" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" -dependencies = [ - "derive_builder_core", - "syn 2.0.77", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -4332,15 +4138,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "des" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" -dependencies = [ - "cipher", -] - [[package]] name = "devnet-compose" version = "0.1.0" @@ -4468,22 +4265,6 @@ dependencies = [ "unionlabs", ] -[[package]] -name = "dsa" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" -dependencies = [ - "digest 0.10.7", - "num-bigint-dig", - "num-traits", - "pkcs8", - "rfc6979", - "sha2 0.10.8", - "signature 2.2.0", - "zeroize", -] - [[package]] name = "dunce" version = "1.0.4" @@ -4496,19 +4277,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" -[[package]] -name = "eax" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" -dependencies = [ - "aead", - "cipher", - "cmac", - "ctr", - "subtle 2.5.0", -] - [[package]] name = "ecdsa" version = "0.16.9" @@ -4647,7 +4415,6 @@ dependencies = [ "ff", "generic-array 0.14.7", "group", - "hkdf 0.12.4", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", @@ -4863,6 +4630,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "evm-state-lens-light-client-types" +version = "0.1.0" +dependencies = [ + "alloy", + "protos", + "serde", + "thiserror", + "unionlabs", +] + [[package]] name = "evm-storage-verifier" version = "0.1.0" @@ -6273,15 +6051,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "idea" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" -dependencies = [ - "cipher", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -6512,12 +6281,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "iter-read" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" - [[package]] name = "itertools" version = "0.10.5" @@ -7215,18 +6978,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.2" @@ -7720,58 +7471,6 @@ dependencies = [ "unionlabs", ] -[[package]] -name = "mpc-client" -version = "0.1.0" -dependencies = [ - "async-sqlite", - "base64 0.21.7", - "crossterm", - "futures-util", - "hex", - "http-body-util", - "httpdate", - "hyper 1.3.1", - "hyper-util", - "mpc-shared", - "pgp", - "ratatui", - "reqwest 0.11.27", - "serde", - "serde_json", - "thiserror", - "throbber-widgets-tui", - "tokio", - "tokio-util", -] - -[[package]] -name = "mpc-coordinator" -version = "0.1.0" -dependencies = [ - "clap 4.5.4", - "hex", - "mpc-shared", - "pgp", - "thiserror", - "tokio", - "tracing", - "tracing-subscriber 0.3.18", -] - -[[package]] -name = "mpc-shared" -version = "0.1.0" -dependencies = [ - "hex", - "postgrest", - "reqwest 0.11.27", - "serde", - "serde_json", - "thiserror", - "tokio", -] - [[package]] name = "multer" version = "3.1.0" @@ -8048,7 +7747,6 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", - "serde", "smallvec", "zeroize", ] @@ -8179,18 +7877,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ocb3" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" -dependencies = [ - "aead", - "cipher", - "ctr", - "subtle 2.5.0", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -8315,32 +8001,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "p521" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "rand_core 0.6.4", - "sha2 0.10.8", -] - [[package]] name = "pairing" version = "0.23.0" @@ -8452,17 +8112,6 @@ dependencies = [ "typeshare", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle 2.5.0", -] - [[package]] name = "pasta_curves" version = "0.5.1" @@ -8653,70 +8302,6 @@ dependencies = [ "voyager-vm", ] -[[package]] -name = "pgp" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6c842436d5fa2b59eac1e9b3d142b50bfff99c1744c816b1f4c2ac55a20754" -dependencies = [ - "aes", - "aes-gcm", - "argon2", - "base64 0.22.1", - "bitfield", - "block-padding 0.3.3", - "blowfish", - "bstr", - "buffer-redux", - "byteorder", - "camellia", - "cast5", - "cfb-mode", - "chrono", - "cipher", - "const-oid", - "crc24", - "curve25519-dalek 4.1.3", - "derive_builder", - "des", - "digest 0.10.7", - "dsa", - "eax", - "ecdsa", - "ed25519-dalek 2.1.1", - "elliptic-curve", - "flate2", - "generic-array 0.14.7", - "hex", - "hkdf 0.12.4", - "idea", - "iter-read", - "k256", - "log", - "md-5", - "nom", - "num-bigint-dig", - "num-traits", - "num_enum", - "ocb3", - "p256", - "p384", - "p521", - "rand 0.8.5", - "ripemd", - "rsa", - "sha1", - "sha1-checked", - "sha2 0.10.8", - "sha3 0.10.8", - "signature 2.2.0", - "smallvec", - "thiserror", - "twofish", - "x25519-dalek 2.0.1", - "zeroize", -] - [[package]] name = "pharos" version = "0.5.3" @@ -8951,15 +8536,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "postgrest" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a966c650b47a064e7082170b4be74fca08c088d893244fc4b70123e3c1f3ee7" -dependencies = [ - "reqwest 0.11.27", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -9527,27 +9103,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "ratatui" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" -dependencies = [ - "bitflags 2.5.0", - "cassowary", - "compact_str", - "crossterm", - "itertools 0.13.0", - "lru 0.12.5", - "paste", - "stability", - "strum 0.26.3", - "strum_macros 0.26.4", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - [[package]] name = "rayon" version = "1.10.0" @@ -10658,16 +10213,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1-checked" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" -dependencies = [ - "digest 0.10.7", - "sha1", -] - [[package]] name = "sha2" version = "0.8.2" @@ -10745,27 +10290,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio 0.8.11", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -11177,16 +10701,6 @@ dependencies = [ "unionlabs", ] -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.77", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -11725,16 +11239,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "throbber-widgets-tui" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685391f5e78b08989c1014f5a0edddf6751e0726b6a8dd1bdcc98d05921b19b6" -dependencies = [ - "rand 0.8.5", - "ratatui", -] - [[package]] name = "tidy" version = "0.1.0" @@ -11862,7 +11366,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -12373,15 +11877,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "twofish" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" -dependencies = [ - "cipher", -] - [[package]] name = "twox-hash" version = "1.6.3" @@ -12568,17 +12063,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools 0.13.0", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "unicode-width" version = "0.1.14" @@ -12954,6 +12438,27 @@ dependencies = [ "voyager-message", ] +[[package]] +name = "voyager-client-bootstrap-module-state-lens-evm" +version = "0.1.0" +dependencies = [ + "alloy", + "beacon-api", + "beacon-api-types", + "cometbft-rpc", + "ethereum-light-client-types", + "evm-state-lens-light-client-types", + "ibc-union-spec", + "jsonrpsee", + "serde", + "serde_json", + "tokio", + "tracing", + "unionlabs", + "voyager-message", + "voyager-vm", +] + [[package]] name = "voyager-client-bootstrap-module-tendermint" version = "0.1.0" @@ -13029,6 +12534,31 @@ dependencies = [ "voyager-message", ] +[[package]] +name = "voyager-client-module-state-lens-evm" +version = "0.1.0" +dependencies = [ + "beacon-api-types", + "chain-utils", + "enumorph", + "ethereum-light-client-types", + "evm-state-lens-light-client-types", + "futures", + "jsonrpsee", + "macros", + "prost 0.12.6", + "serde", + "serde-utils", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber 0.3.18", + "unionlabs", + "voyager-message", + "voyager-vm", +] + [[package]] name = "voyager-client-module-tendermint" version = "0.1.0" @@ -13147,6 +12677,35 @@ dependencies = [ "voyager-vm", ] +[[package]] +name = "voyager-client-update-plugin-state-lens-evm" +version = "0.1.0" +dependencies = [ + "alloy", + "cometbft-rpc", + "cometbft-types", + "dashmap 5.5.3", + "enumorph", + "evm-state-lens-light-client-types", + "futures", + "ibc-union-spec", + "ics23", + "jsonrpsee", + "macros", + "num-bigint 0.4.6", + "prost 0.12.6", + "protos", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber 0.3.18", + "unionlabs", + "voyager-message", + "voyager-vm", +] + [[package]] name = "voyager-client-update-plugin-tendermint" version = "0.1.0" @@ -14163,18 +13722,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek 4.1.3", - "rand_core 0.6.4", - "serde", - "zeroize", -] - [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index e9ed31e9fa..27e54c25f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,13 +121,13 @@ members = [ "voyager/modules/client-bootstrap/ethereum", "voyager/modules/client-bootstrap/movement", "voyager/modules/client-bootstrap/tendermint", + "voyager/modules/client-bootstrap/state-lens/evm", "voyager/modules/consensus/berachain", "voyager/modules/consensus/cometbls", "voyager/modules/consensus/ethereum", "voyager/modules/consensus/movement", "voyager/modules/consensus/tendermint", - "voyager/modules/consensus/state-lens/evm", "voyager/plugins/client-update/berachain", "voyager/plugins/client-update/cometbls", @@ -160,9 +160,9 @@ members = [ "lib/galois-rpc", "lib/beacon-api-types", - "mpc/shared", - "mpc/client", - "mpc/coordinator", + # "mpc/shared", + # "mpc/client", + # "mpc/coordinator", "lib/ibc-union-spec", "lib/ibc-classic-spec", diff --git a/lib/state-lens/evm-light-client-types/src/client_state.rs b/lib/state-lens/evm-light-client-types/src/client_state.rs index f2c27bcddb..325dfdc6a5 100644 --- a/lib/state-lens/evm-light-client-types/src/client_state.rs +++ b/lib/state-lens/evm-light-client-types/src/client_state.rs @@ -22,8 +22,14 @@ pub struct ClientState { #[cfg(feature = "ethabi")] pub mod ethabi { + use core::str; + use std::string::FromUtf8Error; + use alloy::sol_types::SolValue; - use unionlabs::encoding::{Encode, EthAbi}; + use unionlabs::{ + encoding::{Decode, Encode, EthAbi}, + TryFromEthAbiBytesErrorAlloy, + }; use crate::ClientState; @@ -53,4 +59,29 @@ pub mod ethabi { .abi_encode_params() } } + + impl Decode for ClientState { + type Error = TryFromEthAbiBytesErrorAlloy; + + fn decode(bytes: &[u8]) -> Result { + let client_state = SolClientState::abi_decode(bytes, true)?; + + Ok(Self { + l2_chain_id: String::from_utf8(client_state.l2ChainId.into_bytes()) + .map_err(|err| TryFromEthAbiBytesErrorAlloy::Convert(Error::ChainId(err)))?, + l1_client_id: client_state.l1ClientId, + l2_client_id: client_state.l2ClientId, + l2_latest_height: client_state.l2LatestHeight, + timestamp_offset: client_state.timestampOffset, + state_root_offset: client_state.stateRootOffset, + storage_root_offset: client_state.storageRootOffset, + }) + } + } + + #[derive(Debug, Clone, PartialEq, thiserror::Error)] + pub enum Error { + #[error("invalid chain_id")] + ChainId(#[from] FromUtf8Error), + } } diff --git a/lib/voyager-message/src/lib.rs b/lib/voyager-message/src/lib.rs index 179c226f7f..1619acefef 100644 --- a/lib/voyager-message/src/lib.rs +++ b/lib/voyager-message/src/lib.rs @@ -582,11 +582,12 @@ impl VoyagerClient { pub async fn self_client_state( &self, chain_id: ChainId, + client_type: ClientType, height: QueryHeight, ) -> RpcResult { let client_state = self .0 - .self_client_state(chain_id, height) + .self_client_state(chain_id, client_type, height) .await .map_err(json_rpc_error_to_error_object)?; Ok(client_state) @@ -595,11 +596,12 @@ impl VoyagerClient { pub async fn self_consensus_state( &self, chain_id: ChainId, + client_type: ClientType, height: QueryHeight, ) -> RpcResult { let consensus_state = self .0 - .self_consensus_state(chain_id, height) + .self_consensus_state(chain_id, client_type, height) .await .map_err(json_rpc_error_to_error_object)?; Ok(consensus_state) diff --git a/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml b/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml new file mode 100644 index 0000000000..6407ef6fd0 --- /dev/null +++ b/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "voyager-client-bootstrap-module-state-lens-evm" +version = "0.1.0" + +[dependencies] +alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +cometbft-rpc = { workspace = true } +ibc-union-spec.workspace = true +beacon-api = { workspace = true } +beacon-api-types = { workspace = true, features = ["serde"] } +ethereum-light-client-types = { workspace = true, features = ["serde"] } +evm-state-lens-light-client-types = { workspace = true, features = ["serde"] } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +unionlabs = { workspace = true } +voyager-message = { workspace = true } +voyager-vm = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["full"] } diff --git a/voyager/modules/consensus/state-lens/evm/src/main.rs b/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs similarity index 55% rename from voyager/modules/consensus/state-lens/evm/src/main.rs rename to voyager/modules/client-bootstrap/state-lens/evm/src/main.rs index 2883d39215..1a5f553dee 100644 --- a/voyager/modules/consensus/state-lens/evm/src/main.rs +++ b/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs @@ -1,5 +1,3 @@ -use std::fmt::Debug; - use alloy::providers::{Provider, ProviderBuilder}; use ethereum_light_client_types::ConsensusState as EthConsensusState; use evm_state_lens_light_client_types::{ClientState, ConsensusState}; @@ -12,10 +10,10 @@ use serde_json::Value; use tracing::instrument; use unionlabs::ibc::core::client::height::Height; use voyager_message::{ - core::{ChainId, ConsensusType, QueryHeight}, + core::{ChainId, ClientType, QueryHeight}, into_value, - module::{ConsensusModuleInfo, ConsensusModuleServer}, - ConsensusModule, ExtensionsExt, VoyagerClient, + module::{ClientBootstrapModuleInfo, ClientBootstrapModuleServer}, + ClientBootstrapModule, ExtensionsExt, VoyagerClient, }; use voyager_vm::BoxDynError; @@ -26,14 +24,12 @@ async fn main() { #[derive(Debug, Clone)] pub struct Module { - pub l1_chain_id: ChainId, pub l2_chain_id: ChainId, pub l1_client_id: u32, pub l2_client_id: u32, pub timestamp_offset: u16, pub state_root_offset: u16, pub storage_root_offset: u16, - pub l1_tm_client: cometbft_rpc::Client, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -43,34 +39,17 @@ pub struct Config { pub timestamp_offset: u16, pub state_root_offset: u16, pub storage_root_offset: u16, - pub l1_comet_ws_url: String, - pub l2_eth_rpc_url: String, } -impl ConsensusModule for Module { +impl ClientBootstrapModule for Module { type Config = Config; - async fn new(config: Self::Config, info: ConsensusModuleInfo) -> Result { - let l1_tm_client = cometbft_rpc::Client::new(config.l1_comet_ws_url).await?; - - let l1_chain_id = l1_tm_client.status().await?.node_info.network.to_string(); - - info.ensure_chain_id(&l1_chain_id)?; - info.ensure_consensus_type(ConsensusType::COMETBLS)?; - - let provider = ProviderBuilder::new() - .on_builtin(&config.l2_eth_rpc_url) - .await?; - - let l2_chain_id = ChainId::new(provider.get_chain_id().await?.to_string()); - - info.ensure_chain_id(l2_chain_id.to_string())?; - info.ensure_consensus_type(ConsensusType::ETHEREUM)?; - + async fn new( + config: Self::Config, + info: ClientBootstrapModuleInfo, + ) -> Result { Ok(Self { - l1_tm_client, - l1_chain_id: ChainId::new(l1_chain_id), - l2_chain_id: ChainId::new(l2_chain_id.to_string()), + l2_chain_id: info.chain_id, l1_client_id: config.l1_client_id, l2_client_id: config.l2_client_id, timestamp_offset: config.timestamp_offset, @@ -81,25 +60,7 @@ impl ConsensusModule for Module { } #[async_trait] -impl ConsensusModuleServer for Module { - /// Query the latest finalized height of this chain. - #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] - async fn query_latest_height(&self, ext: &Extensions, finalized: bool) -> RpcResult { - let voy_client = ext.try_get::()?; - voy_client - .query_latest_height(self.l2_chain_id.clone(), finalized) - .await - } - - /// Query the latest finalized timestamp of this chain. - #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] - async fn query_latest_timestamp(&self, ext: &Extensions, finalized: bool) -> RpcResult { - let voy_client = ext.try_get::()?; - voy_client - .query_latest_timestamp(self.l2_chain_id.clone(), finalized) - .await - } - +impl ClientBootstrapModuleServer for Module { #[instrument(skip_all, fields(chain_id = %self.l2_chain_id))] async fn self_client_state(&self, _: &Extensions, height: Height) -> RpcResult { Ok(into_value(ClientState { @@ -118,7 +79,11 @@ impl ConsensusModuleServer for Module { async fn self_consensus_state(&self, ext: &Extensions, height: Height) -> RpcResult { let voy_client = ext.try_get::()?; let state = voy_client - .self_consensus_state(self.l2_chain_id.clone(), QueryHeight::Specific(height)) + .self_consensus_state( + self.l2_chain_id.clone(), + ClientType::new(ClientType::ETHEREUM), + QueryHeight::Specific(height), + ) .await? .state; let consensus_state = diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index 91ab194fa0..35f5635f96 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -16,7 +16,7 @@ use unionlabs::{ ErrorReporter, }; use voyager_message::{ - core::{ChainId, ClientStateMeta, ClientType, ConsensusStateMeta}, + core::{ChainId, ClientStateMeta, ClientType, ConsensusStateMeta, ConsensusType, IbcInterface}, module::{ClientModuleInfo, ClientModuleServer}, ClientModule, FATAL_JSONRPC_ERROR_CODE, }; @@ -38,6 +38,8 @@ impl ClientModule for Module { async fn new(_: Self::Config, info: ClientModuleInfo) -> Result { info.ensure_client_type(ClientType::STATE_LENS_EVM)?; + info.ensure_consensus_type(ConsensusType::ETHEREUM)?; + info.ensure_ibc_interface(IbcInterface::IBC_SOLIDITY)?; Ok(Self {}) } } @@ -57,7 +59,7 @@ impl Module { } pub fn decode_client_state(client_state: &[u8]) -> RpcResult { - ::decode_as::(client_state).map_err(|err| { + ::decode_as::(client_state).map_err(|err| { ErrorObject::owned( FATAL_JSONRPC_ERROR_CODE, format!("unable to decode client state: {}", ErrorReporter(err)), @@ -83,7 +85,7 @@ impl ClientModuleServer for Module { Ok(ClientStateMeta { chain_id: ChainId::new(cs.l2_chain_id.to_string()), - height: Module::make_height(cs.l2_latest_height), + counterparty_height: Module::make_height(cs.l2_latest_height), }) } @@ -209,6 +211,7 @@ impl ClientModuleServer for Module { ) })?; // TODO: extract to unionlabs? this is MPT proofs encoding for EVM + // the solidity MPT verifier expects the proof RLP nodes to be serialized in sequence Ok(proof.proof.concat().into()) } } diff --git a/voyager/modules/consensus/state-lens/evm/Cargo.toml b/voyager/modules/consensus/state-lens/evm/Cargo.toml deleted file mode 100644 index 3afc50c389..0000000000 --- a/voyager/modules/consensus/state-lens/evm/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -edition = "2021" -name = "voyager-consensus-module-state-lens-evm" -version = "0.1.0" - -[dependencies] -alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } -cometbft-rpc = { workspace = true } -dashmap = { workspace = true } -enumorph = { workspace = true } -ethereum-light-client-types = { workspace = true, features = ["serde"] } -evm-state-lens-light-client-types = { workspace = true, features = ["serde"] } -futures = { workspace = true } -ics23 = { workspace = true } -jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } -macros = { workspace = true } -num-bigint = { workspace = true } -prost = { workspace = true } -protos = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -unionlabs = { workspace = true } -voyager-message = { workspace = true } -voyager-vm = { workspace = true } diff --git a/voyager/plugins/client-update/state-lens/evm/src/main.rs b/voyager/plugins/client-update/state-lens/evm/src/main.rs index 06f0099369..642792973c 100644 --- a/voyager/plugins/client-update/state-lens/evm/src/main.rs +++ b/voyager/plugins/client-update/state-lens/evm/src/main.rs @@ -281,7 +281,7 @@ impl PluginServer for Module { client_type: ClientType::new(ClientType::COMETBLS), chain_id: self.l1_chain_id.clone(), counterparty_chain_id: counterparty_chain_id.clone(), - update_from: l0_client_meta.height, + update_from: l0_client_meta.counterparty_height, update_to: l1_latest_height, })], [], @@ -294,7 +294,7 @@ impl PluginServer for Module { seq([ call(WaitForTrustedHeight { chain_id: counterparty_chain_id, - ibc_spec_id: IbcSpecId::new(IbcSpecId::UNION), + ibc_spec_id: IbcUnion::ID, client_id: RawClientId::new(self.l0_client_id), height: l1_latest_height, }), diff --git a/voyager/src/main.rs b/voyager/src/main.rs index e8bf5fdc8c..7a5265c428 100644 --- a/voyager/src/main.rs +++ b/voyager/src/main.rs @@ -767,25 +767,25 @@ pub mod utils { .await?; trace!(%self_consensus_state); - let consensus_type = ctx - .rpc_server - .modules()? - .chain_consensus_type(&counterparty_chain_id)?; - - let client_consensus_type = ctx - .rpc_server - .modules()? - .client_consensus_type(&client_type)?; - - if client_consensus_type != consensus_type { - return Err(anyhow!( - "attempted to create a {client_type} client on \ - {chain_id} tracking {counterparty_chain_id}, but \ - the consensus of that chain ({consensus_type}) is \ - not verifiable by a client of type {client_type} \ - (which instead verifies {client_consensus_type})." - )); - } + // let consensus_type = ctx + // .rpc_server + // .modules()? + // .chain_consensus_type(&counterparty_chain_id)?; + + // let client_consensus_type = ctx + // .rpc_server + // .modules()? + // .client_consensus_type(&client_type)?; + + // if client_consensus_type != consensus_type { + // return Err(anyhow!( + // "attempted to create a {client_type} client on \ + // {chain_id} tracking {counterparty_chain_id}, but \ + // the consensus of that chain ({consensus_type}) is \ + // not verifiable by a client of type {client_type} \ + // (which instead verifies {client_consensus_type})." + // )); + // } let client_module = ctx.rpc_server From 03ae6b5408b0397b8e672c96957e6f8938ff323b Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 30 Dec 2024 22:39:03 +0100 Subject: [PATCH 07/11] fix(evm): invalid mpt verification in evm state lens --- evm/contracts/clients/EvmInCosmosClient.sol | 23 ++++++++-------- evm/tests/src/lib/MPTVerifier.t.sol | 30 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/evm/contracts/clients/EvmInCosmosClient.sol b/evm/contracts/clients/EvmInCosmosClient.sol index 76d22ed5ee..8fd01b4250 100644 --- a/evm/contracts/clients/EvmInCosmosClient.sol +++ b/evm/contracts/clients/EvmInCosmosClient.sol @@ -216,14 +216,14 @@ contract EvmInCosmosClient is } bytes32 storageRoot = consensusStates[clientId][height].storageRoot; bytes32 slot = keccak256( - abi.encodePacked( - keccak256(abi.encodePacked(path)), - EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT - ) + abi.encodePacked(path, EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT) + ); + (bool exists, bytes calldata provenValue) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot ); - (bool exists, bytes calldata provenValue) = - MPTVerifier.verifyTrieValue(proof, slot, storageRoot); - return exists && keccak256(value) == keccak256(provenValue); + return exists + && keccak256(RLP.encodeUint(uint256(bytes32(value)))) + == keccak256(provenValue); } function verifyNonMembership( @@ -237,12 +237,11 @@ contract EvmInCosmosClient is } bytes32 storageRoot = consensusStates[clientId][height].storageRoot; bytes32 slot = keccak256( - abi.encodePacked( - keccak256(abi.encodePacked(path)), - EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT - ) + abi.encodePacked(path, EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT) + ); + (bool exists,) = MPTVerifier.verifyTrieValue( + proof, keccak256(abi.encodePacked(slot)), storageRoot ); - (bool exists,) = MPTVerifier.verifyTrieValue(proof, slot, storageRoot); return !exists; } diff --git a/evm/tests/src/lib/MPTVerifier.t.sol b/evm/tests/src/lib/MPTVerifier.t.sol index f185959db8..5aaa57552d 100644 --- a/evm/tests/src/lib/MPTVerifier.t.sol +++ b/evm/tests/src/lib/MPTVerifier.t.sol @@ -5,6 +5,34 @@ import "forge-std/Test.sol"; import "../../../contracts/lib/MPTVerifier.sol"; contract MPTVerifierTests is Test { + function test_verify_ok2() public { + vm.pauseGasMetering(); + bytes[] memory proof = new bytes[](3); + proof[0] = + hex"f90211a0290ff9c2465abdc3e521b0e22d434ca9965d9294f984c4af27b62defa7aa0404a0681afeef44df0f0f3ff44a1fc6b6b1c1b5b3ddf1df4b8334184ac69e06d663cea0e7d87d908639d88cccb5e82139e6969ef7a60ef15f2c1b92a42721c00a684534a0cb0d69ffacac2472aba8113fabe43ae0fb1ec1adc0ba524b4d77a4ae1b9f1834a0d8c1a0faa0ee7b3d651997d9bed61cd1a38fdd1d5811d0f6f35135d505772271a055d3a97b39c767db94b3a1ec2cd527ecee17b2c48b05e478c846f74e8c4b0770a07f84fae77d495ad51e1754ca932a17967af94e0ab56da206569bf581b86ff1a3a0444fdf31592bedd27a9525245ce36aa23bb53767574d2ddf7db0c8ed649b7d08a02724a8992048374ba00a4381a1a0c44a10a3863647977f61e668de5532ae10eda0ceff06700cbb9dc8b2a95604aa18ec3863877f64b0101fdeba6fff45aa220e98a02a3280086775de99c51785b0281459bce312b7231bcd59a030dc3c277bb29854a03506c7687acc02c53b15bb4c15cea1cb1065b7a24a1a931200bd117d88dee84ea055759451409a66a368f9ee9bce914924080954448b54ee7e849fabdd7d5d4124a0fdfe38b6023fb6e7a4728a07872f191a0b173ca2b2f2c9dac4e46b1478594903a03bdaa97bd901df14cad52a8369a832f766f1da1877c72c26dff87e4f8eedf73fa02d69cf9241410b8bd737972cbf4675a8bfdce0f1ce923a7a5c8209579ba0b55180"; + proof[1] = + hex"f901518080a0fe0849c9829308dfeebb656b80c84fd25cddad6195e55da1759fb534872d0565a0492b4ed07e3463e8a3f2280e664efe6dcd914e8b6f96dd457b2fb1514fa4dcf3a0136bc83176214e5c162c0d0ee80ec5c99bb74b612de9d31651547629a6d3bff680a0b03da2ced67fe1e95e2d166e4faeac40f4deb62242e768646e20302e989ab6eb80a06507648f5ee64cf12436e03298b41203bf3bb7344ef267853802bf97fd9b48cba093f776423813a1ca4b75baa37b23e49a02898a68961e357d9e8064663d5bb20380a0450922b2b63e417a5397d6f4346828778a5d58bdf292948e1c542a1ed0319ee480a0d76251a116716185de4f499c9934deb89fb9bfecbd85d4e6d8fa268958fd4eb8a0cc6759e97e0d6b2947385d706a65b69f15512add39d2163b3cbcfd427307df87a036cee042c4ab7e473bfb673d9a8f99eb6d86ad267803005fdeb57eb636121ad180"; + proof[2] = + hex"f843a020d71926b1d4cc00b9747141c15cf96296e56262d843136a42daf00aca967037a1a0faadeddd9e83b87f941ff7ac6c1ff3a55a976f082f579d64ca49253295321ca6"; + bytes memory proofChain = hex""; + for (uint256 i = 0; i < 3; i++) { + proofChain = abi.encodePacked(proofChain, proof[i]); + } + this.checkExistence( + 0x64bec87c43e402ed2648ae3e10c9ba5d980ac30ae0dfcc4c90a47856380ce76a, + proofChain, + keccak256( + abi.encodePacked( + hex"91da3fd0782e51c6b3986e9e672fd566868e71f3dbc2d6c2cd6fbb3e361af2a7", + uint256(0) + ) + ), + RLP.encodeUint( + 0xfaadeddd9e83b87f941ff7ac6c1ff3a55a976f082f579d64ca49253295321ca6 + ) + ); + } + /* Proof extracted from ethereum mainnet * {"method":"eth_getProof","params":["0xd1d2eb1b1e90b638588728b4130137d262c87cae",["0x0"], "0x14655B2"],"id":1,"jsonrpc":"2.0"} */ @@ -82,7 +110,7 @@ contract MPTVerifierTests is Test { bytes32 slot ) public { vm.resumeGasMetering(); - (bool exists, bytes calldata value) = MPTVerifier.verifyTrieValue( + (bool exists, bytes calldata _value) = MPTVerifier.verifyTrieValue( proof, keccak256(abi.encodePacked(slot)), storageRoot ); assertEq(exists, false); From 1270f10e3257413e1fb3ccabb4e0a85bc2c8b879 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 30 Dec 2024 22:40:13 +0100 Subject: [PATCH 08/11] feat(voyager): more state lenses --- evm/evm.nix | 5 --- lib/voyager-message/src/call.rs | 9 +++++- lib/voyager-message/src/rpc/server.rs | 2 +- .../state-lens/evm/Cargo.toml | 26 +++++++-------- .../modules/client/state-lens/evm/src/main.rs | 2 +- .../client-update/state-lens/evm/src/main.rs | 22 ++++++++++--- .../periodic-client-update/src/main.rs | 1 + voyager/plugins/transaction-batch/Cargo.toml | 2 +- .../plugins/transaction-batch/src/callback.rs | 32 ++++++++----------- .../transaction/cosmos-sdk/src/main.rs | 2 +- .../plugins/transaction/ethereum/Cargo.toml | 2 +- 11 files changed, 59 insertions(+), 46 deletions(-) diff --git a/evm/evm.nix b/evm/evm.nix index dfa5f2f619..a7cef45ec5 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -360,11 +360,6 @@ _: { required = true; help = "The sender address that created the contract through the deployer."; } - { - arg = "etherscan_api_key"; - required = true; - help = "The sender address that created the contract through the deployer."; - } ]; text = '' ${ensureAtRepositoryRoot} diff --git a/lib/voyager-message/src/call.rs b/lib/voyager-message/src/call.rs index 7c77ce29c5..6b8c4d6439 100644 --- a/lib/voyager-message/src/call.rs +++ b/lib/voyager-message/src/call.rs @@ -132,6 +132,7 @@ pub struct WaitForTrustedHeight { pub ibc_spec_id: IbcSpecId, pub client_id: RawClientId, pub height: Height, + pub finalized: bool, } impl CallT for Call { @@ -258,6 +259,7 @@ impl CallT for Call { ibc_spec_id, client_id, height, + finalized, }) => { let trusted_client_state_meta = ctx .rpc_server @@ -265,7 +267,11 @@ impl CallT for Call { .client_meta( &chain_id, &ibc_spec_id, - QueryHeight::Latest, + if finalized { + QueryHeight::Finalized + } else { + QueryHeight::Latest + }, client_id.clone(), ) .await @@ -288,6 +294,7 @@ impl CallT for Call { ibc_spec_id, client_id, height, + finalized, }), ])) } diff --git a/lib/voyager-message/src/rpc/server.rs b/lib/voyager-message/src/rpc/server.rs index a37d5c6d15..1a6cc3a4e8 100644 --- a/lib/voyager-message/src/rpc/server.rs +++ b/lib/voyager-message/src/rpc/server.rs @@ -254,7 +254,7 @@ impl Server { .unwrap(), ) .await - .map_err(fatal_error)?; + .map_err(json_rpc_error_to_error_object)?; trace!(%client_state); diff --git a/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml b/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml index 6407ef6fd0..c0f4ad169b 100644 --- a/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml +++ b/voyager/modules/client-bootstrap/state-lens/evm/Cargo.toml @@ -4,21 +4,21 @@ name = "voyager-client-bootstrap-module-state-lens-evm" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +beacon-api = { workspace = true } +beacon-api-types = { workspace = true, features = ["serde"] } cometbft-rpc = { workspace = true } -ibc-union-spec.workspace = true -beacon-api = { workspace = true } -beacon-api-types = { workspace = true, features = ["serde"] } -ethereum-light-client-types = { workspace = true, features = ["serde"] } +ethereum-light-client-types = { workspace = true, features = ["serde"] } evm-state-lens-light-client-types = { workspace = true, features = ["serde"] } -jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -unionlabs = { workspace = true } -voyager-message = { workspace = true } -voyager-vm = { workspace = true } +ibc-union-spec.workspace = true +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +unionlabs = { workspace = true } +voyager-message = { workspace = true } +voyager-vm = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index 35f5635f96..4615411254 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -11,7 +11,7 @@ use tracing::instrument; use unionlabs::{ self, bytes::Bytes, - encoding::{Bincode, DecodeAs, EncodeAs, EthAbi}, + encoding::{DecodeAs, EncodeAs, EthAbi}, ibc::core::client::height::Height, ErrorReporter, }; diff --git a/voyager/plugins/client-update/state-lens/evm/src/main.rs b/voyager/plugins/client-update/state-lens/evm/src/main.rs index 642792973c..6549b04c46 100644 --- a/voyager/plugins/client-update/state-lens/evm/src/main.rs +++ b/voyager/plugins/client-update/state-lens/evm/src/main.rs @@ -12,12 +12,12 @@ use jsonrpsee::{ Extensions, }; use serde::{Deserialize, Serialize}; -use tracing::instrument; +use tracing::{debug, instrument}; use unionlabs::ibc::core::commitment::merkle_proof::MerkleProof; use voyager_message::{ call::{Call, FetchUpdateHeaders, WaitForTrustedHeight}, callback::AggregateMsgUpdateClientsFromOrderedHeaders, - core::{ChainId, ClientType, IbcSpec, IbcSpecId, QueryHeight}, + core::{ChainId, ClientType, IbcSpec, QueryHeight}, data::{Data, DecodedHeaderMeta, OrderedHeaders}, hook::UpdateHook, into_value, @@ -228,6 +228,7 @@ impl PluginServer for Module { ibc_spec_id: IbcUnion::ID, client_id: RawClientId::new(self.l1_client_id), height: update_to, + finalized: true, }), continuation, ]), @@ -236,13 +237,13 @@ impl PluginServer for Module { } ModuleCall::FetchUpdateAfterL1Update(FetchUpdateAfterL1Update { counterparty_chain_id, - update_to, .. }) => { let voy_client = ext.try_get::()?; let l1_latest_height = voy_client - .query_latest_height(self.l1_chain_id.clone(), true) + .query_latest_height(self.l1_chain_id.clone(), false) .await?; + debug!("l1 latest height {}", l1_latest_height); let l0_client_meta = voy_client .client_meta::( counterparty_chain_id.clone(), @@ -250,6 +251,16 @@ impl PluginServer for Module { self.l0_client_id, ) .await?; + let l1_client_meta = voy_client + .client_meta::( + self.l1_chain_id.clone(), + QueryHeight::Specific(l1_latest_height), + self.l1_client_id, + ) + .await?; + // The client has been updated to at least update_to + let update_to = l1_client_meta.counterparty_height; + debug!("l0 client meta {:#?}", l0_client_meta); let l2_consensus_state_path = ConsensusStatePath { client_id: self.l1_client_id, height: update_to.height(), @@ -262,6 +273,7 @@ impl PluginServer for Module { ) .await? .state; + debug!("l2 consensus state {:#?}", l2_consensus_state); let l2_consensus_state_proof = serde_json::from_value::( voy_client .query_ibc_proof( @@ -274,6 +286,7 @@ impl PluginServer for Module { .proof, ) .expect("impossible"); + debug!("l2 consensus state proof {:#?}", l2_consensus_state_proof); // Dispatch an update for the L1 on the destination, then dispatch the L2 update on the destination Ok(conc([ promise( @@ -297,6 +310,7 @@ impl PluginServer for Module { ibc_spec_id: IbcUnion::ID, client_id: RawClientId::new(self.l0_client_id), height: l1_latest_height, + finalized: false, }), data(OrderedHeaders { headers: vec![( diff --git a/voyager/plugins/periodic-client-update/src/main.rs b/voyager/plugins/periodic-client-update/src/main.rs index 245e8e4755..30dbf2df81 100644 --- a/voyager/plugins/periodic-client-update/src/main.rs +++ b/voyager/plugins/periodic-client-update/src/main.rs @@ -158,6 +158,7 @@ impl Module { client_meta.counterparty_height.revision(), client_meta.counterparty_height.height() + max_age, ), + finalized: false, }), call(PluginMessage::new( self.plugin_name(), diff --git a/voyager/plugins/transaction-batch/Cargo.toml b/voyager/plugins/transaction-batch/Cargo.toml index 0876aa1f59..cc22935039 100644 --- a/voyager/plugins/transaction-batch/Cargo.toml +++ b/voyager/plugins/transaction-batch/Cargo.toml @@ -4,7 +4,7 @@ name = "voyager-plugin-transaction-batch" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["sol-types", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +alloy = { workspace = true, features = ["sol-types", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } either = { workspace = true } enumorph = { workspace = true } futures = { workspace = true } diff --git a/voyager/plugins/transaction-batch/src/callback.rs b/voyager/plugins/transaction-batch/src/callback.rs index e51a214905..18fdf5084d 100644 --- a/voyager/plugins/transaction-batch/src/callback.rs +++ b/voyager/plugins/transaction-batch/src/callback.rs @@ -49,7 +49,7 @@ where module_server: &Module, datas: VecDeque, ) -> RpcResult> { - let updates @ OrderedClientUpdates { .. } = datas + let updates: Option = datas .into_iter() .exactly_one() .map_err(|found| serde_json::to_string(&found.collect::>()).unwrap()) @@ -57,17 +57,7 @@ where d.try_into() .map_err(|found| serde_json::to_string(&found).unwrap()) }) - .map_err(|found| { - ErrorObject::owned( - FATAL_JSONRPC_ERROR_CODE, - format!( - "OrderedHeaders not present in data queue for \ - AggregateMsgUpdateClientsFromOrderedHeaders, \ - found {found}", - ), - None::<()>, - ) - })?; + .ok(); let client_meta = voyager_client .client_meta::( @@ -78,17 +68,22 @@ where .await?; let new_trusted_height = updates - .updates - .last() - .expect("must have at least one update") - .0 - .height; + .as_ref() + .map(|updates| { + updates + .updates + .last() + .expect("must have at least one update") + .0 + .height + }) + .unwrap_or(client_meta.counterparty_height); make_msgs( module_server, self.client_id, self.batches, - Some(updates), + updates, client_meta, new_trusted_height, ) @@ -238,6 +233,7 @@ impl MakeBatchTransaction { client_id: RawClientId::new(self.client_id.clone()), ibc_spec_id: V::ID, height: required_consensus_height, + finalized: false, }), call(SubmitTx { chain_id, diff --git a/voyager/plugins/transaction/cosmos-sdk/src/main.rs b/voyager/plugins/transaction/cosmos-sdk/src/main.rs index 714160fdf9..c92a6fc78f 100644 --- a/voyager/plugins/transaction/cosmos-sdk/src/main.rs +++ b/voyager/plugins/transaction/cosmos-sdk/src/main.rs @@ -653,7 +653,7 @@ impl PluginServer for Module { ModuleCall::SubmitTransaction(msgs) => { let mut out = vec![]; - for msgs in msgs.chunks(5) { + for msgs in msgs.chunks(1) { let res = self .do_send_transaction(msgs.to_vec()) .await diff --git a/voyager/plugins/transaction/ethereum/Cargo.toml b/voyager/plugins/transaction/ethereum/Cargo.toml index d61f9c0d81..a4438a1688 100644 --- a/voyager/plugins/transaction/ethereum/Cargo.toml +++ b/voyager/plugins/transaction/ethereum/Cargo.toml @@ -4,7 +4,7 @@ name = "voyager-transaction-plugin-ethereum" version = "0.1.0" [dependencies] -alloy = { workspace = true, features = ["contract", "network", "providers", "signers", "signer-local", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } +alloy = { workspace = true, features = ["contract", "network", "providers", "signers", "signer-local", "rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } bip32 = { workspace = true } chain-utils = { workspace = true } enumorph = { workspace = true } From 24abb83992555ab62f796d7e6dec3880bef88afe Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 30 Dec 2024 22:45:08 +0100 Subject: [PATCH 09/11] chore: clippy & reenable missing packages --- Cargo.toml | 6 +++--- voyager/modules/client/state-lens/evm/src/main.rs | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27e54c25f4..a4412bdc48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,9 +160,9 @@ members = [ "lib/galois-rpc", "lib/beacon-api-types", - # "mpc/shared", - # "mpc/client", - # "mpc/coordinator", + "mpc/shared", + "mpc/client", + "mpc/coordinator", "lib/ibc-union-spec", "lib/ibc-classic-spec", diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index 4615411254..73009afaf9 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -17,6 +17,7 @@ use unionlabs::{ }; use voyager_message::{ core::{ChainId, ClientStateMeta, ClientType, ConsensusStateMeta, ConsensusType, IbcInterface}, + into_value, module::{ClientModuleInfo, ClientModuleServer}, ClientModule, FATAL_JSONRPC_ERROR_CODE, }; @@ -104,7 +105,7 @@ impl ClientModuleServer for Module { #[instrument] async fn decode_client_state(&self, _: &Extensions, client_state: Bytes) -> RpcResult { - Ok(serde_json::to_value(Module::decode_client_state(&client_state)?).unwrap()) + Ok(into_value(Module::decode_client_state(&client_state)?)) } #[instrument] @@ -113,7 +114,9 @@ impl ClientModuleServer for Module { _: &Extensions, consensus_state: Bytes, ) -> RpcResult { - Ok(serde_json::to_value(Module::decode_consensus_state(&consensus_state)?).unwrap()) + Ok(into_value(Module::decode_consensus_state( + &consensus_state, + )?)) } #[instrument] From 264e4613e572e0910f344b6ebea57fe42450556c Mon Sep 17 00:00:00 2001 From: benluelo Date: Tue, 31 Dec 2024 14:49:31 +0000 Subject: [PATCH 10/11] chore: update Cargo.lock --- Cargo.lock | 543 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 541 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25a14a089e..3b41449d88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,7 +892,7 @@ dependencies = [ "thiserror", "tiny-keccak", "typenum", - "x25519-dalek", + "x25519-dalek 1.2.0", ] [[package]] @@ -1400,6 +1400,18 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arith" version = "0.2.3" @@ -2332,6 +2344,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -2467,6 +2485,25 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "blst" version = "0.3.13" @@ -2547,6 +2584,25 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "buffer-redux" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8acf87c5b9f5897cd3ebb9a327f420e0cae9dd4e5c1d2e36f2c84c571a58f1" +dependencies = [ + "memchr", +] + [[package]] name = "bulletproofs" version = "4.0.0" @@ -2626,6 +2682,16 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "camino" version = "1.1.6" @@ -2674,12 +2740,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cast5" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +dependencies = [ + "cipher", +] + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.90" @@ -2692,6 +2782,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-expr" version = "0.15.7" @@ -2900,6 +2999,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest 0.10.7", +] + [[package]] name = "codespan" version = "0.11.1" @@ -3057,6 +3167,19 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "console" version = "0.15.8" @@ -3400,6 +3523,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc24" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" + [[package]] name = "crc32fast" version = "1.4.0" @@ -3524,6 +3653,31 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -4051,6 +4205,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "der" version = "0.7.9" @@ -4104,6 +4267,37 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "derive_builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +dependencies = [ + "derive_builder_core", + "syn 2.0.77", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -4138,6 +4332,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "devnet-compose" version = "0.1.0" @@ -4265,6 +4468,22 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest 0.10.7", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2 0.10.8", + "signature 2.2.0", + "zeroize", +] + [[package]] name = "dunce" version = "1.0.4" @@ -4277,6 +4496,19 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher", + "cmac", + "ctr", + "subtle 2.5.0", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -4415,6 +4647,7 @@ dependencies = [ "ff", "generic-array 0.14.7", "group", + "hkdf 0.12.4", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", @@ -6051,6 +6284,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "idea" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" +dependencies = [ + "cipher", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -6281,6 +6523,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iter-read" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" + [[package]] name = "itertools" version = "0.10.5" @@ -6978,6 +7226,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -7471,6 +7731,58 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "mpc-client" +version = "0.1.0" +dependencies = [ + "async-sqlite", + "base64 0.21.7", + "crossterm", + "futures-util", + "hex", + "http-body-util", + "httpdate", + "hyper 1.3.1", + "hyper-util", + "mpc-shared", + "pgp", + "ratatui", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "throbber-widgets-tui", + "tokio", + "tokio-util", +] + +[[package]] +name = "mpc-coordinator" +version = "0.1.0" +dependencies = [ + "clap 4.5.4", + "hex", + "mpc-shared", + "pgp", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber 0.3.18", +] + +[[package]] +name = "mpc-shared" +version = "0.1.0" +dependencies = [ + "hex", + "postgrest", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "tokio", +] + [[package]] name = "multer" version = "3.1.0" @@ -7747,6 +8059,7 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", + "serde", "smallvec", "zeroize", ] @@ -7877,6 +8190,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "ocb3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle 2.5.0", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -8001,6 +8326,32 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -8112,6 +8463,17 @@ dependencies = [ "typeshare", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle 2.5.0", +] + [[package]] name = "pasta_curves" version = "0.5.1" @@ -8302,6 +8664,70 @@ dependencies = [ "voyager-vm", ] +[[package]] +name = "pgp" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6c842436d5fa2b59eac1e9b3d142b50bfff99c1744c816b1f4c2ac55a20754" +dependencies = [ + "aes", + "aes-gcm", + "argon2", + "base64 0.22.1", + "bitfield", + "block-padding 0.3.3", + "blowfish", + "bstr", + "buffer-redux", + "byteorder", + "camellia", + "cast5", + "cfb-mode", + "chrono", + "cipher", + "const-oid", + "crc24", + "curve25519-dalek 4.1.3", + "derive_builder", + "des", + "digest 0.10.7", + "dsa", + "eax", + "ecdsa", + "ed25519-dalek 2.1.1", + "elliptic-curve", + "flate2", + "generic-array 0.14.7", + "hex", + "hkdf 0.12.4", + "idea", + "iter-read", + "k256", + "log", + "md-5", + "nom", + "num-bigint-dig", + "num-traits", + "num_enum", + "ocb3", + "p256", + "p384", + "p521", + "rand 0.8.5", + "ripemd", + "rsa", + "sha1", + "sha1-checked", + "sha2 0.10.8", + "sha3 0.10.8", + "signature 2.2.0", + "smallvec", + "thiserror", + "twofish", + "x25519-dalek 2.0.1", + "zeroize", +] + [[package]] name = "pharos" version = "0.5.3" @@ -8536,6 +8962,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "postgrest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a966c650b47a064e7082170b4be74fca08c088d893244fc4b70123e3c1f3ee7" +dependencies = [ + "reqwest 0.11.27", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -9103,6 +9538,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "ratatui" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.13.0", + "lru 0.12.5", + "paste", + "stability", + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "rayon" version = "1.10.0" @@ -10213,6 +10669,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest 0.10.7", + "sha1", +] + [[package]] name = "sha2" version = "0.8.2" @@ -10290,6 +10756,27 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -10701,6 +11188,16 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.77", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -11239,6 +11736,16 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "throbber-widgets-tui" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685391f5e78b08989c1014f5a0edddf6751e0726b6a8dd1bdcc98d05921b19b6" +dependencies = [ + "rand 0.8.5", + "ratatui", +] + [[package]] name = "tidy" version = "0.1.0" @@ -11366,7 +11873,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -11877,6 +12384,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twofish" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" +dependencies = [ + "cipher", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -12063,6 +12579,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.14" @@ -13722,6 +14249,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yaml-rust" version = "0.4.5" From ef1fc1e7d979089e1f274b31eca445a50137c4cd Mon Sep 17 00:00:00 2001 From: benluelo Date: Tue, 31 Dec 2024 15:00:34 +0000 Subject: [PATCH 11/11] chore: cleanup --- lib/chain-utils/src/cosmos_sdk.rs | 1 + lib/voyager-core/src/lib.rs | 2 ++ lib/voyager-message/src/lib.rs | 4 ++-- .../modules/client-bootstrap/state-lens/evm/src/main.rs | 1 - voyager/modules/client/state-lens/evm/src/main.rs | 7 +++++-- voyager/modules/consensus/berachain/src/main.rs | 3 --- voyager/modules/consensus/cometbls/src/main.rs | 9 +++------ voyager/modules/consensus/ethereum/src/main.rs | 1 - voyager/modules/consensus/tendermint/src/main.rs | 8 +++----- voyager/modules/state/movement/src/main.rs | 3 +-- voyager/plugins/client-update/berachain/src/main.rs | 1 + voyager/plugins/client-update/state-lens/evm/src/main.rs | 4 ++-- voyager/plugins/transaction-batch/src/callback.rs | 4 ++-- voyager/plugins/transaction/ethereum/src/main.rs | 2 +- voyager/src/main.rs | 2 +- 15 files changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/chain-utils/src/cosmos_sdk.rs b/lib/chain-utils/src/cosmos_sdk.rs index 89a2bc2a6d..7700291389 100644 --- a/lib/chain-utils/src/cosmos_sdk.rs +++ b/lib/chain-utils/src/cosmos_sdk.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +#[allow(unused_imports, reason = "it is used???")] use bip32::secp256k1::ecdsa::signature::SignatureEncoding; use prost::{Message, Name}; use serde::{Deserialize, Serialize}; diff --git a/lib/voyager-core/src/lib.rs b/lib/voyager-core/src/lib.rs index 9f88f2d0d0..a73698251e 100644 --- a/lib/voyager-core/src/lib.rs +++ b/lib/voyager-core/src/lib.rs @@ -113,10 +113,12 @@ impl ClientType { /// [Movement]: https://github.com/movementlabsxyz/movement pub const MOVEMENT: &'static str = "movement"; + // TODO: Update this doc comment and rename the client type to be more accurate /// A client tracking the Ethereum beacon chain consensus verified through the /// [Ethereum Proof-of-Stake Consensus Specifications](spec). As an L2 extracted from [CometBLS]. /// /// [spec]: https://github.com/ethereum/consensus-specs + /// [CometBLS]: https://github.com/unionlabs/cometbls pub const STATE_LENS_EVM: &'static str = "state-lens/evm"; // lots more to come - near, linea, polygon - stay tuned diff --git a/lib/voyager-message/src/lib.rs b/lib/voyager-message/src/lib.rs index 1619acefef..10d5b0458b 100644 --- a/lib/voyager-message/src/lib.rs +++ b/lib/voyager-message/src/lib.rs @@ -32,7 +32,7 @@ use tracing::{ use unionlabs::{bytes::Bytes, ibc::core::client::height::Height, traits::Member, ErrorReporter}; use voyager_core::{ ChainId, ClientInfo, ClientStateMeta, ClientType, IbcInterface, IbcSpec, IbcSpecId, - IbcStorePathKey, QueryHeight, + IbcStorePathKey, QueryHeight, Timestamp, }; use voyager_vm::{ItemId, QueueError, QueueMessage}; @@ -569,7 +569,7 @@ impl VoyagerClient { &self, chain_id: ChainId, finalized: bool, - ) -> RpcResult { + ) -> RpcResult { let latest_timestamp = self .0 .query_latest_timestamp(chain_id, finalized) diff --git a/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs b/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs index 1a5f553dee..a0832116dc 100644 --- a/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs +++ b/voyager/modules/client-bootstrap/state-lens/evm/src/main.rs @@ -1,4 +1,3 @@ -use alloy::providers::{Provider, ProviderBuilder}; use ethereum_light_client_types::ConsensusState as EthConsensusState; use evm_state_lens_light_client_types::{ClientState, ConsensusState}; use jsonrpsee::{ diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index 73009afaf9..4725801ab7 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -16,7 +16,10 @@ use unionlabs::{ ErrorReporter, }; use voyager_message::{ - core::{ChainId, ClientStateMeta, ClientType, ConsensusStateMeta, ConsensusType, IbcInterface}, + core::{ + ChainId, ClientStateMeta, ClientType, ConsensusStateMeta, ConsensusType, IbcInterface, + Timestamp, + }, into_value, module::{ClientModuleInfo, ClientModuleServer}, ClientModule, FATAL_JSONRPC_ERROR_CODE, @@ -99,7 +102,7 @@ impl ClientModuleServer for Module { let cs = Module::decode_consensus_state(&consensus_state)?; Ok(ConsensusStateMeta { - timestamp_nanos: cs.timestamp, + timestamp_nanos: Timestamp::from_nanos(cs.timestamp), }) } diff --git a/voyager/modules/consensus/berachain/src/main.rs b/voyager/modules/consensus/berachain/src/main.rs index e9996bef74..d2e73cf146 100644 --- a/voyager/modules/consensus/berachain/src/main.rs +++ b/voyager/modules/consensus/berachain/src/main.rs @@ -5,13 +5,11 @@ use alloy::{ transports::BoxTransport, }; use beacon_api_types::{ExecutionPayloadHeaderSsz, Mainnet}; -use berachain_light_client_types::{ClientState, ConsensusState}; use jsonrpsee::{ core::{async_trait, RpcResult}, Extensions, }; use serde::{Deserialize, Serialize}; -use serde_json::Value; use tracing::instrument; use unionlabs::{ berachain::LATEST_EXECUTION_PAYLOAD_HEADER_PREFIX, @@ -21,7 +19,6 @@ use unionlabs::{ }; use voyager_message::{ core::{ChainId, ConsensusType, Timestamp}, - into_value, module::{ConsensusModuleInfo, ConsensusModuleServer}, ConsensusModule, ExtensionsExt, VoyagerClient, }; diff --git a/voyager/modules/consensus/cometbls/src/main.rs b/voyager/modules/consensus/cometbls/src/main.rs index 6c9adb1e4d..f41f53b402 100644 --- a/voyager/modules/consensus/cometbls/src/main.rs +++ b/voyager/modules/consensus/cometbls/src/main.rs @@ -127,7 +127,6 @@ impl ConsensusModuleServer for Module { } /// Query the latest finalized timestamp of this chain. - // TODO: Use a better timestamp type here #[instrument(skip_all, fields(chain_id = %self.chain_id))] async fn query_latest_timestamp( &self, @@ -165,10 +164,8 @@ impl ConsensusModuleServer for Module { } } - Ok( - Timestamp::from_nanos(commit_response.signed_header.header.time.as_unix_nanos()) - .try_into() - .expect("should be fine"), - ) + Ok(Timestamp::from_nanos( + commit_response.signed_header.header.time.as_unix_nanos(), + )) } } diff --git a/voyager/modules/consensus/ethereum/src/main.rs b/voyager/modules/consensus/ethereum/src/main.rs index 4aa9aaa081..e63ef9720c 100644 --- a/voyager/modules/consensus/ethereum/src/main.rs +++ b/voyager/modules/consensus/ethereum/src/main.rs @@ -156,7 +156,6 @@ impl ConsensusModuleServer for Module { } /// Query the latest finalized timestamp of this chain. - // TODO: Use a better timestamp type here #[instrument(skip_all, fields(chain_id = %self.chain_id, finalized))] async fn query_latest_timestamp( &self, diff --git a/voyager/modules/consensus/tendermint/src/main.rs b/voyager/modules/consensus/tendermint/src/main.rs index 67e54c215c..66edea08a2 100644 --- a/voyager/modules/consensus/tendermint/src/main.rs +++ b/voyager/modules/consensus/tendermint/src/main.rs @@ -157,10 +157,8 @@ impl ConsensusModuleServer for Module { } } - Ok( - Timestamp::from_nanos(commit_response.signed_header.header.time.as_unix_nanos()) - .try_into() - .expect("should be fine"), - ) + Ok(Timestamp::from_nanos( + commit_response.signed_header.header.time.as_unix_nanos(), + )) } } diff --git a/voyager/modules/state/movement/src/main.rs b/voyager/modules/state/movement/src/main.rs index c85ce706e4..1bfb8f3c7c 100644 --- a/voyager/modules/state/movement/src/main.rs +++ b/voyager/modules/state/movement/src/main.rs @@ -130,7 +130,6 @@ impl Module { } /// Query the latest finalized timestamp of this chain. - // TODO: Use a better timestamp type here #[instrument(skip_all, fields(chain_id = %self.chain_id))] pub async fn query_latest_timestamp(&self, e: &Extensions) -> RpcResult { let latest_height = self.query_latest_height(e).await?; @@ -145,7 +144,7 @@ impl Module { debug!(%timestamp, %latest_height, "latest timestamp"); - Ok(Timestamp::from_nanos(timestamp).try_into().unwrap()) + Ok(Timestamp::from_nanos(timestamp)) } Err(err) => Err(ErrorObject::owned( -1, diff --git a/voyager/plugins/client-update/berachain/src/main.rs b/voyager/plugins/client-update/berachain/src/main.rs index 9d2c40cd32..050f0e3948 100644 --- a/voyager/plugins/client-update/berachain/src/main.rs +++ b/voyager/plugins/client-update/berachain/src/main.rs @@ -265,6 +265,7 @@ impl PluginServer for Module { // `counterparty_chain_id` client_id: RawClientId::new(self.l1_client_id), height: update_to, + finalized: true, })]), data(OrderedHeaders { headers: vec![( diff --git a/voyager/plugins/client-update/state-lens/evm/src/main.rs b/voyager/plugins/client-update/state-lens/evm/src/main.rs index 6549b04c46..04c9d85ace 100644 --- a/voyager/plugins/client-update/state-lens/evm/src/main.rs +++ b/voyager/plugins/client-update/state-lens/evm/src/main.rs @@ -219,7 +219,7 @@ impl PluginServer for Module { AggregateMsgUpdateClientsFromOrderedHeaders { ibc_spec_id: IbcUnion::ID, chain_id: self.l1_chain_id.clone(), - client_id: RawClientId::new(self.l1_client_id.clone()), + client_id: RawClientId::new(self.l1_client_id), }, ), seq([ @@ -301,7 +301,7 @@ impl PluginServer for Module { AggregateMsgUpdateClientsFromOrderedHeaders { ibc_spec_id: IbcUnion::ID, chain_id: counterparty_chain_id.clone(), - client_id: RawClientId::new(self.l0_client_id.clone()), + client_id: RawClientId::new(self.l0_client_id), }, ), seq([ diff --git a/voyager/plugins/transaction-batch/src/callback.rs b/voyager/plugins/transaction-batch/src/callback.rs index 18fdf5084d..15d6c2995b 100644 --- a/voyager/plugins/transaction-batch/src/callback.rs +++ b/voyager/plugins/transaction-batch/src/callback.rs @@ -4,7 +4,7 @@ use enumorph::Enumorph; use ibc_classic_spec::IbcClassic; use ibc_union_spec::IbcUnion; use itertools::Itertools; -use jsonrpsee::{core::RpcResult, types::ErrorObject}; +use jsonrpsee::core::RpcResult; use macros::model; use tracing::{debug, instrument, warn}; use unionlabs::ibc::core::client::height::Height; @@ -12,7 +12,7 @@ use voyager_message::{ call::{SubmitTx, WaitForTrustedHeight}, core::{ChainId, ClientStateMeta, QueryHeight}, data::{Data, IbcDatagram, OrderedClientUpdates}, - PluginMessage, RawClientId, VoyagerClient, VoyagerMessage, FATAL_JSONRPC_ERROR_CODE, + PluginMessage, RawClientId, VoyagerClient, VoyagerMessage, }; use voyager_vm::{call, conc, noop, promise, seq, Op}; diff --git a/voyager/plugins/transaction/ethereum/src/main.rs b/voyager/plugins/transaction/ethereum/src/main.rs index 151bdadaeb..7d8725d9db 100644 --- a/voyager/plugins/transaction/ethereum/src/main.rs +++ b/voyager/plugins/transaction/ethereum/src/main.rs @@ -27,7 +27,7 @@ use unionlabs::{ ErrorReporter, }; use voyager_message::{ - core::{ChainId, IbcSpec}, + core::ChainId, data::Data, hook::SubmitTxHook, module::{PluginInfo, PluginServer}, diff --git a/voyager/src/main.rs b/voyager/src/main.rs index 7a5265c428..4a33d36a26 100644 --- a/voyager/src/main.rs +++ b/voyager/src/main.rs @@ -716,7 +716,7 @@ fn print_json(t: &T) { // TODO: Extract all logic here to a plugin pub mod utils { - use anyhow::{anyhow, bail}; + use anyhow::bail; use ibc_classic_spec::IbcClassic; use ibc_union_spec::IbcUnion; use serde_json::Value;