UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

157 lines (156 loc) 6.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LineaProver = void 0; const vm_js_1 = require("../vm.cjs"); const constants_1 = require("ethers/constants"); const utils_js_1 = require("../utils.cjs"); const types_js_1 = require("./types.cjs"); const BLOCK_MISSING_IN_CHAIN = -32600; const UNKNOWN_RPC_ERROR = -32603; class LineaProver extends vm_js_1.BlockProver { static isInclusionProof = types_js_1.isInclusionProof; static isContract = types_js_1.isContract; static encodeProof = types_js_1.encodeProof; static latest = this._createLatest(); stateRoot; async fetchStateRoot() { if (!this.stateRoot) throw new Error(`unknown stateRoot`); return this.stateRoot; } async isShomeiReady() { // see: LineaRollup.fetchLatestCommitIndex() try { await this.getProofs(constants_1.ZeroAddress); return true; } catch (cause) { if ((0, utils_js_1.isRPCError)(cause, BLOCK_MISSING_IN_CHAIN, UNKNOWN_RPC_ERROR)) return false; throw new Error(`isShomeiReady()`, { cause }); } } async isContract(target) { if (this.fast) { return this.cache.get(target, async () => { const code = await this.provider.getCode(target, this.block); return code.length > 2; }); } const { accountProof } = await this.getProofs(target); return (0, types_js_1.isContract)(accountProof); } async getStorage(target, slot, fast = this.fast) { target = target.toLowerCase(); // check to see if we know this target isn't a contract const accountProof = await this.proofLRU.touch(target); if (accountProof && !(0, types_js_1.isContract)(accountProof)) { return constants_1.ZeroHash; } // check to see if we've already have a proof for this value const storageKey = (0, vm_js_1.makeStorageKey)(target, slot); const storageProof = await this.proofLRU.touch(storageKey); if (storageProof) { return (0, types_js_1.isInclusionProof)(storageProof) ? storageProof.proof.value : constants_1.ZeroHash; } // we didn't have the proof if (fast) { return this.cache.get(storageKey, () => { return (0, utils_js_1.fetchStorage)(this.provider, target, slot, this.block); }); } const proof = await this.getProofs(target, [slot]); return (0, types_js_1.isContract)(proof.accountProof) && (0, types_js_1.isInclusionProof)(proof.storageProofs[0]) ? proof.storageProofs[0].proof.value : constants_1.ZeroHash; } async _proveNeed(need, accountRef, slotRefs) { const m = [...slotRefs]; const accountProof = await this.proofLRU.peek(need.target); if (accountProof && !(0, types_js_1.isContract)(accountProof)) m.length = 0; const proofs = await this.getProofs(need.target, m.map(([slot]) => slot)); accountRef.proof = (0, types_js_1.encodeProof)(proofs.accountProof); if ((0, types_js_1.isContract)(proofs.accountProof)) { m.forEach(([, ref], i) => (ref.proof = (0, types_js_1.encodeProof)(proofs.storageProofs[i]))); } } async getProofs(target, slots = []) { target = target.toLowerCase(); // there are (3) cases: // 1.) account doesn't exist // 2.) account is EOA // 3.) account is contract const missing = []; const { promise, resolve, reject } = (0, utils_js_1.withResolvers)(); // check if we have an account proof let accountProof = this.proofLRU.touch(target); if (!accountProof) { // missing account proof, so block it this.proofLRU.setFuture(target, promise.then(() => accountProof)); } // check if we're missing any slots const storageProofs = slots.map((slot, i) => { const key = (0, vm_js_1.makeStorageKey)(target, slot); const p = this.proofLRU.touch(key); if (!p) { // missing storage proof, so block it this.proofLRU.setFuture(key, promise.then(() => storageProofs[i])); missing.push(i); } return p; }); // check if we need something if (!accountProof || missing.length) { try { const { storageProofs: v, accountProof: a } = await this.fetchProofs(target, missing.map((x) => slots[x])); // update the blocked values accountProof = a; missing.forEach((x, i) => (storageProofs[x] = v[i])); resolve(); } catch (err) { reject(err); throw err; // must throw because accountProof is undefined } } // reassemble const [a, v] = await Promise.all([ accountProof, Promise.all(storageProofs), ]); this.checkStorageProofs((0, types_js_1.isContract)(a), slots, v); return { accountProof: a, storageProofs: v, }; } async fetchProofs(target, slots = []) { const ps = []; for (let i = 0;;) { ps.push( // 20240825: most cloud providers seem to reject batched getProof // since we aren't in control of provider construction (ie. batchMaxSize) // sendImmediate is a temporary hack to avoid this issue // 20241027: use GatewayProvider this.provider.send('linea_getProof', [ target, slots .slice(i, (i += this.proofBatchSize)) .map((slot) => (0, utils_js_1.toPaddedHex)(slot)), this.block, ])); if (i >= slots.length) break; } const vs = await Promise.all(ps); for (let i = 1; i < vs.length; i++) { vs[0].storageProofs.push(...vs[i].storageProofs); } return vs[0]; } } exports.LineaProver = LineaProver;