Files
wikideck/tests/postgres/custody.test.ts
T

1200 lines
91 KiB
TypeScript
Executable File

import test from 'node:test';
import assert from 'node:assert/strict';
import { Pool, type PoolConfig } from 'pg';
import { createHash, generateKeyPairSync, randomUUID, sign } from 'node:crypto';
import { access, chmod, lstat, mkdtemp, rm, writeFile, mkdir, readFile } from 'node:fs/promises';
import { constants } from 'node:fs';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { spawn } from 'node:child_process';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { DatabaseSync } from 'node:sqlite';
import { PostgresCustody, auditPostgres } from '../../server/market/postgres-custody.ts';
import { CustodyIntegrityError } from '../../server/market/custody-integrity.ts';
import { resolveCustodyIncident } from '../../server/market/custody-incident.ts';
import { CustodyMarket } from '../../server/market/custody.ts';
import { migrateCustody, grantCustodyRuntime, grantMoneroObserverRuntime, CUSTODY_SCHEMA_V1, CUSTODY_SCHEMA_V1_DIGEST,
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 { createOrRecoverMoneroDepositSubaddress, registerMoneroDepositSubaddress,
reserveMoneroDepositAllocation } from '../../server/market/monero-deposit-allocation-store.ts';
import { importSqliteCustody } from '../../server/market/custody-import.ts';
import { postgresConfig, custodyStorage } from '../../server/market/postgres-config.ts';
import { openCustodyStorage } from '../../server/market/custody-storage.ts';
import { createBackup, restoreBackup } from '../../server/recovery.ts';
import { createCoordinatedCustodyBackup, verifyCoordinatedCustodyBackup } from '../../server/coordinated-backup.ts';
import { CUSTODY_POLICY, custodyMessage, type CustodyAction, type CustodyCommand, type CustodyActivity,
type CustodyListing, type CustodyOrder, type CustodyWithdrawal } from '../../shared/custody.ts';
import { ADDITIONAL_MARKET_FEE } from '../../shared/market.ts';
import { NETWORK } from '../../shared/network.ts';
import { CHAIN_ID, GENESIS_HASH, address, sha256 } from '../../server/testnet/consensus.ts';
import { saleContext, termsForCustodyOrder, saleCandidateTransactionId, saleId } from '../../server/testnet/sale-candidate.ts';
import { SALE_CANDIDATE, saleTermsMessage, saleCompletionMessage, saleCancellationMessage,
type SaleTransaction } from '../../shared/sale-candidate.ts';
import { replayReservedSale, type SaleRehearsal } from '../../server/market/sale-rehearsal.ts';
import { keccak } from 'hash-wasm';
import type { CanonicalInboxPage } from '../../server/market/canonical-inbox.ts';
import { CanonicalObserver } from '../../server/market/canonical-observer.ts';
import { Ledger } from '../../server/testnet/ledger.ts';
import { RandomX } from '../../server/testnet/randomx.ts';
import { GENESIS_TIME, makeTemplate, miningBlob, mintId, transition } from '../../server/testnet/consensus.ts';
import { deriveStats } from '../../shared/enrichment.ts';
import { blockClaimMessage, type NftStateView, type TestnetBlock } from '../../shared/testnet.ts';
if (process.env.WIKIDECK_PG_DISPOSABLE_TEST !== '1') throw new Error('PostgreSQL tests require the isolated disposable compose fixture');
const config: PoolConfig = { host: '127.0.0.1', database: 'custody_test', user: 'postgres', ssl: false, max: 4,
connectionTimeoutMillis: 5000, statement_timeout: 10000, lock_timeout: 3000 };
function identity() {
const keys = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
const publicKey = keys.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
return { publicKey, actor: address(publicKey), sign: (message: string) => sign('sha256', Buffer.from(message),
{ key: keys.privateKey, dsaEncoding: 'ieee-p1363' }).toString('base64') };
}
const buyer = identity(), seller = identity(), other = identity(), hash = 'a'.repeat(64);
const host = identity();
async function syntheticFeeAddress() {
const prefix = Uint8Array.from([18, ...Array(64).fill(2)]);
const data = Buffer.concat([prefix, Buffer.from((await keccak(prefix, 256)).slice(0, 8), 'hex')]);
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
let encoded = '';
for (let i = 0; i < data.length; i += 8) {
const chunk = data.subarray(i, i + 8); let n = BigInt(`0x${chunk.toString('hex')}`), text = '';
while (n) { text = alphabet[Number(n % 58n)] + text; n /= 58n; }
encoded += text.padStart(chunk.length === 8 ? 11 : 7, '1');
}
return encoded;
}
async function syntheticSubaddress(fill = 3) {
const prefix = Uint8Array.from([42, ...Array(64).fill(fill)]);
const data = Buffer.concat([prefix, Buffer.from((await keccak(prefix, 256)).slice(0, 8), 'hex')]);
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const sizes: Record<number, number> = { 8: 11, 5: 7 };
let encoded = '';
for (let i = 0; i < data.length; i += 8) {
const chunk = data.subarray(i, i + 8); let n = BigInt(`0x${chunk.toString('hex')}`), text = '';
while (n) { text = alphabet[Number(n % 58n)] + text; n /= 58n; }
encoded += text.padStart(sizes[chunk.length], '1');
}
return encoded;
}
function saleTranscript(order: CustodyOrder, custodianId: string, feeBps = 250) {
const policy = { coinbaseMaturity: 2, lockDepth: 2, outcomeDepth: 3, maxLockBlocks: 30, maxTransactions: 64 };
const context = saleContext(policy);
const terms = termsForCustodyOrder(order, context, { custodianId, host: host.actor, feeBps, lockUntilHeight: 12, expiresHeight: 20 });
const authorization = (who: typeof buyer, message: string) => ({ publicKey: who.publicKey, signature: who.sign(message) });
const lock: Extract<SaleTransaction, { type: 'sale-lock' }> = { type: 'sale-lock', terms,
sellerAuthorization: authorization(seller, saleTermsMessage(terms)), buyerAuthorization: authorization(buyer, saleTermsMessage(terms)) };
const input: SaleRehearsal = { policy, seed: { height: 9, mintedHeight: 1, pageId: 1 }, lock, blocks: [[lock]] };
const reservationDigest = replayReservedSale(order, custodianId, input, feeBps).reservationDigest;
const reference = { protocol: SALE_CANDIDATE, chainId: context.chainId, saleId: saleId(terms) } as const;
const completion = { ...reference, reservationDigest };
const complete: SaleTransaction = { type: 'sale-complete', ...completion, hostAuthorization: authorization(host, saleCompletionMessage(completion)) };
const cancel: SaleTransaction = { type: 'sale-cancel', ...reference,
hostAuthorization: authorization(host, saleCancellationMessage(reference)), buyerAuthorization: authorization(buyer, saleCancellationMessage(reference)) };
return { input, complete, cancel, reservationDigest };
}
function canonicalPage(streamId: string, all: CanonicalInboxPage['events'], start: number, count: number): CanonicalInboxPage {
const events = all.slice(start, start + count), cursor = events.at(-1)?.sequence ?? start;
const previousAnchor = start === 0 ? GENESIS_HASH : all[start - 1].eventHash;
const anchor = events.at(-1)?.eventHash ?? previousAnchor;
return { streamId, previousAnchor, anchor, events, cursor, head: all.length, caughtUp: cursor === all.length };
}
function canonicalEvents(streamId: string, linkedTxId = '1'.repeat(64)) {
const rows: CanonicalInboxPage['events'] = [];
let anchor = GENESIS_HASH;
const add = (type: 'connect' | 'disconnect', height: number, hash: string, previousHash: string, transactionIds: string[] = []) => {
const sequence = rows.length + 1;
const eventHash = sha256(JSON.stringify(['wikideck-canonical-event-v1', streamId, sequence, type,
height, hash, previousHash, transactionIds, anchor]));
rows.push({ sequence, type, height, hash, previousHash, transactionIds, eventHash }); anchor = eventHash;
};
const a = 'a'.repeat(64), b = 'b'.repeat(64), c = 'c'.repeat(64);
add('connect', 1, a, GENESIS_HASH, [linkedTxId]);
add('disconnect', 1, a, GENESIS_HASH, [linkedTxId]);
add('connect', 1, b, GENESIS_HASH, [linkedTxId]);
add('connect', 2, c, b, ['3'.repeat(64)]);
return rows;
}
function reanchorCanonicalEvents(streamId: string, rows: CanonicalInboxPage['events']) {
let anchor = GENESIS_HASH;
for (const event of rows) {
event.eventHash = sha256(JSON.stringify(['wikideck-canonical-event-v1', streamId, event.sequence, event.type,
event.height, event.hash, event.previousHash, event.transactionIds, anchor]));
anchor = event.eventHash;
}
}
class FixturePow {
async hash(key: string, blob: Uint8Array): Promise<string> { return sha256(Buffer.concat([Buffer.from(key, 'hex'), Buffer.from(blob)])); }
close(): void {}
}
class EasyFixturePow extends FixturePow {
async hash(_key: string, _blob: Uint8Array): Promise<string> { return '0'.repeat(64); }
}
function fixtureArticle(pageId = 50000) {
return { pageId, title: 'Canonical inbox fixture', extract: `Synthetic local-only canonical observer test fixture ${pageId}.`,
image: 'https://upload.wikimedia.org/wikipedia/commons/test-image.jpg', url: `https://en.wikipedia.org/?curid=${pageId}`,
revisionId: 1, source: 'live' as const, category: 'Culture' };
}
function fixtureStats() {
const pageBytes = 2048, languageCount = 3;
return { capturedAt: '2026-10-19T00:00:00.000Z', revisionId: 1, pageBytes, languageCount,
languagesTruncated: false, pageviews: { status: 'unavailable' as const, months: [], latestMonth: '2026-09',
total: null, dailyAverage: null, trendPercent: null }, stats: deriveStats(pageBytes, languageCount, null) };
}
async function fixtureBlock(ancestors: TestnetBlock[], state: NftStateView, pageId = 50000, proof = new FixturePow()) {
const miner = seller;
const template = makeTemplate(ancestors, state, miner.actor, miner.publicKey, [], fixtureArticle(pageId), fixtureStats(),
GENESIS_TIME + (ancestors.length + 1) * 30);
const header = structuredClone(template.header);
for (header.nonce = 0; header.nonce < 100000; header.nonce++) {
const blob = miningBlob(header), powHash = await proof.hash(header.key, blob);
if (BigInt(`0x${powHash}`) > BigInt(`0x${header.target}`)) continue;
const hash = sha256(blob);
return { ...header, hash, tokenId: mintId(header), powHash,
signature: miner.sign(blockClaimMessage(hash)), transactions: [] } satisfies TestnetBlock;
}
throw new Error('Canonical fixture nonce search exhausted');
}
const options = { feeBps: 250, card: (token: string, owner: string, sequence: number) =>
token === hash && owner === seller.actor && sequence === 0 ? { title: 'PostgreSQL fixture' } : undefined };
function envelope(id: string, who: typeof buyer, action: CustodyAction, nonce: string = randomUUID()): CustodyCommand {
const c: Omit<CustodyCommand, 'signature'> = { version: 1, mode: 'sandbox', policy: CUSTODY_POLICY, network: NETWORK,
chainId: CHAIN_ID, custodianId: id, actor: who.actor, publicKey: who.publicKey, nonce, expiresAt: Date.now() + 240000, action };
return { ...c, signature: who.sign(custodyMessage(c)) };
}
async function fixture(t: { after(fn: () => Promise<void>): void }) {
const admin = new Pool(config);
await admin.query('DROP SCHEMA IF EXISTS wk_custody CASCADE'); // This database exists ONLY in isolated tmpfs fixture.
await migrateCustody(admin);
await admin.query('DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname=\'custody_runtime\') THEN CREATE ROLE custody_runtime LOGIN; END IF; END $$');
await grantCustodyRuntime(admin, 'custody_runtime');
const runtime = { ...config, user: 'custody_runtime' };
const market = await PostgresCustody.open(runtime, options);
t.after(async () => { await market.close(); await admin.end(); });
const execute = (who: typeof buyer, a: CustodyAction) => market.execute(envelope(market.id, who, a));
const activity = async (who = buyer) => await execute(who, { type: 'activity', offset: 0 }) as CustodyActivity;
return { admin, runtime, market, execute, activity };
}
test('explicit PostgreSQL migrations are idempotent and runtime role cannot alter financial history or schema', async t => {
const f = await fixture(t); await migrateCustody(f.admin);
await assert.rejects(PostgresCustody.open(config, options), /non-administrative/);
await f.execute(buyer, { type: 'mock-credit' });
const runtime = new Pool(f.runtime);
try {
for (const sql of ["UPDATE wk_custody.accounts SET balance=999", 'DELETE FROM wk_custody.entries', 'TRUNCATE wk_custody.journals',
"ALTER TABLE wk_custody.accounts ADD COLUMN bad text", "UPDATE wk_custody.receipts SET digest='bad'"]) await assert.rejects(runtime.query(sql));
await assert.rejects(grantCustodyRuntime(f.admin, 'postgres'), /administrative/);
await assert.rejects(grantCustodyRuntime(f.admin, 'bad;DROP SCHEMA'), /identifier/);
} finally { await runtime.end(); }
assert.equal((await f.market.audit()).assetsAtomicXmr, '1000000000000');
});
test('custody schema v1 upgrades additively through v3 without dropping existing metadata', async t => {
const admin = new Pool(config);
await admin.query('DROP SCHEMA IF EXISTS wk_custody CASCADE');
const client = await admin.connect();
try {
await client.query('BEGIN'); await client.query(CUSTODY_SCHEMA_V1);
await client.query('INSERT INTO wk_custody.schema_versions VALUES(1,$1)', [CUSTODY_SCHEMA_V1_DIGEST]);
await client.query('INSERT INTO wk_custody.custody_meta VALUES($1,$2)', ['v1-preserved-marker', 'existing-test-data']);
await client.query('COMMIT');
} catch (error) { await client.query('ROLLBACK'); throw error; }
finally { client.release(); }
try {
await migrateCustody(admin); await migrateCustody(admin);
const versions = await admin.query('SELECT version FROM wk_custody.schema_versions ORDER BY version');
assert.deepEqual(versions.rows.map(row => Number(row.version)), [1, 2, 3]);
assert.equal((await admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='v1-preserved-marker'")).rows[0].value,
'existing-test-data');
for (const table of ['monero_observer_state', 'monero_transfer_observations', 'monero_observation_events', 'monero_observation_scans']) {
assert.ok((await admin.query('SELECT to_regclass($1) AS name', [`wk_custody.${table}`])).rows[0].name);
}
assert.ok((await admin.query("SELECT to_regclass('wk_custody.monero_deposit_allocations') AS name")).rows[0].name);
} finally { await admin.end(); }
});
test('custody schema v2 upgrades explicitly to v3 address-allocation registry', async t => {
const admin = new Pool(config);
await admin.query('DROP SCHEMA IF EXISTS wk_custody CASCADE');
const client = await admin.connect();
try {
await client.query('BEGIN'); await client.query(CUSTODY_SCHEMA_V1);
await client.query('INSERT INTO wk_custody.schema_versions VALUES(1,$1)', [CUSTODY_SCHEMA_V1_DIGEST]);
await client.query(CUSTODY_MIGRATION_V2);
await client.query('INSERT INTO wk_custody.schema_versions VALUES(2,$1)', [CUSTODY_SCHEMA_V2_DIGEST]);
await client.query("INSERT INTO wk_custody.custody_meta VALUES('v2-marker','preserve')");
await client.query('COMMIT');
await migrateCustody(admin); await migrateCustody(admin);
const versions = await admin.query('SELECT version FROM wk_custody.schema_versions ORDER BY version');
assert.deepEqual(versions.rows.map(row => Number(row.version)), [1, 2, 3]);
assert.equal((await admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='v2-marker'")).rows[0].value, 'preserve');
} catch (error) { await client.query('ROLLBACK').catch(() => {}); throw error; }
finally { client.release(); await admin.end(); }
});
test('deposit allocation reserves unique beneficiary-bound indexes before RPC and only registers an immutable subaddress', async t => {
const f = await fixture(t);
await f.admin.query("DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname='monero_allocation_observer') THEN CREATE ROLE monero_allocation_observer LOGIN; END IF; END $$");
await grantMoneroObserverRuntime(f.admin, 'monero_allocation_observer');
const observer = new Pool({ ...config, user: 'monero_allocation_observer' });
const custodyRuntime = new Pool(f.runtime);
const secondAllocatorProcess = new Pool(config);
const generation = randomUUID(), wallet = 'f'.repeat(64), reservedAt = 1_800_000_000;
const input = (requestId: string, beneficiary: string) => ({ requestId, custodianId: f.market.id,
marketNetwork: NETWORK, marketChainId: CHAIN_ID, beneficiary, walletGenerationId: generation,
walletFingerprint: wallet, accountIndex: 0, reservedAt });
try {
const firstRequest = randomUUID();
const firstInput = input(firstRequest, buyer.actor);
const first = await reserveMoneroDepositAllocation(f.admin, firstInput);
assert.equal(first.allocation.addressIndex, 0);
assert.equal(first.allocation.status, 'reserved');
assert.equal(first.allocation.moneroAddress, null, 'reservation is committed before any address is available');
assert.equal(first.replayed, false);
const replay = await reserveMoneroDepositAllocation(f.admin, firstInput);
assert.equal(replay.replayed, true); assert.deepEqual(replay.allocation, first.allocation);
assert.deepEqual((await reserveMoneroDepositAllocation(f.admin, input(randomUUID(), buyer.actor))).allocation,
first.allocation, 'beneficiary receives the same reserved allocation rather than a new index');
await assert.rejects(reserveMoneroDepositAllocation(f.admin, input(firstRequest, seller.actor)), /reused with different terms/);
await assert.rejects(reserveMoneroDepositAllocation(f.admin, { ...firstInput, accountIndex: 1 }), /reused with different terms/);
const concurrent = await Promise.all([
reserveMoneroDepositAllocation(f.admin, input(randomUUID(), other.actor)),
reserveMoneroDepositAllocation(secondAllocatorProcess, input(randomUUID(), seller.actor)),
]);
assert.deepEqual(concurrent.map(item => item.allocation.addressIndex).sort((a, b) => a - b), [1, 2]);
const subaddress = await syntheticSubaddress();
await assert.rejects(registerMoneroDepositSubaddress(f.admin, { allocationId: concurrent[0].allocation.allocationId,
walletGenerationId: generation, moneroAddress: await syntheticFeeAddress(), registeredAt: reservedAt + 1 }), /Expected a Monero mainnet subaddress/);
const registered = await registerMoneroDepositSubaddress(f.admin, { allocationId: first.allocation.allocationId,
walletGenerationId: generation, moneroAddress: subaddress, registeredAt: reservedAt + 1 });
assert.equal(registered.allocation.status, 'registered');
assert.equal(registered.allocation.moneroAddress, subaddress);
assert.equal((await registerMoneroDepositSubaddress(f.admin, { allocationId: first.allocation.allocationId,
walletGenerationId: generation, moneroAddress: subaddress, registeredAt: reservedAt + 2 })).replayed, true);
await assert.rejects(registerMoneroDepositSubaddress(f.admin, { allocationId: first.allocation.allocationId,
walletGenerationId: generation, moneroAddress: await syntheticSubaddress(4), registeredAt: reservedAt + 3 }), /cannot be replaced/);
await assert.rejects(registerMoneroDepositSubaddress(f.admin, { allocationId: concurrent[0].allocation.allocationId,
walletGenerationId: generation, moneroAddress: subaddress, registeredAt: reservedAt + 4 }), /already registered/);
await assert.rejects(custodyRuntime.query('SELECT * FROM wk_custody.monero_deposit_allocations'), /permission denied/);
await assert.rejects(observer.query('SELECT * FROM wk_custody.monero_deposit_allocations'), /permission denied/);
await assert.rejects(observer.query("INSERT INTO wk_custody.monero_deposit_allocations(allocation_id) VALUES($1)", [randomUUID()]), /permission denied/);
await assert.rejects(f.admin.query('DELETE FROM wk_custody.monero_deposit_allocations WHERE allocation_id=$1',
[first.allocation.allocationId]), /cannot be deleted/);
await assert.rejects(f.admin.query('UPDATE wk_custody.monero_deposit_allocations SET beneficiary=$2 WHERE allocation_id=$1',
[first.allocation.allocationId, other.actor]), /identity is immutable/);
const recoveryGeneration = randomUUID();
const recoveryFingerprint = await moneroWalletFingerprint(ADDITIONAL_MARKET_FEE.address);
const recoveryReservation = await reserveMoneroDepositAllocation(f.admin, { ...input(randomUUID(), buyer.actor),
walletGenerationId: recoveryGeneration, walletFingerprint: recoveryFingerprint });
const walletAddresses = new Map<string, { address: string; label: string }>();
const recoveryAddress = await syntheticSubaddress(6);
let createCalls = 0, addressReads = 0, liveFingerprint = 'b'.repeat(64);
const fakeRpc = {
async verifyWallet() { return liveFingerprint; },
async getSubaddress(accountIndex: number, addressIndex: number) {
addressReads++;
return walletAddresses.get(`${accountIndex}:${addressIndex}`) ?? null;
},
async createSubaddress(accountIndex: number, label: string) {
createCalls++;
const row = { address: recoveryAddress, label };
walletAddresses.set(`${accountIndex}:${recoveryReservation.allocation.addressIndex}`, row);
if (createCalls === 1) throw new Error('fixture: RPC response lost after wallet persisted address');
return { address: row.address, addressIndex: recoveryReservation.allocation.addressIndex };
},
};
await assert.rejects(createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
recoveryReservation.allocation.allocationId, recoveryGeneration, reservedAt + 4), /identity differs/);
assert.equal(addressReads, 0); assert.equal(createCalls, 0, 'wallet mismatch must fail before address operations');
liveFingerprint = recoveryFingerprint;
await assert.rejects(createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
recoveryReservation.allocation.allocationId, recoveryGeneration, reservedAt + 5), /response lost/);
const recovered = await createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
recoveryReservation.allocation.allocationId, recoveryGeneration, reservedAt + 6);
assert.equal(recovered.allocation.status, 'registered');
assert.equal(recovered.allocation.moneroAddress, recoveryAddress);
assert.equal(recovered.replayed, false);
assert.equal(createCalls, 1, 'retry must recover by deterministic wallet label rather than create a duplicate');
assert.equal((await f.admin.query('SELECT count(*)::int AS n FROM wk_custody.journals')).rows[0].n, 0,
'address allocation must never create financial postings');
} finally { await secondAllocatorProcess.end(); await custodyRuntime.end(); await observer.end(); }
});
test('Monero scan persistence is idempotent, reorg-visible and never posts customer credit', async t => {
const f = await fixture(t);
await f.admin.query("DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_roles WHERE rolname='monero_observer') THEN CREATE ROLE monero_observer LOGIN; END IF; END $$");
await grantMoneroObserverRuntime(f.admin, 'monero_observer');
await assert.rejects(grantMoneroObserverRuntime(f.admin, 'custody_runtime'), /separate role/);
const runtime = new Pool({ ...f.runtime, user: 'monero_observer' });
const custodyRuntime = new Pool(f.runtime), wallet = 'd'.repeat(64), scan1 = randomUUID();
const txid = 'e'.repeat(64), observationId = `${txid}:0:4`;
const observation = { observationId, transactionId: txid, amountAtomicXmr: '9007199254740993', height: 95,
address: ADDITIONAL_MARKET_FEE.address,
confirmations: 6, timestamp: 1_800_000_000, unlockTime: '0', accountIndex: 0, addressIndex: 4,
locked: false, doubleSpendSeen: false };
const page = { network: 'mainnet' as const, walletHeight: 100, fromHeight: 0, throughHeight: 100,
primaryAddress: ADDITIONAL_MARKET_FEE.address, incoming: [observation] };
try {
await assert.rejects(custodyRuntime.query('SELECT * FROM wk_custody.monero_transfer_observations'));
await assert.rejects(runtime.query('SELECT * FROM wk_custody.accounts'));
const first = await persistMoneroObservationScan(runtime, { scanId: scan1, walletFingerprint: wallet,
expectedScanHeight: 0, scannedAt: 1_800_000_100, page });
assert.equal(first.observed, 1); assert.equal(first.scanHeight, 100); assert.equal(first.replayed, false);
const replay = await persistMoneroObservationScan(runtime, { scanId: scan1, walletFingerprint: wallet,
expectedScanHeight: 0, scannedAt: 1_800_000_100, page });
assert.equal(replay.replayed, true);
await assert.rejects(persistMoneroObservationScan(runtime, { scanId: scan1, walletFingerprint: wallet,
expectedScanHeight: 0, scannedAt: 1_800_000_100, page: { ...page, incoming: [] } }), /reused/);
const confirmationRefresh = await persistMoneroObservationScan(runtime, { scanId: randomUUID(), walletFingerprint: wallet,
expectedScanHeight: 100, scannedAt: 1_800_000_150,
page: { ...page, fromHeight: 90, walletHeight: 105, throughHeight: 105,
incoming: [{ ...observation, confirmations: 11 }] } });
assert.equal(confirmationRefresh.changed, 0, 'normal confirmation growth is a projection update, not an immutable event per poll');
const missing = await persistMoneroObservationScan(runtime, { scanId: randomUUID(), walletFingerprint: wallet,
expectedScanHeight: 105, scannedAt: 1_800_000_200,
page: { ...page, fromHeight: 90, walletHeight: 110, throughHeight: 110, incoming: [] } });
assert.equal(missing.notSeen, 1); // Review signal only; never an automatic financial reversal.
const reappeared = await persistMoneroObservationScan(runtime, { scanId: randomUUID(), walletFingerprint: wallet,
expectedScanHeight: 110, scannedAt: 1_800_000_300,
page: { ...page, fromHeight: 90, walletHeight: 120, throughHeight: 120,
incoming: [{ ...observation, confirmations: 26 }] } });
assert.equal(reappeared.reappeared, 1);
const observed = await runtime.query(`SELECT amount_atomic_xmr::text AS amount,monero_address,currently_present FROM wk_custody.monero_transfer_observations
WHERE wallet_fingerprint=$1 AND observation_id=$2`, [wallet, observationId]);
assert.equal(observed.rows[0].amount, '9007199254740993'); assert.equal(observed.rows[0].currently_present, true);
assert.equal(observed.rows[0].monero_address, ADDITIONAL_MARKET_FEE.address);
const eventTypes = await runtime.query('SELECT event_type FROM wk_custody.monero_observation_events ORDER BY sequence');
assert.deepEqual(eventTypes.rows.map(row => row.event_type), ['seen', 'not-seen', 'reappeared']);
await assert.rejects(runtime.query('UPDATE wk_custody.monero_observation_events SET event_type=\'seen\''));
await assert.rejects(runtime.query('DELETE FROM wk_custody.monero_observation_scans'));
await assert.rejects(runtime.query('SELECT count(*) FROM wk_custody.journals'), /permission denied/);
assert.equal((await f.admin.query('SELECT count(*)::int AS n FROM wk_custody.journals')).rows[0].n, 0,
'observation persistence must never create financial postings');
await assert.rejects(persistMoneroObservationScan(runtime, { scanId: randomUUID(), walletFingerprint: wallet,
expectedScanHeight: 110, scannedAt: 1_800_000_400,
page: { ...page, fromHeight: 90, walletHeight: 121, throughHeight: 121,
incoming: [{ ...observation, confirmations: 27 }] } }), /cursor changed/);
} finally { await custodyRuntime.end(); await runtime.end(); }
});
test('database rejects unbalanced, fractional, negative, historical append and oversized postings at commit', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const c = await f.admin.connect();
try {
await c.query('BEGIN');
await c.query("INSERT INTO wk_custody.journals(id,kind,created) VALUES('bad','test',0)");
await c.query("INSERT INTO wk_custody.entries VALUES('bad',0,'treasury:mock',1,0)");
await assert.rejects(c.query('COMMIT'), /Unbalanced/); await c.query('ROLLBACK');
await assert.rejects(c.query("INSERT INTO wk_custody.entries VALUES($1,9,'treasury:mock',1,0)", [`credit:${buyer.actor}`]), /committed/);
for (const amount of ['-1', '1.5', '1000000000000000000000000000000']) await assert.rejects(c.query('INSERT INTO wk_custody.accounts VALUES($1,$2,$3)', [randomUUID(), 'debit', amount]));
} finally { c.release(); }
assert.equal((await f.market.audit()).journals, 1);
});
test('two independent pools serialize duplicate receipts, competing reservations and withdrawals without overspending', async t => {
const f = await fixture(t), second = await PostgresCustody.open(f.runtime, options);
try {
const credit = envelope(f.market.id, buyer, { type: 'mock-credit' });
const credits = await Promise.all([f.market.execute(credit), second.execute(credit)]);
assert.deepEqual(credits[0], credits[1]);
const listing = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '800000000000', feeBps: 250 }) as CustodyListing;
await f.execute(other, { type: 'mock-credit' });
const races = await Promise.allSettled([f.market.execute(envelope(f.market.id, buyer, { type: 'reserve', listingId: listing.id, priceAtomicXmr: listing.priceAtomicXmr, feeBps: 250 })),
second.execute(envelope(f.market.id, other, { type: 'reserve', listingId: listing.id, priceAtomicXmr: listing.priceAtomicXmr, feeBps: 250 }))]);
assert.equal(races.filter(r => r.status === 'fulfilled').length, 1);
const winner = races.find(r => r.status === 'fulfilled') as PromiseFulfilledResult<CustodyOrder>;
assert.equal(winner.value.sellerAtomicXmr, '772000000000');
assert.equal((await f.activity(seller)).availableAtomicXmr, '0'); // No unimplemented NFT completion credit.
const loser = winner.value.buyer === buyer.actor ? other : buyer;
const withdrawals = await Promise.allSettled([f.market.execute(envelope(f.market.id, loser, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '900000000000', maxFeeAtomicXmr: '1000' })),
second.execute(envelope(f.market.id, loser, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '900000000000', maxFeeAtomicXmr: '1000' }))]);
assert.equal(withdrawals.filter(r => r.status === 'fulfilled').length, 1);
assert.equal((await f.activity(loser)).withdrawalReservedAtomicXmr, '900000001000');
assert.equal((await f.market.audit()).assetsAtomicXmr, '2000000000000');
assert.equal((await f.activity(identity())).orders.length, 0);
} finally { await second.close(); }
});
test('cancel/replay/reconnect preserves exact money, and receipt-insert failure rolls back balance and outbox', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const w = await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '123', maxFeeAtomicXmr: '5' }) as CustodyWithdrawal;
const cancel = envelope(f.market.id, buyer, { type: 'cancel-withdrawal', withdrawalId: w.id });
await f.market.execute(cancel);
const reopened = await PostgresCustody.open(f.runtime, options);
try {
assert.equal(reopened.id, f.market.id); assert.deepEqual(await reopened.execute(cancel), await f.market.execute(cancel));
await assert.rejects(reopened.execute(envelope(f.market.id, buyer, { type: 'mock-credit' }, cancel.nonce)), /Nonce/);
await f.admin.query("CREATE FUNCTION wk_custody.inject_failure() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'fixture failure'; END $$; CREATE TRIGGER fail_receipt BEFORE INSERT ON wk_custody.receipts FOR EACH ROW EXECUTE FUNCTION wk_custody.inject_failure()");
await assert.rejects(f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '200', maxFeeAtomicXmr: '0' }));
assert.equal((await f.activity()).availableAtomicXmr, '1000000000000');
assert.equal((await f.activity()).withdrawals.length, 1);
await f.admin.query('DROP TRIGGER fail_receipt ON wk_custody.receipts');
assert.equal((await f.market.audit()).journals, 3);
} finally { await reopened.close(); }
});
test('exact amounts above JS safe integer survive NUMERIC, JSON signatures and audit', async t => {
const f = await fixture(t);
// Seed a balanced synthetic fixture through SQL, not an HTTP mint/admin endpoint.
const c = await f.admin.connect(), large = '9007199254740993';
try {
await c.query('BEGIN');
await c.query("INSERT INTO wk_custody.accounts(id,normal) VALUES('treasury:mock','debit'),($1,'credit')", [`customer:${buyer.actor}:available`]);
await c.query("INSERT INTO wk_custody.journals(id,kind,created) VALUES('large','fixture',0)");
await c.query("INSERT INTO wk_custody.entries VALUES('large',0,'treasury:mock',$1,0),('large',1,$2,0,$1)", [large, `customer:${buyer.actor}:available`]);
await c.query('COMMIT');
} finally { c.release(); }
assert.equal((await f.activity()).availableAtomicXmr, large);
await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: large, maxFeeAtomicXmr: '0' });
assert.equal((await f.activity()).withdrawalReservedAtomicXmr, large); assert.equal((await f.market.audit()).assetsAtomicXmr, large);
});
test('SQLite cutover preserves identity, history, prepared attempts, ordering and replay; source cannot restart', async t => {
const f = await fixture(t);
// Import requires truly empty target, even a previously initialized empty custodian is rejected.
await f.admin.query('DELETE FROM wk_custody.custody_meta');
const dir = await mkdtemp(join(tmpdir(), 'custody-pg-import-'));
t.after(() => rm(dir, { recursive: true, force: true }));
const source = await CustodyMarket.open(dir, options);
const originalId = source.id, credit = envelope(originalId, buyer, { type: 'mock-credit' });
source.execute(credit);
const w = source.execute(envelope(originalId, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '12345', maxFeeAtomicXmr: '100' })) as CustodyWithdrawal;
const attempt = randomUUID(), tx = `fixture:${'b'.repeat(64)}`;
source.prepareWithdrawalFixture(w.id, attempt, tx, '50');
const listing = source.execute(envelope(originalId, seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '10000', feeBps: 250 })) as CustodyListing;
const order = source.execute(envelope(originalId, buyer, { type: 'reserve', listingId: listing.id, priceAtomicXmr: listing.priceAtomicXmr, feeBps: 250 })) as CustodyOrder;
const oldAudit = source.audit(); source.close();
const file = join(dir, 'testnet', 'custody-sandbox.sqlite');
const imported = await importSqliteCustody(file, f.admin);
assert.equal(imported.custodianId, originalId);
assert.deepEqual(await importSqliteCustody(file, f.admin), imported);
await assert.rejects(CustodyMarket.open(dir, options), /frozen/);
const pg = await PostgresCustody.open(f.runtime, options);
try {
assert.deepEqual(await pg.audit(), oldAudit);
assert.equal((await pg.execute(credit) as CustodyActivity).availableAtomicXmr, '1000000000000'); // Original receipt, not current balance.
const activity = await pg.execute(envelope(originalId, buyer, { type: 'activity', offset: 0 })) as CustodyActivity;
assert.equal(activity.withdrawals[0].attemptId, attempt); assert.equal(activity.withdrawals[0].transactionId, tx);
assert.equal(activity.orders[0].id, order.id);
await pg.execute(envelope(originalId, buyer, { type: 'cancel-reservation', orderId: order.id }));
assert.equal((await pg.execute(envelope(originalId, buyer, { type: 'activity', offset: 0 })) as CustodyActivity).purchaseReservedAtomicXmr, '0');
await assert.rejects(pg.execute(envelope(originalId, buyer, { type: 'cancel-withdrawal', withdrawalId: w.id })), /unprepared/);
} finally { await pg.close(); }
const sqlite = new DatabaseSync(file); try { sqlite.prepare("UPDATE accounts SET balance='1' WHERE id=?").run(`customer:${buyer.actor}:available`); } finally { sqlite.close(); }
await assert.rejects(importSqliteCustody(file, f.admin), /source changed/);
});
test('PostgreSQL HTTP custody integration, safe storage binding and backup refusal use the real runtime role', { timeout: 45000 }, async t => {
const f = await fixture(t), dir = await mkdtemp(join(tmpdir(), 'custody-pg-http-'));
const urlFile = join(dir, 'pg-url'); await writeFile(urlFile, 'postgresql://custody_runtime@127.0.0.1/custody_test', { mode: 0o600 });
const env = { ...process.env, HOST: '127.0.0.1', PORT: '0', NODE_ENV: 'test', WIKIDECK_DATA_DIR: dir,
WIKIDECK_CUSTODY_MODE: 'sandbox', WIKIDECK_CUSTODY_STORAGE: 'postgres', WIKIDECK_CUSTODY_PG_URL_FILE: urlFile,
WIKIDECK_CUSTODY_PG_LOCAL_TEST: '1', WIKIDECK_PG_DISPOSABLE_TEST: '1', WIKIDECK_CUSTODY_CANONICAL_REHEARSAL: '1',
WIKIDECK_PAYMENT_MODE: 'mock', WIKIDECK_PEERS: '' };
const child = spawn(process.execPath, ['--experimental-strip-types', 'server/index.ts'], { env, stdio: ['ignore', 'pipe', 'pipe'] });
const closed = new Promise<void>(resolve => child.once('close', () => resolve()));
t.after(async () => { if (child.exitCode === null) child.kill('SIGTERM'); await closed; await rm(dir, { recursive: true, force: true }); });
const base = await new Promise<string>((resolve, reject) => {
let log = ''; const timer = setTimeout(() => reject(new Error('HTTP startup timeout')), 25000);
const listener = (chunk: Buffer) => { log += chunk.toString(); const m = /Wikideck RandomX testnet: (http:\/\/127\.0\.0\.1:\d+)/.exec(log); if (m) { clearTimeout(timer); resolve(m[1]); } };
child.stdout.on('data', listener); child.stderr.on('data', listener);
child.once('exit', () => { clearTimeout(timer); reject(new Error('HTTP startup failed')); });
});
const page = await (await fetch(`${base}/api/custody`)).json(); assert.equal(page.info.custodianId, f.market.id);
const observerStream = await f.admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='canonical-inbox:v1:stream'");
assert.match(observerStream.rows[0].value, /^[a-f0-9-]{36}$/);
const command = envelope(f.market.id, buyer, { type: 'mock-credit' });
const response = await fetch(`${base}/api/custody/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(command) });
assert.equal(response.status, 200); assert.equal((await response.json()).availableAtomicXmr, '1000000000000');
assert.match(response.headers.get('cache-control') ?? '', /no-store/);
assert.equal(JSON.parse(await readFile(join(dir, 'testnet', 'custody-postgres.json'), 'utf8')).custodianId, f.market.id);
child.kill('SIGTERM'); await closed; assert.equal(child.exitCode, 0, 'opt-in observer should drain before custody shutdown');
await assert.rejects(createBackup(dir, join(dir, '..', `refused-${randomUUID()}`)), /coordinated/);
await assert.rejects(openCustodyStorage(dir, options, { WIKIDECK_CUSTODY_STORAGE: 'sqlite' }), /fallback prohibited/);
});
test('configuration requires secret files, verified TLS, literal backend, and loopback-only plaintext test transport', async t => {
const dir = await mkdtemp(join(tmpdir(), 'custody-pg-config-')); t.after(() => rm(dir, { recursive: true, force: true }));
const file = join(dir, 'connection');
assert.equal(custodyStorage({}), 'sqlite'); assert.throws(() => custodyStorage({ WIKIDECK_CUSTODY_STORAGE: 'other' }));
await assert.rejects(openCustodyStorage(dir, options, { WIKIDECK_CUSTODY_STORAGE: 'sqlite',
WIKIDECK_CUSTODY_CANONICAL_REHEARSAL: '1', WIKIDECK_PG_DISPOSABLE_TEST: '1' }), /requires PostgreSQL/);
await assert.rejects(openCustodyStorage(dir, options, { WIKIDECK_CUSTODY_STORAGE: 'postgres',
WIKIDECK_CUSTODY_CANONICAL_REHEARSAL: '1' }), /disposable database test guard/);
await assert.rejects(openCustodyStorage(dir, options, { NODE_ENV: 'production', WIKIDECK_CUSTODY_STORAGE: 'postgres',
WIKIDECK_CUSTODY_CANONICAL_REHEARSAL: '1', WIKIDECK_PG_DISPOSABLE_TEST: '1' }), /requires PostgreSQL/);
await assert.rejects(openCustodyStorage(dir, options, { WIKIDECK_CUSTODY_STORAGE: 'postgres',
WIKIDECK_CUSTODY_CANONICAL_REHEARSAL: 'yes', WIKIDECK_PG_DISPOSABLE_TEST: '1' }), /exactly 0 or 1/);
await assert.rejects(postgresConfig({}), /configuration/);
await writeFile(file, 'postgresql://user:secret@database.example/custody');
const secure = await postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: file }); assert.equal((secure.ssl as { rejectUnauthorized: boolean }).rejectUnauthorized, true);
await assert.rejects(postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: file, WIKIDECK_CUSTODY_PG_LOCAL_TEST: '1' }), /configuration/);
await writeFile(file, 'postgresql://user:secret@127.0.0.1/custody?sslmode=disable');
await assert.rejects(postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: file }), /configuration/);
});
test('PostgreSQL market disable preserves existing claims and signed withdrawal cancellation', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const listing = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100', feeBps: 250 }) as CustodyListing;
const order = await f.execute(buyer, { type: 'reserve', listingId: listing.id, priceAtomicXmr: '100', feeBps: 250 }) as CustodyOrder;
const disabled = await PostgresCustody.open(f.runtime, { ...options, marketEnabled: false });
try {
assert.equal(disabled.info().marketEnabled, false); assert.deepEqual((await disabled.browse()).listings, []);
await assert.rejects(disabled.execute(envelope(disabled.id, other, { type: 'mock-credit' })), /disabled/);
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-reservation', orderId: order.id }));
const w = await disabled.execute(envelope(disabled.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' })) as CustodyWithdrawal;
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-withdrawal', withdrawalId: w.id }));
assert.equal((await disabled.audit()).assetsAtomicXmr, '1000000000000');
} finally { await disabled.close(); }
});
test('full PostgreSQL dump/isolated restore preserves custody identity, receipts and journal backing', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const cmd = envelope(f.market.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '500', maxFeeAtomicXmr: '20' });
const original = await f.market.execute(cmd), audit = await f.market.audit();
const dir = await mkdtemp(join(tmpdir(), 'custody-pg-restore-')), archive = join(dir, 'custody.dump');
const run = promisify(execFile), database = `restore_${randomUUID().replaceAll('-', '')}`;
try {
// Fixture role and TCP transport are local/trust-only on the isolated tmpfs network. No production credentials.
await run('pg_dump', ['-h', '127.0.0.1', '-U', 'postgres', '-d', 'custody_test', '-Fc', '-f', archive]);
await f.admin.query(`CREATE DATABASE "${database}"`);
await run('pg_restore', ['-h', '127.0.0.1', '-U', 'postgres', '-d', database, '--exit-on-error', archive]);
const restored = await PostgresCustody.open({ ...f.runtime, database }, options);
try {
assert.equal(restored.id, f.market.id); assert.deepEqual(await restored.audit(), audit);
assert.deepEqual(await restored.execute(cmd), original);
const balance = await restored.execute(envelope(restored.id, buyer, { type: 'activity', offset: 0 })) as CustodyActivity;
assert.equal(balance.availableAtomicXmr, '999999999480'); assert.equal(balance.withdrawalReservedAtomicXmr, '520');
} finally { await restored.close(); }
} finally {
await f.admin.query(`DROP DATABASE IF EXISTS "${database}" WITH (FORCE)`);
await rm(dir, { recursive: true, force: true });
}
});
test('coordinated node/PostgreSQL backup holds both writer fences and verifies matching custodian identity', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const dir = await mkdtemp(join(tmpdir(), 'wikideck-coordinated-backup-'));
t.after(() => rm(dir, { recursive: true, force: true }));
const data = join(dir, 'node-data'), networkDir = join(data, 'testnet');
await mkdir(networkDir, { recursive: true });
const nativeEngine = new RandomX({ binary: process.env.RANDOMX_BINARY });
const sourceLedger = await Ledger.open(data, nativeEngine);
const blockTemplate = makeTemplate([], {}, seller.actor, seller.publicKey, [], fixtureArticle(56000), fixtureStats(), GENESIS_TIME + 30);
const header = structuredClone(blockTemplate.header);
let mined: Awaited<ReturnType<typeof nativeEngine.mine>> = null;
for (let batch = 0; batch < 16 && !mined; batch++) {
mined = await nativeEngine.mine(header.key, miningBlob(header), header.target, 32, 64);
if (!mined) header.nonce += 64;
}
assert.ok(mined, 'Native RandomX cold-backup fixture mining exceeded bounded attempts');
header.nonce = mined.nonce;
const blockHash = sha256(miningBlob(header));
const sourceBlock: TestnetBlock = { ...header, hash: blockHash, tokenId: mintId(header), powHash: mined.hash,
signature: seller.sign(blockClaimMessage(blockHash)), transactions: [] };
await sourceLedger.submit(sourceBlock);
sourceLedger.close(); nativeEngine.close();
const marketDb = new DatabaseSync(join(networkDir, 'market.sqlite'));
try {
marketDb.exec('CREATE TABLE market_meta(key TEXT PRIMARY KEY,value TEXT NOT NULL)');
marketDb.prepare('INSERT INTO market_meta VALUES(?,?)').run('identity', JSON.stringify({ version: 1,
network: NETWORK, chainId: CHAIN_ID, id: randomUUID() }));
} finally { marketDb.close(); }
await writeFile(join(networkDir, 'custody-postgres.json'), JSON.stringify({ version: 1, custodianId: f.market.id }), { mode: 0o600 });
// Model a legitimate post-cutover node: its frozen SQLite source is historical and PostgreSQL is authoritative.
const cutoverDigest = 'a'.repeat(64), legacyDb = new DatabaseSync(join(networkDir, 'custody-sandbox.sqlite'));
try {
legacyDb.exec('CREATE TABLE custody_meta(key TEXT PRIMARY KEY,value TEXT NOT NULL)');
legacyDb.prepare('INSERT INTO custody_meta VALUES(?,?)').run('postgres_cutover', cutoverDigest);
} finally { legacyDb.close(); }
await f.admin.query('INSERT INTO wk_custody.custody_meta(key,value) VALUES($1,$2)', ['import_source', cutoverDigest]);
const runtimeUrl = join(dir, 'runtime-url'), backupUrl = join(dir, 'backup-url');
await writeFile(runtimeUrl, 'postgresql://custody_runtime@127.0.0.1/custody_test', { mode: 0o600 });
const backupRole = `custody_backup_${randomUUID().replaceAll('-', '')}`;
await f.admin.query(`CREATE ROLE ${backupRole} LOGIN; GRANT CONNECT ON DATABASE custody_test TO ${backupRole};
GRANT USAGE ON SCHEMA wk_custody TO ${backupRole}; GRANT SELECT ON ALL TABLES IN SCHEMA wk_custody TO ${backupRole};
GRANT SELECT ON ALL SEQUENCES IN SCHEMA wk_custody TO ${backupRole}`);
await writeFile(backupUrl, `postgresql://${backupRole}@127.0.0.1/custody_test`, { mode: 0o600 });
const env = { ...process.env, WIKIDECK_CUSTODY_PG_URL_FILE: runtimeUrl,
WIKIDECK_CUSTODY_PG_BACKUP_URL_FILE: backupUrl, WIKIDECK_CUSTODY_PG_LOCAL_TEST: '1' };
const destination = join(dir, 'paired-backup');
const inheritedLibpq = { hostAddr: process.env.PGHOSTADDR, sslMode: process.env.PGSSLMODE, sslRoot: process.env.PGSSLROOTCERT };
let manifest: Awaited<ReturnType<typeof createCoordinatedCustodyBackup>>;
try {
process.env.PGHOSTADDR = '192.0.2.123'; process.env.PGSSLMODE = 'disable'; process.env.PGSSLROOTCERT = 'invalid-inherited-root.pem';
manifest = await createCoordinatedCustodyBackup(data, destination, env);
} finally {
if (inheritedLibpq.hostAddr === undefined) delete process.env.PGHOSTADDR; else process.env.PGHOSTADDR = inheritedLibpq.hostAddr;
if (inheritedLibpq.sslMode === undefined) delete process.env.PGSSLMODE; else process.env.PGSSLMODE = inheritedLibpq.sslMode;
if (inheritedLibpq.sslRoot === undefined) delete process.env.PGSSLROOTCERT; else process.env.PGSSLROOTCERT = inheritedLibpq.sslRoot;
}
assert.equal(manifest.custodianId, f.market.id); assert.equal(manifest.database, 'custody_test');
assert.match(manifest.postgresServerVersion, /^\d+\.\d+/);
assert.match(manifest.postgresServerVersionNum, /^\d{5,6}$/);
assert.match(manifest.pgDumpVersion, /^pg_dump \(PostgreSQL\) /);
assert.match(manifest.pgRestoreVersion, /^pg_restore \(PostgreSQL\) /);
assert.equal(manifest.sqliteCutoverSourceDigest, cutoverDigest);
assert.equal((await verifyCoordinatedCustodyBackup(destination)).nodeManifestSha256, manifest.nodeManifestSha256);
if (process.platform !== 'win32') {
const oldRestore = join(dir, 'old-pg-restore');
await writeFile(oldRestore, '#!/bin/sh\nprintf "pg_restore (PostgreSQL) 9.6\\n"\n', { mode: 0o700 });
await chmod(oldRestore, 0o700);
await assert.rejects(verifyCoordinatedCustodyBackup(destination, oldRestore), /older than archive pg_dump/);
}
const extraNodeFile = join(destination, 'node', 'unmanifested.txt');
await writeFile(extraNodeFile, 'not part of backup manifest');
await assert.rejects(verifyCoordinatedCustodyBackup(destination), /Unexpected coordinated node backup contents/);
await rm(extraNodeFile);
await assert.rejects(lstat(join(destination, 'node', 'custody-sandbox.sqlite')), { code: 'ENOENT' });
const restoredNodeDir = join(dir, 'restored-node');
await restoreBackup(join(destination, 'node'), restoredNodeDir);
const verifierEngine = new RandomX({ binary: process.env.RANDOMX_BINARY });
const verifierLedger = await Ledger.open(restoredNodeDir, verifierEngine);
try {
assert.equal(verifierLedger.summary().tip, sourceBlock.hash);
assert.equal(verifierLedger.nft(sourceBlock.tokenId)?.owner, seller.actor);
} finally { verifierLedger.close(); verifierEngine.close(); }
const restoreDatabase = `pair_restore_${randomUUID().replaceAll('-', '')}`, run = promisify(execFile);
await f.admin.query(`CREATE DATABASE "${restoreDatabase}"`);
try {
await run('pg_restore', ['-h', '127.0.0.1', '-U', 'postgres', '-d', restoreDatabase, '--exit-on-error', join(destination, 'custody.dump')]);
const restored = await PostgresCustody.open({ ...f.runtime, database: restoreDatabase }, options);
try {
assert.equal(restored.id, f.market.id);
assert.deepEqual(await restored.audit(), await f.market.audit());
assert.equal(await restored.importSource(), cutoverDigest);
const activity = await restored.execute(envelope(restored.id, buyer, { type: 'activity', offset: 0 })) as CustodyActivity;
assert.equal(activity.availableAtomicXmr, '1000000000000');
} finally { await restored.close(); }
} finally { await f.admin.query(`DROP DATABASE IF EXISTS "${restoreDatabase}" WITH (FORCE)`); }
const backupRolePool = new Pool({ ...config, user: backupRole });
try {
await assert.rejects(backupRolePool.query("UPDATE wk_custody.accounts SET balance=0"));
await assert.rejects(backupRolePool.query("DELETE FROM wk_custody.entries"));
await assert.rejects(backupRolePool.query("SELECT nextval('wk_custody.journals_sequence_seq')"));
} finally { await backupRolePool.end(); }
assert.equal((await f.market.audit()).assetsAtomicXmr, '1000000000000');
await assert.rejects(createCoordinatedCustodyBackup(data, destination, env), /already exists/);
const staleLock = join(data, 'testnet-node.lock'), lockedDestination = join(dir, 'locked-backup');
await writeFile(staleLock, 'operator-lock');
await assert.rejects(createCoordinatedCustodyBackup(data, lockedDestination, env), /Node directory is locked/);
await assert.rejects(lstat(lockedDestination)); await rm(staleLock);
const failedDump = join(dir, 'failed-dump'), brokenEnv = { ...env, WIKIDECK_PG_DUMP_BINARY: join(dir, 'missing-pg-dump') };
await assert.rejects(createCoordinatedCustodyBackup(data, failedDump, brokenEnv), /client utility failed to start/);
await assert.rejects(verifyCoordinatedCustodyBackup(failedDump), /ENOENT/);
// A failed external dump releases PostgreSQL's shared-writer fence; normal sandbox commands still work.
const recoveredWrite = await f.execute(other, { type: 'mock-credit' }) as CustodyActivity;
assert.equal(recoveredWrite.availableAtomicXmr, '1000000000000');
const mismatchedBinding = join(dir, 'mismatch-binding'), bindingPath = join(networkDir, 'custody-postgres.json');
const originalBinding = await readFile(bindingPath, 'utf8');
await writeFile(bindingPath, JSON.stringify({ version: 1, custodianId: randomUUID() }), { mode: 0o600 });
await assert.rejects(createCoordinatedCustodyBackup(data, mismatchedBinding, env), /identities differ/);
await writeFile(bindingPath, originalBinding, { mode: 0o600 });
await assert.rejects(verifyCoordinatedCustodyBackup(mismatchedBinding), /ENOENT/);
const corruptDump = join(destination, 'custody.dump');
await writeFile(corruptDump, 'corrupt');
await assert.rejects(verifyCoordinatedCustodyBackup(destination), /hash\/size mismatch/);
// Even if an operator or attacker rewrites the unsigned manifest hashes, pg_restore parsing rejects junk bytes.
const manifestPath = join(destination, 'manifest.json'), altered = JSON.parse(await readFile(manifestPath, 'utf8'));
altered.postgresDump.bytes = Buffer.byteLength('corrupt');
altered.postgresDump.sha256 = createHash('sha256').update('corrupt').digest('hex');
await writeFile(manifestPath, JSON.stringify(altered));
await assert.rejects(verifyCoordinatedCustodyBackup(destination), /dump archive is invalid/);
const unrelatedDatabase = `empty_${randomUUID().replaceAll('-', '')}`, unrelatedDump = join(dir, 'unrelated.dump');
await f.admin.query(`CREATE DATABASE "${unrelatedDatabase}"`);
try {
await run('pg_dump', ['-h', '127.0.0.1', '-U', 'postgres', '-d', unrelatedDatabase, '-Fc', '-f', unrelatedDump]);
await writeFile(corruptDump, await readFile(unrelatedDump));
const validButWrong = JSON.parse(await readFile(manifestPath, 'utf8'));
const unrelated = await readFile(unrelatedDump);
validButWrong.postgresDump.bytes = unrelated.length;
validButWrong.postgresDump.sha256 = createHash('sha256').update(unrelated).digest('hex');
await writeFile(manifestPath, JSON.stringify(validButWrong));
await assert.rejects(verifyCoordinatedCustodyBackup(destination), /missing required custody accounting tables/);
} finally { await f.admin.query(`DROP DATABASE IF EXISTS "${unrelatedDatabase}" WITH (FORCE)`); }
});
test('failed cutover freezes the source, rolls back the entire target, and resumes without duplicate balances', async t => {
const f = await fixture(t); await f.admin.query('DELETE FROM wk_custody.custody_meta');
const dir = await mkdtemp(join(tmpdir(), 'custody-pg-failed-import-')); t.after(() => rm(dir, { recursive: true, force: true }));
const source = await CustodyMarket.open(dir, options);
source.execute(envelope(source.id, buyer, { type: 'mock-credit' })); source.close();
const file = join(dir, 'testnet', 'custody-sandbox.sqlite');
await f.admin.query("CREATE FUNCTION wk_custody.import_failure() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'injected import failure'; END $$; CREATE TRIGGER fail_import BEFORE INSERT ON wk_custody.receipts FOR EACH ROW EXECUTE FUNCTION wk_custody.import_failure()");
await assert.rejects(importSqliteCustody(file, f.admin), /injected import/);
assert.equal((await f.admin.query('SELECT count(*)::integer AS n FROM wk_custody.accounts')).rows[0].n, 0);
await assert.rejects(CustodyMarket.open(dir, options), /frozen/);
await f.admin.query('DROP TRIGGER fail_import ON wk_custody.receipts');
await importSqliteCustody(file, f.admin);
const restored = await PostgresCustody.open(f.runtime, options);
try { assert.equal((await restored.audit()).assetsAtomicXmr, '1000000000000'); }
finally { await restored.close(); }
});
test('business audit rejects metadata corruption even when every journal and total remains balanced', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '10000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: l.feeBps }) as CustodyOrder;
const w = await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '500', maxFeeAtomicXmr: '20' }) as CustodyWithdrawal;
const probes: [string, unknown[], string][] = [
["UPDATE wk_custody.orders SET data=jsonb_set(data,'{buyer}',to_jsonb($1::text)) WHERE id=$2", [other.actor, o.id], 'order-terms-mismatch'],
["UPDATE wk_custody.orders SET data=jsonb_set(data,'{sellerAtomicXmr}','\"1\"') WHERE id=$1", [o.id], 'order-allocation-mismatch'],
["UPDATE wk_custody.orders SET data=jsonb_set(data,'{grossAtomicXmr}','\"1.0\"') WHERE id=$1", [o.id], 'order-record-invalid'],
["UPDATE wk_custody.orders SET status='settled',data=jsonb_set(data,'{status}','\"settled\"') WHERE id=$1", [o.id], 'order-reservation-mismatch'],
["DELETE FROM wk_custody.outbox WHERE id=$1", [o.id], 'order-state-mismatch'],
["UPDATE wk_custody.outbox SET data='{}' WHERE id=$1", [o.id], 'order-outbox-invalid'],
["UPDATE wk_custody.listings SET data=jsonb_set(data,'{feeBps}','500') WHERE id=$1", [l.id], 'order-allocation-mismatch'],
["UPDATE wk_custody.withdrawals SET data=jsonb_set(data,'{maxFeeAtomicXmr}','\"21\"') WHERE id=$1", [w.id], 'withdrawal-reservation-mismatch'],
["UPDATE wk_custody.withdrawals SET data=jsonb_set(data,'{actor}',to_jsonb($1::text)) WHERE id=$2", [other.actor, w.id], 'withdrawal-identity-mismatch'],
["UPDATE wk_custody.outbox SET status='fixture-prepared' WHERE id=$1", [w.id], 'withdrawal-outbox-mismatch'],
["INSERT INTO wk_custody.outbox VALUES($1,'withdrawal','blocked','{}')", [randomUUID()], 'orphan-outbox'],
["INSERT INTO wk_custody.accounts(id,normal) VALUES($1,'credit')", [`customer:${buyer.actor}:purchase:${randomUUID()}`], 'orphan-purchase-reserve'],
];
const c = await f.admin.connect();
try {
for (const [sql, args, code] of probes) {
await c.query('BEGIN');
try {
await c.query(sql, args);
const totals = await c.query("SELECT sum(CASE WHEN normal='debit' THEN balance ELSE -balance END)::text AS net FROM wk_custody.accounts");
assert.equal(totals.rows[0].net, '0'); // This would pass the old aggregate-only accounting audit.
await assert.rejects(auditPostgres(c), (e: unknown) => e instanceof CustodyIntegrityError && e.code === code, code);
} finally { await c.query('ROLLBACK'); }
}
} finally { c.release(); }
assert.equal((await f.market.audit()).journals, 3);
});
test('integrity incident blocks new mutations across instances/restarts but preserves private reads and original receipts', async t => {
const f = await fixture(t), second = await PostgresCustody.open(f.runtime, options);
const credit = envelope(f.market.id, buyer, { type: 'mock-credit' }); await f.market.execute(credit);
const w = await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '100', maxFeeAtomicXmr: '0' }) as CustodyWithdrawal;
try {
await f.admin.query("UPDATE wk_custody.withdrawals SET data=jsonb_set(data,'{amountAtomicXmr}','\"101\"') WHERE id=$1", [w.id]);
await assert.rejects(f.market.audit(), (e: unknown) => e instanceof CustodyIntegrityError && e.code === 'withdrawal-reservation-mismatch');
const status = f.market.diagnostics(); assert.equal(status.integrityHold, true); assert.equal(status.mutationHold, true);
const text = JSON.stringify(status); for (const secret of [buyer.actor, w.id, 'postgresql:', 'password']) assert.equal(text.includes(secret), false);
const marker = await f.admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'");
assert.equal(JSON.parse(marker.rows[0].value).code, 'withdrawal-reservation-mismatch');
await assert.rejects(second.execute(envelope(second.id, other, { type: 'mock-credit' })), /integrity incident/);
assert.equal((await f.market.execute(credit) as CustodyActivity).availableAtomicXmr, '1000000000000');
assert.equal((await f.activity()).withdrawalReservedAtomicXmr, '100');
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity-incident/);
// Restore this disposable corruption to prove that repair alone does not silently clear the incident.
await f.admin.query('UPDATE wk_custody.withdrawals SET data=$1 WHERE id=$2', [JSON.stringify(w), w.id]);
await assert.rejects(f.market.audit(), /unresolved-integrity-incident/);
assert.equal((await f.activity()).availableAtomicXmr, '999999999900');
const runtimePool = new Pool(f.runtime);
try { await assert.rejects(resolveCustodyIncident(runtimePool, randomUUID()), /Schema-owner/); }
finally { await runtimePool.end(); }
const resolved = await resolveCustodyIncident(f.admin, randomUUID()); assert.ok(resolved.resolutionId);
// Previously detecting instances remain latched even after authorized acknowledgement.
await f.market.audit(); assert.equal(f.market.diagnostics().integrityHold, true);
await assert.rejects(f.execute(other, { type: 'mock-credit' }), /reconciliation/);
const verified = await PostgresCustody.open(f.runtime, options);
try { assert.equal(verified.diagnostics().mutationHold, false); await verified.audit(); }
finally { await verified.close(); }
} finally { await second.close(); }
});
test('targeted mutation checks reject corrupt reservations before posting or cancelling their outbox', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const w = await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '500', maxFeeAtomicXmr: '20' }) as CustodyWithdrawal;
await f.admin.query("UPDATE wk_custody.outbox SET data='{}' WHERE id=$1", [w.id]);
await assert.rejects(f.execute(buyer, { type: 'cancel-withdrawal', withdrawalId: w.id }), /integrity check/);
assert.equal((await f.admin.query('SELECT status FROM wk_custody.withdrawals WHERE id=$1', [w.id])).rows[0].status, 'reserved');
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.journals WHERE kind='withdrawal-cancellation'")).rows[0].n, 0);
assert.equal((await f.activity()).availableAtomicXmr, '999999999480');
assert.equal(f.market.diagnostics().integrityHold, true);
await assert.rejects(resolveCustodyIncident(f.admin, randomUUID()), /integrity check/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'")).rows[0].n, 1);
});
test('business audit pages past 128 records and catches later corrupted output without dropping claims', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
let last: CustodyWithdrawal | undefined;
for (let i = 0; i < 130; i++) {
const w = await f.execute(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' }) as CustodyWithdrawal;
if (!last || last.id < w.id) last = w;
}
assert.equal((await f.market.audit()).journals, 131);
await f.admin.query("UPDATE wk_custody.outbox SET data='{}' WHERE id=$1", [last!.id]);
await assert.rejects(f.market.audit(), /withdrawal-outbox-invalid/);
assert.equal((await f.activity()).withdrawalReservedAtomicXmr, '130');
});
test('imported fixture completion and cancellation states reconcile their exact terminal postings', async t => {
const f = await fixture(t); await f.admin.query('DELETE FROM wk_custody.custody_meta');
const dir = await mkdtemp(join(tmpdir(), 'custody-terminal-audit-')); t.after(() => rm(dir, { recursive: true, force: true }));
const source = await CustodyMarket.open(dir, options), id = source.id;
const run = (who: typeof buyer, action: CustodyAction) => source.execute(envelope(id, who, action));
run(buyer, { type: 'mock-credit' });
const l = run(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250 }) as CustodyListing;
const o = run(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
source.settleFixture(o.id, `fixture:${'c'.repeat(64)}`);
const w = run(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1000', maxFeeAtomicXmr: '50' }) as CustodyWithdrawal;
source.prepareWithdrawalFixture(w.id, randomUUID(), `fixture:${'d'.repeat(64)}`, '10'); source.confirmWithdrawalFixture(w.id, `fixture:${'d'.repeat(64)}`);
const cancelled = run(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '100', maxFeeAtomicXmr: '5' }) as CustodyWithdrawal;
run(buyer, { type: 'cancel-withdrawal', withdrawalId: cancelled.id });
const expected = source.audit(); source.close();
await importSqliteCustody(join(dir, 'testnet', 'custody-sandbox.sqlite'), f.admin);
const pg = await PostgresCustody.open(f.runtime, options);
try { assert.deepEqual(await pg.audit(), expected); }
finally { await pg.close(); }
});
test('sale rehearsal requires explicit disposable instance consent and exact trader-signed reservation terms', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
await assert.rejects(PostgresCustody.open({ ...f.runtime, database: 'postgres' }, { ...options, disposableSaleRehearsal: true }), /disposable database/);
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100000000000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const t1 = saleTranscript(o, f.market.id);
assert.throws(() => f.market.observeSaleRehearsal(o.id, t1.input), /unavailable/);
await assert.rejects(f.market.observeCanonicalEvents({}), /unavailable/);
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
const wrong = structuredClone(t1.input); wrong.lock = { ...t1.input.lock, terms: { ...t1.input.lock.terms, priceAtomicXmr: '1' } } as typeof wrong.lock;
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, wrong), /operation failed/);
const locked = await rehearsal.observeSaleRehearsal(o.id, t1.input);
assert.equal(locked.action, 'hold'); assert.equal(locked.reservationDigest, t1.reservationDigest);
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, { ...t1.input, seed: { ...t1.input.seed, pageId: 2 } }), /authorization changed/);
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, { ...t1.input, blocks: Array(129).fill([]) }), /operation failed/);
const cycle: Record<string, unknown> = {}; cycle.self = cycle;
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, cycle), /operation failed/);
await assert.rejects(f.execute(buyer, { type: 'cancel-reservation', orderId: o.id }), /authorization exists/);
assert.equal((await f.activity()).purchaseReservedAtomicXmr, o.grossAtomicXmr);
assert.equal((await f.activity(seller)).availableAtomicXmr, '0');
assert.equal(rehearsal.info().nftSettlement, false);
await rehearsal.audit();
} finally { await rehearsal.close(); }
});
test('canonical connect/disconnect inbox resumes bounded pages, retries idempotently, and audits after reconnect', async t => {
const f = await fixture(t);
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const streamId = randomUUID(), events = canonicalEvents(streamId);
const first = canonicalPage(streamId, events, 0, 2), second = canonicalPage(streamId, events, 2, 2);
try {
const accepted = await observer.observeCanonicalEvents(first);
assert.equal(accepted.cursor, 2); assert.equal(accepted.height, 0); assert.equal(accepted.tip, GENESIS_HASH);
assert.equal((await observer.observeCanonicalEvents(first)).replayed, true);
// A second consumer may have fetched a larger page before the first writer advanced the inbox.
const overlapping = await observer.observeCanonicalEvents(canonicalPage(streamId, events, 0, 4));
assert.equal(overlapping.cursor, 4); assert.equal(overlapping.replayed, false);
const stale = await observer.observeCanonicalEvents({ ...first, head: 2, caughtUp: true });
assert.equal(stale.cursor, 4); assert.equal(stale.head, 4); assert.equal(stale.replayed, true);
const tip = await observer.observeCanonicalEvents(second);
assert.equal(tip.cursor, 4); assert.equal(tip.head, 4); assert.equal(tip.height, 2); assert.equal(tip.tip, 'c'.repeat(64));
assert.equal(tip.replayed, true);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 4);
await observer.audit();
} finally { await observer.close(); }
const reopened = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
const replay = await reopened.observeCanonicalEvents(second);
assert.equal(replay.cursor, 4); assert.equal(replay.height, 2); assert.equal(replay.replayed, true);
await reopened.audit();
} finally { await reopened.close(); }
});
test('gated adapter consumes actual validated local Ledger connect and reorg pages', async t => {
const dir = await mkdtemp(join(tmpdir(), 'wikideck-pg-local-ledger-'));
const pow = new FixturePow(), ledger = await Ledger.open(dir, pow), f = await fixture(t);
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
t.after(async () => { await observer.close(); ledger.close(); pow.close(); await rm(dir, { recursive: true, force: true }); });
const canonical = await fixtureBlock([], {});
await ledger.submit(canonical);
const first = await observer.observeLedgerCanonicalEvents(ledger);
assert.equal(first.cursor, 1); assert.equal(first.height, 1); assert.equal(first.tip, canonical.hash);
const side = await fixtureBlock([], {}, 50001);
const sideResult = await ledger.submit(side); // Equal-work branch remains noncanonical and emits no event.
assert.equal(sideResult.accepted, true);
assert.equal(ledger.canonicalHead(), 1);
const sideChain = [side]; let sideState = transition({}, side, []), sideChild = side;
let reorgResult = { accepted: true, reorg: false };
for (let i = 0; i < 16 && !reorgResult.reorg; i++) {
sideChild = await fixtureBlock(sideChain, sideState, 50002 + i);
reorgResult = await ledger.submit(sideChild);
sideState = transition(sideState, sideChild, []); sideChain.push(sideChild);
}
assert.equal(reorgResult.reorg, true, `Expected longer-work side branch to win; events=${JSON.stringify(ledger.canonicalEvents(0))}`);
const expectedCursor = sideChain.length + 2; // initial connect, old-tip disconnect, then each side-branch connect
assert.equal(ledger.canonicalHead(), expectedCursor);
const reorg = await observer.observeLedgerCanonicalEvents(ledger);
assert.equal(reorg.cursor, expectedCursor); assert.equal(reorg.height, sideChain.length); assert.equal(reorg.tip, sideChild.hash);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, expectedCursor);
await observer.audit();
});
test('observer consumes a block independently validated with the native RandomX engine', { timeout: 120000 }, async t => {
const binary = resolve(process.env.RANDOMX_BINARY ?? join(process.cwd(), 'native', 'build', 'wikideck-randomx'));
try { await access(binary, constants.X_OK); }
catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
t.skip(`Native RandomX binary absent: ${binary}`); return;
}
const dir = await mkdtemp(join(tmpdir(), 'wikideck-pg-real-randomx-'));
const engine = new RandomX({ binary }), ledger = await Ledger.open(dir, engine), f = await fixture(t);
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
t.after(async () => { await observer.close(); ledger.close(); engine.close(); await rm(dir, { recursive: true, force: true }); });
const template = makeTemplate([], {}, seller.actor, seller.publicKey, [], fixtureArticle(55000), fixtureStats(), GENESIS_TIME + 30);
const header = structuredClone(template.header);
let mined: Awaited<ReturnType<typeof engine.mine>> = null;
for (let batch = 0; batch < 16 && !mined; batch++) {
mined = await engine.mine(header.key, miningBlob(header), header.target, 32, 64);
if (!mined) header.nonce += 64;
}
assert.ok(mined, 'Native RandomX fixture mining exceeded bounded attempts');
header.nonce = mined.nonce;
const blockHash = sha256(miningBlob(header));
const block: TestnetBlock = { ...header, hash: blockHash, tokenId: mintId(header), powHash: mined.hash,
signature: seller.sign(blockClaimMessage(blockHash)), transactions: [] };
await ledger.submit(block); // Ledger independently recalculates and validates native RandomX.
const page = await observer.observeLedgerCanonicalEvents(ledger);
assert.equal(page.cursor, 1); assert.equal(page.height, 1); assert.equal(page.tip, block.hash);
await observer.audit();
});
test('scheduled observer bounds catch-up, stops cleanly, and resumes its durable cursor after restart', async t => {
const dir = await mkdtemp(join(tmpdir(), 'wikideck-pg-observer-runner-'));
const pow = new EasyFixturePow(), ledger = await Ledger.open(dir, pow), f = await fixture(t);
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
t.after(async () => { await observer.close(); ledger.close(); pow.close(); await rm(dir, { recursive: true, force: true }); });
const chain: TestnetBlock[] = []; let state: NftStateView = {};
for (let i = 0; i < 66; i++) {
const block = await fixtureBlock(chain, state, 51000 + i, pow);
await ledger.submit(block); state = transition(state, block, []); chain.push(block);
}
const firstRunner = new CanonicalObserver(observer, ledger, { intervalMs: 60000, pagesPerCycle: 1 });
try {
const priorMode = process.env.NODE_ENV, priorGate = process.env.WIKIDECK_CUSTODY_CANONICAL_REHEARSAL;
try {
process.env.NODE_ENV = 'production';
assert.throws(() => new CanonicalObserver(observer, ledger), /non-production/);
process.env.NODE_ENV = 'test'; delete process.env.WIKIDECK_CUSTODY_CANONICAL_REHEARSAL;
assert.throws(() => new CanonicalObserver(observer, ledger), /non-production/);
} finally {
if (priorMode === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = priorMode;
if (priorGate === undefined) delete process.env.WIKIDECK_CUSTODY_CANONICAL_REHEARSAL;
else process.env.WIKIDECK_CUSTODY_CANONICAL_REHEARSAL = priorGate;
}
const first = await firstRunner.start();
assert.equal(first.state, 'running'); assert.equal(first.cursor, 64); assert.equal(first.head, 66); assert.equal(first.caughtUp, false);
const caughtUp = await firstRunner.pollNow();
assert.equal(caughtUp.cursor, 66); assert.equal(caughtUp.caughtUp, true); assert.equal(caughtUp.tip, chain.at(-1)!.hash);
await firstRunner.close(); assert.equal(firstRunner.status().state, 'stopped');
const next = await fixtureBlock(chain, state, 52000, pow);
await ledger.submit(next);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 66);
await assert.rejects(firstRunner.pollNow(), /not running/);
const restarted = new CanonicalObserver(observer, ledger, { intervalMs: 60000, pagesPerCycle: 1 });
try {
const resumed = await restarted.start();
assert.equal(resumed.cursor, 67); assert.equal(resumed.head, 67); assert.equal(resumed.caughtUp, true);
assert.equal(resumed.tip, next.hash);
} finally { await restarted.close(); }
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 67);
chain.push(next); state = transition(state, next, []);
const automatic = new CanonicalObserver(observer, ledger, { intervalMs: 25, pagesPerCycle: 1 });
try {
assert.equal((await automatic.start()).cursor, 67);
const automaticBlock = await fixtureBlock(chain, state, 52001, pow);
await ledger.submit(automaticBlock);
const deadline = Date.now() + 3000;
while (automatic.status().cursor < 68 && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 10));
assert.equal(automatic.status().cursor, 68, 'scheduled poll did not consume the new ledger event');
} finally { await automatic.close(); }
} finally { await firstRunner.close(); }
});
test('canonical inbox rejects a disconnected block with a forged but rehashed ancestor', async t => {
const f = await fixture(t), observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const streamId = randomUUID(), events = canonicalEvents(streamId);
try {
events[1].previousHash = '9'.repeat(64);
reanchorCanonicalEvents(streamId, events);
await assert.rejects(observer.observeCanonicalEvents(canonicalPage(streamId, events, 0, 2)), /disconnect-ancestry-mismatch/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 0);
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity/);
} finally { await observer.close(); }
});
test('canonical inbox rejects tampered event anchors, persists an integrity hold and refuses another stream', async t => {
const f = await fixture(t), observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const streamId = randomUUID(), events = canonicalEvents(streamId);
try {
const tampered = canonicalPage(streamId, events, 0, 2);
tampered.events[0].transactionIds = ['f'.repeat(64)];
await assert.rejects(observer.observeCanonicalEvents(tampered), /canonical-inbox-event-hash-invalid/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 0);
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity/);
} finally { await observer.close(); }
});
test('canonical inbox startup audit detects owner-side history mutation and durably fences writers', async t => {
const f = await fixture(t), observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const streamId = randomUUID(), events = canonicalEvents(streamId);
try {
await observer.observeCanonicalEvents(canonicalPage(streamId, events, 0, 2));
const changed = { ...events[0], transactionIds: ['f'.repeat(64)] };
await f.admin.query('UPDATE wk_custody.custody_meta SET value=$1 WHERE key=$2',
[JSON.stringify(changed), `canonical-inbox:v1:event:${'1'.padStart(16, '0')}`]);
await assert.rejects(observer.audit(), /canonical-inbox-event-anchor-invalid/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'")).rows[0].n, 1);
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity/);
} finally { await observer.close(); }
});
test('signed candidate completion settles actual PostgreSQL balances once across pools and reconnect', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100000000000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const transcript = saleTranscript(o, f.market.id);
const a = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const b = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const mature = { ...transcript.input, blocks: [[transcript.input.lock], [], [transcript.complete], [], []] };
try {
const immature = { ...mature, blocks: mature.blocks.slice(0, 3) };
assert.equal((await a.observeSaleRehearsal(o.id, immature)).action, 'hold');
const results = await Promise.all([a.observeSaleRehearsal(o.id, mature), b.observeSaleRehearsal(o.id, mature)]);
assert.deepEqual(results.map(r => r.action).sort(), ['none', 'settle']);
assert.equal((await f.activity(seller)).availableAtomicXmr, '96500000000');
assert.equal((await f.activity()).purchaseReservedAtomicXmr, '0');
const claims = await f.admin.query("SELECT id,balance::text FROM wk_custody.accounts WHERE id LIKE 'author:%' OR id LIKE 'revenue:host:%'");
assert.deepEqual(claims.rows.map(r => r.balance).sort(), ['1000000000', '2500000000']);
assert.equal((await a.audit()).journals, 3);
const reopened = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try { assert.equal((await reopened.observeSaleRehearsal(o.id, mature)).action, 'none'); await reopened.audit(); }
finally { await reopened.close(); }
// Fork replaces completion with valid cancellation. Financial history stays intact and incident is durable.
const fork = { ...mature, blocks: [[transcript.input.lock], [], [transcript.cancel], [], []] };
await assert.rejects(a.observeSaleRehearsal(o.id, fork), /outcome-reorg/);
await assert.rejects(b.observeSaleRehearsal(o.id, mature), /incident unresolved/);
assert.equal((await f.activity(seller)).availableAtomicXmr, '96500000000');
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.journals WHERE kind='mock-sale-settlement'")).rows[0].n, 1);
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity/);
} finally { await a.close(); await b.close(); }
});
test('disconnect of the exact posted candidate terminal transaction commits an order-linked review hold', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100000000000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const s = saleTranscript(o, f.market.id), transcript = { ...s.input, blocks: [[s.input.lock], [], [s.complete], [], []] };
const terminalTxId = saleCandidateTransactionId(s.complete);
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const observer = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
const streamId = randomUUID(), events = canonicalEvents(streamId, terminalTxId);
try {
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'settle');
const credited = await f.activity(seller);
assert.equal(credited.availableAtomicXmr, '96500000000');
const first = await observer.observeCanonicalEvents(canonicalPage(streamId, events, 0, 1));
assert.equal(first.cursor, 1);
// The same transaction reappears on the replacement branch in this page; still review, never auto-clear.
const review = await observer.observeCanonicalEvents(canonicalPage(streamId, events, 1, 2));
assert.equal(review.reviewRequired, true);
assert.equal(review.cursor, 3); // Disconnect and reappearance are durably retained with the review marker.
await assert.rejects(observer.observeCanonicalEvents(canonicalPage(streamId, events, 1, 2)), /hold|incident unresolved/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'canonical-inbox:v1:event:%'")).rows[0].n, 3);
assert.equal((await f.activity(seller)).availableAtomicXmr, credited.availableAtomicXmr);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.journals WHERE id=$1", [`settle:${o.id}`])).rows[0].n, 1);
const incident = await f.admin.query("SELECT value FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'");
assert.match(incident.rows[0].value, /posted-sale-terminal-disconnected/);
await assert.rejects(observer.audit(), /unresolved-integrity-incident/);
await assert.rejects(resolveCustodyIncident(f.admin, randomUUID()), /posted-sale-terminal-disconnected/);
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'sale-reorg-review:%'")).rows[0].n, 1);
await assert.rejects(PostgresCustody.open(f.runtime, options), /unresolved-integrity/);
await assert.rejects(f.execute(other, { type: 'mock-credit' }), /incident is unresolved/);
} finally { await observer.close(); await rehearsal.close(); }
});
test('candidate cancellation and withheld-lock expiry release actual reservations only after depth', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
for (const withheld of [false, true]) {
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const s = saleTranscript(o, f.market.id);
const transcript = { ...s.input, blocks: withheld ? [[], [], [], [], []] : [[s.input.lock], [s.cancel], []] };
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'hold');
transcript.blocks.push([]);
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'refund');
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'none');
assert.equal((await f.activity()).availableAtomicXmr, '1000000000000');
await rehearsal.audit();
}
} finally { await rehearsal.close(); }
});
test('rehearsal rejects a different fee rate even when tiny-order commission rounding is identical', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const s = saleTranscript(o, f.market.id, 251);
assert.throws(() => replayReservedSale(o, f.market.id, s.input, l.feeBps), /fee rate/);
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, s.input), /operation failed/);
assert.equal((await f.activity()).purchaseReservedAtomicXmr, '1000');
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'sale-rehearsal-%'")).rows[0].n, 0);
} finally { await rehearsal.close(); }
});
test('rehearsal preserves one-atomic sales and the signed custom host recipient across policy changes', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const recipient = await syntheticFeeAddress();
const custom = await PostgresCustody.open(f.runtime, { ...options, hostFeeAddress: recipient });
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
for (const price of ['1', '1000']) {
const l = await custom.execute(envelope(custom.id, seller, { type: 'list', cardHash: hash, sequence: 0,
priceAtomicXmr: price, feeBps: 250, hostFeeAddress: recipient })) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: price, feeBps: 250, hostFeeAddress: recipient }) as CustodyOrder;
const s = saleTranscript(o, f.market.id);
const changed = structuredClone(s.input); changed.lock.terms.hostFeeAddress = f.market.info().hostFeeAddress;
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, changed), /operation failed/);
const transcript = { ...s.input, blocks: [[s.input.lock], [], [s.complete], [], []] };
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'settle');
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'none');
await rehearsal.audit();
}
assert.equal((await f.activity(seller)).availableAtomicXmr, '966');
assert.equal((await f.admin.query('SELECT balance::text FROM wk_custody.accounts WHERE id=$1', [`revenue:host:${recipient}`])).rows[0].balance, '25');
assert.equal((await f.admin.query("SELECT count(*)::int AS n FROM wk_custody.custody_meta WHERE key LIKE 'sale-rehearsal-observation:%'")).rows[0].n, 2);
} finally { await custom.close(); await rehearsal.close(); }
});
test('sale terminal-posting evidence failure rolls back seller credit and remains safely retryable', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const l = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250 }) as CustodyListing;
const o = await f.execute(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250 }) as CustodyOrder;
const s = saleTranscript(o, f.market.id), transcript = { ...s.input, blocks: [[s.input.lock], [], [s.complete], [], []] };
const rehearsal = await PostgresCustody.open(f.runtime, { ...options, disposableSaleRehearsal: true });
try {
await f.admin.query("CREATE FUNCTION wk_custody.fail_terminal() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.key LIKE 'sale-rehearsal-terminal:%' THEN RAISE EXCEPTION 'fixture terminal failure'; END IF; RETURN NEW; END $$; CREATE TRIGGER fail_terminal BEFORE INSERT ON wk_custody.custody_meta FOR EACH ROW EXECUTE FUNCTION wk_custody.fail_terminal()");
await assert.rejects(rehearsal.observeSaleRehearsal(o.id, transcript), /operation failed/);
assert.equal((await f.activity(seller)).availableAtomicXmr, '0');
assert.equal((await f.activity()).purchaseReservedAtomicXmr, '1000');
await f.admin.query('DROP TRIGGER fail_terminal ON wk_custody.custody_meta');
assert.equal((await rehearsal.observeSaleRehearsal(o.id, transcript)).action, 'settle');
await rehearsal.audit();
await f.admin.query("UPDATE wk_custody.custody_meta SET value='{}' WHERE key=$1", [`sale-rehearsal-terminal:${o.id}`]);
await assert.rejects(rehearsal.audit(), /evidence-mismatch/);
} finally { await rehearsal.close(); }
});