UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

296 lines (295 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BoLDRollup = exports.ROLLUP_ABI = exports.MACHINE_STATUS_FINISHED = exports.ASSERTION_STATUS_CONFIRMED = void 0; const ArbitrumRollup_js_1 = require("./ArbitrumRollup.cjs"); const chains_js_1 = require("../chains.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 EthProver_js_1 = require("../eth/EthProver.cjs"); const utils_js_1 = require("../utils.cjs"); const rlp_js_1 = require("../rlp.cjs"); const cached_js_1 = require("../cached.cjs"); // https://docs.arbitrum.io/how-arbitrum-works/bold/gentle-introduction // https://github.com/OffchainLabs/bold // https://github.com/OffchainLabs/nitro-contracts/blob/94999b3e2d3b4b7f8e771cc458b9eb229620dd8f/src/rollup/Assertion.sol exports.ASSERTION_STATUS_CONFIRMED = 2n; // https://github.com/OffchainLabs/nitro-contracts/blob/94999b3e2d3b4b7f8e771cc458b9eb229620dd8f/src/state/Machine.sol exports.MACHINE_STATUS_FINISHED = 1n; exports.ROLLUP_ABI = new abi_1.Interface([ `function latestConfirmed() view returns (bytes32)`, `function confirmPeriodBlocks() view returns (uint256)`, `function getAssertion(bytes32) view returns (( uint64 firstChildBlock, uint64 secondChildBlock, uint64 createdAtBlock, bool isFirstChild, uint8 status, bytes32 configHash ))`, `event AssertionConfirmed( bytes32 indexed assertionHash, bytes32 blockHash, bytes32 sendRoot )`, `event AssertionCreated( bytes32 indexed assertionHash, bytes32 indexed parentAssertionHash, ( ( bytes32 prevPrevAssertionHash, bytes32 sequencerBatchAcc, ( bytes32 wasmModuleRoot, uint256 requiredStake, address challengeManager, uint64 confirmPeriodBlocks, uint64 nextInboxPosition ) configData ) beforeStateData, ( ( bytes32[2] bytes32Vals, uint64[2] u64Vals ) globalState, uint8 machineStatus, bytes32 endHistoryRoot ) beforeState, ( ( bytes32[2] bytes32Vals, uint64[2] u64Vals ) globalState, uint8 machineStatus, bytes32 endHistoryRoot ) afterState ) assertion, bytes32 afterInboxBatchAcc, uint256 inboxMaxCount, bytes32 wasmModuleRoot, uint256 requiredStake, address challengeManager, uint64 confirmPeriodBlocks )`, ]); function knownFromCreatedEvent(event) { return { createdAtBlock: BigInt(event.blockNumber), assertionHash: event.args.assertionHash, parentAssertionHash: event.args.parentAssertionHash, afterState: event.args.assertion.afterState, afterInboxBatchAcc: event.args.afterInboxBatchAcc, confirmed: false, children: 0, }; } function isValidAssertionChain(chain) { return (chain.length >= 2 && chain[0].confirmed && chain[chain.length - 1].afterState.machineStatus == exports.MACHINE_STATUS_FINISHED && chain.slice(0, -1).every((x) => x.children < 2)); } class BoLDRollup extends ArbitrumRollup_js_1.AbstractArbitrumRollup { // TODO: get docs link once arbitrum updates their website static arb1MainnetConfig = { chain1: chains_js_1.CHAINS.MAINNET, chain2: chains_js_1.CHAINS.ARB1, Rollup: '0x4DCeB440657f21083db8aDd07665f8ddBe1DCfc0', isBoLD: true, }; static arb1SepoliaConfig = { chain1: chains_js_1.CHAINS.SEPOLIA, chain2: chains_js_1.CHAINS.ARB1_SEPOLIA, Rollup: '0x042B2E6C5E99d4c521bd49beeD5E99651D9B0Cf4', isBoLD: true, }; static arbNovaMainnetConfig = { chain1: chains_js_1.CHAINS.MAINNET, chain2: chains_js_1.CHAINS.ARB_NOVA, Rollup: '0xE7E8cCC7c381809BDC4b213CE44016300707B7Bd', isBoLD: true, }; constructor(providers, config, minAgeBlocks = 0) { super(providers, true, config, exports.ROLLUP_ABI, minAgeBlocks); } async _fetchNode(assertionHash) { return this.Rollup.getAssertion(assertionHash); } _lastBlock = -1n; _assertionMap = new Map(); unfinalizedGuard = new cached_js_1.CachedValue(async () => { const latest = await (0, utils_js_1.fetchBlockNumber)(this.provider1, this.latestBlockTag); const block1 = latest - BigInt(this.minAgeBlocks); let block0 = this._lastBlock + 1n; if (!block0) { const assertionHash = await this.Rollup.latestConfirmed({ blockTag: block1, }); const node = await this._fetchNode(assertionHash); if (!node.status) { throw new Error(`expected latest assertion: ${assertionHash}`); } block0 = node.createdAtBlock; } await this._syncAssertions(block0, block1); this._lastBlock = block1; }, 60000); async _syncAssertions(block, block1) { const topics = [ [ this.Rollup.filters.AssertionCreated().fragment.topicHash, this.Rollup.filters.AssertionConfirmed().fragment.topicHash, ], ]; // this is processed by increasing time (insertion order for Map) const inclusiveStep = BigInt(this.getLogsStepSize - 1); while (block <= block1) { let next = block + inclusiveStep; if (next > block1) next = block1; const events = await this.Rollup.queryFilter(topics, block, next); for (const e of events) { if (e instanceof contract_1.EventLog) { if (e.eventName == 'AssertionCreated') { const known = knownFromCreatedEvent(e); const parent = this._assertionMap.get(known.parentAssertionHash); if (parent) parent.children++; this._assertionMap.set(known.assertionHash, known); } else { const known = this._assertionMap.get(e.args.assertionHash); if (known) { known.confirmed = true; // we only need to keep 1 confirmed assertion for (const key of this._assertionMap.keys()) { if (key == known.assertionHash) break; this._assertionMap.delete(key); } } } } } block = next + 1n; } } _latestAssertions(fn) { return Array.from(this._assertionMap.values()) .filter((x) => fn(x.createdAtBlock)) .reverse(); // ordered by descending createdAtBlock } async _unfinalizedAssertionChain(assertionHash) { await this.unfinalizedGuard.get(); const chain = []; for (;;) { const known = this._assertionMap.get(assertionHash); if (!known) break; // broken chain chain.push(known); if (known.confirmed) break; // complete chain assertionHash = known.parentAssertionHash; } // returns sequence of assertions from oldest (confirmed?) to newest // NOTE: may not be a valid chain return chain.reverse(); } async fetchLatestCommitIndex() { let blockTag = this.latestBlockTag; if (this.minAgeBlocks) { await this.unfinalizedGuard.get(); const block = this._lastBlock; for (const known of this._latestAssertions((x) => x <= block)) { const chain = await this._unfinalizedAssertionChain(known.assertionHash); if (isValidAssertionChain(chain)) { return known.createdAtBlock; } } blockTag = block; } const assertionHash = await this.Rollup.latestConfirmed({ blockTag, }); const node = await this._fetchNode(assertionHash); if (!node.status) throw new Error(`expected latest assertion`); return node.createdAtBlock; } async _fetchParentCommitIndex(commit) { const node = await this._fetchNode(commit.assertions[commit.assertions.length - 2]); return node.status ? node.createdAtBlock : -1n; } async _findAssertionChainAtIndex(index) { if (this.minAgeBlocks) { await this.unfinalizedGuard.get(); for (const known of this._latestAssertions((x) => x == index)) { const chain = await this._unfinalizedAssertionChain(known.assertionHash); if (isValidAssertionChain(chain)) return chain; } } const events = await this.Rollup.queryFilter(this.Rollup.filters.AssertionCreated(), index, index); // most likely there aren't 2+ assertions in 1 block... for (let i = events.length - 1; i >= 0; i--) { const event = events[i]; if (event instanceof contract_1.EventLog && event.args.assertion.afterState.machineStatus == exports.MACHINE_STATUS_FINISHED) { const [node, [parentEvent]] = await Promise.all([ this._fetchNode(event.args.assertionHash), this.Rollup.queryFilter(this.Rollup.filters.AssertionCreated(event.args.parentAssertionHash)), ]); if (node.status == exports.ASSERTION_STATUS_CONFIRMED && parentEvent instanceof contract_1.EventLog) { const parent = knownFromCreatedEvent(parentEvent); parent.confirmed = true; // by construction const child = knownFromCreatedEvent(event); child.confirmed = true; // by status assertion // isValidAssertionChain() is true by construction // parent.children = 1; // not needed return [parent, child]; } } } throw new Error('invalid assertion chain'); } async _fetchCommit(index) { const chain = await this._findAssertionChainAtIndex(index); const { afterState, confirmed } = chain[chain.length - 1]; const blockHash = afterState.globalState[0][0]; const block = await (0, utils_js_1.fetchBlockFromHash)(this.provider2, blockHash); const encodedRollupProof = utils_js_1.ABI_CODER.encode(['(bytes32, bytes, ((bytes32[2], uint64[2]), uint8, bytes32), bytes)'], [ [ chain[0].assertionHash, encodeAssertionChain(chain.slice(1)), afterState, (0, rlp_js_1.encodeRlpBlock)(block), ], ]); return { index, prover: new EthProver_js_1.EthProver(this.provider2, block.number), confirmed, assertions: chain.map((x) => x.assertionHash), encodedRollupProof, }; } async isCommitStillValid(commit) { if (commit.confirmed) return true; // one of the links may have been challenged const chain = await this._findAssertionChainAtIndex(commit.index); return chain.every((x, i) => commit.assertions[i] == x.assertionHash); } } exports.BoLDRollup = BoLDRollup; function encodeAssertionChain(chain) { // starting from hash[0] (confirmed), we need to construct: // hash[n] = keccak(encode(hash[n-1], keccak256(abi.encode(p.afterState)), p.inboxAcc) return (0, utils_1.concat)(chain.flatMap((known) => [ (0, crypto_1.keccak256)(utils_js_1.ABI_CODER.encode(['((bytes32[2], uint64[2]), uint8, bytes32)'], [known.afterState])), known.afterInboxBatchAcc, ])); }