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

Create and submit receipt proof #2

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
"@cosmjs/proto-signing": "^0.31.1",
"@cosmjs/stargate": "^0.31.1",
"@cosmjs/tendermint-rpc": "^0.31.1",
"@ethereumjs/rlp": "^5.0.0",
"@ethereumjs/trie": "^6.0.0",
"@ethereumjs/vm": "^7.0.0",
"@lodestar/api": "^1.11.3",
"@lodestar/config": "^1.11.3",
"@lodestar/light-client": "^1.11.3",
"@lodestar/types": "^1.11.3",
"axios": "^1.5.1"
"axios": "^1.5.1",
"dotenv": "^16.3.1"
},
"devDependencies": {
"ts-node": "^10.9.1",
Expand Down
37 changes: 37 additions & 0 deletions src/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,41 @@ export class EthAPI {
const participation = syncAggregate.syncCommitteeBits.getTrueBitIndexes().length
return participation >= MIN_SYNC_COMMITTEE_PARTICIPATION
}

async getBlock(number: number) {
try {
const res = await this.execution.post('/', {
jsonrpc: '2.0',
method: 'eth_getBlockByNumber',
params: [`0x${number.toString(16)}`, true],
id: 0
})
return res.data.result;
} catch (e) {
console.error(e);
throw new Error(`Error fetching block ${number}`);
}
}

// Requires an Alchemy RPC
async getBlockTransactionReceipts(number: number) {
try {
const res = await this.execution.post('/', {
jsonrpc: '2.0',
method: 'alchemy_getTransactionReceipts',
params: [{
blockNumber: `0x${number.toString(16)}`
}],
id: 1
})
if (res.data.error) throw res.data.error;
return res.data.result.receipts;
} catch (e) {
console.error(e);
throw new Error(`Error fetching block receipts ${number}`);
}
}

async getBlockWithProof(period: number) {
}
}
17 changes: 12 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { getEnv } from './utils.js'
import { getEnv } from './utils.js';
import { EthAPI } from './eth.js';
import { LightClientAPI } from './lightclient.js';
import { serializeBlockVerificationData } from './types.js';
import { toHexString } from '@chainsafe/ssz';
import * as capella from '@lodestar/types/capella';
import { UserInput, serializeBlockVerificationData } from './types.js';

const CONTRACT_ADDRESS = getEnv("CONTRACT_ADDRESS")
const CONTRACT_ADDRESS = getEnv("CONTRACT_ADDRESS")

const main = async () => {
const api = new EthAPI()
Expand All @@ -18,6 +16,15 @@ const main = async () => {

console.log(json)

const input: UserInput = {
blockNumber: 17875570,
transactionHash: '0x022edd6e5e56c918b0ec5177ec41569e606957af7d16d9e9e65174ed522830dc',
topic: '0xa945e51eec50ab98c161376f0db4cf2aeba3ec92755fe2fcd388bdbbb80ff196'
};

console.log(await lightClient.verifyTopicInTransaction(api, input));

// const t = await api.getBeaconBlock(7061552);
// const update = await api.getUpdate(864);
// await lightClient.applyNewUpdate(update, 864);

Expand Down
66 changes: 61 additions & 5 deletions src/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'
import { getEnv } from './utils.js'
import { bytesToHex, formatReceipt, getEnv } from './utils.js'
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"
import { GasPrice } from '@cosmjs/stargate'
import { GasPrice, SearchByHeightQuery, SearchTxQuery } from '@cosmjs/stargate'
import * as capella from '@lodestar/types/capella'
import { Receipt, UserInput } from './types.js'
import { EthAPI } from './eth.js'
import { TrieWrapper } from './triewrapper.js'
import assert from 'assert';

export class LightClientAPI {
private myAddress: string
private client: SigningCosmWasmClient

constructor(
private address: string,
private mnemonic = getEnv("MNEMONIC"),
private mnemonic = getEnv("MNEMONIC"),
private rpcUrl = getEnv("AXELAR_RPC_URL")
) {}

Expand All @@ -33,10 +37,10 @@ export class LightClientAPI {
console.log("Error applying update for period", period, e)
return false
}

const contractPeriod = await this.getPeriod();
console.log("Current contract period after update", contractPeriod)

return true
}

Expand All @@ -45,11 +49,63 @@ export class LightClientAPI {
return Math.floor(res.finalized_header.slot / 32 / 256)
}

async verify_proof(msg: any): Promise<any> {
return this.execute(msg);
}

async verify_topic_inclusion(msg: any): Promise<any> {
return this.execute(msg);
}

private async query(msg: any): Promise<any> {
return await this.client.queryContractSmart(this.address, msg)
}

private async execute(msg: any): Promise<any> {
return await this.client.execute(this.myAddress, this.address, msg, 'auto')
}

async verifyTopicInTransaction(api: EthAPI, input: UserInput): Promise<boolean> {
const block = await api.getBlock(input.blockNumber);

const receipts: Receipt[] = (await api.getBlockTransactionReceipts(input.blockNumber)).map(formatReceipt);
const requestedTxReceipt = receipts.find((r) => r.transactionHash === input.transactionHash);
if (!requestedTxReceipt) throw new Error(`Could not find receipt for transaction ${input.transactionHash} in block ${input.blockNumber}`);

const transactionIndex = parseInt(requestedTxReceipt.transactionIndex, 16);
const receiptsTrie = await TrieWrapper.trieFromReceipts(receipts);
assert(
block.receiptsRoot.slice(2) === bytesToHex(receiptsTrie.root()),
`Expected receipts root (${block.receiptsRoot}) doesn't match the actual (${bytesToHex(receiptsTrie.root())})`);
const proof = await TrieWrapper.createProof(receiptsTrie, transactionIndex);

const response = await this.verify_proof({
VerifyProof: {
proof: proof.map(n => `0x${Buffer.from(n).toString('hex')}`),
key: Buffer.from(TrieWrapper.encodeKey(transactionIndex)).toString('hex'),
root: block.receiptsRoot
}
});
// wanted: 276233
// used: 211619

const receiptEncoded = response.events
.find((event: any) =>
event.type === 'wasm' &&
event.attributes.find((a: any) => a.key === 'result')
)?.attributes.find((a: any) => a.key === 'result')?.value;
if (parseInt(receiptEncoded, 16) !== 0) {
const response = await this.verify_topic_inclusion({
VerifyTopicInclusion: {
receipt: `0x${receiptEncoded}`,
topic: input.topic,
}
});
// "gasWanted": 169275,
// "gasUsed": 135219
return response.events.find((e: any) => e.type === 'wasm').attributes.find((a: any) => a.key === 'result').value === 'true';
} else {
return false;
}
}
}
26 changes: 26 additions & 0 deletions src/triewrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Proof, Trie } from '@ethereumjs/trie';
import { Receipt } from './types.js';
import { TxReceipt, encodeReceipt } from '@ethereumjs/vm';
import { RLP } from '@ethereumjs/rlp';

export class TrieWrapper {
static encodeKey(key: number) {
return RLP.encode(key);
}

static async trieFromReceipts(receipts: Receipt[]): Promise<Trie> {
const trie = new Trie();
for (const [i, receipt] of receipts.entries()) {
await trie.put(this.encodeKey(i), encodeReceipt(receipt as unknown as TxReceipt, receipt.type))
}
return trie;
}

static trieFromTransactions(receipts: any[]): Trie {
throw new Error('not implemented');
}

static async createProof(trie: Trie, key: number): Promise<Proof> {
return trie.createProof(this.encodeKey(key));
}
}
23 changes: 23 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,27 @@ export function serializeBlockHeaders(data: BlockHeaders) {
beaconHeader: phase0.ssz.BeaconBlockHeader.toJson(data.beaconHeader),
executionHeader: data.executionHeader,
}
}

export type UserInput = {
blockNumber: number;
transactionHash: string;
topic: string;
};

export type Receipt = {
transactionHash: string;
blockHash: string;
blockNumber: string;
logs: [string, string[], string];
contractAddress: null | string;
effectiveGasPrice: string;
cumulativeBlockGasUsed: number;
from: string;
gasUsed: string;
logsBloom: string;
status: 0 | 1;
to: string;
transactionIndex: string;
type: 0 | 1 | 2 | 3;
}
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "dotenv/config"
import { Receipt } from "./types"

export const getEnv = (key: string, defaultValue ?: string): string => {
if (!process.env[key] && !defaultValue) {
Expand All @@ -8,3 +9,18 @@ export const getEnv = (key: string, defaultValue ?: string): string => {
// @ts-ignore
return process.env[key] || defaultValue
}

export const bytesToHex = (bytes: Uint8Array): string => {
return Buffer.from(bytes).toString('hex');
}

export const formatReceipt = (receipt: any): Receipt => {
return {
...receipt,
cumulativeBlockGasUsed: parseInt(receipt.cumulativeGasUsed, 16),
bitvector: receipt.logsBloom,
logs: receipt.logs.map((log: any) => [log.address, log.topics, log.data]),
type: parseInt(receipt.type, 16),
status: parseInt(receipt.status, 16),
};
}
92 changes: 71 additions & 21 deletions testdata/instantiate.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,76 @@
"chain_id": 1,
"genesis_time": 1606824023,
"genesis_root": [
75, 54, 61, 185, 78, 40, 97, 32, 215, 110, 185, 5, 52, 15, 221, 78, 84,
191, 233, 240, 107, 243, 63, 246, 207, 90, 210, 127, 81, 27, 254, 149
]
},
"forks": {
"genesis": {
"epoch": 0,
"fork_version": [0, 0, 0, 0]
},
"altair": {
"epoch": 74240,
"fork_version": [1, 0, 0, 0]
},
"bellatrix": {
"epoch": 144896,
"fork_version": [2, 0, 0, 0]
},
"capella": {
"epoch": 194048,
"fork_version": [3, 0, 0, 0]
75,
54,
61,
185,
78,
40,
97,
32,
215,
110,
185,
5,
52,
15,
221,
78,
84,
191,
233,
240,
107,
243,
63,
246,
207,
90,
210,
127,
81,
27,
254,
149
],
"forks": {
"genesis": {
"epoch": 0,
"fork_version": [
0,
0,
0,
0
]
},
"altair": {
"epoch": 74240,
"fork_version": [
1,
0,
0,
0
]
},
"bellatrix": {
"epoch": 144896,
"fork_version": [
2,
0,
0,
0
]
},
"capella": {
"epoch": 194048,
"fork_version": [
3,
0,
0,
0
]
}
}
},
"bootstrap": {
Expand Down Expand Up @@ -583,4 +633,4 @@
"0x219d1c19a57bd8d0edb544d4a8e1d12bfcfe074eae2edb4d2490268e8bb6b616"
]
}
}
}
Loading