Implemented PostgreSQL v3 allocation-registry groundwork

This commit is contained in:
2026-10-21 14:59:15 -04:00
parent ee5dc35330
commit e70199ecb0
6 changed files with 360 additions and 9 deletions
+97 -4
View File
@@ -14,8 +14,10 @@ import { PostgresCustody, auditPostgres } from '../../server/market/postgres-cus
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 } from '../../server/market/postgres-schema.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 { 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';
@@ -62,6 +64,19 @@ async function syntheticFeeAddress() {
}
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);
@@ -175,7 +190,7 @@ test('explicit PostgreSQL migrations are idempotent and runtime role cannot alte
assert.equal((await f.market.audit()).assetsAtomicXmr, '1000000000000');
});
test('custody schema v1 upgrades additively to observation-only v2 without dropping existing metadata', async t => {
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();
@@ -189,18 +204,96 @@ test('custody schema v1 upgrades additively to observation-only v2 without dropp
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]);
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/);
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('CREATE ROLE monero_observer LOGIN');
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' });