Security
This commit is contained in:
+5
-1
@@ -1,7 +1,11 @@
|
||||
// A node independently validates RandomX work and follows cumulative-work consensus.
|
||||
import { assertLaunchAllowed, launchManifest } from './launch.ts';
|
||||
import { launchPreflight } from './preflight.ts';
|
||||
|
||||
if (process.argv.includes('--network-manifest')) console.log(JSON.stringify(launchManifest(), null, 2));
|
||||
if (process.argv.includes('--launch-preflight')) {
|
||||
console.log(JSON.stringify(launchPreflight(), null, 2));
|
||||
process.exitCode = 1; // Fail closed: a report with unresolved production blockers is never approval.
|
||||
} else if (process.argv.includes('--network-manifest')) console.log(JSON.stringify(launchManifest(), null, 2));
|
||||
else {
|
||||
assertLaunchAllowed();
|
||||
await import('./testnet/http.ts');
|
||||
|
||||
+17
-5
@@ -105,6 +105,12 @@ export class Market {
|
||||
snapshot.transfers.filter(tx => transactionCardIds(tx).includes(hash)).length === sequence &&
|
||||
snapshot.blocks.length + 1 - block.height >= CONSENSUS.coinbaseMaturity;
|
||||
}
|
||||
private deliveryId(order: MarketOrder): string {
|
||||
// Identity excludes signature bytes. Recognize exact sale delivery through ANY submission route,
|
||||
// including peer gossip or a direct /api/transfer, even before the market records delivery intent.
|
||||
return transactionId({ network: NETWORK, chainId: CHAIN_ID, cardHash: order.cardHash,
|
||||
from: order.seller, to: order.buyer, sequence: order.sequence, publicKey: '', signature: '' });
|
||||
}
|
||||
/** Reconcile from canonical transaction identity, never from a mempool acknowledgement. */
|
||||
private reconcile() {
|
||||
const s = this.chain.snapshot(), now = this.now();
|
||||
@@ -112,7 +118,8 @@ export class Market {
|
||||
for (const o of this.rows<MarketOrder>('orders')) {
|
||||
const l = this.listing(o.listingId);
|
||||
if (o.status === 'refunded' || o.status === 'expired') continue;
|
||||
const delivery = o.transferId && s.blocks.find(b => b.transactions.some(tx => transactionId(tx) === o.transferId));
|
||||
const deliveryId = this.deliveryId(o);
|
||||
const delivery = s.blocks.find(b => b.transactions.some(tx => transactionId(tx) === deliveryId));
|
||||
const confirmations = delivery ? s.blocks.length - delivery.height + 1 : 0;
|
||||
o.deliveryConfirmations = confirmations;
|
||||
if (o.status === 'complete' || o.status === 'review') {
|
||||
@@ -122,7 +129,12 @@ export class Market {
|
||||
}
|
||||
this.saveOrder(o); continue; // Never automatically pay a second time after a reorganization.
|
||||
}
|
||||
if (delivery) {
|
||||
if (delivery && o.status === 'awaiting-payment') {
|
||||
// A gift/direct delivery is not evidence that payment was received. Never pay an unpaid order.
|
||||
o.status = 'review'; o.payout = 'review';
|
||||
if (o.additionalFee) o.additionalFee.payout = 'review';
|
||||
} else if (delivery) {
|
||||
o.transferId = deliveryId;
|
||||
o.status = 'delivery-pending';
|
||||
if (confirmations >= o.requiredConfirmations) {
|
||||
o.status = 'complete'; o.payout = 'mock-paid'; l.status = 'sold';
|
||||
@@ -131,8 +143,8 @@ export class Market {
|
||||
} else if (o.status === 'awaiting-payment' && now >= o.payBy) {
|
||||
o.status = 'expired'; l.status = l.kind === 'fixed' ? 'open' : 'expired';
|
||||
} else if (o.status !== 'awaiting-payment') {
|
||||
const pending = s.pendingTransfers.some(tx => transactionId(tx) === o.transferId);
|
||||
const conflict = s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash) && transactionId(tx) !== o.transferId);
|
||||
const pending = s.pendingTransfers.some(tx => transactionId(tx) === deliveryId);
|
||||
const conflict = s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash) && transactionId(tx) !== deliveryId);
|
||||
if (!this.card(s, o.cardHash, o.seller, o.sequence) || conflict || (now >= o.deliverBy && !pending)) o.status = 'refund-required';
|
||||
else if (o.status === 'delivery-pending' && !pending) o.status = 'paid';
|
||||
}
|
||||
@@ -252,7 +264,7 @@ export class Market {
|
||||
o.status = 'paid'; o.deliverBy = now + DELIVERY_WINDOW;
|
||||
} else {
|
||||
if (o.status !== 'refund-required' || s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash)) ||
|
||||
s.blocks.some(b => b.transactions.some(tx => transactionId(tx) === o.transferId))) throw new Error('Refund requires failed delivery and no pending/canonical sale transfer');
|
||||
s.blocks.some(b => b.transactions.some(tx => transactionId(tx) === this.deliveryId(o)))) throw new Error('Refund requires failed delivery and no pending/canonical sale transfer');
|
||||
o.status = 'refunded'; const l = this.listing(o.listingId); l.status = 'cancelled'; this.saveListing(l);
|
||||
}
|
||||
this.saveOrder(o); return o;
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
import { IS_MAINNET } from '../shared/network.ts';
|
||||
import { ADDITIONAL_MARKET_FEE } from '../shared/market.ts';
|
||||
import { assertLaunchAllowed, launchManifest } from './launch.ts';
|
||||
import { marketConfig } from './market/store.ts';
|
||||
|
||||
export type LaunchCheck = { id: string; status: 'pass' | 'blocked' | 'unverified'; detail: string };
|
||||
/** Read-only local assessment. Never opens a ledger, starts PoW, contacts peers, or sets approvals.
|
||||
* Configured approvals cannot resolve findings that require new code or independent evidence. */
|
||||
export function launchPreflight(env: NodeJS.ProcessEnv = process.env, now = Date.now()) {
|
||||
const checks: LaunchCheck[] = [{ id: 'network-selection', status: IS_MAINNET ? 'pass' : 'blocked',
|
||||
detail: IS_MAINNET ? 'Separate mainnet candidate selected.' : 'Testnet selected; this is not a production profile.' }];
|
||||
try {
|
||||
marketConfig(env);
|
||||
checks.push({ id: 'mock-payment-boundary', status: 'pass', detail: 'Only mock payments are supported; market configuration is valid.' });
|
||||
} catch {
|
||||
checks.push({ id: 'mock-payment-boundary', status: 'blocked', detail: 'Invalid market configuration or unsupported live payment mode. No secret values are included in this report.' });
|
||||
}
|
||||
if (IS_MAINNET) {
|
||||
try {
|
||||
assertLaunchAllowed(env, now);
|
||||
checks.push({ id: 'operator-startup-gates', status: 'pass', detail: 'Local startup gates satisfied; this does not establish production readiness or peer independence.' });
|
||||
} catch (error) {
|
||||
checks.push({ id: 'operator-startup-gates', status: 'blocked', detail: error instanceof Error ? error.message : 'Startup gate failed.' });
|
||||
}
|
||||
}
|
||||
checks.push(
|
||||
{ id: 'public-node-resource-controls', status: 'blocked', detail: 'Full-chain RPC, in-memory DAG and synchronous journal scans need a reviewed capacity/abuse strategy and load-test evidence.' },
|
||||
{ id: 'consensus-launch-calibration', status: 'blocked', detail: 'Initial RandomX difficulty, retarget behavior, final genesis and economic security have not been calibrated and independently reviewed for production.' },
|
||||
{ id: 'wallet-key-protection', status: 'blocked', detail: 'Browser signing keys remain extractable and persist unencrypted in IndexedDB; production wallet policy and recovery review are required.' },
|
||||
{ id: 'live-market-settlement', status: 'blocked', detail: 'No live SHKeeper adapter or reviewed payout/refund/reorg system exists. Mock payment cannot constitute real payment for valuable cards.' },
|
||||
{ id: 'release-security-review', status: 'unverified', detail: 'Independent consensus/application review and release-specific dependency/native/container scans require external evidence; an operator environment flag is not evidence.' },
|
||||
{ id: 'deployment-rehearsal', status: 'unverified', detail: 'Verify real HTTPS endpoints, proxy/IP limits, independent peers and miners, alerts, incident response and backup/restore on the intended hosts.' },
|
||||
{ id: 'fee-recipient-control', status: 'unverified', detail: 'Confirm the fixed public XMR fee address, checksum and recipient control out of band before any future live payout. No keys or proof are requested here.' },
|
||||
);
|
||||
return { ...launchManifest(), assessedAt: new Date(now).toISOString(), productionReady: false,
|
||||
decision: 'NO-GO', scope: 'Local code/configuration preflight; not an independent audit or launch approval.',
|
||||
additionalFee: { ...ADDITIONAL_MARKET_FEE }, checks };
|
||||
}
|
||||
Reference in New Issue
Block a user