55 lines
3.0 KiB
TypeScript
Executable File
55 lines
3.0 KiB
TypeScript
Executable File
import { Pool } from 'pg';
|
|
import { createMoneroWalletObserver, loadMoneroObserverConfig, moneroWalletFingerprint } from '../server/market/monero-observer.ts';
|
|
import { loadMoneroMtlsTransport, loadMoneroMtlsTransportConfig } from '../server/market/monero-mtls-transport.ts';
|
|
import { postgresConfig } from '../server/market/postgres-config.ts';
|
|
import { runMoneroObserverWorker } from '../server/market/monero-observer-worker.ts';
|
|
|
|
if (process.platform === 'win32') throw new Error('Monero observer worker requires a POSIX deployment with verifiable protected-file permissions');
|
|
if (process.env.WIKIDECK_MONERO_OBSERVER_WORKER !== '1') throw new Error('Set WIKIDECK_MONERO_OBSERVER_WORKER=1 to start the private observer worker');
|
|
|
|
const observerConfig = await loadMoneroObserverConfig();
|
|
const transportConfig = await loadMoneroMtlsTransportConfig();
|
|
if (new URL(observerConfig.url).href !== new URL(transportConfig.url).href) {
|
|
throw new Error('Observer and mTLS transport endpoints do not match');
|
|
}
|
|
const transport = await loadMoneroMtlsTransport(transportConfig);
|
|
const scanner = createMoneroWalletObserver(observerConfig, transport);
|
|
const primaryAddress = await scanner.verifyMainnetWallet();
|
|
const walletFingerprint = await moneroWalletFingerprint(primaryAddress);
|
|
const pool = new Pool(await postgresConfig());
|
|
pool.on('error', () => console.error(JSON.stringify({ component: 'monero-observer', event: 'database-unavailable' })));
|
|
const abort = new AbortController();
|
|
const stop = () => abort.abort();
|
|
process.once('SIGINT', stop);
|
|
process.once('SIGTERM', stop);
|
|
|
|
try {
|
|
await runMoneroObserverWorker(pool, walletFingerprint, scanner, {
|
|
pollIntervalMs: parsePollInterval(process.env.WIKIDECK_MONERO_OBSERVER_POLL_MS),
|
|
signal: abort.signal,
|
|
onBatch(result) {
|
|
console.log(JSON.stringify({ component: 'monero-observer', event: 'scan-complete',
|
|
scanHeight: result.scanHeight, walletHeight: result.walletHeight, caughtUp: result.caughtUp,
|
|
pages: result.pages.length, observed: result.pages.reduce((sum, page) => sum + page.observed, 0),
|
|
changed: result.pages.reduce((sum, page) => sum + page.changed, 0),
|
|
reappeared: result.pages.reduce((sum, page) => sum + page.reappeared, 0),
|
|
notSeen: result.pages.reduce((sum, page) => sum + page.notSeen, 0) }));
|
|
},
|
|
onFailure() { console.error(JSON.stringify({ component: 'monero-observer', event: 'scan-failed', action: 'retrying' })); },
|
|
});
|
|
} finally {
|
|
process.off('SIGINT', stop);
|
|
process.off('SIGTERM', stop);
|
|
await pool.end();
|
|
}
|
|
|
|
function parsePollInterval(value: string | undefined): number {
|
|
if (value === undefined) return 30_000;
|
|
if (!/^[1-9]\d{0,5}$/.test(value)) throw new Error('Invalid Monero observer polling interval');
|
|
const interval = Number(value);
|
|
if (!Number.isSafeInteger(interval) || interval < 1_000 || interval > 15 * 60_000) {
|
|
throw new Error('Invalid Monero observer polling interval');
|
|
}
|
|
return interval;
|
|
}
|