61 lines
4.0 KiB
TypeScript
61 lines
4.0 KiB
TypeScript
// Explicit opt-in live-network test. Creates disposable wallets and mines three test NFTs.
|
|
import assert from 'node:assert/strict';
|
|
import { generateKeyPairSync, sign } from 'node:crypto';
|
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
import { RandomX } from '../server/testnet/randomx.ts';
|
|
import { address, CHAIN_ID, miningBlob, mintId, sha256 } from '../server/testnet/consensus.ts';
|
|
import { blockClaimMessage, nftTransferMessage, NETWORK, testnetOwnerOf, transactionCardIds,
|
|
type MiningTemplate, type TestnetSnapshot } from '../shared/testnet.ts';
|
|
|
|
if(!process.argv.includes('--consent'))throw new Error('This test mines three blocks with a native CPU worker. Pass --consent explicitly. Disposable NFT rewards are not recoverable.');
|
|
const nodes=(process.env.WIKIDECK_SMOKE_NODES??'http://127.0.0.1:3101,http://127.0.0.1:3102,http://127.0.0.1:3103').split(',');
|
|
for(const endpoint of nodes)if(!/^http:\/\/127\.0\.0\.1:\d+$/.test(endpoint))throw new Error('Smoke test only accepts explicit loopback test nodes');
|
|
async function api(node:string,path:string,body?:unknown){
|
|
const r=await fetch(node+path,{method:body?'POST':'GET',headers:body?{'Content-Type':'application/json'}:{},
|
|
body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(30000),redirect:'error'});
|
|
const value=await r.json();if(!r.ok)throw new Error(value.error??`HTTP${r.status}`);return value;
|
|
}
|
|
const keys=generateKeyPairSync('ec',{namedCurve:'prime256v1'});
|
|
const publicKey=keys.publicKey.export({format:'der',type:'spki'}).toString('base64'),owner=address(publicKey);
|
|
const receiver=generateKeyPairSync('ec',{namedCurve:'prime256v1'});
|
|
const to=address(receiver.publicKey.export({format:'der',type:'spki'}).toString('base64'));
|
|
const signed=(m:string)=>sign('sha256',Buffer.from(m),{key:keys.privateKey,dsaEncoding:'ieee-p1363'}).toString('base64');
|
|
const engine=new RandomX();
|
|
try {
|
|
const initial=await api(nodes[0],'/api/state') as TestnetSnapshot;
|
|
assert.equal(initial.chainId,CHAIN_ID);
|
|
console.log(`Testing ${nodes.length} independent nodes on ${CHAIN_ID}; starting height ${initial.blocks.length}`);
|
|
let first='';
|
|
for(let count=0;count<3;count++) {
|
|
if(count===2){
|
|
const tx={network:NETWORK,chainId:CHAIN_ID,cardHash:first,from:owner,to,sequence:0};
|
|
await api(nodes[0],'/api/transfer',{...tx,publicKey,signature:signed(nftTransferMessage(tx))});
|
|
const pending=await api(nodes[0],'/api/state') as TestnetSnapshot;
|
|
assert.equal(testnetOwnerOf(pending,first),owner);
|
|
assert.ok(pending.pendingTransfers.some(t=>transactionCardIds(t).includes(first)));
|
|
}
|
|
const template=await api(nodes[0],'/api/mining/template',{owner,publicKey}) as MiningTemplate;
|
|
let found;
|
|
for(let batch=0;batch<100;batch++){
|
|
found=await engine.mine(template.header.key,miningBlob(template.header),template.header.target,32,32);
|
|
if(found)break;template.header.nonce+=32;
|
|
}
|
|
assert.ok(found,'Native proof not found within smoke budget');
|
|
template.header.nonce=found.nonce;
|
|
const hash=sha256(miningBlob(template.header)),tokenId=mintId(template.header);
|
|
await api(nodes[0],'/api/blocks',{...template.header,hash,tokenId,powHash:found.hash,signature:signed(blockClaimMessage(hash)),transactions:template.transactions});
|
|
if(count===0)first=tokenId;
|
|
console.log(`Native RandomX block #${template.header.height} accepted: ${hash}`);
|
|
}
|
|
const canonical=await api(nodes[0],'/api/state') as TestnetSnapshot;
|
|
const deadline=Date.now()+60000;
|
|
for(;;){
|
|
const states=await Promise.all(nodes.map(n=>api(n,'/api/state') as Promise<TestnetSnapshot>));
|
|
if(states.every(s=>s.tip===canonical.tip&&s.cumulativeWork===canonical.cumulativeWork&&testnetOwnerOf(s,first)===to)){
|
|
console.log(`PASS: ${states.length} independent nodes agreed on work ${canonical.cumulativeWork}, tip, NFT issuance and mined transfer.`);break;
|
|
}
|
|
if(Date.now()>deadline)throw new Error('Peer convergence timeout');
|
|
await delay(500);
|
|
}
|
|
}finally{engine.close();}
|