"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Publisher = void 0; const interfaces = __importStar(require("../lib/utils/interfaces")); const rover_1 = require("../lib/utils/rover"); const batchBuilder_1 = require("../lib/utils/batchBuilder"); class Publisher { constructor(config, signer) { this.proofsStarted = 0; this.proofsCompleted = 0; this.initialize = async () => { await this.prover.initialize(); await this.batchBuilder.initialize(); this.provingQueue = this.batchBuilder.initBatchCircuitInputs(); // Start watching for events this.prover.momiji.contract.on('TransactionSent', async (res) => { let tx = this.prover.resultToTransaction(res); console.log(`🟢 Transaction enqueued: ${tx.tx_id}.`); // This proves 1 tx and updates prover.status this.queueToProve(tx); }); this.prover.momiji.contract.on('BatchPublish', async (event) => { // Someone else published (or we did? - check) //console.log(`🎯 Batch published: ${event}.`); // reset proving queue and mempool.queue this.resetCounters(); this.batchBuilder.status = { txs_hash: "", tx_ids: [], proof: "", aggregation_object: [] }; await this.batchBuilder.fetchQueue(); await this.batchBuilder.refreshState(); this.provingQueue = this.batchBuilder.initBatchCircuitInputs(); this.printRoot(); this.publishTimeout = setInterval(this.publish, Number(process.env.TIMEOUT)); // every minute, publish (if there's a batch to publish) return; }); console.log(`⌚ Event watcher started.`); this.printRoot(); console.log(`🟣 Welcome to the Offshift Prover Network.`); this.signer && console.log(`🔑 Publisher Address: ${await this.signer.getAddress()}.`); const _thisQueue = await this.prover.fetchQueue(); this.publishTimeout = setInterval(this.publish, Number(process.env.TIMEOUT)); // every minute, publish (if there's a batch to publish) // for each item in queue, add it to the proving queue if (_thisQueue.length > 0) { console.log(`📃 Fetched ${_thisQueue.length} unpublished transactions from queue.`); for (let i = 0; i < _thisQueue.length; i++) { this.queueToProve(_thisQueue[i]); // queue every queued tx for proving } } }; this.printRoot = () => console.log(`🌳 Current State Root: ${this.prover.stateRoot.value}`); // compare this.batchBuilder.status to the state contract and see if the last non-zero tx_id matches this.stillProving = () => this.proofsCompleted < this.proofsStarted; this.queueToProve = async (tx) => { this.proofsStarted++; this.provingQueue = this.provingQueue.then(async (data) => { return this.prove(data, tx); }).catch(async (e) => { console.log(e); console.log(`❌ An error occured! Batch proofs reset.`); // TODO: Gracefully restart proving with the queue when this happens return await this.batchBuilder.initBatchCircuitInputs(); // reset the batch }); }; this.prove = async (d, tx) => { return new Promise(async (res, rej) => { // check to make sure a batch wasn't published during the time, and kick out if it was if (await this.prover.momiji.contract.merkleRoot() != this.prover.stateRoot.value) { // make batchBuilder dump the queue and start over this.batchBuilder.clearQueue(); this.provingQueue = this.batchBuilder.initBatchCircuitInputs(); await this.provingQueue; this.resetCounters(); return res(this.provingQueue); } console.log(`⏳ Proving ${tx.tx_id}.`); const proof = await this.batchBuilder.addProvenTransaction(tx, d); console.log(`✔️ Proved ${tx.tx_id}.`); this.proofsCompleted++; return res(proof); }); }; this.publish = async () => { if (this.stillProving()) { //console.log(`⚠️ Still proving...`) return; // If we're still proving something, don't publish right now } if (this.batchBuilder.status.proof === "") { //console.log(`⚠️ No batch to publish...`) return; } if (this.proofsCompleted == 0) return; // nothing to submit clearInterval(this.publishTimeout); console.log(`🗞️ Publishing batch...`); // Publish to chain const merkleRoot = await this.prover.momiji.contract.merkleRoot(); const filter = this.prover.momiji.contract.filters.TransactionSent(null, merkleRoot, null); const txEvents = await this.prover.momiji.contract.queryFilter(filter); const txContractInputs = txEvents.map(e => this.prover.resultToTransaction(this.prover.toResult(e))); let finals = await this.batchBuilder.createFinalPublisherInputs({ ...(await this.provingQueue), proof: this.batchBuilder.status.proof, aggregation_object: this.batchBuilder.status.aggregation_object, transactions: txContractInputs }); const _contract = new interfaces.Contract(this.prover.config.stateContract.address, JSON.stringify(this.prover.config.stateContract.abi), this.signer); await _contract.publish(finals.proof, finals.aggregation_object, finals.batch_calldata) .then((tx) => console.log(`📡 Batch published: ${tx.hash}.`), (error) => console.log(error.message)); return; }; this.resetCounters = () => { // reset proof counters this.proofsStarted = 0; this.proofsCompleted = 0; }; this.publishTimeout = {}; this.provingQueue = {}; this.prover = new rover_1.Prover(config); this.batchBuilder = new batchBuilder_1.BatchBuilder(config); this.signer = signer; if (!this.signer) throw new Error("momiji.constructor: Signer is undefined, but is required"); const address = config.stateContract.address; if (!address) throw new Error("momiji.constructor: State Contract address is not set, but is required."); } } exports.Publisher = Publisher;