Implemented and validated the separate Monero observation-worker path

This commit is contained in:
2026-10-22 09:38:20 -04:00
parent 12f80fdc53
commit 158981e965
12 changed files with 253 additions and 12 deletions
+46
View File
@@ -18,6 +18,7 @@ import { migrateCustody, grantCustodyRuntime, grantMoneroObserverRuntime, CUSTOD
grantMoneroDepositCreditRuntime, CUSTODY_MIGRATION_V2, CUSTODY_SCHEMA_V2_DIGEST } from '../../server/market/postgres-schema.ts';
import { persistMoneroObservationScan } from '../../server/market/monero-observation-store.ts';
import { moneroWalletFingerprint } from '../../server/market/monero-observer.ts';
import { runMoneroObserverBatch, runMoneroObserverWorker } from '../../server/market/monero-observer-worker.ts';
import { depositCreditApprovalMessage, depositCreditReleaseApprovalMessage, depositReorgResolutionMessage,
postReviewedDepositPendingCredit, prepareDepositCreditReleaseReview, prepareDepositCreditReview, prepareDepositReorgResolution,
reconcileDepositCreditReorgHolds, releaseReviewedDepositCredit, resolveReleasedDepositReorgHold,
@@ -413,6 +414,51 @@ test('Monero scan persistence is idempotent, reorg-visible and never posts custo
} finally { await custodyRuntime.end(); await runtime.end(); }
});
test('Monero observer worker catches up through bounded durable windows, resumes, and admits one leader', async t => {
const f = await fixture(t);
await f.admin.query("DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname='monero_worker_test') THEN CREATE ROLE monero_worker_test LOGIN; END IF; END $$");
await grantMoneroObserverRuntime(f.admin, 'monero_worker_test');
const workerPool = new Pool({ ...config, user: 'monero_worker_test' });
const secondWorkerPool = new Pool({ ...config, user: 'monero_worker_test' });
const wallet = '7'.repeat(64), walletHeight = 250_005, now = Date.now();
const requested: number[] = [];
const scanner = {
async scanIncoming(fromHeight: number, maxBlocks = 100_000) {
requested.push(fromHeight);
const throughHeight = Math.min(walletHeight, fromHeight + maxBlocks - 1);
return { network: 'mainnet' as const, walletHeight, fromHeight, throughHeight,
primaryAddress: ADDITIONAL_MARKET_FEE.address, incoming: [] };
},
};
try {
const failedScanner = { scanIncoming: async () => { throw new Error('fixture RPC failure'); } };
await assert.rejects(runMoneroObserverBatch(workerPool, wallet, failedScanner));
assert.equal((await f.admin.query('SELECT count(*)::int AS n FROM wk_custody.monero_observer_state WHERE wallet_fingerprint=$1',
[wallet])).rows[0].n, 0, 'failed RPC must not create or advance durable cursor state');
const batch = await runMoneroObserverBatch(workerPool, wallet, scanner, { now: () => now });
assert.equal(batch.pages.length, 3, 'catch-up is split into a bounded number of pages');
assert.equal(batch.caughtUp, true);
assert.equal(batch.scanHeight, walletHeight);
assert.deepEqual(requested, [0, 98_999, 197_998]);
const restarted = await runMoneroObserverBatch(secondWorkerPool, wallet, scanner, { now: () => now + 1, maxPages: 1 });
assert.equal(restarted.pages.length, 1);
assert.equal(restarted.scanHeight, walletHeight);
assert.equal(requested.at(-1), walletHeight - 1_000, 'restart resumes at a bounded reorg overlap');
const controller = new AbortController();
let signalReady!: () => void;
const batchObserved = new Promise<void>(resolve => { signalReady = resolve; });
const firstWorker = runMoneroObserverWorker(workerPool, wallet, scanner, { pollIntervalMs: 60_000,
signal: controller.signal, onBatch: () => signalReady() });
await batchObserved;
await assert.rejects(runMoneroObserverWorker(secondWorkerPool, wallet, scanner, { pollIntervalMs: 60_000 }), /already holds/);
controller.abort(); await firstWorker;
assert.equal((await f.admin.query('SELECT count(*)::int AS n FROM wk_custody.journals')).rows[0].n, 0,
'observer worker only persists chain observations and never posts money');
} finally { await secondWorkerPool.end(); await workerPool.end(); }
});
test('reviewed Monero outputs post once to pending liability and reorgs create holds without reversal', async t => {
const f = await fixture(t);
await f.admin.query(`DO $$ BEGIN