Bounded test succeeded; native RandomX miner accepted one block
This commit is contained in:
Executable
+265
@@ -0,0 +1,265 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import type { Pool, PoolClient } from 'pg';
|
||||
import { z } from 'zod';
|
||||
import { XMR_RPC_UINT64_MAX } from '../../shared/xmr-amount.ts';
|
||||
import { CHAIN_ID } from '../testnet/consensus.ts';
|
||||
import { IS_PILOT, NETWORK } from '../../shared/network.ts';
|
||||
import { verifyCustodySchema } from './postgres-schema.ts';
|
||||
import { pilotCustodyIdentityFromRow } from './pilot-custody-identity.ts';
|
||||
import { validateXmrSubaddress } from './xmr.ts';
|
||||
|
||||
const HEX64 = z.string().regex(/^[a-f0-9]{64}$/);
|
||||
const UUID = z.string().uuid();
|
||||
const OBSERVATION_ID = /^[a-f0-9]{64}:[a-f0-9]{64}$/;
|
||||
const UINT64 = /^(0|[1-9]\d{0,19})$/;
|
||||
const MAX_EVENT_HISTORY = 256;
|
||||
const MAX_BLOCK_UNLOCK = 500_000_000n;
|
||||
|
||||
export type PilotDepositReviewPolicy = {
|
||||
minimumConfirmations: number;
|
||||
maximumObservationAgeMs: number;
|
||||
};
|
||||
|
||||
const subjectWithoutDigestSchema = z.object({
|
||||
policyVersion: z.literal('pilot-monero-deposit-review-v1'),
|
||||
preparationOnly: z.literal(true),
|
||||
crediting: z.literal('disabled'),
|
||||
marketNetwork: z.literal('wikideck-randomx-pilot-1'),
|
||||
marketChainId: HEX64,
|
||||
moneroNetwork: z.literal('mainnet'),
|
||||
custodianId: UUID,
|
||||
activationStatus: z.literal('identity-only'),
|
||||
walletGenerationId: UUID,
|
||||
walletFingerprint: HEX64,
|
||||
allocationId: UUID,
|
||||
beneficiary: HEX64,
|
||||
accountIndex: z.number().int().nonnegative().max(0xffff_ffff).safe(),
|
||||
addressIndex: z.number().int().nonnegative().max(0xffff_ffff).safe(),
|
||||
moneroAddress: z.string().length(95),
|
||||
observationId: z.string().regex(OBSERVATION_ID),
|
||||
transactionId: HEX64,
|
||||
outputPublicKey: HEX64,
|
||||
amountAtomicXmr: z.string().regex(/^[1-9]\d{0,19}$/),
|
||||
blockHeight: z.number().int().positive().safe(),
|
||||
walletHeight: z.number().int().positive().safe(),
|
||||
confirmations: z.number().int().positive().safe(),
|
||||
minimumConfirmations: z.number().int().min(10).max(100_000).safe(),
|
||||
unlockTime: z.string().regex(UINT64),
|
||||
locked: z.literal(false),
|
||||
doubleSpendSeen: z.literal(false),
|
||||
currentlyPresent: z.literal(true),
|
||||
globalOutputIndex: z.number().int().nonnegative().safe(),
|
||||
observationDigest: HEX64,
|
||||
scanId: UUID,
|
||||
scanFromHeight: z.number().int().nonnegative().safe(),
|
||||
scanThroughHeight: z.number().int().nonnegative().safe(),
|
||||
observerScanHeight: z.number().int().nonnegative().safe(),
|
||||
scanCompletedAt: z.number().int().nonnegative().safe(),
|
||||
observerUpdatedAt: z.number().int().nonnegative().safe(),
|
||||
lastObservedAt: z.number().int().nonnegative().safe(),
|
||||
scanFreshnessMs: z.number().int().nonnegative().safe(),
|
||||
maximumObservationAgeMs: z.number().int().min(10_000).max(15 * 60_000).safe(),
|
||||
eventHistorySequence: z.number().int().nonnegative().safe(),
|
||||
eventHistoryCount: z.number().int().positive().max(MAX_EVENT_HISTORY).safe(),
|
||||
eventHistoryDigest: HEX64,
|
||||
preparedAt: z.number().int().nonnegative().safe(),
|
||||
}).strict();
|
||||
|
||||
export type PilotDepositReviewSubject = z.infer<typeof subjectWithoutDigestSchema> & { subjectDigest: string };
|
||||
|
||||
const reviewSubjectSchema = subjectWithoutDigestSchema.extend({ subjectDigest: HEX64 }).strict();
|
||||
const inputSchema = z.object({
|
||||
walletGenerationId: UUID,
|
||||
observationId: z.string().regex(OBSERVATION_ID),
|
||||
}).strict();
|
||||
|
||||
function digest(value: string): string {
|
||||
return createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
|
||||
function safeInteger(value: unknown, name: string, minimum = 0): number {
|
||||
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'bigint') {
|
||||
throw new Error(`Invalid pilot review ${name}`);
|
||||
}
|
||||
const number = Number(value);
|
||||
if (!Number.isSafeInteger(number) || number < minimum) throw new Error(`Invalid pilot review ${name}`);
|
||||
return number;
|
||||
}
|
||||
|
||||
function validatePolicy(policy: PilotDepositReviewPolicy): void {
|
||||
if (!Number.isSafeInteger(policy.minimumConfirmations) || policy.minimumConfirmations < 10 ||
|
||||
policy.minimumConfirmations > 100_000 || !Number.isSafeInteger(policy.maximumObservationAgeMs) ||
|
||||
policy.maximumObservationAgeMs < 10_000 || policy.maximumObservationAgeMs > 15 * 60_000) {
|
||||
throw new Error('Invalid pilot deposit review policy');
|
||||
}
|
||||
}
|
||||
|
||||
function assertUnlockMature(unlockTime: string, walletHeight: number, scanCompletedAt: number): void {
|
||||
if (!UINT64.test(unlockTime)) throw new Error('Pilot deposit unlock time is malformed');
|
||||
const unlock = BigInt(unlockTime);
|
||||
if (unlock === 0n) return;
|
||||
const mature = unlock < MAX_BLOCK_UNLOCK
|
||||
? BigInt(walletHeight) >= unlock
|
||||
: BigInt(Math.floor(scanCompletedAt / 1000)) >= unlock;
|
||||
if (!mature) throw new Error('Pilot deposit output has a future unlock time');
|
||||
}
|
||||
|
||||
function validateEvents(rows: Record<string, unknown>[], observationDigest: string) {
|
||||
if (!rows.length || rows.length > MAX_EVENT_HISTORY) {
|
||||
throw new Error('Pilot observation event history is missing or exceeds the review bound');
|
||||
}
|
||||
const events = rows.map(row => {
|
||||
const sequence = safeInteger(row.sequence, 'event sequence');
|
||||
const observedAt = safeInteger(row.observed_at, 'event timestamp');
|
||||
const walletHeight = safeInteger(row.wallet_height, 'event wallet height');
|
||||
const payloadDigest = HEX64.parse(row.payload_digest);
|
||||
const eventType = z.enum(['seen', 'changed', 'reappeared', 'not-seen']).parse(row.event_type);
|
||||
const scanId = UUID.parse(row.scan_id);
|
||||
return { sequence, observedAt, walletHeight, payloadDigest, eventType, scanId };
|
||||
});
|
||||
if (events.some(event => event.eventType !== 'seen' || event.payloadDigest !== observationDigest)) {
|
||||
throw new Error('Pilot observation history contains change, disappearance, or reappearance evidence and needs separate review');
|
||||
}
|
||||
const last = events.at(-1)!;
|
||||
const historyDigest = digest(JSON.stringify(events.map(event => [event.sequence, event.eventType,
|
||||
event.payloadDigest, event.walletHeight, event.observedAt, event.scanId])));
|
||||
return { sequence: last.sequence, count: events.length, digest: historyDigest };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an operator-facing pilot deposit review snapshot from persisted observations only.
|
||||
* This function is read-only and does not approve, stage, post, release, or credit funds.
|
||||
*/
|
||||
export async function preparePilotMoneroDepositReview(pool: Pool, inputValue: unknown,
|
||||
policy: PilotDepositReviewPolicy, now = Date.now()): Promise<PilotDepositReviewSubject> {
|
||||
if (!IS_PILOT || NETWORK !== 'wikideck-randomx-pilot-1') {
|
||||
throw new Error('Pilot deposit review preparation requires the pilot profile');
|
||||
}
|
||||
const input = inputSchema.parse(inputValue);
|
||||
validatePolicy(policy);
|
||||
if (!Number.isSafeInteger(now) || now < 0) throw new Error('Invalid pilot review time');
|
||||
|
||||
const client: PoolClient = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
await client.query("SET LOCAL statement_timeout='10s'; SET LOCAL lock_timeout='3s'; SET LOCAL idle_in_transaction_session_timeout='10s'");
|
||||
await verifyCustodySchema(client);
|
||||
|
||||
const identityResult = await client.query('SELECT * FROM wk_custody.pilot_custody_identity WHERE singleton=1');
|
||||
if (identityResult.rowCount !== 1) throw new Error('Pilot custody identity is not initialized');
|
||||
const identity = pilotCustodyIdentityFromRow(identityResult.rows[0]);
|
||||
if (identity.marketChainId !== CHAIN_ID || identity.marketNetwork !== NETWORK ||
|
||||
identity.moneroNetwork !== 'mainnet' || identity.activationStatus !== 'identity-only') {
|
||||
throw new Error('Pilot custody identity does not permit review preparation');
|
||||
}
|
||||
|
||||
const observations = await client.query(`SELECT
|
||||
o.wallet_generation_id::text,o.observation_id,o.allocation_id::text,o.classification,o.txid,o.monero_address,
|
||||
o.amount_atomic_xmr::text AS amount_atomic_xmr,o.block_height::text AS block_height,
|
||||
o.wallet_height::text AS wallet_height,o.confirmations::text AS confirmations,o.unlock_time,
|
||||
o.account_index::text AS account_index,o.address_index::text AS address_index,o.output_public_key,
|
||||
o.global_output_index::text AS global_output_index,o.locked,o.double_spend_seen,o.payload_digest,
|
||||
o.currently_present,o.last_seen_scan_id::text AS scan_id,o.last_observed_at::text AS last_observed_at,
|
||||
s.wallet_fingerprint,s.network,s.scan_height::text AS observer_scan_height,s.updated_at::text AS observer_updated_at,
|
||||
sc.from_height::text AS scan_from_height,sc.through_height::text AS scan_through_height,
|
||||
sc.completed_at::text AS scan_completed_at
|
||||
FROM wk_custody.pilot_monero_observations o
|
||||
JOIN wk_custody.pilot_monero_observer_state s USING(wallet_generation_id)
|
||||
JOIN wk_custody.pilot_monero_observation_scans sc
|
||||
ON sc.scan_id=o.last_seen_scan_id AND sc.wallet_generation_id=o.wallet_generation_id
|
||||
WHERE o.wallet_generation_id=$1 AND o.observation_id=$2`, [input.walletGenerationId, input.observationId]);
|
||||
if (observations.rowCount !== 1) throw new Error('Pilot deposit observation was not found for this wallet generation');
|
||||
const row = observations.rows[0];
|
||||
if (row.wallet_generation_id !== input.walletGenerationId || row.observation_id !== input.observationId ||
|
||||
row.network !== identity.moneroNetwork || row.classification !== 'assigned' || row.allocation_id === null ||
|
||||
row.currently_present !== true || row.double_spend_seen !== false || row.locked !== false) {
|
||||
throw new Error('Pilot deposit observation is not an assigned, present, unlocked, non-double-spent output');
|
||||
}
|
||||
|
||||
const txid = HEX64.parse(row.txid);
|
||||
const outputPublicKey = HEX64.parse(row.output_public_key);
|
||||
if (row.observation_id !== `${txid}:${outputPublicKey}`) throw new Error('Pilot observation ID does not match its output identity');
|
||||
const amountAtomicXmr = z.string().regex(/^[1-9]\d{0,19}$/).parse(row.amount_atomic_xmr);
|
||||
const amount = BigInt(amountAtomicXmr);
|
||||
if (amount > XMR_RPC_UINT64_MAX) throw new Error('Pilot deposit amount exceeds the Monero RPC uint64 range');
|
||||
|
||||
const blockHeight = safeInteger(row.block_height, 'block height', 1);
|
||||
const walletHeight = safeInteger(row.wallet_height, 'wallet height', 1);
|
||||
const confirmations = safeInteger(row.confirmations, 'confirmation count', 1);
|
||||
const observerScanHeight = safeInteger(row.observer_scan_height, 'observer scan height');
|
||||
const scanFromHeight = safeInteger(row.scan_from_height, 'scan start height');
|
||||
const scanThroughHeight = safeInteger(row.scan_through_height, 'scan end height');
|
||||
const scanCompletedAt = safeInteger(row.scan_completed_at, 'scan completion time');
|
||||
const observerUpdatedAt = safeInteger(row.observer_updated_at, 'observer update time');
|
||||
const lastObservedAt = safeInteger(row.last_observed_at, 'last observation time');
|
||||
const accountIndex = safeInteger(row.account_index, 'account index');
|
||||
const addressIndex = safeInteger(row.address_index, 'address index');
|
||||
const globalOutputIndex = safeInteger(row.global_output_index, 'global output index');
|
||||
if (confirmations !== walletHeight - blockHeight + 1 || confirmations < policy.minimumConfirmations) {
|
||||
throw new Error('Pilot deposit output is below the configured confirmation floor');
|
||||
}
|
||||
if (observerScanHeight < walletHeight || scanThroughHeight < walletHeight ||
|
||||
scanFromHeight > blockHeight || scanThroughHeight < blockHeight || lastObservedAt !== scanCompletedAt ||
|
||||
observerUpdatedAt < scanCompletedAt) {
|
||||
throw new Error('Pilot observation scan is not caught up or its persisted scan context is inconsistent');
|
||||
}
|
||||
const scanFreshnessMs = now - observerUpdatedAt;
|
||||
if (scanFreshnessMs < 0 || now - scanCompletedAt < 0 || now - lastObservedAt < 0 ||
|
||||
scanFreshnessMs > policy.maximumObservationAgeMs || now - scanCompletedAt > policy.maximumObservationAgeMs ||
|
||||
now - lastObservedAt > policy.maximumObservationAgeMs) {
|
||||
throw new Error('Pilot Monero observation scan is stale or has a future timestamp');
|
||||
}
|
||||
assertUnlockMature(row.unlock_time, walletHeight, scanCompletedAt);
|
||||
|
||||
const allocations = await client.query(`SELECT allocation_id::text,custodian_id::text,market_network,market_chain_id,
|
||||
monero_network,beneficiary,wallet_generation_id::text,wallet_fingerprint,account_index::text AS account_index,
|
||||
address_index::text AS address_index,status,monero_address
|
||||
FROM wk_custody.pilot_monero_deposit_reservations WHERE allocation_id=$1`, [row.allocation_id]);
|
||||
if (allocations.rowCount !== 1) throw new Error('Pilot assigned output has no unique durable reservation');
|
||||
const allocation = allocations.rows[0];
|
||||
const beneficiary = HEX64.parse(allocation.beneficiary);
|
||||
if (allocation.custodian_id !== identity.custodianId || allocation.market_network !== identity.marketNetwork ||
|
||||
allocation.market_chain_id !== identity.marketChainId || allocation.monero_network !== identity.moneroNetwork ||
|
||||
allocation.wallet_generation_id !== input.walletGenerationId || allocation.wallet_fingerprint !== row.wallet_fingerprint ||
|
||||
safeInteger(allocation.account_index, 'reserved account index') !== accountIndex ||
|
||||
safeInteger(allocation.address_index, 'reserved address index') !== addressIndex ||
|
||||
allocation.status !== 'registered' || allocation.monero_address !== row.monero_address) {
|
||||
throw new Error('Pilot output does not exactly match its immutable registered reservation and identity');
|
||||
}
|
||||
const moneroAddress = await validateXmrSubaddress(z.string().length(95).parse(row.monero_address));
|
||||
const walletFingerprint = HEX64.parse(row.wallet_fingerprint);
|
||||
const observationDigest = HEX64.parse(row.payload_digest);
|
||||
const scanId = UUID.parse(row.scan_id);
|
||||
|
||||
const history = await client.query(`SELECT sequence::text,event_type,payload_digest,wallet_height::text AS wallet_height,
|
||||
observed_at::text AS observed_at,scan_id::text
|
||||
FROM wk_custody.pilot_monero_observation_events
|
||||
WHERE wallet_generation_id=$1 AND observation_id=$2 ORDER BY sequence LIMIT $3`,
|
||||
[input.walletGenerationId, input.observationId, MAX_EVENT_HISTORY + 1]);
|
||||
if (history.rows.length > MAX_EVENT_HISTORY) throw new Error('Pilot observation event history exceeds the review bound');
|
||||
const eventHistory = validateEvents(history.rows, observationDigest);
|
||||
|
||||
const unsigned = subjectWithoutDigestSchema.parse({
|
||||
policyVersion: 'pilot-monero-deposit-review-v1', preparationOnly: true, crediting: 'disabled',
|
||||
marketNetwork: identity.marketNetwork, marketChainId: identity.marketChainId, moneroNetwork: identity.moneroNetwork,
|
||||
custodianId: identity.custodianId, activationStatus: identity.activationStatus,
|
||||
walletGenerationId: input.walletGenerationId, walletFingerprint, allocationId: allocation.allocation_id,
|
||||
beneficiary, accountIndex, addressIndex, moneroAddress, observationId: input.observationId,
|
||||
transactionId: txid, outputPublicKey, amountAtomicXmr, blockHeight, walletHeight, confirmations,
|
||||
minimumConfirmations: policy.minimumConfirmations, unlockTime: z.string().regex(UINT64).parse(row.unlock_time),
|
||||
locked: false, doubleSpendSeen: false, currentlyPresent: true, globalOutputIndex, observationDigest,
|
||||
scanId, scanFromHeight, scanThroughHeight, observerScanHeight, scanCompletedAt, observerUpdatedAt,
|
||||
lastObservedAt, scanFreshnessMs, maximumObservationAgeMs: policy.maximumObservationAgeMs,
|
||||
eventHistorySequence: eventHistory.sequence, eventHistoryCount: eventHistory.count,
|
||||
eventHistoryDigest: eventHistory.digest, preparedAt: now,
|
||||
});
|
||||
const subject = reviewSubjectSchema.parse({ ...unsigned, subjectDigest: digest(JSON.stringify(unsigned)) });
|
||||
await client.query('COMMIT');
|
||||
return subject;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user