From 31665e5fec3f287d80fea26b5daee12d4fb3485b Mon Sep 17 00:00:00 2001 From: Korbinian Date: Mon, 26 Feb 2024 09:05:15 +0100 Subject: [PATCH] proofer update --- .../bridge-ui/src/libs/proof/BridgeProver.ts | 312 ++++++++---------- packages/bridge-ui/src/libs/proof/types.ts | 44 ++- 2 files changed, 184 insertions(+), 172 deletions(-) diff --git a/packages/bridge-ui/src/libs/proof/BridgeProver.ts b/packages/bridge-ui/src/libs/proof/BridgeProver.ts index 5bcd1e9877a..7b0caecac04 100644 --- a/packages/bridge-ui/src/libs/proof/BridgeProver.ts +++ b/packages/bridge-ui/src/libs/proof/BridgeProver.ts @@ -4,23 +4,20 @@ import { BlockNotFoundError, encodeAbiParameters, encodePacked, - type Hash, type Hex, keccak256, numberToHex, toBytes, toHex, - toRlp, } from 'viem'; import { signalServiceAbi } from '$abi'; import { routingContractsMap } from '$bridgeConfig'; -import { MessageStatus } from '$libs/bridge'; -import { InvalidProofError, WrongBridgeConfigError } from '$libs/error'; +import { BlockError, ClientError, ProofGenerationError } from '$libs/error'; import { getLogger } from '$libs/util/logger'; import { config } from '$libs/wagmi'; -import type { ClientWithEthGetProofRequest, GenerateProofArgs, Hop } from './types'; +import { CacheOptions, type ClientWithEthGetProofRequest, type GetProofArgs, type HopProof } from './types'; const log = getLogger('proof:Prover'); @@ -46,196 +43,174 @@ export class BridgeProver { blockNumber: blockNumber, }); } - // const client = getPublicClient(config, { chainId: srcChainId }); - // if (!client) throw new Error('Could not get public client'); - // const latestBlockHash = syncedSnippet['blockHash']; - - // const block = await client.getBlock({ blockHash: latestBlockHash }); - // if (block.hash === null || block.number === null) { - // throw new PendingBlockError('block is pending'); - // } return blockNumber; } - async encodedStorageProof(args: GenerateProofArgs) { - const { msgHash, clientChainId, contractAddress, proofForAccountAddress, blockNumber } = args; - const client = getPublicClient(config, { chainId: clientChainId }); - const key = await this.getSignalSlot(clientChainId, contractAddress, msgHash); - log('Signal slot', key); - - // Unfortunately, since this method is stagnant, it hasn't been included into Viem lib - // as supported methods. Still stupported by Alchmey, Infura and others. - // See https://eips.ethereum.org/EIPS/eip-1186 - // Following is a workaround to support this method. - const clientWithEthProofRequest = client as ClientWithEthGetProofRequest; - - // RPC call to get the merkle proof what value is at key on the SignalService contract - const proof = await clientWithEthProofRequest.request({ - method: 'eth_getProof', - params: [ - // Address of the account to get the proof for - proofForAccountAddress, - - // Array of storage-keys that should be proofed and included - [key], - - numberToHex(blockNumber as bigint), - ], - }); - - log('Proof from eth_getProof', proof); - - if (proof.storageProof[0].value !== toHex(true)) { - throw new InvalidProofError('storage proof value is not 1'); - } - - // RLP encode the proof together for LibTrieProof to decode - const rlpEncodedStorageProof = toRlp(proof.storageProof[0].proof); - - return { proof, rlpEncodedStorageProof }; - } - - async encodedSignalProof(msgHash: Hash, srcChainId: number, destChainId: number) { - const hops = routingContractsMap[srcChainId][destChainId].hops; - if (hops && hops.length > 0) { - return await this._encodedSignalProofWithHops(msgHash, srcChainId, destChainId); - } else { - return await this._encodedSignalProofWithoutHops(msgHash, srcChainId, destChainId); - } - } - - // Reference: EncodedSignalProof in relayer/proof/encoded_signal_proof.go - // protocol/contracts/signal/SignalService.sol - async _encodedSignalProofWithoutHops(msgHash: Hash, srcChainId: number, destChainId: number) { - const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; - const srcSignalServiceAddress = routingContractsMap[srcChainId][destChainId].signalServiceAddress; - const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; + async getEncodedSignalProof(args: GetProofArgs) { + const { bridgeTx } = args; + const { blockNumber, message, msgHash } = bridgeTx; + if (!message) throw new ProofGenerationError('Message is not defined'); + const { srcChainId, destChainId } = message; - // Get the block from chain A based on the latest block hash - // we get cross chain (Taiko contract on chain B) - const blockNumber = await this.getBlockNumber(srcChainId, destChainId, destCrossChainSyncAddress); - - const { rlpEncodedStorageProof } = await this.encodedStorageProof({ + let previousSrcChainId = srcChainId; // Initialize with the original source chain ID + const key = await this.getSignalSlot( + Number(srcChainId), + routingContractsMap[Number(srcChainId)][Number(destChainId)].bridgeAddress, msgHash, - clientChainId: srcChainId, - contractAddress: srcBridgeAddress, - proofForAccountAddress: srcSignalServiceAddress, - blockNumber, - }); - - const signalProof = this._encodeAbiParameters( - destCrossChainSyncAddress, - BigInt(blockNumber), - rlpEncodedStorageProof, - [], ); - return signalProof; - } - - async _encodedSignalProofWithHops(msgHash: Hash, srcChainId: number, destChainId: number) { - const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; - const srcSignalServiceAddress = routingContractsMap[srcChainId][destChainId].signalServiceAddress; - const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; - const hopParams = routingContractsMap[srcChainId][destChainId].hops; - if (hopParams === undefined) throw new WrongBridgeConfigError('hops is undefined'); - - let blockNumber = BigInt(0); - // Initialize hopChainId with src chain - let hopChainId: number = srcChainId; - for (const hop of hopParams) { - blockNumber = await this.getBlockNumber(hopChainId, hop.chainId, hop.crossChainSyncAddress); - hopChainId = hop.chainId; - } - // Get the block number from last hop chain to dest chain - blockNumber = await this.getBlockNumber(hopChainId, destChainId, destCrossChainSyncAddress); - - // Generate main storage proof with receipt.blockNumber - const { proof, rlpEncodedStorageProof } = await this.encodedStorageProof({ - msgHash, - clientChainId: srcChainId, - contractAddress: srcBridgeAddress, - proofForAccountAddress: srcSignalServiceAddress, - blockNumber, - }); - // The first signalRoot - let signalRoot = proof.storageHash; - log('successfully generated main storage proof', signalRoot); - - const hops: Hop[] = []; - for (const hop of hopParams) { - const { proof: hopProof, rlpEncodedStorageProof: hopRlpEncodedStorageProof } = await this.encodedStorageProof({ - msgHash: signalRoot, - clientChainId: hop.chainId, - contractAddress: hop.crossChainSyncAddress, - proofForAccountAddress: hop.signalServiceAddress, - blockNumber, + const configuredHops = routingContractsMap[Number(srcChainId)][Number(destChainId)].hops; + + if (configuredHops && configuredHops.length > 0) { + const hopProofs: HopProof[] = []; + + for (let i = 0; i < configuredHops.length; i++) { + const currentHop = configuredHops[i]; + let currentSignalServiceAddress: Address; + + if (i + 1 < configuredHops.length) { + // There is a next hop + currentSignalServiceAddress = configuredHops[i + 1].signalServiceAddress; + } else { + // This is the last hop, so use the destination's signal service address + currentSignalServiceAddress = + routingContractsMap[Number(destChainId)][Number(srcChainId)].signalServiceAddress; + } + + const syncedChainData = await readContract(config, { + address: currentSignalServiceAddress, + abi: signalServiceAbi, + functionName: 'getSyncedChainData', + args: [BigInt(destChainId), keccak256(toBytes('STATE_ROOT')), 0n], + chainId: Number(previousSrcChainId), + }); + + const latestBlockNumber = syncedChainData[0]; + + const hopClient = getPublicClient(config, { chainId: Number(currentHop.chainId) }); + if (!hopClient) throw new Error('Could not get public client'); + + const block = await hopClient.getBlock({ blockNumber: blockNumber }); + if (block.hash === null || block.number === null) { + throw new BlockNotFoundError({ blockHash: block.hash, blockNumber: block.number }); + } + if (latestBlockNumber < block.number) { + //TODO handle this earlier somehow + throw new Error('block is not synced yet'); + } + + const clientWithEthProofRequest = hopClient as ClientWithEthGetProofRequest; + const hopProof = await clientWithEthProofRequest.request({ + method: 'eth_getProof', + params: [currentSignalServiceAddress, [key], numberToHex(latestBlockNumber as bigint)], + }); + log('Proof from eth_getProof', hopProof); + + //TODO check for caching then do the following if cached + // hopProofs.push( + // { + // chainId: BigInt(currentHop.chainId), + // blockId: BigInt(latestBlockNumber), + // rootHash: <- root of signalservice -> + // cacheOption: CacheOptions.CACHE_NOTHING, + // accountProof: [], + // storageProof: hopProof.storageProof[0].proof, + // } + + // Full proof + hopProofs.push({ + chainId: BigInt(currentHop.chainId), + blockId: BigInt(latestBlockNumber), + rootHash: block.stateRoot, + cacheOption: CacheOptions.CACHE_NOTHING, // Todo: could be configurable + accountProof: hopProof.accountProof, + storageProof: hopProof.storageProof[0].proof, + }); + + previousSrcChainId = BigInt(currentHop.chainId); // Update previousSrcChainId for the next iteration + } + + return this._encodeAbiParameters(hopProofs); + } else { + const destSignalServiceAddress = + routingContractsMap[Number(destChainId)][Number(srcChainId)].signalServiceAddress; + + const syncedChainData = await readContract(config, { + address: destSignalServiceAddress, + abi: signalServiceAbi, + functionName: 'getSyncedChainData', + args: [destChainId, keccak256(toBytes('STATE_ROOT')), 0n], + chainId: Number(srcChainId), }); - log('successfully generated hop storage proof', hopProof.storageHash); - hops.push({ - signalRootRelay: hop.crossChainSyncAddress, - signalRoot: signalRoot, - storageProof: hopRlpEncodedStorageProof, + const latestBlockNumber = syncedChainData[0]; + const hopClient = getPublicClient(config, { chainId: Number(srcChainId) }); + if (!hopClient) throw new ClientError('Could not get public client'); + + const block = await hopClient.getBlock({ blockNumber: blockNumber }); + if (block.hash === null || block.number === null) { + throw new BlockNotFoundError({ blockHash: block.hash, blockNumber: block.number }); + } + if (latestBlockNumber < block.number) { + //TODO handle this earlier somehow? + throw new BlockError('block is not synced yet'); + } + + const clientWithEthProofRequest = hopClient as ClientWithEthGetProofRequest; + const hopProof = await clientWithEthProofRequest.request({ + method: 'eth_getProof', + params: [destSignalServiceAddress, [key], numberToHex(latestBlockNumber as bigint)], }); - signalRoot = hopProof.storageHash; + log('Proof from eth_getProof', hopProof); + + if (hopProof.storageProof[0].value === toHex(0)) { + throw new Error('proof will not be valid, expected storageProof to not be 0 but was not'); + } + + const proof = { + chainId: BigInt(srcChainId), + blockId: BigInt(latestBlockNumber), + rootHash: block.stateRoot, + cacheOption: CacheOptions.CACHE_NOTHING, // Todo: could be configurable + accountProof: hopProof.accountProof, + storageProof: hopProof.storageProof[0].proof, + }; + return this._encodeAbiParameters([proof]); } - - const signalProof = this._encodeAbiParameters( - destCrossChainSyncAddress, - BigInt(blockNumber), - rlpEncodedStorageProof, - hops, - ); - return signalProof; } - async generateProofToRelease(msgHash: Hash, srcChainId: number, destChainId: number) { - const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; - const destBridgeAddress = routingContractsMap[destChainId][srcChainId].bridgeAddress; - const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; - - const blockNumber = await this.getBlockNumber(srcChainId, destChainId, destCrossChainSyncAddress); - - const { proof } = await this.encodedStorageProof({ - msgHash, - clientChainId: destChainId, - contractAddress: srcBridgeAddress, - proofForAccountAddress: destBridgeAddress, - blockNumber, - }); - - // Value must be 0x3 => MessageStatus.FAILED - if (proof.storageProof[0].value !== toHex(MessageStatus.FAILED)) { - throw new InvalidProofError('storage proof value is not FAILED'); - } - - return this._encodedSignalProofWithoutHops(msgHash, destChainId, srcChainId); - } - - _encodeAbiParameters(crossChainSync: Address, height: bigint, storageProof: Hex, hops: Hop[]) { + _encodeAbiParameters(hops: HopProof[]) { return encodeAbiParameters( [ { type: 'tuple', components: [ - { name: 'crossChainSync', type: 'address' }, - { name: 'height', type: 'uint64' }, - { name: 'storageProof', type: 'bytes' }, { type: 'tuple[]', name: 'hops', components: [ { - type: 'address', - name: 'signalRootRelay', + type: 'uint64', + name: 'chainId', + }, + { + type: 'uint64', + name: 'blockId', }, { type: 'bytes32', - name: 'signalRoot', + name: 'rootHash', + }, + { + type: 'enum', + name: 'cacheOption', + }, + { + type: 'bytes[]', + name: 'accountProof', }, { - type: 'bytes', + type: 'bytes[]', name: 'storageProof', }, ], @@ -245,9 +220,6 @@ export class BridgeProver { ], [ { - crossChainSync, - height, - storageProof, hops, }, ], diff --git a/packages/bridge-ui/src/libs/proof/types.ts b/packages/bridge-ui/src/libs/proof/types.ts index 7bb722f701f..470215ff210 100644 --- a/packages/bridge-ui/src/libs/proof/types.ts +++ b/packages/bridge-ui/src/libs/proof/types.ts @@ -1,17 +1,24 @@ import type { Address, Hash, Hex } from 'viem'; +import type { BridgeTransaction } from '$libs/bridge'; + export type GenerateProofArgs = { msgHash: Hash; contractAddress: Address; - proofForAccountAddress: Address; + signalServiceAddress: Address; clientChainId: number; blockNumber: bigint; + action: ProofAction; + hops: HopParams[]; +}; + +export type GetProofArgs = { + bridgeTx: BridgeTransaction; }; export type StorageEntry = { key: string; value: Hex; - // Array of rlp-serialized MerkleTree-Nodes, starting with the storageHash-Node, following the path of the SHA3 (key) as path. proof: Hex[]; }; @@ -22,6 +29,39 @@ export type Hop = { storageProof: Hex; }; +export const enum ProofAction { + SEND, + RELEASE, + CLAIM, + RETRY, +} + +export const enum CacheOptions { + CACHE_NOTHING, + CACHE_SIGNAL_ROOT, + CACHE_STATE_ROOT, + CACHE_BOTH, +} + +export type HopProof = { + chainId: bigint; + blockId: bigint; + rootHash: Hash; + cacheOption: CacheOptions; + accountProof: Hex[]; + storageProof: Hex[]; +}; + +export type HopParams = { + chainId: bigint; + signalServiceAddress: Address; + // signalService: Address; + key: Hex; + // blocker: Address; + // caller: Address; + blockNumber: bigint; +}; + export type EthGetProofResponse = { balance: bigint; codeHash: Hash;