First deposit integration slice

This commit is contained in:
2026-10-21 20:07:27 -04:00
parent e3b2261da3
commit af646dcc32
10 changed files with 223 additions and 46 deletions
+31 -3
View File
@@ -1,7 +1,7 @@
import { createHash } from 'node:crypto';
import type { Pool, PoolClient } from 'pg';
export const CUSTODY_PG_VERSION = 3;
export const CUSTODY_PG_VERSION = 4;
export const CUSTODY_WRITE_LOCK = '7140824361';
export const CUSTODY_SCHEMA_V1 = `
CREATE SCHEMA IF NOT EXISTS wk_custody;
@@ -175,9 +175,32 @@ REVOKE ALL ON FUNCTION wk_custody.monero_deposit_allocations_immutable() FROM PU
`;
export const CUSTODY_SCHEMA_V3_DIGEST = createHash('sha256')
.update(`${CUSTODY_SCHEMA_V1}\n-- custody migration v2 --\n${CUSTODY_MIGRATION_V2}\n-- custody migration v3 --\n${CUSTODY_MIGRATION_V3}`).digest('hex');
export const CUSTODY_MIGRATION_V4 = `
DO $$
DECLARE old_constraint text;
BEGIN
SELECT conname INTO old_constraint FROM pg_constraint
WHERE conrelid='wk_custody.monero_transfer_observations'::regclass AND contype='u'
AND pg_get_constraintdef(oid)='UNIQUE (wallet_fingerprint, txid, account_index, address_index)';
IF old_constraint IS NOT NULL THEN
EXECUTE format('ALTER TABLE wk_custody.monero_transfer_observations DROP CONSTRAINT %I',old_constraint);
END IF;
END $$;
ALTER TABLE wk_custody.monero_transfer_observations
ADD COLUMN output_public_key text CHECK(output_public_key IS NULL OR output_public_key ~ '^[a-f0-9]{64}$'),
ADD COLUMN global_output_index bigint CHECK(global_output_index IS NULL OR global_output_index>=0),
ADD CONSTRAINT monero_observation_output_identity CHECK(
(output_public_key IS NULL AND global_output_index IS NULL) OR
(output_public_key IS NOT NULL AND global_output_index IS NOT NULL AND observation_id=(txid || ':' || output_public_key)));
CREATE UNIQUE INDEX monero_observations_output_key
ON wk_custody.monero_transfer_observations(wallet_fingerprint,output_public_key)
WHERE output_public_key IS NOT NULL;
`;
export const CUSTODY_SCHEMA_V4_DIGEST = createHash('sha256')
.update(`${CUSTODY_SCHEMA_V1}\n-- custody migration v2 --\n${CUSTODY_MIGRATION_V2}\n-- custody migration v3 --\n${CUSTODY_MIGRATION_V3}\n-- custody migration v4 --\n${CUSTODY_MIGRATION_V4}`).digest('hex');
/** Retained alias for callers that need the initial schema definition in disposable fixtures. */
export const CUSTODY_SCHEMA = CUSTODY_SCHEMA_V1;
export const CUSTODY_SCHEMA_DIGEST = CUSTODY_SCHEMA_V2_DIGEST;
export const CUSTODY_SCHEMA_DIGEST = CUSTODY_SCHEMA_V4_DIGEST;
/** Explicit operator migration; the HTTP runtime never creates or upgrades schema. */
export async function migrateCustody(pool: Pool): Promise<void> {
@@ -200,6 +223,11 @@ export async function migrateCustody(pool: Pool): Promise<void> {
if (current === 2) {
await c.query(CUSTODY_MIGRATION_V3);
await c.query('INSERT INTO wk_custody.schema_versions VALUES(3,$1)', [CUSTODY_SCHEMA_V3_DIGEST]);
current = 3;
}
if (current === 3) {
await c.query(CUSTODY_MIGRATION_V4);
await c.query('INSERT INTO wk_custody.schema_versions VALUES(4,$1)', [CUSTODY_SCHEMA_V4_DIGEST]);
}
if (current > CUSTODY_PG_VERSION) throw new Error('Custody PostgreSQL schema is newer than this application');
await verifyCustodySchema(c);
@@ -213,7 +241,7 @@ export async function verifyCustodySchema(c: PoolClient): Promise<void> {
}
async function verifyCustodySchemaHistory(c: PoolClient): Promise<number> {
const rows = await c.query('SELECT version,digest FROM wk_custody.schema_versions ORDER BY version');
const expected = [CUSTODY_SCHEMA_V1_DIGEST, CUSTODY_SCHEMA_V2_DIGEST, CUSTODY_SCHEMA_V3_DIGEST];
const expected = [CUSTODY_SCHEMA_V1_DIGEST, CUSTODY_SCHEMA_V2_DIGEST, CUSTODY_SCHEMA_V3_DIGEST, CUSTODY_SCHEMA_V4_DIGEST];
if (!rows.rowCount || rows.rowCount > expected.length || rows.rows.some((row, index) =>
Number(row.version) !== index + 1 || row.digest !== expected[index])) {
throw new Error('Custody PostgreSQL schema version/digest history mismatch; explicit migration required');