From dc9adfcdf850a4181e47914b29eb3770f4f11f72 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Thu, 26 Dec 2024 19:31:39 -0500 Subject: [PATCH] update ts-filler to handle mock proofs for hashi --- services/ts-filler/README.md | 2 +- services/ts-filler/src/abis/ShoyuBashi.ts | 23 +++++++++++++++ services/ts-filler/src/chain/chain.service.ts | 14 +++++++--- .../ts-filler/src/common/utils/attributes.ts | 12 ++++++++ .../ts-filler/src/prover/prover.service.ts | 28 ++++++++++++++++++- .../ts-filler/src/rewards/monitor.service.ts | 23 +++++++++++++-- 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 services/ts-filler/src/abis/ShoyuBashi.ts diff --git a/services/ts-filler/README.md b/services/ts-filler/README.md index ae68491..effd9b8 100644 --- a/services/ts-filler/README.md +++ b/services/ts-filler/README.md @@ -59,7 +59,7 @@ bun run index.ts ## Docker Support -The `ts-filler` service can also be run inside a Docker container. The provided `docker-compose.tml` file sets up the environment and installs the necessary dependencies. +The `ts-filler` service can also be run inside a Docker container. The provided `docker-compose.yml` file sets up the environment and installs the necessary dependencies. To build and run the Docker container, use the following command: diff --git a/services/ts-filler/src/abis/ShoyuBashi.ts b/services/ts-filler/src/abis/ShoyuBashi.ts new file mode 100644 index 0000000..48d7b04 --- /dev/null +++ b/services/ts-filler/src/abis/ShoyuBashi.ts @@ -0,0 +1,23 @@ +export default [ + { + type: "function", + name: "getThresholdHash", + inputs: [ + { name: "domain", type: "uint256", internalType: "uint256" }, + { name: "id", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }], + stateMutability: "view", + }, + { + type: "function", + name: "setHash", + inputs: [ + { name: "domain", type: "uint256", internalType: "uint256" }, + { name: "id", type: "uint256", internalType: "uint256" }, + { name: "hash", type: "bytes32", internalType: "bytes32" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, +] as const; diff --git a/services/ts-filler/src/chain/chain.service.ts b/services/ts-filler/src/chain/chain.service.ts index e554c95..a03b7e7 100644 --- a/services/ts-filler/src/chain/chain.service.ts +++ b/services/ts-filler/src/chain/chain.service.ts @@ -179,10 +179,16 @@ export default class ChainService { } private async getL2BlockNumber(l1BlockNumber?: bigint): Promise { - if (!this.usingHashi) { - if (!l1BlockNumber) { - throw new Error("Block number is required"); - } + if (this.usingHashi) { + // NOTE: This is only for a proof of concept. We have a mock shoyu bashi contract that allows us to directly set the block hash for the l2 block number. + // In production, more sophisticated logic will be needed to determine the latest block number accounted for in the Hashi system. + return await exponentialBackoff( + async () => await this.activeChains.dst.publicClient.getBlockNumber() + ); + } + + if (!l1BlockNumber) { + throw new Error("Block number is required"); } const config = this.activeChains.l1; diff --git a/services/ts-filler/src/common/utils/attributes.ts b/services/ts-filler/src/common/utils/attributes.ts index e8de7b8..c69ecb4 100644 --- a/services/ts-filler/src/common/utils/attributes.ts +++ b/services/ts-filler/src/common/utils/attributes.ts @@ -5,6 +5,7 @@ const REWARD_ATTRIBUTE_SELECTOR = "0xa362e5db"; const DELAY_ATTRIBUTE_SELECTOR = "0x84f550e0"; const FULFILLER_ATTRIBUTE_SELECTOR = "0x138a03fc"; const L2_ORACLE_ATTRIBUTE_SELECTOR = "0x7ff7245a"; +const SHOYU_BASHI_ATTRIBUTE_SELECTOR = "0xda07e15d"; export default class Attributes { constructor(private attributes: Hex[]) {} @@ -51,6 +52,17 @@ export default class Attributes { }; } + getShoyuBashi(): Hex { + const attribute = this.getAttribute(SHOYU_BASHI_ATTRIBUTE_SELECTOR); + + const [hash] = decodeAbiParameters( + [{ type: "bytes32" }], + ("0x" + attribute.slice(10)) as Hex + ); + + return hash; + } + setFulfiller(fulfiller: Address): void { this.attributes.push( `${FULFILLER_ATTRIBUTE_SELECTOR}${fulfiller.slice(2).padStart(64, "0")}` diff --git a/services/ts-filler/src/prover/prover.service.ts b/services/ts-filler/src/prover/prover.service.ts index f2de632..5936d88 100644 --- a/services/ts-filler/src/prover/prover.service.ts +++ b/services/ts-filler/src/prover/prover.service.ts @@ -49,6 +49,13 @@ export default class ProverService { } async generateProof(requestHash: Address): Promise { + const { proof } = await this.generateProofWithL2Block(requestHash); + return proof; + } + + async generateProofWithL2Block( + requestHash: Address + ): Promise<{ proof: ProofType; l2Block: Block }> { let beaconData: GetBeaconRootAndL2TimestampReturnType | undefined; let l1BlockNumber: bigint | undefined; let stateRootInclusion: StateRootProofReturnType | undefined; @@ -89,7 +96,7 @@ export default class ProverService { }; const storageProofs = await this.getStorageProofs(storageProofOpts); - return this.storeProofObj( + const proof = this.storeProofObj( storageProofs, l2Block, beaconData, @@ -98,6 +105,8 @@ export default class ProverService { parentAssertionHash, afterInboxBatchAcc ); + + return { proof, l2Block }; } private getExecutionStateRootProof(block: any): StateRootProofReturnType { @@ -336,6 +345,13 @@ export default class ProverService { throw new Error("Storage proof is required for Arbitrum proofs"); } + if (proofs.storageProof.storageProof[0].value === 0n) { + throw new Error("Storage proof value is 0"); + } + if (proofs.l2StorageProof.storageProof[0].value === 0n) { + throw new Error("L2 storage proof value is 0"); + } + return { stateProofParams: { beaconRoot: beaconData.beaconRoot, @@ -380,6 +396,12 @@ export default class ProverService { if (!proofs.storageProof) { throw new Error("Storage proof is required for OPStack proofs"); } + if (proofs.storageProof.storageProof[0].value === 0n) { + throw new Error("Storage proof value is 0"); + } + if (proofs.l2StorageProof.storageProof[0].value === 0n) { + throw new Error("L2 storage proof value is 0"); + } return { l2MessagePasserStorageRoot: @@ -411,6 +433,10 @@ export default class ProverService { } private buildHashiProof(proofs: Proofs, l2Block: Block): HashiProofType { + if (proofs.l2StorageProof.storageProof[0].value === 0n) { + throw new Error("L2 storage proof value is 0"); + } + return { rlpEncodedBlockHeader: this.getEncodedBlockArray(l2Block), dstAccountProofParams: { diff --git a/services/ts-filler/src/rewards/monitor.service.ts b/services/ts-filler/src/rewards/monitor.service.ts index 9f6159b..ecabc5d 100644 --- a/services/ts-filler/src/rewards/monitor.service.ts +++ b/services/ts-filler/src/rewards/monitor.service.ts @@ -19,6 +19,8 @@ import type { import CAIP10 from "../common/utils/caip10"; import HashiProof from "../abis/HashiProof"; import Attributes from "../common/utils/attributes"; +import ShoyuBashi from "../abis/ShoyuBashi"; +import bytes32ToAddress from "../common/utils/bytes32ToAddress"; export default class RewardMonitorService { private processing = false; @@ -69,11 +71,28 @@ export default class RewardMonitorService { ); const { requestHash, sender, receiver, payload, attributes } = job; - const proof = await proverService.generateProof(requestHash); - const payTo = signerService.getFulfillerAddress(); + const { proof, l2Block } = await proverService.generateProofWithL2Block( + requestHash + ); const attributesClass = new Attributes(attributes); attributesClass.removeFulfiller(); + const usingHashi = + !activeChains.src.exposesL1State || !activeChains.dst.sharesStateWithL1; + + if (usingHashi) { + // NOTE: This is only for a proof of concept. We have a mock shoyu bashi contract that allows us to directly set the block hash for the l2 block number. + // In production, more sophisticated logic will be needed to determine the latest block number accounted for in the Hashi system. + const shoyuBashi = attributesClass.getShoyuBashi(); + await signerService.sendTransaction( + bytes32ToAddress(shoyuBashi), + ShoyuBashi, + "setHash", + [activeChains.dst.chainId, l2Block.number as bigint, l2Block.hash] + ); + } + + const payTo = signerService.getFulfillerAddress(); const encodedProof = this.encodeProof(proof); const functionName = "claimReward";