Removed markets
This commit is contained in:
+50
-34
@@ -16,6 +16,7 @@ import { wikipediaEnrichment } from '../enrichment.ts';
|
||||
import { snapshotStats } from '../../shared/enrichment.ts';
|
||||
import { wikipediaArticles } from '../wiki.ts';
|
||||
import { Market, marketConfig } from '../market/store.ts';
|
||||
import { paymentMode } from '../market/payments.ts';
|
||||
import { marketCommandSchema } from '../../shared/market.ts';
|
||||
import { marketCommandV3Schema } from '../../shared/market-v3.ts';
|
||||
import { custodyCommandSchema } from '../../shared/custody.ts';
|
||||
@@ -40,9 +41,18 @@ import { trustedProxies, operationsPort, startOperations } from '../operations.t
|
||||
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url));
|
||||
assertLaunchAllowed();
|
||||
const paymentConfig = marketConfig(); // Fail before opening node resources if live payments are requested.
|
||||
await validateMarketPolicy(paymentConfig); // Reject invalid recipient checksum/network before opening node resources.
|
||||
const marketRuntimeSetting = process.env.WIKIDECK_MARKET_RUNTIME_ENABLED ?? '0';
|
||||
if (marketRuntimeSetting !== '0' && marketRuntimeSetting !== '1') {
|
||||
throw new Error('WIKIDECK_MARKET_RUNTIME_ENABLED must be exactly 0 or 1');
|
||||
}
|
||||
const marketRuntimeEnabled = marketRuntimeSetting === '1';
|
||||
const custodyConfig = custodyMode(); // Explicit opt-in sandbox only. Unknown/live modes fail before opening resources.
|
||||
paymentMode(); // Keep real-payment modes fail-closed even when the optional market runtime is off.
|
||||
let paymentConfig: ReturnType<typeof marketConfig> | undefined;
|
||||
if (marketRuntimeEnabled || custodyConfig === 'sandbox') {
|
||||
paymentConfig = marketConfig();
|
||||
await validateMarketPolicy(paymentConfig); // Reject invalid recipient checksum/network before opening node resources.
|
||||
}
|
||||
const canonicalObserverMode = process.env.WIKIDECK_CUSTODY_CANONICAL_REHEARSAL;
|
||||
if (canonicalObserverMode !== undefined && canonicalObserverMode !== '0' && canonicalObserverMode !== '1') {
|
||||
throw new Error('WIKIDECK_CUSTODY_CANONICAL_REHEARSAL must be exactly 0 or 1');
|
||||
@@ -86,8 +96,8 @@ let peers: PeerNetwork;
|
||||
try { peers = new PeerNetwork({ ledger, urls:(process.env.WIKIDECK_PEERS ?? '').split(',').map(s=>s.trim()).filter(Boolean),
|
||||
allowPrivate:process.env.WIKIDECK_ALLOW_PRIVATE_PEERS === '1' }); }
|
||||
catch(e) {ledger.close();engine.close();await lock.close();await unlink(lockPath);throw e;}
|
||||
let market: Market;
|
||||
try { market = await Market.open(dataDir, ledger, paymentConfig, Date.now, { worker: true }); }
|
||||
let market: Market | undefined;
|
||||
try { if (marketRuntimeEnabled) market = await Market.open(dataDir, ledger, paymentConfig!, Date.now, { worker: true }); }
|
||||
catch(e) {peers.close();ledger.close();engine.close();await lock.close();await unlink(lockPath);throw e;}
|
||||
let custody: CustodyMarket | PostgresCustody | undefined;
|
||||
let custodyObserver: CanonicalObserver | undefined;
|
||||
@@ -97,7 +107,7 @@ let moneroDepositContext: MainnetEnrollmentContext | undefined;
|
||||
let moneroWithdrawalPool: Pool | undefined;
|
||||
let moneroWithdrawalCustodianId: string | undefined;
|
||||
try {
|
||||
if (custodyConfig === 'sandbox') custody = await openCustodyStorage(dataDir, { ...paymentConfig,
|
||||
if (custodyConfig === 'sandbox') custody = await openCustodyStorage(dataDir, { ...paymentConfig!,
|
||||
card: (hash, owner, sequence) => {
|
||||
const nft = ledger.nft(hash);
|
||||
if (!nft || nft.owner !== owner || nft.confirmations < CONSENSUS.coinbaseMaturity || nft.sequence !== sequence ||
|
||||
@@ -147,7 +157,7 @@ try {
|
||||
await verifyMainnetCustodyIdentity(moneroWithdrawalPool, moneroWithdrawalCustodianId);
|
||||
await moneroWithdrawalPool.query('SELECT 1 FROM wk_custody.monero_withdrawal_intents LIMIT 0');
|
||||
}
|
||||
} catch(e) {await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custodyObserver?.close();await custody?.close();await market.close();peers.close();ledger.close();engine.close();await lock.close();await unlink(lockPath);throw e;}
|
||||
} catch(e) {await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custodyObserver?.close();await custody?.close();await market?.close();peers.close();ledger.close();engine.close();await lock.close();await unlink(lockPath);throw e;}
|
||||
const app = express();
|
||||
app.disable('x-powered-by');
|
||||
if(proxyTrust.length)app.set('trust proxy',proxyTrust);
|
||||
@@ -181,7 +191,23 @@ app.get('/api/canonical',(req,res)=>{
|
||||
app.get('/api/network',(_req,res)=>res.json(launchManifest()));
|
||||
app.get('/api/chain',(_req,res)=>res.json(ledger.legacySnapshot(limits.legacyBlocks)));
|
||||
app.get('/api/peers',(_req,res)=>res.json({peers:peers.status()}));
|
||||
app.get('/api/market', (req,res)=>res.json(market.browse(Number(req.query.offset ?? 0))));
|
||||
if (market) {
|
||||
app.get('/api/market', (req,res)=>res.json(market!.browse(Number(req.query.offset ?? 0))));
|
||||
app.post('/api/market/command',rateLimit({windowMs:60000,limit:60,standardHeaders:'draft-8',legacyHeaders:false}),async(req,res)=>{
|
||||
const current = marketCommandV3Schema.safeParse(req.body);
|
||||
const command = current.success ? current.data : marketCommandSchema.parse(req.body);
|
||||
try {
|
||||
const result = await market!.execute(command);
|
||||
res.json(result);
|
||||
if(command.action.type==='deliver')void peers.broadcastTransfer(command.action.transfer).catch(()=>{});
|
||||
} catch(error) {
|
||||
if(error instanceof RpcError)throw error;
|
||||
// Marketplace storage/payment internals must never leak through the generic node error handler.
|
||||
if(error instanceof Error && !/SQLITE|constraint|database/i.test(error.message))res.status(400).json({error:error.message.slice(0,200)});
|
||||
else res.status(409).json({error:'Marketplace state conflict. Refresh and retry.'});
|
||||
}
|
||||
});
|
||||
}
|
||||
app.get('/api/custody', async (req,res)=> {
|
||||
if (!custody) { res.json({ enabled: false }); return; }
|
||||
try { res.json(await custody.browse(Number(req.query.offset ?? 0))); }
|
||||
@@ -252,20 +278,6 @@ if (withdrawalRequestMode === '1' && moneroWithdrawalPool && moneroWithdrawalCus
|
||||
}
|
||||
});
|
||||
}
|
||||
app.post('/api/market/command',rateLimit({windowMs:60000,limit:60,standardHeaders:'draft-8',legacyHeaders:false}),async(req,res)=>{
|
||||
const current = marketCommandV3Schema.safeParse(req.body);
|
||||
const command = current.success ? current.data : marketCommandSchema.parse(req.body);
|
||||
try {
|
||||
const result = await market.execute(command);
|
||||
res.json(result);
|
||||
if(command.action.type==='deliver')void peers.broadcastTransfer(command.action.transfer).catch(()=>{});
|
||||
} catch(error) {
|
||||
if(error instanceof RpcError)throw error;
|
||||
// Marketplace storage/payment internals must never leak through the generic node error handler.
|
||||
if(error instanceof Error && !/SQLITE|constraint|database/i.test(error.message))res.status(400).json({error:error.message.slice(0,200)});
|
||||
else res.status(409).json({error:'Marketplace state conflict. Refresh and retry.'});
|
||||
}
|
||||
});
|
||||
app.get('/api/decks/:address', rateLimit({windowMs:60000,limit:60,standardHeaders:'draft-8',legacyHeaders:false}), (req,res)=>{
|
||||
if(typeof req.params.address!=='string'||!/^[a-f0-9]{64}$/.test(req.params.address)){
|
||||
res.status(400).json({error:'Invalid wallet address'});return;
|
||||
@@ -359,7 +371,7 @@ const server=app.listen(port,host,()=>{
|
||||
const operations=startOperations(opsPort,()=>({network:NETWORK,paymentMode:'mock',
|
||||
ledger:ledger.diagnostics(),native:engine.diagnostics(),
|
||||
peers:{configured:peers.status().length,connected:peers.status().filter(p=>p.connected).length},
|
||||
rpc:{api:apiBudget.status(),p2p:peerBudget.status()},market:market.diagnostics(),
|
||||
rpc:{api:apiBudget.status(),p2p:peerBudget.status()},market:market?.diagnostics() ?? { enabled:false },
|
||||
canonicalObserver:custodyObserver?.status(),
|
||||
custody: custody && 'diagnostics' in custody ? custody.diagnostics() : { backend: custody ? 'sqlite-sandbox' : 'off' }}),resolve(dataDir,PROFILE));
|
||||
server.maxConnections=256;
|
||||
@@ -368,16 +380,16 @@ server.requestTimeout=15000;
|
||||
server.keepAliveTimeout=5000;
|
||||
server.setTimeout(30000,socket=>socket.destroy());
|
||||
// Advance simulated payout/expiry state even when no browser is open. No real funds are sent.
|
||||
const marketTimer=setInterval(()=>{try {market.browse();}catch{console.error('Marketplace reconciliation failed; operator review required.');}},15000);
|
||||
marketTimer.unref();
|
||||
let stopping=false;
|
||||
let catchupPaused=false;
|
||||
let catchupImmediate:NodeJS.Immediate|undefined;
|
||||
let marketTimer:NodeJS.Timeout|undefined;
|
||||
let marketCatchupTimer:NodeJS.Timeout|undefined;
|
||||
// Drain durable catch-up without page-load dependence. Each pass is a smaller synchronous
|
||||
// SQLite slice, then setImmediate yields so HTTP and peer work can run between passes.
|
||||
const advanceMarketCatchup=()=>{
|
||||
catchupImmediate=undefined;
|
||||
if(stopping||catchupPaused||market.isReconciliationWorkerBusy()||!market.hasReconciliationBacklog())return;
|
||||
if(!market||stopping||catchupPaused||market.isReconciliationWorkerBusy()||!market.hasReconciliationBacklog())return;
|
||||
if(market.isReconciliationWorkerFailed()){
|
||||
catchupPaused=true;console.error('Marketplace reconciliation worker failed; operator review required.');return;
|
||||
}
|
||||
@@ -387,17 +399,21 @@ const advanceMarketCatchup=()=>{
|
||||
catchupImmediate=setImmediate(advanceMarketCatchup);catchupImmediate.unref();
|
||||
}
|
||||
};
|
||||
const marketCatchupTimer=setInterval(()=>{
|
||||
if(!stopping&&!catchupPaused&&!catchupImmediate&&market.hasReconciliationBacklog()){
|
||||
catchupImmediate=setImmediate(advanceMarketCatchup);catchupImmediate.unref();
|
||||
}
|
||||
},250);
|
||||
marketCatchupTimer.unref();
|
||||
if (market) {
|
||||
marketTimer=setInterval(()=>{try {market!.browse();}catch{console.error('Marketplace reconciliation failed; operator review required.');}},15000);
|
||||
marketTimer.unref();
|
||||
marketCatchupTimer=setInterval(()=>{
|
||||
if(!stopping&&!catchupPaused&&!catchupImmediate&&market!.hasReconciliationBacklog()){
|
||||
catchupImmediate=setImmediate(advanceMarketCatchup);catchupImmediate.unref();
|
||||
}
|
||||
},250);
|
||||
marketCatchupTimer.unref();
|
||||
}
|
||||
for(const signal of ['SIGTERM','SIGINT']as const)process.once(signal,()=>{
|
||||
if(stopping)return;stopping=true;peers.close();operations.close();clearInterval(marketTimer);clearInterval(marketCatchupTimer);
|
||||
if(stopping)return;stopping=true;peers.close();operations.close();if(marketTimer)clearInterval(marketTimer);if(marketCatchupTimer)clearInterval(marketCatchupTimer);
|
||||
if(catchupImmediate)clearImmediate(catchupImmediate);
|
||||
const observerDrain=custodyObserver?.close()??Promise.resolve();
|
||||
const timeout=setTimeout(()=>process.exit(1),25000);timeout.unref();
|
||||
server.close(async()=>{await observerDrain;await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custody?.close();await market.close();ledger.close();engine.close();await lock.close();await unlink(lockPath).catch(()=>{});clearTimeout(timeout);process.exit(0);});
|
||||
server.close(async()=>{await observerDrain;await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custody?.close();await market?.close();ledger.close();engine.close();await lock.close();await unlink(lockPath).catch(()=>{});clearTimeout(timeout);process.exit(0);});
|
||||
});
|
||||
server.on('error',async()=>{stopping=true;clearInterval(marketTimer);clearInterval(marketCatchupTimer);if(catchupImmediate)clearImmediate(catchupImmediate);operations.close();peers.close();await custodyObserver?.close();await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custody?.close();await market.close();ledger.close();engine.close();await lock.close();await unlink(lockPath).catch(()=>{});process.exit(1);});
|
||||
server.on('error',async()=>{stopping=true;if(marketTimer)clearInterval(marketTimer);if(marketCatchupTimer)clearInterval(marketCatchupTimer);if(catchupImmediate)clearImmediate(catchupImmediate);operations.close();peers.close();await custodyObserver?.close();await moneroWithdrawalPool?.end();await moneroDepositPool?.end();await custody?.close();await market?.close();ledger.close();engine.close();await lock.close();await unlink(lockPath).catch(()=>{});process.exit(1);});
|
||||
|
||||
Reference in New Issue
Block a user