UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

201 lines (200 loc) 8.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EuclidRollup = void 0; const rollup_js_1 = require("../rollup.cjs"); const contract_1 = require("ethers/contract"); const abi_1 = require("ethers/abi"); const crypto_1 = require("ethers/crypto"); const utils_1 = require("ethers/utils"); const chains_js_1 = require("../chains.cjs"); const EthProver_js_1 = require("../eth/EthProver.cjs"); const utils_js_1 = require("../utils.cjs"); const cached_js_1 = require("../cached.cjs"); const beacon_js_1 = require("../beacon.cjs"); const fzstd_1 = require("fzstd"); // https://github.com/scroll-tech/go-ethereum/tree/24757865c6bbd9becb0256e97e8492d1f73987d9 // https://github.com/scroll-tech/scroll-contracts/blob/8e6a02b120d3a997f7c8e948b62bfb0e5b3ac185/src/L1/rollup/IScrollChain.sol const ROLLUP_ABI = new abi_1.Interface([ `function lastFinalizedBatchIndex() view returns (uint256)`, `function finalizedStateRoots(uint256 batchIndex) view returns (bytes32)`, `event CommitBatch( uint256 indexed batchIndex, bytes32 indexed batchHash )`, `event FinalizeBatch( uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot )`, `function commitBatches( uint8 version, bytes32 parentBatchHash, bytes32 lastBatchHash, )`, `function commitAndFinalizeBatch( uint8 version, bytes32 parentBatchHash, ( bytes batchHeader, uint256 totalL1MessagesPoppedOverall, bytes32 postStateRoot, bytes32 withdrawRoot, bytes zkProof ) finalizeStruct )`, ]); const BATCH_VERSION = 7; class EuclidRollup extends rollup_js_1.AbstractRollup { beaconAPI; // https://etherscan.io/address/0xa13BAF47339d63B743e7Da8741db5456DAc1E556 static mainnetConfig = { chain1: chains_js_1.CHAINS.MAINNET, chain2: chains_js_1.CHAINS.SCROLL, ScrollChain: '0xa13BAF47339d63B743e7Da8741db5456DAc1E556', }; // https://sepolia.etherscan.io/address/0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0 static sepoliaConfig = { chain1: chains_js_1.CHAINS.SEPOLIA, chain2: chains_js_1.CHAINS.SCROLL_SEPOLIA, ScrollChain: '0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0', }; ScrollChain; beaconConfig = new cached_js_1.CachedValue(async () => { const [genesis, spec] = await Promise.all([ `${this.beaconAPI}/eth/v1/beacon/genesis`, `${this.beaconAPI}/eth/v1/config/spec`, ].map(beacon_js_1.fetchBeaconData)); return { genesisTime: BigInt(genesis.genesis_time), secondsPerSlot: BigInt(spec.SECONDS_PER_SLOT), }; }, Infinity); constructor(providers, config, beaconAPI) { super(providers); this.beaconAPI = beaconAPI; this.ScrollChain = new contract_1.Contract(config.ScrollChain, ROLLUP_ABI, this.provider1); } async fetchLatestCommitIndex() { return this.ScrollChain.lastFinalizedBatchIndex({ blockTag: this.latestBlockTag, }); } async _fetchParentCommitIndex(commit) { return this.ScrollChain.lastFinalizedBatchIndex({ blockTag: commit.l1BlockNumber - 1, }); } async _fetchCommit(index) { // const [commitEvent] = await this.ScrollChain.queryFilter( // this.ScrollChain.filters.CommitBatch(index) // ); // const [finalEvent] = await this.ScrollChain.queryFilter( // this.ScrollChain.filters.FinalizeBatch(index) // ); const [[commitEvent], [finalEvent]] = await Promise.all([ this.ScrollChain.queryFilter(this.ScrollChain.filters.CommitBatch(index)), this.ScrollChain.queryFilter(this.ScrollChain.filters.FinalizeBatch(index)), ]); if (!commitEvent) throw new Error(`unknown batch`); if (!(finalEvent instanceof contract_1.EventLog)) throw new Error('not finalized'); const tx = await commitEvent.getTransaction(); const desc = this.ScrollChain.interface.parseTransaction(tx); if (!desc) throw new Error(`expected commit tx: ${tx.hash}`); switch (desc.name) { case 'commitBatches': case 'commitAndFinalizeBatch': break; default: throw new Error(`unsupported commit tx: ${desc.name}`); } if (desc.args.version != BATCH_VERSION) { throw new Error(`unexpected version: ${desc.args.version}`); } if (!tx.blobVersionedHashes || !tx.blobVersionedHashes.length) { throw new Error(`expected blobs`); } const [config, block] = await Promise.all([ this.beaconConfig.get(), (0, utils_js_1.fetchBlock)(this.provider1, tx.blockNumber), ]); const sidecars = await (0, beacon_js_1.fetchSidecars)(this.beaconAPI, (BigInt(block.timestamp) - config.genesisTime) / config.secondsPerSlot); let batchIndex = finalEvent.args.batchIndex - BigInt(tx.blobVersionedHashes.length - 1); let batchHash = desc.args.parentBatchHash; let sidecar; for (const bvh of tx.blobVersionedHashes) { sidecar = sidecars[bvh]; if (!sidecar) throw new Error(`expected sidecar: ${bvh}`); // https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/codecv7.go#L168 // https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/codecv7_types.go#L125 batchHash = (0, crypto_1.keccak256)((0, utils_1.concat)([ (0, utils_js_1.toPaddedHex)(BATCH_VERSION, 1), (0, utils_js_1.toPaddedHex)(batchIndex++, 8), bvh, batchHash, ])); } if (batchHash !== finalEvent.args.batchHash) { //desc.args.lastBatchHash) { throw new Error(`invalid batchHash chain: ${batchHash}`); } const prover = new EthProver_js_1.EthProver(this.provider2, lastBlockFromBlobV7(sidecar.blob)); return { index, prover, l1BlockNumber: finalEvent.blockNumber }; } encodeWitness(commit, proofSeq) { return utils_js_1.ABI_CODER.encode(['(uint256, bytes[], bytes)'], [[commit.index, proofSeq.proofs, proofSeq.order]]); } windowFromSec(sec) { // finalization time is not on-chain // https://etherscan.io/advanced-filter?eladd=0xa13baf47339d63b743e7da8741db5456dac1e556&eltpc=0x26ba82f907317eedc97d0cbef23de76a43dd6edb563bdb6e9407645b950a7a2d const span = 20; // every 10-20 batches const freq = 3600; // every hour? return span * Math.ceil(sec / freq); // units of batchIndex } } exports.EuclidRollup = EuclidRollup; function makeBlobCanonical(blob) { // https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/da.go#L469 const N = 4096; const v = new Uint8Array(N * 31); for (let i = 0; i < N; i++) { const offset = 4 + (i << 6); v.set((0, utils_1.getBytes)('0x' + blob.slice(offset, offset + 62)), i * 31); } return v; } function lastBlockFromBlobV7(blob) { //https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/codecv7.go#L176 let v = makeBlobCanonical(blob); if (v[0] != BATCH_VERSION) { throw new Error(`unexpected version: ${v[0]}`); } const compressed = v[4]; if (compressed != 0 && compressed != 1) { throw new Error(`unexpected compression: ${v[4]}`); } const size = (v[1] << 16) | (v[2] << 8) | v[3]; // uint24 if (compressed) { v = v.slice(1, 5 + size); // https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/da.go#L50 v[0] = 0x28; // zstdMagicNumber v[1] = 0xb5; v[2] = 0x2f; v[3] = 0xfd; v = (0, fzstd_1.decompress)(v); } else { v = v.subarray(5, 5 + size); } // https://github.com/scroll-tech/da-codec/blob/344f2d5e33e1930c63cd6a082ef77e27dbe50cea/encoding/codecv7_types.go#L275 if (v.length < 74) throw new Error(`payload too small: ${v.length}`); // blobPayloadV7MinEncodedLength const dv = new DataView(v.buffer, v.byteOffset, v.byteLength); const l2BlockNumber = dv.getBigUint64(64); // blobPayloadV7OffsetInitialL2BlockNumber const numBlocks = dv.getUint16(72); // blobPayloadV7OffsetNumBlocks return l2BlockNumber + BigInt(numBlocks - 1); }