@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
204 lines (203 loc) • 8.43 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PolygonPoSRollup = void 0;
const rollup_js_1 = require("../rollup.cjs");
const types_js_1 = require("./types.cjs");
const constants_1 = require("ethers/constants");
const contract_1 = require("ethers/contract");
const hash_1 = require("ethers/hash");
const chains_js_1 = require("../chains.cjs");
const EthProver_js_1 = require("../eth/EthProver.cjs");
const utils_js_1 = require("../utils.cjs");
const rlp_js_1 = require("../rlp.cjs");
class PolygonPoSRollup extends rollup_js_1.AbstractRollup {
// https://docs.polygon.technology/pos/reference/contracts/genesis-contracts/
static mainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.POLYGON_POS,
RootChain: '0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287',
apiURL: 'https://proof-generator.polygon.technology/api/v1/matic/',
poster: {
// https://polygonscan.com/tx/0x092f9929973fee6a4fa101e9ed45c2b6ce072ac6e2f338f49cac70b41cacbc73
address: '0x591663413423Dcf7c7806930E642951E0dDdf10B',
blockNumberStart: 61150865n,
topicHash: (0, hash_1.id)('NewRoot(bytes32)'),
},
};
apiURL;
RootChain;
poster;
constructor(providers, config) {
super(providers);
this.apiURL = config.apiURL;
this.poster = config.poster;
this.RootChain = new contract_1.Contract(config.RootChain, types_js_1.ROOT_CHAIN_ABI, this.provider1);
}
async findPosterEventBefore(l2BlockNumber) {
// find the most recent post from poster
// stop searching when earlier than poster deployment
// (otherwise we scan back to genesis)
const step = BigInt(this.getLogsStepSize);
for (let i = l2BlockNumber; i > this.poster.blockNumberStart; i -= step) {
const logs = await this.provider2.getLogs({
address: this.poster.address,
topics: [this.poster.topicHash],
fromBlock: i < step ? 0n : i - step,
toBlock: i - 1n,
});
if (logs.length)
return logs[logs.length - 1];
}
throw new Error(`no earlier root: ${l2BlockNumber}`);
}
async findPosterHeaderBefore(l2BlockNumber) {
// find the most recent post that occurred before this block
const event = await this.findPosterEventBefore(l2BlockNumber);
// find the header that contained this transaction
// 20240830: we want the header for the transaction
// not the header containing the logged block hash
return this.fetchAPIFindHeader(BigInt(event.blockNumber));
}
async fetchJSON(url) {
const res = await fetch(url);
if (!res.ok)
throw new Error(`${res.url}: HTTP(${res.status})`);
return res.json();
}
async fetchAPIFindHeader(l2BlockNumber) {
const url = new URL(`./block-included/${l2BlockNumber}`, this.apiURL);
const json = await this.fetchJSON(url);
if (json.error)
throw new Error(`Block(${l2BlockNumber}): ${json.message}`);
const number = BigInt(json.headerBlockNumber);
const l2BlockNumberStart = BigInt(json.start);
const l2BlockNumberEnd = BigInt(json.end);
const rootHash = json.root;
return {
number,
l2BlockNumberStart,
l2BlockNumberEnd,
rootHash,
};
}
// async fetchAPIHeaderProof(
// l2BlockNumber: bigint,
// l2BlockNumberStart: bigint,
// l2BlockNumberEnd: bigint
// ) {
// const url = new URL(`./fast-merkle-proof`, this.apiURL);
// url.searchParams.set('start', l2BlockNumberStart.toString());
// url.searchParams.set('end', l2BlockNumberEnd.toString());
// url.searchParams.set('number', l2BlockNumber.toString());
// const json = await this.fetchJSON(url);
// const v = ethers.getBytes(json.proof);
// if (!v.length || v.length & 31) throw new Error('expected bytes32xN');
// return Array.from({ length: v.length >> 5 }, (_, i) =>
// v.subarray(i << 5, (i + 1) << 5)
// );
// }
async fetchAPIReceiptProof(txHash) {
const url = new URL(`./exit-payload/${txHash}?eventSignature=${this.poster.topicHash}`, this.apiURL);
const json = await this.fetchJSON(url);
if (json.error)
throw new Error(`receipt proof: ${json.message}`);
return json.result;
}
async fetchLatestCommitIndex() {
// find the end range of the last header
const l2BlockNumberEnd = await this.RootChain.getLastChildBlock({
blockTag: this.latestBlockTag,
});
// find the header before the end of the last header with a post
const header = await this.findPosterHeaderBefore(l2BlockNumberEnd + 1n);
return header.number;
}
async _fetchParentCommitIndex(commit) {
const header = await this.findPosterHeaderBefore(commit.l2BlockNumberStart);
return header.number;
}
async _fetchCommit(index) {
// ensure checkpoint was finalized
const { rootHash, l2BlockNumberStart, l2BlockNumberEnd } = await this.RootChain.headerBlocks(index);
if (rootHash === constants_1.ZeroHash) {
throw new Error(`null checkpoint hash`);
}
// ensure checkpoint contains post
const events = await this.provider2.getLogs({
address: this.poster.address,
topics: [this.poster.topicHash],
fromBlock: l2BlockNumberStart,
toBlock: l2BlockNumberEnd,
});
if (!events.length)
throw new Error('no poster');
const event = events[events.length - 1];
const prevBlockHash = event.topics[1];
// rlpEncodedProof:
// 1. checkpoint index
// 2. fast-merkle-proof => block in checkpoint
// 3. receipt merkle patricia proof => tx in block
// 4. receipt data: topic[1] w/prevBlockHash + logIndex
// rlpEncodedBlock:
// 5. hash() = prevBlockHash
// 6. usable stateRoot!
const [rlpEncodedProof, prevBlock] = await Promise.all([
this.fetchAPIReceiptProof(event.transactionHash),
(0, utils_js_1.fetchBlockFromHash)(this.provider2, prevBlockHash),
]);
const rlpEncodedBlock = (0, rlp_js_1.encodeRlpBlock)(prevBlock);
// if (ethers.keccak256(rlpEncodedBlock) !== prevBlockHash) {
// throw new Error('block hash mismatch`);
// }
const prover = new EthProver_js_1.EthProver(this.provider2, prevBlock.number);
return {
index,
prover,
rootHash,
l2BlockNumberStart,
l2BlockNumberEnd,
rlpEncodedProof,
rlpEncodedBlock,
};
}
encodeWitness(commit, proofSeq) {
return utils_js_1.ABI_CODER.encode(['(bytes, bytes, bytes[], bytes)'], [
[
commit.rlpEncodedProof,
commit.rlpEncodedBlock,
proofSeq.proofs,
proofSeq.order,
],
]);
}
windowFromSec(sec) {
// finalization time is on-chain
return sec;
}
// experimental idea: commit serialization
JSONFromCommit(commit) {
return {
index: (0, utils_js_1.toUnpaddedHex)(commit.index),
l2BlockNumber: commit.prover.block,
l2BlockNumberStart: (0, utils_js_1.toUnpaddedHex)(commit.l2BlockNumberStart),
l2BlockNumberEnd: (0, utils_js_1.toUnpaddedHex)(commit.l2BlockNumberEnd),
rlpEncodedBlock: commit.rlpEncodedBlock,
rlpEncodedProof: commit.rlpEncodedProof,
rootHash: commit.rootHash,
};
}
commitFromJSON(json) {
const commit = {
index: BigInt(json.index),
prover: new EthProver_js_1.EthProver(this.provider2, json.l2BlockNumber),
l2BlockNumberStart: BigInt(json.l2BlockNumberStart),
l2BlockNumberEnd: BigInt(json.l2BlockNumberEnd),
rlpEncodedProof: json.rlpEncodedProof,
rlpEncodedBlock: json.rlpEncodedBlock,
rootHash: json.rootHash,
};
this.configure?.(commit);
return commit;
}
}
exports.PolygonPoSRollup = PolygonPoSRollup;