From 0ffed621c564cc6675393dae708a60a8d0ca617d Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Mon, 4 Nov 2024 16:09:40 +0000 Subject: [PATCH] feat: add getSyncTxs --- example/index.ts | 14 +++--- package.json | 4 +- src/UtuProvider.ts | 112 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/example/index.ts b/example/index.ts index 4a74dd3..f8b4eb3 100644 --- a/example/index.ts +++ b/example/index.ts @@ -25,19 +25,17 @@ async function main() { const txId = "fa89c32152bf324cd1d47d48187f977c7e0f380f6f78132c187ce27923f62fcc"; const rawTransaction = await bitcoinProvider.getRawTransaction(txId, true); - const blockHeader = await bitcoinProvider.getBlockHeader( + const header = await bitcoinProvider.getBlockHeader( rawTransaction.blockhash ); // Generate actual transactions - const registerBlocksTx = await utuProvider.getRegisterBlocksTx([ - rawTransaction.blockhash, - ]); - const canonicalChainUpdateTx = await utuProvider.getCanonicalChainUpdateTx( - blockHeader.height, - blockHeader.height, - true + const syncTransactions = await utuProvider.getSyncTxs( + starknetProvider, + header.height, + 0n ); + const txInclusionProof = await utuProvider.getTxInclusionProof(txId); } catch (error) { console.error("Error:", error); diff --git a/package.json b/package.json index cd05c8b..d9ac8a6 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "ts-jest": "^29.2.5" }, "devDependencies": { - "@rollup/plugin-alias": "^4.0.2", - "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.3.0", "rollup": "^4.24.0", "rollup-plugin-typescript2": "^0.36.0", diff --git a/src/UtuProvider.ts b/src/UtuProvider.ts index 6d50e3f..4a34d73 100644 --- a/src/UtuProvider.ts +++ b/src/UtuProvider.ts @@ -2,6 +2,7 @@ import { BitcoinProvider } from "./BitcoinProvider"; import { BlockHeightProof, RegisterBlocksTx } from "@/UtuTypes"; import { BlockHeader } from "./BitcoinTypes"; import { BigNumberish, ByteArray } from "starknet"; +import { Contract, RpcProvider } from "starknet"; const CONTRACT_ADDRESS = "0x064e21f88caa162294fdda7f73d67ad09b81419e97df3409a5eb13ba39b88c31"; @@ -69,6 +70,15 @@ const byteArrayFromHexString = (hex: string): ByteArray => { }; }; +// Add this helper function with other utility functions at the top +const isResultEmpty = (blockStatus: string[]): boolean => { + return blockStatus.every((value: string) => + value.startsWith("0x") + ? parseInt(value.slice(2), 16) === 0 + : parseInt(value, 16) === 0 + ); +}; + export interface UtuProviderResult { inclusionProof: string; bitcoinRelayerTx?: string; @@ -417,4 +427,106 @@ export class UtuProvider { } return [value, newPosition]; } + + /** + * Gets the sync transactions needed to register Bitcoin blocks on Starknet. + * This is a simple version that only registers new blocks and does not handle + * chain reorganizations or attempt to override an incorrect canonical chain. + */ + async getSyncTxs( + starknetProvider: RpcProvider, + blockHeight: number, + minCPOW: bigint + ) { + // Prepare the calls array + const calls = []; + + // Convert bits field to target threshold and compute proof of work + function computePOWFromBits(bits: string): bigint { + // Convert bits to target threshold + // Format is: 0x1d00ffff where: + // First byte (1d) is the number of bytes + // Next byte (00) is unused + // Last bytes (ffff) are the significant digits + const exponent = parseInt(bits.slice(0, 2), 16); + const coefficient = parseInt(bits.slice(2), 16); + + // Target = coefficient * 256^(exponent-3) + const target = BigInt(coefficient) * BigInt(256) ** BigInt(exponent - 3); + + // Compute POW as (2^256-1)/target + const maxValue = (BigInt(1) << BigInt(256)) - BigInt(1); + return maxValue / target; + } + + let cPOW = 0n; + const blocksToRegister = []; + let canonicalRewriteMin = undefined; + let canonicalRewriteMax = undefined; + let currentBlockHeight = blockHeight; + + // even if we need 0 cpow, we still need at least this block + while (cPOW < minCPOW || cPOW === 0n) { + // Get block information + const blockHash = await this.bitcoinProvider.getBlockHash( + currentBlockHeight + ); + const blockHeader = await this.bitcoinProvider.getBlockHeader(blockHash); + cPOW += computePOWFromBits(blockHeader.bits); + + // Check if block is written + const blockStatus = await starknetProvider.callContract({ + contractAddress: CONTRACT_ADDRESS, + entrypoint: "get_status", + calldata: serializedHash(blockHash), + }); + if (isResultEmpty(blockStatus)) { + blocksToRegister.push(blockHash); + if (canonicalRewriteMin === undefined) { + canonicalRewriteMin = currentBlockHeight; + } + canonicalRewriteMax = currentBlockHeight; + } else { + // if the block is written, we then need to check the status on the canonical chain + const canonicalChainBlock = await starknetProvider.callContract({ + contractAddress: CONTRACT_ADDRESS, + entrypoint: "get_block", + calldata: ["0x" + currentBlockHeight.toString(16)], + }); + // if it is not written, then we want to update it + if (isResultEmpty(canonicalChainBlock)) { + if (canonicalRewriteMin === undefined) { + canonicalRewriteMin = currentBlockHeight; + } + canonicalRewriteMax = currentBlockHeight; + } + } + + currentBlockHeight += 1; + } + + // 1. Register the block itself + const registerBlocks = await this.getRegisterBlocksTx(blocksToRegister); + calls.push(registerBlocks); + + // 2. Update the canonical chain to include this block + if (canonicalRewriteMin !== undefined) { + // we require a height proof if there is not already the prev block on the canonical chain + const previousChainBlock = await starknetProvider.callContract({ + contractAddress: CONTRACT_ADDRESS, + entrypoint: "get_block", + calldata: ["0x" + (blockHeight - 1).toString(16)], + }); + + const chainUpdate = await this.getCanonicalChainUpdateTx( + canonicalRewriteMin, + // it is defined if min is defined + canonicalRewriteMax as number, + isResultEmpty(previousChainBlock) + ); + calls.push(chainUpdate); + } + + return calls; + } }