169 lines
11 KiB
TypeScript
Executable File
169 lines
11 KiB
TypeScript
Executable File
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import { createHash } from 'node:crypto';
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { NETWORK, CONSENSUS } from '../shared/testnet.ts';
|
|
import { networkProfile } from '../shared/network.ts';
|
|
import { CHAIN_ID, GENESIS_TIME, GENESIS_HASH, MAX_TARGET } from '../server/testnet/consensus.ts';
|
|
import { paymentMode } from '../server/market/payments.ts';
|
|
|
|
function script(code: string, env: Record<string,string> = {}) {
|
|
return execFileSync(process.execPath, ['--experimental-strip-types', '--input-type=module', '-e', code], {
|
|
env: { ...process.env, WIKIDECK_NETWORK: 'mainnet', ...env }, encoding: 'utf8', timeout: 30000,
|
|
}).trim();
|
|
}
|
|
test('default testnet v6 manifest is unchanged and unknown network profiles fail closed', () => {
|
|
const hash = (text: string) => createHash('sha256').update(text).digest('hex');
|
|
assert.equal(NETWORK, 'wikideck-randomx-testnet-6');
|
|
assert.equal(GENESIS_TIME, 1_792_411_200);
|
|
assert.equal(CHAIN_ID, hash(JSON.stringify(['wikideck-randomx-testnet-6', CONSENSUS, 1_792_411_200, MAX_TARGET])));
|
|
assert.equal(networkProfile(undefined), 'testnet');
|
|
assert.equal(networkProfile('pilot'), 'pilot');
|
|
assert.throws(() => networkProfile('production')); assert.throws(() => networkProfile(''));
|
|
});
|
|
test('private pilot is a separate deterministic chain with an explicit mock-only startup gate', () => {
|
|
const output = JSON.parse(script(`
|
|
import { NETWORK, NETWORK_GENESIS_TIME, NETWORK_LABEL } from './shared/network.ts';
|
|
import { CHAIN_ID, GENESIS_HASH, GENESIS_TIME } from './server/testnet/consensus.ts';
|
|
import { launchManifest, assertLaunchAllowed } from './server/launch.ts';
|
|
const allowed = (env) => { try { assertLaunchAllowed(env); return true; } catch { return false; } };
|
|
console.log(JSON.stringify({ NETWORK, NETWORK_GENESIS_TIME, NETWORK_LABEL, CHAIN_ID, GENESIS_HASH, GENESIS_TIME,
|
|
manifest:launchManifest(), missing:allowed({}), approved:allowed({WIKIDECK_PILOT_MODE:'private-mock-only'}),
|
|
livePayment:allowed({WIKIDECK_PILOT_MODE:'private-mock-only',WIKIDECK_PAYMENT_MODE:'live'}) }));
|
|
`, { WIKIDECK_NETWORK: 'pilot' }));
|
|
assert.equal(output.NETWORK, 'wikideck-randomx-pilot-1');
|
|
assert.equal(output.GENESIS_TIME, 1_792_627_200);
|
|
assert.equal(output.NETWORK_LABEL, 'PRIVATE PILOT');
|
|
assert.equal(output.manifest.launchStatus, 'private-pilot-mock-only');
|
|
assert.equal(output.manifest.paymentMode, 'mock');
|
|
assert.equal(output.missing, false);
|
|
assert.equal(output.approved, true);
|
|
assert.equal(output.livePayment, false);
|
|
assert.notEqual(output.CHAIN_ID, CHAIN_ID);
|
|
assert.notEqual(output.GENESIS_HASH, GENESIS_HASH);
|
|
const missingMode = spawnSync(process.execPath, ['--experimental-strip-types', 'server/index.ts'], {
|
|
env: { ...process.env, WIKIDECK_NETWORK: 'pilot', WIKIDECK_PILOT_MODE: '' },
|
|
encoding: 'utf8', timeout: 30000,
|
|
});
|
|
assert.notEqual(missingMode.status, 0);
|
|
assert.match(missingMode.stderr, /Private pilot requires WIKIDECK_PILOT_MODE/);
|
|
});
|
|
test('mainnet candidate has distinct deterministic genesis, signatures and strict transaction schemas', () => {
|
|
const output = JSON.parse(script(`
|
|
import { NETWORK, nftTransferMessage } from './shared/testnet.ts';
|
|
import { CHAIN_ID, GENESIS_HASH, transferSchema } from './server/testnet/consensus.ts';
|
|
console.log(JSON.stringify({ NETWORK, CHAIN_ID, GENESIS_HASH,
|
|
message:nftTransferMessage({chainId:CHAIN_ID,cardHash:'a'.repeat(64),from:'b'.repeat(64),to:'c'.repeat(64),sequence:0}),
|
|
acceptsTestnet:transferSchema.safeParse({network:'wikideck-randomx-testnet-6',chainId:${JSON.stringify(CHAIN_ID)},
|
|
cardHash:'a'.repeat(64),from:'b'.repeat(64),to:'c'.repeat(64),sequence:0,publicKey:'AA==',signature:'A'.repeat(86)+'=='}).success }));
|
|
`));
|
|
assert.equal(output.NETWORK, 'wikideck-randomx-mainnet-1');
|
|
assert.notEqual(output.CHAIN_ID, CHAIN_ID); assert.notEqual(output.GENESIS_HASH, GENESIS_HASH);
|
|
assert.match(output.message, /wikideck-randomx-mainnet-1/); assert.equal(output.acceptsTestnet, false);
|
|
});
|
|
test('mainnet startup is blocked before resources open, while manifest inspection needs no launch approval', () => {
|
|
const args = ['--experimental-strip-types', 'server/index.ts'];
|
|
const env = { ...process.env, WIKIDECK_NETWORK: 'mainnet', WIKIDECK_MAINNET_LAUNCH: '', WIKIDECK_MAINNET_REVIEW_ACK: '' };
|
|
const result = spawnSync(process.execPath, args, { env, encoding: 'utf8', timeout: 30000 });
|
|
assert.notEqual(result.status, 0); assert.match(result.stderr, /launch-gated/);
|
|
const manifest = JSON.parse(execFileSync(process.execPath, [...args, '--network-manifest'], { env, encoding: 'utf8' }));
|
|
assert.equal(manifest.profile, 'mainnet'); assert.equal(manifest.paymentMode, 'mock'); assert.equal(manifest.audited, false);
|
|
assert.equal(manifest.launchStatus, 'candidate-gated');
|
|
});
|
|
test('mainnet gates require genesis time, chain-specific approval, HTTPS, independent peers, and mock payment', () => {
|
|
const result = JSON.parse(script(`
|
|
import { assertLaunchAllowed } from './server/launch.ts';
|
|
import { CHAIN_ID, GENESIS_TIME } from './server/testnet/consensus.ts';
|
|
const env={WIKIDECK_MAINNET_LAUNCH:CHAIN_ID,WIKIDECK_MAINNET_REVIEW_ACK:'independent-review-complete',
|
|
PUBLIC_ORIGIN:'https://market.example',WIKIDECK_PEERS:'https://peer-a.example,https://peer-b.example'};
|
|
const results=[];
|
|
for(const [changes,now] of [[{},0],[{WIKIDECK_MAINNET_LAUNCH:'wrong'},Infinity],
|
|
[{PUBLIC_ORIGIN:'http://market.example'},Infinity],[{WIKIDECK_PEERS:''},Infinity],
|
|
[{WIKIDECK_PEERS:'https://peer-a.example,https://peer-a.example/'},Infinity],
|
|
[{WIKIDECK_ALLOW_PRIVATE_PEERS:'1'},Infinity],[{WIKIDECK_PAYMENT_MODE:'processor'},Infinity],[{},Infinity]]) {
|
|
try { assertLaunchAllowed({...env,...changes},now); results.push(true); } catch { results.push(false); }
|
|
}
|
|
console.log(JSON.stringify(results));
|
|
`));
|
|
assert.deepEqual(result, [false,false,false,false,false,false,false,true]);
|
|
assert.throws(() => paymentMode({ WIKIDECK_NETWORK: 'mainnet', WIKIDECK_PAYMENT_MODE: 'processor' }));
|
|
});
|
|
test('mainnet ledger and market files use isolated folders; testnet backup headers are rejected', async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), 'wikideck-network-'));
|
|
try {
|
|
const result = JSON.parse(script(`
|
|
import { Ledger } from './server/testnet/ledger.ts';
|
|
import { Market } from './server/market/store.ts';
|
|
import { decryptWallet } from './shared/wallet-backup.ts';
|
|
import { existsSync } from 'node:fs'; import { join } from 'node:path';
|
|
const ledger=await Ledger.open(process.env.PROFILE_TEST_DIR,{hash:async()=>{throw Error('unused')},close(){}});
|
|
const market=await Market.open(process.env.PROFILE_TEST_DIR,ledger);
|
|
let rejected=false;
|
|
try {await decryptWallet({format:'wikideck-wallet-backup',version:1,network:'wikideck-randomx-testnet-6',
|
|
kdf:'PBKDF2-SHA256',iterations:600000,cipher:'AES-256-GCM',salt:'',iv:'',publicKey:'',ciphertext:''},'password');}
|
|
catch(e){rejected=/network/.test(e.message);}
|
|
console.log(JSON.stringify({ledger:existsSync(join(process.env.PROFILE_TEST_DIR,'mainnet','ledger.sqlite')),
|
|
market:existsSync(join(process.env.PROFILE_TEST_DIR,'mainnet','market.sqlite')),
|
|
testnet:existsSync(join(process.env.PROFILE_TEST_DIR,'testnet')),rejected}));
|
|
market.close();ledger.close();
|
|
`, { PROFILE_TEST_DIR: dir }));
|
|
assert.deepEqual(result, { ledger: true, market: true, testnet: false, rejected: true });
|
|
} finally { await rm(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('pilot ledger and market files use their own profile directory', async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), 'wikideck-pilot-network-'));
|
|
try {
|
|
const result = JSON.parse(script(`
|
|
import { Ledger } from './server/testnet/ledger.ts';
|
|
import { Market } from './server/market/store.ts';
|
|
import { existsSync } from 'node:fs'; import { join } from 'node:path';
|
|
const ledger=await Ledger.open(process.env.PROFILE_TEST_DIR,{hash:async()=>{throw Error('unused')},close(){}});
|
|
const market=await Market.open(process.env.PROFILE_TEST_DIR,ledger,undefined,Date.now,{worker:false});
|
|
const result={ledger:existsSync(join(process.env.PROFILE_TEST_DIR,'pilot','ledger.sqlite')),
|
|
market:existsSync(join(process.env.PROFILE_TEST_DIR,'pilot','market.sqlite')),
|
|
testnet:existsSync(join(process.env.PROFILE_TEST_DIR,'testnet'))};
|
|
market.close();ledger.close();console.log(JSON.stringify(result));
|
|
`, { WIKIDECK_NETWORK: 'pilot', WIKIDECK_PILOT_MODE: 'private-mock-only', PROFILE_TEST_DIR: dir }));
|
|
assert.deepEqual(result, { ledger: true, market: true, testnet: false });
|
|
} finally { await rm(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('operator preflight is read-only, fails closed, and does not disclose credentials', async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), 'wikideck-preflight-'));
|
|
try {
|
|
const { existsSync } = await import('node:fs');
|
|
const dataDir = join(dir, 'must-not-be-created');
|
|
const secret = 'test-only-sensitive-value-must-not-appear';
|
|
const result = spawnSync(process.execPath, ['--experimental-strip-types', 'server/index.ts', '--launch-preflight'], {
|
|
env: { ...process.env, WIKIDECK_NETWORK: 'mainnet', WIKIDECK_DATA_DIR: dataDir,
|
|
WIKIDECK_MAINNET_LAUNCH: '', WIKIDECK_MAINNET_REVIEW_ACK: '', PAYMENT_PROVIDER_API_KEY: secret },
|
|
encoding: 'utf8', timeout: 30000,
|
|
});
|
|
assert.equal(result.status, 1);
|
|
const report = JSON.parse(result.stdout);
|
|
assert.equal(report.productionReady, false);
|
|
assert.equal(report.decision, 'NO-GO');
|
|
assert.equal(report.paymentMode, 'mock');
|
|
assert.ok(report.checks.some((c: { id: string; status: string }) => c.id === 'consensus-launch-calibration' && c.status === 'blocked'));
|
|
assert.equal(result.stdout.includes(secret), false);
|
|
assert.equal(existsSync(dataDir), false);
|
|
} finally { await rm(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('Monero deposit and withdrawal request APIs cannot be enabled on testnet', () => {
|
|
for (const [name, value] of [['WIKIDECK_MONERO_DEPOSIT_ENROLLMENT', '1'],
|
|
['WIKIDECK_MONERO_WITHDRAWAL_REQUESTS', '1']]) {
|
|
const result = spawnSync(process.execPath, ['--experimental-strip-types', 'server/index.ts'], {
|
|
env: { ...process.env, WIKIDECK_NETWORK: 'testnet', [name]: value },
|
|
encoding: 'utf8', timeout: 30000,
|
|
});
|
|
assert.equal(result.status, 1);
|
|
assert.match(result.stderr, new RegExp(name.includes('DEPOSIT')
|
|
? 'Monero deposit enrollment requires the mainnet profile' : 'Monero withdrawal requests require the mainnet profile'));
|
|
}
|
|
});
|