Added native RandomX integration check
This commit is contained in:
@@ -37,7 +37,7 @@ The same explicitly disposable PostgreSQL instance now has `observeCanonicalEven
|
||||
|
||||
An opt-in `CanonicalObserver` now schedules one bounded poll cycle on the server runtime, resumes from its durable cursor after restart, and stops/drains before PostgreSQL and the ledger close. Each cycle processes at most four pages of 64 events; lag is exposed on the private operations listener. Poll failures or sale-review events stop the loop fail-closed; it does not retry a failed observer automatically. The runtime opt-in requires `WIKIDECK_CUSTODY_CANONICAL_REHEARSAL=1`, PostgreSQL custody and `WIKIDECK_PG_DISPOSABLE_TEST=1`; the configured and actual database must also be `custody_test`, and `NODE_ENV=production` is explicitly rejected. Invalid/missing combinations fail closed, including direct construction of the polling controller. Shutdown stops scheduling immediately and drains the current page before custody/ledger close. This is **not enabled in the persistent sandbox or any regular deployment** and has no public HTTP control endpoint.
|
||||
|
||||
PostgreSQL tests include synthetic event pages for malformed/retry cases and local `Ledger` blocks configured with a **test-only fake PoW engine**, including an equal-work side branch and a reorg. They exercise the adapter and bounded scheduler lifecycle, not real RandomX validation, external provenance or production multi-node behavior. Hash-anchor consistency is not proof of honest observation; the stream ID and unkeyed hashes are stored beside the events, so a schema owner able to coherently rewrite the complete history can also recompute the hashes. The included corruption test detects a partial edit, not a fully rewritten valid history. Production must anchor custody checkpoints outside the database's mutable trust domain. The disposable inbox is capped at 4,096 total events, stores the append-only event history and fully rescans it during audits. That is intentionally a small-fixture design, not production storage/cursor architecture or a scale claim.
|
||||
PostgreSQL tests include synthetic event pages for malformed/retry cases, local `Ledger` blocks configured with a **test-only fake PoW engine** for equal-work side-branch/reorg transitions, and one separately mined and independently revalidated block using the **native RandomX engine** before its canonical event is consumed. This proves the real PoW validation → local event API → PostgreSQL inbox path for a single block; it does not establish production multi-node reliability or sale-candidate transaction support. Hash-anchor consistency is not proof of honest observation; the stream ID and unkeyed hashes are stored beside the events, so a schema owner able to coherently rewrite the complete history can also recompute the hashes. The included corruption test detects a partial edit, not a fully rewritten valid history. Production must anchor custody checkpoints outside the database's mutable trust domain. The disposable inbox is capped at 4,096 total events, stores the append-only event history and fully rescans it during audits. That is intentionally a small-fixture design, not production storage/cursor architecture or a scale claim.
|
||||
|
||||
The synthetic PostgreSQL event-page seam now links an explicitly terminal candidate transaction ID to its posted order. A later **submitted disconnect event** containing that exact ID commits both the canonical event page and a durable per-order `sale-reorg-review` record plus global `posted-sale-terminal-disconnected` mutation hold in one transaction; the original journal is not reversed or paid again. This remains true if the transaction reappears later in the same replacement-branch page: review does not auto-clear on apparent re-inclusion. Generic incident resolution re-audits the retained review record and therefore cannot clear it. This exercises order-to-event linkage and atomic review behavior. The hold only detects disconnects actually delivered to this seam; it cannot detect an unseen intermediate fork.
|
||||
|
||||
@@ -45,7 +45,7 @@ The local-ledger adapter is **not** linked to these terminal candidate IDs: acti
|
||||
|
||||
## Validation and remaining gates
|
||||
|
||||
TypeScript/Vite Docker build passed; **172 offline regressions and 29 PostgreSQL integration tests passed, zero failures/skips**. [Candidate tests](../tests/sale-candidate.test.ts) cover signature compatibility/recipient tampering and candidate transition rules. [PostgreSQL tests](../tests/postgres/custody.test.ts) add:
|
||||
TypeScript/Vite Docker build passed; **172 offline regressions and 30 PostgreSQL integration tests passed, zero failures/skips**. [Candidate tests](../tests/sale-candidate.test.ts) cover signature compatibility/recipient tampering and candidate transition rules. [PostgreSQL tests](../tests/postgres/custody.test.ts) add:
|
||||
|
||||
- explicit instance/database consent and exact signed reservation matching;
|
||||
- seed-binding changes, malformed/cyclic/oversized collections, and denial of ordinary cancellation after binding;
|
||||
@@ -54,7 +54,7 @@ TypeScript/Vite Docker build passed; **172 offline regressions and 29 PostgreSQL
|
||||
- identical-rounded-allocation but different fee-rate rejection;
|
||||
- one-atomic settlement and signed custom-recipient revenue across current-policy changes;
|
||||
- conflicting outcome → durable cross-instance/restart hold without a second credit;
|
||||
- anchored canonical-event connect/disconnect paging, overlapping/stale consumer retries, reconnect, partial history mutation audit, forged disconnect-ancestor rejection and tamper-triggered hold; actual locally validated blocks using test-only fake PoW, excluded equal-work branch and a canonical reorg through the adapter (no candidate sale link);
|
||||
- anchored canonical-event connect/disconnect paging, overlapping/stale consumer retries, reconnect, partial history mutation audit, forged disconnect-ancestor rejection and tamper-triggered hold; actual native-RandomX block validation and event ingestion plus test-only fake-PoW excluded branch/reorg fixture (no active-chain candidate sale link);
|
||||
- opt-in HTTP runtime startup, initial observer checkpoint, 64-event bounded catch-up, clean shutdown, durable restart resume and invalid-flag rejection;
|
||||
- synthetic exact candidate-terminal transaction disconnect/reappearance in one page → committed event history plus order-linked durable hold, generic incident resolution denied, one preserved seller credit and no second payout;
|
||||
- injected terminal-evidence failure → complete rollback and safe retry; corrupted evidence → audit hold.
|
||||
|
||||
@@ -88,7 +88,7 @@ Blocked on P0 ADR; use P1 amount types and P2 durable events. Consensus work is
|
||||
|
||||
**Accounting rehearsal increment, 2026-10-20:** [signed candidate → PostgreSQL reservation resolution](custody-sale-rehearsal.md) now exercises actual restricted-role SQL terminal postings from bounded synthetic transcripts, behind explicit disposable database/instance guards and without an HTTP route. Exact terms/hosting recipients and reservation commitments are checked; mature completion credits seller/fees once, cancellation/non-admission releases buyer funds, and conflicting post-settlement evidence durably holds mutations rather than repaying. Insert-only first/terminal evidence is audited. Final build, 172 offline tests and 22 PostgreSQL integration tests passed. This supersedes the earlier absence of any balance-posting test integration, **not** the production/activation boundary: caller-supplied seeds/blocks are not canonical evidence, intermediate unseen disconnects cannot be detected, and no signer, canonical inbox, live deposit/withdrawal or current-chain paid sale is enabled. M3 remains open.
|
||||
|
||||
**Canonical/per-order reorg rehearsal increment, 2026-10-20:** the gated disposable PostgreSQL observer stores bounded pages from an in-process `Ledger` adapter, checks stream/anchor/hash/tip/ancestor continuity, supports retries, and audits retained history. A local-ledger test using fake PoW covers an excluded equal-work branch and reorg. A separate synthetic-event test links the deterministic candidate terminal transaction ID to its posted order; disconnecting that ID (even followed by same-page re-inclusion) commits the event history and durable order review hold atomically, without undoing or repeating the payout. Generic incident resolution cannot clear a sale-reorg record. The server now has an opt-in polling lifecycle, restricted to PostgreSQL `custody_test`, both disposable flags and non-production mode; it drains on shutdown, resumes the persistent cursor after restart and stops on review/failure. Final build, 172 offline regressions and 29 PostgreSQL tests pass. The worker remains disabled in all regular deployments, active v6 cannot emit candidate IDs, and synthetic linkage is not canonical sale evidence. Stream hashes are not tamper-proof against the schema owner; 4,096-event cap/full scan are non-production. M3 remains blocked on reviewed candidate activation, actual candidate IDs from an activated local ledger, production observer/checkpoint design and production readiness.
|
||||
**Canonical/per-order reorg rehearsal increment, 2026-10-20:** the gated disposable PostgreSQL observer stores bounded pages from an in-process `Ledger` adapter, checks stream/anchor/hash/tip/ancestor continuity, supports retries, and audits retained history. Tests cover a block mined and independently verified with native RandomX flowing through the local ledger into PostgreSQL, plus a fake-PoW equal-work branch/reorg. A separate synthetic-event test links the deterministic candidate terminal transaction ID to its posted order; disconnecting that ID (even followed by same-page re-inclusion) commits the event history and durable order review hold atomically, without undoing or repeating the payout. Generic incident resolution cannot clear a sale-reorg record. The server now has an opt-in polling lifecycle, restricted to PostgreSQL `custody_test`, both disposable flags and non-production mode; it drains on shutdown, resumes the persistent cursor after restart and stops on review/failure. Final build, 172 offline regressions and 30 PostgreSQL tests pass. The worker remains disabled in all regular deployments, active v6 cannot emit candidate IDs, and synthetic linkage is not canonical sale evidence. Stream hashes are not tamper-proof against the schema owner; 4,096-event cap/full scan are non-production. M3 remains blocked on reviewed candidate activation, actual candidate IDs from an activated local ledger, production observer/checkpoint design and production readiness.
|
||||
|
||||
- If a reviewed trustless design is selected: implement its actual cross-chain validity/evidence rules, compatible cryptographic primitives, both-party failure/refund paths and commission semantics. Run maintained reference vectors, negative tests, fuzz/property tests and specialist audit before claiming atomicity.
|
||||
- If a custodial/attested fallback is explicitly approved: define chain-enforced NFT lock, order identity, authorized releaser, cancellation/refund/expiry and stale-signature rules. Separate host control keys from seller wallet and processor payout keys. Reserve/refund procedures must not permit both buyer refund and later seller delivery/release. Describe who can steal, censor or lock assets and how recovery is authorized.
|
||||
|
||||
@@ -2,8 +2,9 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Pool, type PoolConfig } from 'pg';
|
||||
import { generateKeyPairSync, randomUUID, sign } from 'node:crypto';
|
||||
import { mkdtemp, rm, writeFile, mkdir, readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { access, 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';
|
||||
@@ -30,6 +31,7 @@ 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';
|
||||
@@ -577,6 +579,35 @@ test('gated adapter consumes actual validated local Ledger connect and reorg pag
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user