-
Notifications
You must be signed in to change notification settings - Fork 417
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'ccip-server' into telepathy-ism
- Loading branch information
Showing
19 changed files
with
2,828 additions
and
128 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.env* | ||
/dist | ||
/cache | ||
/configs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
# 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')]); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "ccip-server", | ||
"version": "0.0.1", | ||
"description": "CCIP server", | ||
"typings": "dist/index.d.ts", | ||
"typedocMain": "src/index.ts", | ||
"files": [ | ||
"src" | ||
], | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"scripts": { | ||
"start": "ts-node src/server.ts", | ||
"dev": "nodemon src/server.ts", | ||
"test": "jest" | ||
}, | ||
"author": "brolee", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/jest": "^29.5.11", | ||
"@types/node": "^15.12.2", | ||
"@types/sinon": "^17", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"jest": "^29.7.0", | ||
"nodemon": "^3.0.3", | ||
"prettier": "^2.3.2", | ||
"sinon": "^17.0.1", | ||
"ts-jest": "^29.1.2", | ||
"ts-node": "^10.9.2", | ||
"typescript": "4.8.4" | ||
}, | ||
"dependencies": { | ||
"@chainlink/ccip-read-server": "^0.2.1", | ||
"dotenv-flow": "^4.1.0", | ||
"ethers": "5.7.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
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, [proofsService.handler('getProofs')]); | ||
|
||
// Start Server | ||
const app = server.makeApp(config.SERVER_URL_PREFIX); | ||
app.listen(config.SERVER_PORT, () => | ||
console.log(`Listening on port ${config.SERVER_PORT}`), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// @ts-nocheck | ||
import axios from 'axios'; | ||
import { ethers, utils } from 'ethers'; | ||
|
||
import { TelepathyCcipReadIsmAbi } from '../abis/TelepathyCcipReadIsmAbi'; | ||
|
||
import { Requestor } from './common/Requestor'; | ||
|
||
enum ProofStatus { | ||
running = 'running', | ||
success = 'success', | ||
} | ||
|
||
// Service that interacts with the LightClient/ISM | ||
class LightClientService extends Requestor { | ||
// Stores the current ProofId that is being generated. Clears once proof is ready. | ||
pendingProofId: string; | ||
|
||
constructor( | ||
private readonly lightClientContract: ethers.Contract, | ||
private readonly stepFunctionId: string, | ||
private readonly chainId: string, | ||
readonly platformUrl: string, | ||
readonly platformApiKey: string, | ||
) { | ||
super(axios, platformApiKey); | ||
} | ||
|
||
private getSyncCommitteePeriod(slot: BigInt): BigInt { | ||
return slot / 8192n; // Slots Per Period | ||
} | ||
|
||
/** | ||
* Gets syncCommitteePoseidons from ISM/LightClient | ||
* @param slot | ||
* @returns | ||
*/ | ||
async getSyncCommitteePoseidons(slot: BigInt): Promise<string> { | ||
return await this.lightClientContract.syncCommitteePoseidons( | ||
this.getSyncCommitteePeriod(slot), | ||
); | ||
} | ||
|
||
/** | ||
* Request the proof from Succinct. | ||
* @param slot | ||
* @param syncCommitteePoseidon | ||
*/ | ||
async requestProof(syncCommitteePoseidon: string, slot: BigInt) { | ||
if (!this.pendingProofId) { | ||
// Request a Proof, set pendingProofId | ||
// Note that Succinct will asynchronously call step() on the ISM/LightClient | ||
const telepathyIface = new utils.Interface(TelepathyCcipReadIsmAbi); | ||
|
||
const body = { | ||
chainId: this.chainId, | ||
to: this.lightClientContract.address, | ||
data: telepathyIface.encodeFunctionData('step', [slot]), | ||
functionId: this.stepFunctionId, | ||
input: utils.defaultAbiCoder.encode( | ||
['bytes32', 'uint64'], | ||
[syncCommitteePoseidon, slot], | ||
), | ||
retry: true, | ||
}; | ||
|
||
const results: { proof_id: string } = await this.postWithAuthorization( | ||
`${this.platformUrl}/new`, | ||
body, | ||
); | ||
this.pendingProofId = results.proof_id; | ||
|
||
// Proof is being generated. Force the Relayer to re-check. | ||
throw new Error('Proof is not ready'); | ||
} else { | ||
// Proof is being generated, check status | ||
const proofResults: { status: ProofStatus } = await this.get( | ||
`${this.platformUrl}/${this.pendingProofId}`, | ||
); | ||
if (proofResults.status === ProofStatus.success) { | ||
// Proof is ready, clear pendingProofId | ||
this.pendingProofId = null; | ||
} | ||
// Proof is not ready. Force the Relayer to re-check. | ||
throw new Error('Proof is not ready'); | ||
} | ||
} | ||
} | ||
|
||
export { LightClientService, ProofStatus }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { ethers } from 'ethers'; | ||
|
||
import { TelepathyCcipReadIsmAbi } from '../abis/TelepathyCcipReadIsmAbi'; | ||
|
||
import { LightClientService } from './LightClientService'; | ||
import { ProofResult, RPCService } from './RPCService'; | ||
import { HandlerDescriptionEnumerated } from './common/HandlerDescriptionEnumerated'; | ||
|
||
// Service that requests proofs from Succinct and RPC Provider | ||
class ProofsService extends HandlerDescriptionEnumerated { | ||
rpcService: RPCService; | ||
lightClientService: LightClientService; | ||
|
||
constructor( | ||
readonly lightClientAddress: string, | ||
readonly rpcAddress: string, | ||
readonly stepFunctionId: string, | ||
readonly chainId: string, | ||
readonly succinctPlatformUrl: string, | ||
readonly succinctPlatformApiKey: string, | ||
) { | ||
super(); | ||
this.rpcService = new RPCService(rpcAddress); | ||
const lightClientContract = new ethers.Contract( | ||
lightClientAddress, | ||
TelepathyCcipReadIsmAbi, | ||
this.rpcService.provider, | ||
); | ||
this.lightClientService = new LightClientService( | ||
lightClientContract, | ||
stepFunctionId, | ||
chainId, | ||
succinctPlatformUrl, | ||
succinctPlatformApiKey, | ||
); | ||
} | ||
|
||
/** | ||
* Requests the Succinct proof, state proof, and returns account and storage proof | ||
* @dev Note that the abi encoding will happen within ccip-read-server | ||
* @param target contract address to get the proof for | ||
* @param storageKeys storage keys to get the proof for | ||
* @param blockNumber block to get the proof for. Will decode as a BigInt. | ||
* Note that JS BigInt can only handle 2^53 - 1. For block number, this should be plenty. | ||
*/ | ||
async getProofs([ | ||
address, | ||
storageKey, | ||
blockNumber, | ||
]: ethers.utils.Result): Promise<Array<[string[], string[]]>> { | ||
const proofs: Array<[string[], string[]]> = []; | ||
try { | ||
// TODO Implement request Proof from Succinct | ||
// await this.lightClientService.requestProof(syncCommitteePoseidon, slot); | ||
|
||
// Get storage proofs | ||
const { accountProof, storageProof }: ProofResult = | ||
await this.rpcService.getProofs( | ||
address, | ||
[storageKey], | ||
blockNumber.toHexString(), | ||
); | ||
proofs.push([accountProof, storageProof[0].proof]); | ||
} catch (e) { | ||
console.log('Error getting proofs', e); | ||
} | ||
|
||
return proofs; | ||
} | ||
} | ||
|
||
export { ProofsService }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { ethers } from 'ethers'; | ||
|
||
type ProofResultStorageProof = { | ||
key: string; | ||
proof: Array<string>; | ||
value: string; | ||
}; | ||
|
||
type ProofResult = { | ||
accountProof: Array<string>; | ||
storageProof: Array<ProofResultStorageProof>; | ||
address: string; | ||
balance: string; | ||
codeHash: string; | ||
nonce: string; | ||
storageHash: string; | ||
}; | ||
|
||
class RPCService { | ||
provider: ethers.providers.JsonRpcProvider; | ||
constructor(private readonly providerAddress: string) { | ||
this.provider = new ethers.providers.JsonRpcProvider(this.providerAddress); | ||
} | ||
|
||
/** | ||
* Request state proofs using eth_getProofs | ||
* @param address | ||
* @param storageKeys | ||
* @param block | ||
* @returns | ||
*/ | ||
async getProofs( | ||
address: string, | ||
storageKeys: string[], | ||
block: string, | ||
): Promise<ProofResult> { | ||
const results = await this.provider.send('eth_getProof', [ | ||
address, | ||
storageKeys, | ||
block, | ||
]); | ||
|
||
return results; | ||
} | ||
} | ||
|
||
export { RPCService, ProofResult }; |
17 changes: 17 additions & 0 deletions
17
typescript/ccip-server/src/services/common/HandlerDescriptionEnumerated.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { HandlerDescription, HandlerFunc } from '@chainlink/ccip-read-server'; | ||
|
||
// Abstract class used to create HandlerDescriptions from a class. | ||
abstract class HandlerDescriptionEnumerated { | ||
handler<K extends keyof this>(func: K): HandlerDescription { | ||
if (typeof this[func] == 'function') { | ||
return { | ||
type: func as string, | ||
func: (this[func] as HandlerFunc).bind(this), | ||
}; | ||
} | ||
|
||
throw Error(`Invalid function name: ${func.toString()}`); | ||
} | ||
} | ||
|
||
export { HandlerDescriptionEnumerated }; |
Oops, something went wrong.