diff --git a/Dockerfile b/Dockerfile index 35292a2..c66c1ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,9 @@ COPY package*.json ./ RUN npm install RUN npm run build +RUN apk --update add redis +RUN npm install -g concurrently + EXPOSE 3000 -CMD [ "npm", "run", "start" ] \ No newline at end of file +CMD concurrently "/usr/bin/redis-server --bind '0.0.0.0'" "sleep 5s; npm run start" \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index f02eed9..30ab940 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,9 +21,9 @@ export interface NotificationConfig { export interface XdcZeroConfig { isEnabled: boolean; - subnetZeroContractAddress: string; - parentChainZeroContractAddress: string; - walletPk: string; + subnetZeroContractAddress?: string; + parentChainZeroContractAddress?: string; + walletPk?: string; } export interface Config { @@ -32,13 +32,14 @@ export interface Config { cronJob: { liteJobExpression: string; jobExpression: string; + zeroJobExpression: string; }; subnet: SubnetConfig; mainnet: MainnetConfig; reBootstrapWaitingTime: number; notification: NotificationConfig; chunkSize: number; - xdcZero: XdcZeroConfig | undefined; + xdcZero: XdcZeroConfig; relayerCsc: { isEnabled: boolean; } @@ -53,8 +54,8 @@ const getZeroConfig = (): XdcZeroConfig => { isEnabled, subnetZeroContractAddress: process.env.SUBNET_ZERO_CONTRACT, parentChainZeroContractAddress: process.env.PARENTNET_ZERO_CONTRACT, - walletPk: process.env.PARENTNET_ZERO_WALLET_PK - }: undefined; + walletPk: process.env.PARENTNET_ZERO_WALLET_PK.startsWith("0x") ? process.env.PARENTNET_ZERO_WALLET_PK : `0x${process.env.PARENTNET_ZERO_WALLET_PK}` + }: { isEnabled: false }; }; const config: Config = { @@ -63,6 +64,7 @@ const config: Config = { cronJob: { liteJobExpression: "0 */2 * * * *", // every 2min jobExpression: "*/20 * * * * *", // every 20s + zeroJobExpression: "*/10 * * * * *", // every 10s }, subnet: { url: process.env.SUBNET_URL || "https://devnetstats.apothem.network/subnet", diff --git a/src/processors/README.md b/src/processors/README.md index e69de29..c0acc47 100644 --- a/src/processors/README.md +++ b/src/processors/README.md @@ -0,0 +1,7 @@ +# How to add new processors? + +1. Read our `lite.ts`(simple version) or the `standard.ts`(more complex version) as examples +2. Assume you plan to add a new processor called `XXX`. First create the file `XXX.ts` under current directory. +3. Add `export class XXX implements ProcessorInterface` where all our processors has some common methods such as `init` and `reset`. Implement those methods. +4. Go to `index.ts` in this directory, register your processors with `enum Mode`, `private processors` (class property), `reset` method and add your custom start up condition in `getRunningModes` method +5. Done \ No newline at end of file diff --git a/src/processors/index.ts b/src/processors/index.ts index 2a767a7..1c2dd2c 100644 --- a/src/processors/index.ts +++ b/src/processors/index.ts @@ -1,3 +1,4 @@ +import { Zero } from "./zero"; import { config } from "./../config"; import bunyan from "bunyan"; import * as _ from "lodash"; @@ -17,6 +18,7 @@ export class Processors implements ProcessorInterface { private processors: { lite: Lite; standard: Standard; + zero: Zero; } private mainnetService: MainnetService; @@ -24,7 +26,8 @@ export class Processors implements ProcessorInterface { this.logger = logger; this.processors = { lite: new Lite(logger), - standard: new Standard(logger) + standard: new Standard(logger), + zero: new Zero(logger) // Register more processors here }; this.mainnetService = new MainnetService(config.mainnet, logger); @@ -50,6 +53,9 @@ export class Processors implements ProcessorInterface { case Mode.STANDARD: await this.processors.standard.reset(); break; + case Mode.ZERO: + await this.processors.zero.reset(); + break; default: throw new Error("No avaiable modes to choose from"); } @@ -72,7 +78,10 @@ export class Processors implements ProcessorInterface { } } - // TODO: Now check xdc-zero + if (config.xdcZero.isEnabled) { + modes.push(Mode.ZERO); + } + this.logger.info("Running modes: ", modes); return modes; } diff --git a/src/processors/lite.ts b/src/processors/lite.ts index 54f5319..cc5b199 100644 --- a/src/processors/lite.ts +++ b/src/processors/lite.ts @@ -22,6 +22,7 @@ export class Lite implements ProcessorInterface { } init() { + this.logger.info("Initialising XDC Lite relayer"); this.queue.process(async (_, done) => { this.logger.info("⏰ Executing lite flow periodically"); try { diff --git a/src/processors/standard.ts b/src/processors/standard.ts index b7dc6c5..729d339 100644 --- a/src/processors/standard.ts +++ b/src/processors/standard.ts @@ -29,6 +29,7 @@ export class Standard implements ProcessorInterface { } init() { + this.logger.info("Initialising XDC relayer"); this.queue.process(async (_, done) => { this.logger.info("⏰ Executing normal flow periodically"); try { @@ -50,7 +51,6 @@ export class Standard implements ProcessorInterface { try { // Stop and remove repeatable jobs await this.queue.removeRepeatable(NAME, REPEAT_JOB_OPT.repeat); - // Clean timer this.cache.cleanCache(); // Pull latest confirmed tx from mainnet @@ -58,12 +58,11 @@ export class Standard implements ProcessorInterface { // Pull latest confirm block from subnet const lastestSubnetCommittedBlock = await this.subnetService.getLastCommittedBlockInfo(); - const { shouldProcess, from } = await this.shouldProcessSync( smartContractData, lastestSubnetCommittedBlock ); - + if (shouldProcess) { await this.submitTxs( from, diff --git a/src/processors/zero.ts b/src/processors/zero.ts new file mode 100644 index 0000000..f8b0d62 --- /dev/null +++ b/src/processors/zero.ts @@ -0,0 +1,80 @@ +import Bull from "bull"; +import bunyan from "bunyan"; +import { ProcessorInterface } from "./type"; +import { ZeroService } from "../service/zero"; +import { config } from "../config"; + +const NAME = "ZERO"; + +export class Zero implements ProcessorInterface { + private queue: Bull.Queue; + private logger: bunyan; + private zeroService: ZeroService; + + constructor(logger: bunyan) { + this.logger = logger; + this.queue = new Bull(NAME); + this.zeroService = new ZeroService(logger); + } + init() { + this.logger.info("Initialising XDC-Zero"); + this.queue.process(async (_, done) => { + this.logger.info("⏰ Executing xdc-zero periodically"); + try { + done(null, await this.processEvent()); + } catch (error) { + this.logger.error("Fail to process xdc-zero job", { + message: error.message, + }); + // Report the error + done(error); + await this.reset(); + } + }); + return this; + } + + async reset(): Promise { + await this.queue.add({}, { jobId: NAME, repeat: { cron: config.cronJob.zeroJobExpression}}); + } + + async processEvent() { + const payloads = await this.zeroService.getPayloads(); + if (payloads.length) { + this.logger.info("Nothing to process in xdc-zero, wait for the next event log"); + return; + } + const lastPayload = payloads[payloads.length - 1]; + const lastIndexFromSubnet = lastPayload[0]; + + const lastIndexfromParentnet = await this.zeroService.getIndexFromParentnet(); + + const lastBlockNumber = lastPayload[7]; + const cscBlockNumber = await this.zeroService.getLatestBlockNumberFromCsc(); + if (cscBlockNumber < lastBlockNumber) { + this.logger.info( + "wait for csc block lastBlockNumber:" + + lastBlockNumber + + " cscBlockNumber:" + + cscBlockNumber + ); + return; + } + + if (lastIndexFromSubnet > lastIndexfromParentnet) { + for (let i = lastIndexfromParentnet; i < lastIndexFromSubnet; i++) { + if (payloads?.[i]?.[6]) { + const proof = await this.zeroService.getProof(payloads[i][6]); + await this.zeroService.validateTransactionProof( + proof.key, + proof.receiptProofValues, + proof.txProofValues, + proof.blockHash + ); + this.logger.info("sync zero index " + i + " success"); + } + } + } + this.logger.info("Completed the xdc-zero sync, wait for the next cycle"); + } +} \ No newline at end of file diff --git a/src/service/mainnet/extensions.ts b/src/service/mainnet/extensions.ts new file mode 100644 index 0000000..9643b2f --- /dev/null +++ b/src/service/mainnet/extensions.ts @@ -0,0 +1,21 @@ +import Web3 from "web3"; +import { NetworkInformation } from "../types"; + +const MAINNET_EXTENSION_NAME = "xdcMainnet"; + +export interface Web3WithExtension extends Web3 { + xdcMainnet: { + getNetworkInformation: () => Promise + } +} + +export const mainnetExtensions = { + property: MAINNET_EXTENSION_NAME, + methods: [ + { + name: "networkInformation", + call: "XDPoS_networkInformation" + } + ] +}; + diff --git a/src/service/mainnet/index.ts b/src/service/mainnet/index.ts index a640c1b..9f7de05 100644 --- a/src/service/mainnet/index.ts +++ b/src/service/mainnet/index.ts @@ -8,6 +8,8 @@ import { MainnetConfig } from "../../config"; import { sleep } from "../../utils/index"; import FullABI from "./ABI/FullABI.json"; import LiteABI from "./ABI/LiteABI.json"; +import { Web3WithExtension, mainnetExtensions } from "./extensions"; +import { NetworkInformation } from "../types"; export interface SmartContractData { smartContractHash: string; @@ -19,7 +21,7 @@ export interface SmartContractData { const TRANSACTION_GAS_NUMBER = 12500000000; export class MainnetService { - private web3: Web3; + private web3: Web3WithExtension; private smartContractInstance: Contract; private mainnetAccount: Account; private mainnetConfig: MainnetConfig; @@ -32,7 +34,7 @@ export class MainnetService { keepAlive: true, agent: { https: keepaliveAgent }, }); - this.web3 = new Web3(provider); + this.web3 = new Web3(provider).extend(mainnetExtensions); this.smartContractInstance = new this.web3.eth.Contract( FullABI as AbiItem[], config.smartContractAddress @@ -42,6 +44,10 @@ export class MainnetService { ); this.mainnetConfig = config; } + + async getNetworkInformation(): Promise { + return this.web3.xdcMainnet.getNetworkInformation(); + } /* A method to fetch the last subnet block that has been stored/audited in mainnet XDC diff --git a/src/service/subnet/extensions.ts b/src/service/subnet/extensions.ts index 79a9891..5fc36ed 100644 --- a/src/service/subnet/extensions.ts +++ b/src/service/subnet/extensions.ts @@ -1,4 +1,5 @@ import Web3 from "web3"; +import { NetworkInformation } from "../types"; const SUBNET_EXTENSION_NAME = "xdcSubnet"; @@ -18,12 +19,25 @@ export interface FetchedV2BlockInfo { Error: string; } +export interface TxReceiptProof { + blockHash: string; + key: string; + receiptProofKeys: string[]; + receiptProofValues: string[]; + receiptRoot: string; + txProofKeys: string; + txProofValues: string[]; + txRoot: string; +} + export interface Web3WithExtension extends Web3 { xdcSubnet: { getLatestCommittedBlockInfo: () => Promise getV2Block: (number: string) => Promise getV2BlockByNumber: (bluckNum: string) => Promise getV2BlockByHash: (blockHash: string) => Promise + getNetworkInformation: () => Promise + getTransactionAndReceiptProof: (txHash: string) => Promise } } @@ -48,6 +62,15 @@ export const subnetExtensions = { name: "getV2BlockByHash", params: 1, call: "XDPoS_getV2BlockByHash" + }, { + name: "networkInformation", + params: 0, + call: "XDPoS_networkInformation" + }, + { + name: "getTransactionAndReceiptProof", + params: 1, + call: "eth_getTransactionAndReceiptProof" } ] }; diff --git a/src/service/subnet/index.ts b/src/service/subnet/index.ts index 50f694f..a03e4bc 100644 --- a/src/service/subnet/index.ts +++ b/src/service/subnet/index.ts @@ -4,6 +4,7 @@ import bunyan from "bunyan"; import { SubnetConfig } from "../../config"; import { sleep } from "../../utils/index"; import { subnetExtensions, Web3WithExtension } from "./extensions"; +import { NetworkInformation } from "../types"; export interface SubnetBlockInfo { subnetBlockHash: string; @@ -14,8 +15,8 @@ export interface SubnetBlockInfo { } export class SubnetService { - private web3: Web3WithExtension; - private subnetConfig: SubnetConfig; + protected web3: Web3WithExtension; + protected subnetConfig: SubnetConfig; logger: bunyan; constructor(config: SubnetConfig, logger: bunyan) { @@ -29,7 +30,11 @@ export class SubnetService { this.subnetConfig = config; this.web3 = new Web3(provider).extend(subnetExtensions); } - + + async getNetworkInformation(): Promise { + return this.web3.xdcSubnet.getNetworkInformation(); + } + async getLastCommittedBlockInfo(): Promise { try { const { Hash, Number, Round, HexRLP, ParentHash } = @@ -122,6 +127,15 @@ export class SubnetService { throw error; } } + + async getTransactionAndReceiptProof(txHash: string) { + try { + return this.web3.xdcSubnet.getTransactionAndReceiptProof(txHash); + } catch (error) { + this.logger.error("Error while trying to fetch the transaction receipt proof", error); + throw error; + } + } async bulkGetRlpHeaders( startingBlockNumber: number, diff --git a/src/service/types.ts b/src/service/types.ts new file mode 100644 index 0000000..1b0f6ec --- /dev/null +++ b/src/service/types.ts @@ -0,0 +1,5 @@ +export interface NetworkInformation { + Denom: string; + NetworkId: number; + NetworkName: string; +} \ No newline at end of file diff --git a/src/service/zero/index.ts b/src/service/zero/index.ts index d1fe79a..44d6d1a 100644 --- a/src/service/zero/index.ts +++ b/src/service/zero/index.ts @@ -1,233 +1,171 @@ -// import { -// createPublicClient, -// createWalletClient, -// http, -// decodeAbiParameters, -// } from "viem"; -// import { privateKeyToAccount, PrivateKeyAccount } from "viem/accounts"; -// import endpointABI from "../../abi/endpointABI.json"; -// import cscABI from "../../abi/cscABI.json"; -// import fetch from "node-fetch"; -// import { sleep } from "../../utils"; -// import Web3 from "web3"; - -// let account: PrivateKeyAccount = null; -// if (process.env.PARENTNET_ZERO_WALLET_PK) { -// account = privateKeyToAccount(process.env.PARENTNET_ZERO_WALLET_PK as any); -// } - -// const csc = process.env.CHECKPOINT_CONTRACT; - -// const parentnetCSCContract = { -// address: csc, -// abi: cscABI, -// }; - -// const subnetEndpointContract = { -// address: process.env.SUBNET_ZERO_CONTRACT, -// abi: endpointABI, -// }; - -// const parentnetEndpointContract = { -// address: process.env.PARENTNET_ZERO_CONTRACT, -// abi: endpointABI, -// }; -// const xdcparentnet = async () => { -// return { -// id: await getChainId(process.env.PARENTNET_URL), -// name: "XDC Devnet", -// network: "XDC Devnet", -// nativeCurrency: { -// decimals: 18, -// name: "XDC", -// symbol: "XDC", -// }, -// rpcUrls: { -// public: { http: [process.env.PARENTNET_URL] }, -// default: { http: [process.env.PARENTNET_URL] }, -// }, -// }; -// }; -// const xdcsubnet = async () => { -// return { -// id: await getChainId(process.env.SUBNET_URL), -// name: "XDC Subnet", -// network: "XDC Subnet", -// nativeCurrency: { -// decimals: 18, -// name: "XDC", -// symbol: "XDC", -// }, -// rpcUrls: { -// public: { http: [process.env.SUBNET_URL] }, -// default: { http: [process.env.SUBNET_URL] }, -// }, -// }; -// }; - -// const getChainId = async (url: string) => { -// const web3 = new Web3(url); -// return web3.eth.getChainId(); -// }; - -// const createParentnetWalletClient = async () => { -// return createWalletClient({ -// account, -// chain: await xdcparentnet(), -// transport: http(), -// }); -// }; - -// export const createSubnetPublicClient = async () => { -// return createPublicClient({ -// chain: await xdcsubnet(), -// transport: http(), -// }); -// }; - -// export const createParentnetPublicClient = async () => { -// return createPublicClient({ -// chain: await xdcparentnet(), -// transport: http(), -// }); -// }; - -// export const validateTransactionProof = async ( -// cid: string, -// key: string, -// receiptProof: string[], -// transactionProof: string[], -// blockhash: string -// ) => { -// const parentnetPublicClient = await createParentnetPublicClient(); -// const parentnetWalletClient = await createParentnetWalletClient(); -// const { request } = await parentnetPublicClient.simulateContract({ -// ...(parentnetEndpointContract as any), -// account, -// functionName: "validateTransactionProof", -// args: [cid, key, receiptProof, transactionProof, blockhash], -// }); - -// const tx = await parentnetWalletClient.writeContract(request as any); -// console.info(tx); -// }; - -// export const getLatestBlockNumberFromCsc = async () => { -// const parentnetPublicClient = await createParentnetPublicClient(); -// const blocks = (await parentnetPublicClient.readContract({ -// ...(parentnetCSCContract as any), -// functionName: "getLatestBlocks", -// args: [], -// })) as [any, any]; - -// return blocks[1]?.number; -// }; - -// export const getIndexFromParentnet = async (): Promise => { -// const parentnetPublicClient = await createParentnetPublicClient(); -// const subnet = await xdcsubnet(); -// const chain = (await parentnetPublicClient.readContract({ -// ...(parentnetEndpointContract as any), -// functionName: "getChain", -// args: [subnet.id], -// })) as { lastIndex: number }; - -// return chain?.lastIndex; -// }; - -// export const getProof = async (txhash: string): Promise => { -// const res = await fetch(process.env.SUBNET_URL, { -// method: "POST", -// body: JSON.stringify({ -// jsonrpc: "2.0", -// id: 1, -// method: "eth_getTransactionAndReceiptProof", -// params: [txhash], -// }), -// headers: { "Content-Type": "application/json" }, -// }); -// const json = await res.json(); -// return json?.result; -// }; - -// export const getPayloads = async () => { -// const subnetPublicClient = await createSubnetPublicClient(); -// const payloads = [] as any; -// const logs = await subnetPublicClient.getContractEvents({ -// ...(subnetEndpointContract as any), -// fromBlock: BigInt(0), -// eventName: "Packet", -// }); -// const parentnet = await xdcparentnet(); -// logs?.forEach((log) => { -// const values = decodeAbiParameters( -// [ -// { name: "index", type: "uint" }, -// { name: "sid", type: "uint" }, -// { name: "sua", type: "address" }, -// { name: "rid", type: "uint" }, -// { name: "rua", type: "address" }, -// { name: "data", type: "bytes" }, -// ], -// `0x${log.data.substring(130)}` -// ); - -// if (Number(values[3]) == parentnet.id) { -// const list = [...values]; -// list.push(log.transactionHash); -// list.push(log.blockNumber); -// payloads.push(list); -// } -// }); - -// return payloads; -// }; - -// export const sync = async () => { -// while (true) { -// console.info("start sync zero"); -// const payloads = await getPayloads(); -// if (payloads.length == 0) return; - -// const lastPayload = payloads[payloads.length - 1]; -// const lastIndexFromSubnet = lastPayload[0]; - -// const lastIndexfromParentnet = await getIndexFromParentnet(); - -// const lastBlockNumber = lastPayload[7]; - -// const cscBlockNumber = await getLatestBlockNumberFromCsc(); - -// if (cscBlockNumber < lastBlockNumber) { -// console.info( -// "wait for csc block lastBlockNumber:" + -// lastBlockNumber + -// " cscBlockNumber:" + -// cscBlockNumber -// ); -// await sleep(1000); -// continue; -// } - -// //it's better to fetch data from csc on parentnet , to get the latest subnet header data -// const subnet = await xdcsubnet(); - -// if (lastIndexFromSubnet > lastIndexfromParentnet) { -// for (let i = lastIndexfromParentnet; i < lastIndexFromSubnet; i++) { -// if (payloads?.[i]?.[6]) { -// const proof = await getProof(payloads[i][6]); -// await validateTransactionProof( -// subnet.id.toString(), -// proof.key, -// proof.receiptProofValues, -// proof.txProofValues, -// proof.blockHash -// ); -// console.info("sync zero index " + i + " success"); -// } -// } -// } -// console.info("end sync zero ,sleep 1 seconds"); -// await sleep(1000); -// } -// }; +import { Hex, PrivateKeyAccount, createWalletClient, PublicClient, WalletClient, createPublicClient, decodeAbiParameters, http } from "viem"; +import bunyan from "bunyan"; +import { config } from "../../config"; +import { SubnetService } from "../subnet"; +import endpointABI from "../../abi/endpointABI.json"; +import cscABI from "../../abi/cscABI.json"; +import { MainnetService } from "../mainnet"; +import Logger from "bunyan"; +import { privateKeyToAccount } from "viem/accounts"; + +// This class must be called with init() in order to use it +export class ZeroService { + private subnetViemClient: PublicClient; + private mainnetViemClient: PublicClient; + private mainnetWalletClient: WalletClient; + private subnetService: SubnetService; + private mainnetService: MainnetService; + private logger: Logger; + + private parentChainWalletAccount: PrivateKeyAccount; + + constructor(logger: bunyan) { + this.subnetService = new SubnetService(config.subnet, logger); + this.mainnetService = new MainnetService(config.mainnet, logger); + this.logger = logger; + } + + // Initialise the client services + async init() { + this.parentChainWalletAccount = privateKeyToAccount(config.xdcZero.walletPk as Hex); + const subnetNetworkInformation = await this.subnetService.getNetworkInformation(); + const subnetInfo = { + id: subnetNetworkInformation.NetworkId, + name: subnetNetworkInformation.NetworkName, + network: subnetNetworkInformation.NetworkName, + nativeCurrency: { + decimals: 18, + name: subnetNetworkInformation.Denom, + symbol: subnetNetworkInformation.Denom, + }, + rpcUrls: { + public: { http: [config.subnet.url] }, + default: { http: [config.subnet.url] }, + } + }; + + this.subnetViemClient = createPublicClient({ + chain: subnetInfo, + transport: http() + }); + + const mainnetNetworkInformation = await this.mainnetService.getNetworkInformation(); + const mainnetInfo = { + id: mainnetNetworkInformation.NetworkId, + name: mainnetNetworkInformation.NetworkName, + network: mainnetNetworkInformation.NetworkName, + nativeCurrency: { + decimals: 18, + name: mainnetNetworkInformation.Denom, + symbol: mainnetNetworkInformation.Denom, + }, + rpcUrls: { + public: { http: [config.mainnet.url] }, + default: { http: [config.mainnet.url] }, + } + }; + + this.mainnetViemClient = createPublicClient({ + chain: mainnetInfo, + transport: http() + }); + + this.mainnetWalletClient = createWalletClient({ + account: this.parentChainWalletAccount, + chain: mainnetInfo, + transport: http() + }); + } + + async getPayloads() { + const payloads = [] as any; + const subnetEndpointContract = { + address: config.xdcZero.subnetZeroContractAddress, + abi: endpointABI, + }; + + const logs = await this.subnetViemClient.getContractEvents({ + ...(subnetEndpointContract as any), + fromBlock: BigInt(0), + eventName: "Packet", + }); + const parentChainId = await this.mainnetViemClient.getChainId(); + logs?.forEach((log) => { + const values = decodeAbiParameters( + [ + { name: "index", type: "uint" }, + { name: "sid", type: "uint" }, + { name: "sua", type: "address" }, + { name: "rid", type: "uint" }, + { name: "rua", type: "address" }, + { name: "data", type: "bytes" }, + ], + `0x${log.data.substring(130)}` + ); + + if (Number(values[3]) == parentChainId) { + const list = [...values]; + list.push(log.transactionHash); + list.push(log.blockNumber); + payloads.push(list); + } + }); + + return payloads; + } + + async getIndexFromParentnet() { + const subnetChainId = await this.subnetViemClient.getChainId(); + const parentnetEndpointContract = { + address: config.xdcZero.parentChainZeroContractAddress, + abi: endpointABI, + }; + const chain = (await this.mainnetViemClient.readContract({ + ...(parentnetEndpointContract as any), + functionName: "getChain", + args: [subnetChainId], + })) as { lastIndex: number }; + + return chain?.lastIndex; + } + + async getLatestBlockNumberFromCsc() { + const parentnetCSCContract = { + address: config.mainnet.smartContractAddress, + abi: cscABI, + }; + const blocks = (await this.mainnetViemClient.readContract({ + ...(parentnetCSCContract as any), + functionName: "getLatestBlocks", + args: [], + })) as [any, any]; + + return blocks[1]?.number; + } + + async getProof(txHash: string) { + return this.subnetService.getTransactionAndReceiptProof(txHash); + } + + async validateTransactionProof( + key: string, + receiptProof: string[], + transactionProof: string[], + blockhash: string + ) { + const parentnetEndpointContract = { + address: config.xdcZero.parentChainZeroContractAddress, + abi: endpointABI, + }; + const subnetChainId = await this.subnetViemClient.getChainId(); + const { request } = await this.mainnetViemClient.simulateContract({ + ...(parentnetEndpointContract as any), + functionName: "validateTransactionProof", + args: [subnetChainId, key, receiptProof, transactionProof, blockhash], + account: this.parentChainWalletAccount + }); + + const tx = await this.mainnetWalletClient.writeContract(request as any); + this.logger.info(tx); + } +} \ No newline at end of file