Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCIP Gateway Server #3273

Merged
merged 13 commits into from
Mar 5, 2024
25 changes: 25 additions & 0 deletions solidity/test/test-data/getProof-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"address": "0x3ef546f04a1b24eaf9dce2ed4338a1b5c32e2a56",
"balance": "0x0",
"codeHash": "0x2a5a7c6a053518aea9c1affe22c95e2e692204d39c9bdb0826de5012e7c93c4d",
"nonce": "0x1",
"storageHash": "0x3f85413bd5bccd8348f123724fb024c5fbab1ab934b0a7485202f75d84803743",
"accountProof": [
"0xf90211a0857923ed0df1a7e89c6c157d9f228ca778b5028cce0fe977bbacdc29ebdcbe1da04cc511d34d843e5ed02112368d4885e8bc25e8e394abbc526459f4e38b842737a07f59950cdd53d4bb97096bbd99e7c04a5d21a97dac8a049f9b308610ea66c98fa02b8ce5f6b67ac79c8947b1919460f7bed77488762c059e5e88c64dfc722aaa56a03521e9571b798e0f86681b9ce0f0e9bf698e9059542d778cc4cde5659a31f9d2a0b90c6a5217bfd233f103dba76e18067f90001297c801cb6feb0c1f7912e0a522a0831099c940ebe93be0a1044566150044d22a8d414518eeb5d919d05468c8f510a054a09da1ca52551a35be75a623e419840060f2e56f86eb3678a689f66b38051da05945b528b186c0162d7cbfa281d30a361ece2a9f1788b33e7fda6bd7c3c5eb07a0401c3ac89520c5a59a19086e82314bb30ad2ecc5ddce09cdf613dd19adb41feca031e6288dc6db773d11cd579b974658473547abfcd82531cf1833484079baff58a099184994c461f1d6ce42e5e91ac66d6c6a5b4cc7ed845f058b795005b46328c1a054bde23ceae3fe9564654f16f867f3f321e0b7e16e3b08e925beb5fb5ffcb1fda040f8d48347ec397a261b090c7e3506f939b6033993c2836c24a487318310da67a0a6e90ed0e95843baeec2c8a6a017e61702556b67ca90c9576342fe268d0a9edba0ca8b4b3ea82cc66299ee45708b555de9b6272d3df24cb472c7baffaa72b39a0d80",
"0xf90211a0d124c71cf01214346e6facb7cd9e7743fca3a36f4252045573e37dd5b1fc208aa09318652584a16019f80279509f3133ee0f6fed3d391c1c0522b590b923b9283fa0fc90168fe3e76ff17c23781513065c71d13b5f963e492106f2b9a05ed1c9e3dca0c919acc2a76e4fef630534efea6f77a197646db3823ff861c430944385391672a0708cd1310ea8f59c163d0d7b905b456ed377000e989a343dbc61523ec4da6de3a0ade6811b1e1dc547362bfb0cdc943d6ae5d8283252f1e73d3000176a24ed8320a0effada816260fe1f7053c9c6f0cb49e0ee5ebb72be8a05f0cbcad3e7c95dd518a087d09c9bd032f470eed0637da12daa8e6d7c52102d51e87dfdecfdd0f01fc665a0d830bd0ebb1b7d0ddc716138e4341e64fa0c96d4330ac643c3306a842fdfb72ea05fb1c8a24c36a2ccae1ab7c39c0efdf56325dbc5de011f3c548827d592bd7f3ba0e127892391fcf8d6640c45c518ea6f1014b6983586e97e3ec4ae0e546ac22696a0573a0337bba203efa7a2ba3fab1a145e3cfd3b7a70d096489c2c549f9abad184a0daeb93e98ba67210f9e62088838adf7b2e83caa8ec9902e669095066f5abd1f4a0907a2f066f7bad1ebb9ba8998e217c82b2169336daa0f4c0db3b8ac05ad59fc5a03ac759b9a285ceb7293f13071d838261585bb87ae6a8c5489d13b7a818a60e8ea042abb78fd5f125d0dfd6ea10da83f064ed39920c3616e97b7810ee1f306559ec80",
"0xf90211a0316755c60ab5ac6d1ca0ce7fc7131544cb41bb5907b6c44b0659731c5196f505a0f4d87833b3b7fdb40011cd61f5e4fddf82a99e195e15a6650310d59f276111aba04c923074395b960cd08d04910a3c14ec3cb7d1ae858f9aef5cc888e6e03fbfb8a07c9d54b33c8a9db1e3efe12d0adb042d741fa2f2bccab2bf7d0afcb03626fba8a066400964da5b44e27fadbac20d48cfafc511c3c6bfaa68c1b92ae215e914f61ca038e0e323e92c7a20aaf13c302a493a5241c52a2c169aaaf8d48d23e17f44f643a0646a80b8ac762550cf167377f0837a5993536789eb065369829fd3b13bb52cc5a0de5c001063a09ee8b228199fcd393226a04ac68bc38f600729f7af2305c10368a035cb84ac8c8bb1ddb01e3377a7a17e22e23de20c89f4fa1bd7997bdc0421516aa01a598f35bf00309ed051c4096b8aac157d3411013211b2df2a02a8246eb5d0b6a0fab8ac4cedece1fe1ba89c617413a8df9a28e287e32f92bc52db1485ebb2396ea036f69b2bdc01e9b90102d5df53f9c02f3474db4f613f91d13c46f341b9fb9746a0d0bda7e73681df8b78903454ae252ebd8d34e48af88aa0ad18d6c2fb86a99d89a00d98621a3d3071cc04c492fd2c666637f27138eea11b83f516467f33a00b5eefa0198f566c5350232b08192dfe9cb7dd0a07b21c15638a80f912114a2b5c96be59a035092d3d51e3cd65ad2dcdb2d6c19aadb63a944ac9cedb7093ea68be58b7d83e80",
"0xf90211a0a7aea3e55e49172a7da1f511fceb247072cb0b4d8a87d9cd5cde0bc87046665ca08a057cd8f49b6ad4841f8e23b81706b8a424eb092663e4e1e60f98dba1e07e44a064098aa80e0d7e101f4e87fa5983f9a43ff6c26d8adaafea0c8f745e0fb9099aa09a5ec75b929e80a80ae115a1aec2827dcdb3eec116fce7a805eebe02d51ff3d7a027c8ef5bfc576ad5e7577005716a18d16ae203c440d65e12a9581a252b9f0e4ba01d352d5770faf1ab8a8096739876d9af10846effbd5f0a4bbb3a53bcb687fc9ca0a9da86853b0a80f1be569b6563aa35e8ab5d635132836b9dc95952b83192bdd3a0aa42135324b35a446ec3620ab361bb98d4636ebd2774715f142550b66cb5c723a0cc9734326b403f694d73ee6dd774e56c5675428ebf24b2ec28b319146f0d1ebda0a0d2c0664e654776405127a978a6edf6c249a71e94747158b2cae156b795b9eaa07a5811d019afead667944b567e595b7ef6084036be9bfde82097a479f3b9d01ca009e0186fe2bbb6f5f8399fabcbda45902405241f25b34352ff7ec66c78fa124aa0cacc8baa6b996e057fe8abaa433f50ba5f9b7afd501a1dbb8b952371e4b604efa081e453659b14017d3f3492fecfa4e9b3a03b8b8d3236fd2c3082c9abf528b6aca030e4ddd56b0b072e820f1afd7bd32cf5cf06f92d69231cbdc236a968c1f43283a0961d902b6b6ee68fb1d75a633a76786a7783f7603f8812ada01f865639e9daf880",
"0xf90211a045376ea7070c6b6bba71480a083c5ee916a4c72aa89ddc0539f69ca2f951ce96a0786afe5e76d004a35a17fb7931e70f40b0ce3a57e5246c94ebc4629e0a61e5bca0ce44e7e569e415dc667a71d212d260023fee99415bd0bcfd1e5aeee1cdbd8ec9a051cabbabc99fbd009590821598f342f3c2606a4241c81c46dd7e65c18d9ee098a0dbae505b1ce907ff5d451c5d113736ed911b239897d1f6887d3350da314f174ba047f6b90756987d0268044b85bc44da5a34e988044014a6c27cf62d185c7b46e9a0f7566eb4d5c9bc2361ecfffe8fc6a5ac5b5b9380c8b8152284cd714d44e79b12a0cc4a7ee9c4e83d67d64a9bfb0aec93cc3b8966c235b5b65f17333832b023a464a00b9024cfba692f2385d58ea3dca746f9e062982fb21f6f16ead2cefb70d25657a0d187348c2d25747788855d8094ce6e3df6940e26dca20cec4cc26e4f2292ee67a0355474e9a646331b74d171dd023e68f7c50f4bc1257a6433ee01a9ea8ce1577aa096f7bfa110a35e95127808a58ec44f3478b8b67f98ff3b8b140a3e9d249a6deba0dac2dea453ebb23f8a6a8a82db61ca160792aa967db905d98ee0dbcb2b81ef88a0bf7bac1cc4c2e30b47c5bc62ef9aedba995e46ad0432cf78cc8ec064ebf62ec7a0a0b931ee3c50545a4f91864c9501fb02eaa879ab02e4f677b11a1667d42cd55ca0654f679d8743df36bb61fe7eecca444d1a1773435b876fcafadd36c1c9bfe24980",
"0xf901d1a0c6d1570250983b95bd625c871d0627cc5bf6557738904ec34f6ce54c4d4782dba098975098243c73d0de47c1e3edf85d54a2010b50e85ffdac2192cc71484993e0a028e68d0c580151049649c5c882ddc4fa3f946f832b6fe34f210420f82ad18e0c80a0a0c6abc7c7b8c738ddb43a72c19c6a0dc4eee7808e4653d2ac67d5d777c5cfe8a0a3843b012a7d6170298a6e0024a79e466614296d4d09a72876be8049294d7dcfa02370c2a2892d2eacc15cdc46cef02125499df7afe41a16af6ce78d5fb8d5a7b9a0e9c8fa9b7269050b18b76ac5f07847e93e2403a438783e27bb69bb89484a7a52a08a7fef1f6d80c56ae256362f9de34169d3d66b17a8f0be6dbd0fe82c31a0c4da80a081b05ed42fc65a32afabe8f3ab58eedc8658472f383150e4c631ce1b3a36bacda04fe5deb98851372cb69ab014dba185f1c488ccd9810b4998d7737d0bee884718a0c8d739dcf5707542a9f751ec6a0acb228f28e55659c35973ee8a46aefa9a7a4fa0f0128b39c576d4e23e8180b154d7e8f7031a7d6775191188c87cc11573730008a0d391bf99084848ebff2c014653ec6f9634c80b1115850174cdf0edf83669b073a031d14af58dae3d2fdf510a3c48e988b860d1bfe406e6365be92252d63f4c316080",
"0xf8679e20bc9ed3e4003248a3f81d89ecc98e14f0359535a5195a1d33a6fe19556bb846f8440180a03f85413bd5bccd8348f123724fb024c5fbab1ab934b0a7485202f75d84803743a02a5a7c6a053518aea9c1affe22c95e2e692204d39c9bdb0826de5012e7c93c4d"
],
"storageProof": [
{
"key": "0x02c1eed75677f1bd39cc3abdd3042974bf12ab4a12ecc40df73fe3aa103e5e0e",
"proof": [
"0xf844a120443dd0be11dd8e645a2e5675fd62011681443445ea8b04c77d2cdeb1326739eca1a031ede38d2e93c5aee49c836f329a626d8c6322abfbff3783e82e5759f870d7e9"
],
"value": "0x31ede38d2e93c5aee49c836f329a626d8c6322abfbff3783e82e5759f870d7e9"
}
]
}
6 changes: 6 additions & 0 deletions typescript/ccip-server/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rules": {
"no-console": ["off"]
}
}

4 changes: 4 additions & 0 deletions typescript/ccip-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env*
/dist
/cache
/configs
45 changes: 45 additions & 0 deletions typescript/ccip-server/README.md
ltyu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# CCIP-read service framework

This package contains the service framework for the CCIP-read project, built off of the [CCIP-server framework](https://github.com/smartcontractkit/ccip-read). It allows building of any execution logic, given a Hyperlane Relayer call.
ltyu marked this conversation as resolved.
Show resolved Hide resolved

# Definitions

- Server: The main entry point, and refers to `server.ts`.
- Service: A class that handles all logic for a particular service, e.g. ProofService, RPCService, etc.
- Service ABI: The interface for a service that tells the Server what input and output to expect. It serves similar functionalities as the Solidity ABIs, i.e., used for encoding and decoding data.

# Usage

The Relayer will make a POST request to the Server with a request body similar to the following:

```json
{
"data": "0x0ee9bb2f000000000000000000000000873afca0319f5c04421e90e882566c496877aff8000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a2d9059b6d822aa460229510c754e9ecec100bb9f649186f5c7d4da8edf59858",
"sender": "0x4a679253410272dd5232b3ff7cf5dbb88f295319"
}
```

The `data` property will be ABI-encoded, and server will parse it according to the Service ABI. It then will call the handler function with the parsed input.

# Building a Service

1. Create a Service ABI for your Service. This ABI tells the Server how to parse the incoming `data`, and how to encode the output. See `/abi/ProofsServiceAbi.ts` for an example.
2. Create a new Service class to handle your logic. This should inherit from `HandlerDescriptionEnumerated` if a function will be used to handle a Server request. The handler function should return a Promise that resolves to the output of the Service. See `/service/ProofsService.ts` for examples.
3. Instantiate the new Service in `server.ts`. For example:

```typescript
const proofsService = new ProofsService(
config.LIGHT_CLIENT_ADDR,
config.RPC_ADDRESS,
config.STEP_FN_ID,
config.CHAIN_ID,
config.SUCCINCT_PLATFORM_URL,
config.SUCCINCT_API_KEY,
);
```

4. Add the new Service by calling `server.add(...)` by providing the Service ABI, and the handler function. For example:

```typescript
server.add(ProofsServiceAbi, [proofsService.handler('getProofs')]);
```
5 changes: 5 additions & 0 deletions typescript/ccip-server/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
38 changes: 38 additions & 0 deletions typescript/ccip-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
ltyu marked this conversation as resolved.
Show resolved Hide resolved
"name": "@hyperlane-xyz/ccip-server",
"version": "0.0.0",
"description": "CCIP server",
"typings": "dist/index.d.ts",
"typedocMain": "src/index.ts",
"private": true,
"files": [
"src"
],
"engines": {
"node": ">=14"
},
"scripts": {
"start": "ts-node src/server.ts",
"dev": "nodemon src/server.ts",
"test": "jest",
"prettier": "prettier --write ./src/* ./tests/"
},
"author": "brolee",
"license": "Apache-2.0",
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/node": "^16.9.1",
"jest": "^29.7.0",
"nodemon": "^3.0.3",
"prettier": "^2.8.8",
"ts-jest": "^29.1.2",
"ts-node": "^10.8.0",
"typescript": "5.1.6"
},
"dependencies": {
"@chainlink/ccip-read-server": "^0.2.1",
"dotenv-flow": "^4.1.0",
"ethers": "5.7.2",
"hyperlane-explorer": "https://github.com/hyperlane-xyz/hyperlane-explorer.git"
ltyu marked this conversation as resolved.
Show resolved Hide resolved
}
}
7 changes: 7 additions & 0 deletions typescript/ccip-server/src/abis/ProofsServiceAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This is the ABI for the ProofsService.
// This is used to 1) Select the function 2) encode output
const ProofsServiceAbi = [
'function getProofs(address, bytes32, uint256) public view returns (string[][])',
];

export { ProofsServiceAbi };
7 changes: 7 additions & 0 deletions typescript/ccip-server/src/abis/TelepathyCcipReadIsmAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const TelepathyCcipReadIsmAbi = [
'function verify(bytes, bytes) public view returns (bool)',
'function step(uint256) external',
'function syncCommitteePoseidons(uint256) external view returns (bytes32)',
];

export { TelepathyCcipReadIsmAbi };
23 changes: 23 additions & 0 deletions typescript/ccip-server/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import dotenvFlow from 'dotenv-flow';

dotenvFlow.config();

const RPC_ADDRESS = process.env.RPC_ADDRESS as string;
const LIGHT_CLIENT_ADDR = process.env.LIGHT_CLIENT_ADDR as string;
const STEP_FN_ID = process.env.STEP_FN_ID as string;
const CHAIN_ID = process.env.CHAIN_ID as string;
const SUCCINCT_PLATFORM_URL = process.env.SUCCINCT_PLATFORM_URL as string;
const SUCCINCT_API_KEY = process.env.SUCCINCT_API_KEY as string;
const SERVER_PORT = process.env.SERVER_PORT as string;
const SERVER_URL_PREFIX = process.env.SERVER_URL_PREFIX as string;

export {
RPC_ADDRESS,
LIGHT_CLIENT_ADDR,
STEP_FN_ID,
CHAIN_ID,
SUCCINCT_PLATFORM_URL,
SUCCINCT_API_KEY,
SERVER_PORT,
SERVER_URL_PREFIX,
};
28 changes: 28 additions & 0 deletions typescript/ccip-server/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Server } from '@chainlink/ccip-read-server';

import { ProofsServiceAbi } from './abis/ProofsServiceAbi';
import * as config from './config';
import { ProofsService } from './services/ProofsService';

// Initalize Services
const proofsService = new ProofsService(
config.LIGHT_CLIENT_ADDR,
config.RPC_ADDRESS,
config.STEP_FN_ID,
config.CHAIN_ID,
config.SUCCINCT_PLATFORM_URL,
config.SUCCINCT_API_KEY,
);

// Initalize Server and add Service handlers
const server = new Server();

server.add(ProofsServiceAbi, [
{ type: 'getProofs', func: proofsService.getProofs.bind(this) },
]);

// Start Server
const app = server.makeApp(config.SERVER_URL_PREFIX);
app.listen(config.SERVER_PORT, () =>
console.log(`Listening on port ${config.SERVER_PORT}`),
);
36 changes: 36 additions & 0 deletions typescript/ccip-server/src/services/HyperlaneService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { info } from 'console';
import { Message, MessageTx } from 'hyperlane-explorer/src/types';

// These types are copied from hyperlane-explorer. TODO: export them so this file can use them directly.
interface ApiResult<R> {
status: '0' | '1';
message: string;
result: R;
}

enum API_ACTION {
GetMessages = 'get-messages',
}

class HyperlaneService {
constructor(readonly baseUrl: string) {}

/**
* Makes a request to the Explorer API to get the block info by message Id. Throws if request fails, or no results
* @param id: Message id to look up
*/
async getOriginBlockByMessageId(id: string): Promise<MessageTx> {
info(`Fetching block for id: ${id}`);
const response = await fetch(
`${this.baseUrl}?module=message&action=${API_ACTION.GetMessages}&id=${id}`,
);
const responseAsJson: ApiResult<Message[]> = await response.json();
if (responseAsJson.status === '1') {
return responseAsJson.result[0]?.origin;
} else {
throw new Error(responseAsJson.message);
}
}
}

export { HyperlaneService };
99 changes: 99 additions & 0 deletions typescript/ccip-server/src/services/LightClientService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ethers, utils } from 'ethers';

import { TelepathyCcipReadIsmAbi } from '../abis/TelepathyCcipReadIsmAbi';

import { ProofStatus } from './common/ProofStatusEnum';

export type SuccinctConfig = {
readonly lightClientAddress: string;
readonly stepFunctionId: string;
readonly platformUrl: string;
readonly apiKey: string;
};

// Service that interacts with the LightClient/ISM
class LightClientService {
constructor(
private readonly lightClientContract: ethers.Contract, // TODO USE TYPECHAIN
private succinctConfig: SuccinctConfig,
) {}

private getSyncCommitteePeriod(slot: bigint): bigint {
return slot / 8192n; // Slots Per Period
ltyu marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Gets syncCommitteePoseidons from ISM/LightClient
* @param slot
* @returns
*/
async getSyncCommitteePoseidons(slot: bigint): Promise<string> {
return await this.lightClientContract.syncCommitteePoseidons(
this.getSyncCommitteePeriod(slot),
);
}

/**
* Calculates the slot given a timestamp, and the LightClient's configured Genesis Time and Secods Per Slot
* @param timestamp timestamp to calculate slot with
*/
async calculateSlot(timestamp: bigint): Promise<bigint> {
return (
(timestamp - (await this.lightClientContract.GENESIS_TIME())) /
(await this.lightClientContract.SECONDS_PER_SLOT())
);
}

/**
* Request the proof from Succinct.
* @param slot
* @param syncCommitteePoseidon
*/
async requestProof(
syncCommitteePoseidon: string,
slot: bigint,
): Promise<string> {
console.log(`Requesting proof for${slot}`);

// Note that Succinct will asynchronously call step() on the ISM/LightClient
const telepathyIface = new utils.Interface(TelepathyCcipReadIsmAbi);

const body = {
chainId: this.lightClientContract.chainId,
to: this.lightClientContract.address,
data: telepathyIface.encodeFunctionData('step', [slot]),
functionId: this.lightClientContract.stepFunctionId,
input: utils.defaultAbiCoder.encode(
['bytes32', 'uint64'],
[syncCommitteePoseidon, slot],
),
retry: true,
};

const response = await fetch(
`${this.lightClientContract.platformUrl}/new`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.succinctConfig.apiKey}`,
},
body: JSON.stringify(body),
},
);
const responseAsJson = await response.json();

return responseAsJson.proof_id;
}

// @dev in the case of when a proof doesn't exist, the request returns an object of { error: 'failed to get proof' }.
// Example: GET https://alpha.succinct.xyz/api/proof/4dfd2802-4edf-4c4f-91db-b2d05eb69791
async getProofStatus(proofId: string): Promise<ProofStatus> {
const response = await fetch(
`${this.lightClientContract.platformUrl}/${proofId}`,
);
const responseAsJson = await response.json();
return responseAsJson.status ?? ProofStatus.error;
}
}

export { LightClientService, ProofStatus };
Loading
Loading