This commit is contained in:
2026-10-20 13:00:08 -04:00
parent a1de67ea14
commit bcd8630a8e
18 changed files with 483 additions and 172 deletions
+21 -13
View File
@@ -14,8 +14,8 @@ import { address, CHAIN_ID, GENESIS_HASH, MAX_TARGET, miningBlob, mintId, sha256
transferId, workFor } from '../server/testnet/consensus.ts';
import { NETWORK, CONSENSUS, blockClaimMessage, nftTransferMessage,
type MiningTemplate, type NftTransfer, type TestnetBlock, type TestnetSnapshot } from '../shared/testnet.ts';
import { marketMessage, type MarketAction, type MarketInfo, type MarketListing, type MarketOrder,
type MarketActivity } from '../shared/market.ts';
import { type MarketAction, type MarketInfo } from '../shared/market.ts';
import { marketMessageV3, type MarketActionV3, type MarketActivityV3, type MarketListingV3, type MarketOrderV3 } from '../shared/market-v3.ts';
import { StateClient } from '../shared/rpc-client.ts';
import { createBackup,restoreBackup } from '../server/recovery.ts';
import { Ledger } from '../server/testnet/ledger.ts';
@@ -139,9 +139,17 @@ test('native RandomX HTTP: three blocks, mined transfer, peer gossip/sync and du
}
const snapshot = (base: string) => json<TestnetSnapshot>(base, '/api/chain');
async function marketCommand<T>(base: string, info: MarketInfo, who: ReturnType<typeof wallet>, action: MarketAction) {
const terms = { network: NETWORK, chainId: CHAIN_ID, marketId: info.marketId, actor: who.owner,
publicKey: who.publicKey, nonce: randomUUID(), expiresAt: Date.now() + 60000, action };
return json<T>(base, '/api/market/command', 200, { ...terms, signature: who.signed(marketMessage(terms)) });
const atomicFromCents = (cents: number) => ((BigInt(cents) * 1_000_000_000_000n + 14999n) / 15000n).toString();
let current: MarketActionV3;
if (action.type === 'list') current = { type: 'list', cardHash: action.cardHash, sequence: action.sequence,
kind: action.kind, priceAtomicXmr: atomicFromCents(action.priceCents), endsAt: action.endsAt, feeBps: action.feeBps };
else if (action.type === 'buy') current = { type: 'buy', listingId: action.listingId,
priceAtomicXmr: atomicFromCents(action.priceCents), feeBps: action.feeBps };
else if (action.type === 'bid') current = { type: 'bid', listingId: action.listingId, priceAtomicXmr: atomicFromCents(action.priceCents) };
else current = action;
const terms = { version: 3 as const, network: NETWORK, chainId: CHAIN_ID, marketId: info.marketId, actor: who.owner,
publicKey: who.publicKey, nonce: randomUUID(), expiresAt: Date.now() + 60000, action: current };
return json<T>(base, '/api/market/command', 200, { ...terms, signature: who.signed(marketMessageV3(terms)) });
}
async function waitFor<T>(label: string, read: () => Promise<T>, predicate: (value: T) => boolean): Promise<T> {
const until = Date.now() + 25_000;
@@ -264,15 +272,15 @@ test('native RandomX HTTP: three blocks, mined transfer, peer gossip/sync and du
assert.equal(aliceDeck.tip, block2.hash);
assert.equal(aliceDeck.total, 2);
assert.deepEqual(aliceDeck.nfts.map(item => item.block.tokenId), [block1.tokenId, block2.tokenId]);
const market = await json<{ info: MarketInfo; listings: MarketListing[] }>(first.base, '/api/market');
const market = await json<{ info: MarketInfo; listings: MarketListingV3[] }>(first.base, '/api/market');
assert.equal(market.info.paymentMode, 'mock');
assert.deepEqual(market.listings, []);
await (await request(first.base, '/api/market/command', 400, {})).text();
await (await request(first.base, '/api/market/command', 403, {}, { Origin: 'https://evil.example' })).text();
const sale = await marketCommand<MarketListing>(first.base, market.info, alice, { type: 'list',
const sale = await marketCommand<MarketListingV3>(first.base, market.info, alice, { type: 'list',
cardHash: block1.tokenId, sequence: 0, kind: 'fixed', priceCents: 1500, endsAt: null, feeBps: 250 });
assert.equal((await json<{ listings: MarketListing[] }>(first.base, '/api/market')).listings[0].id, sale.id);
const order = await marketCommand<MarketOrder>(first.base, market.info, bob, { type: 'buy', listingId: sale.id,
assert.equal((await json<{ listings: MarketListingV3[] }>(first.base, '/api/market')).listings[0].id, sale.id);
const order = await marketCommand<MarketOrderV3>(first.base, market.info, bob, { type: 'buy', listingId: sale.id,
priceCents: 1500, feeBps: 250 });
assert.equal(order.invoice.address, null);
assert.equal(order.invoice.amountXmr, '0.100000000000');
@@ -292,7 +300,7 @@ test('native RandomX HTTP: three blocks, mined transfer, peer gossip/sync and du
{ ...transfer, signature: tamperSignature(transfer.signature) });
assert.match(badTransfer.error, /signature/i);
await marketCommand(first.base, market.info, alice, { type: 'deliver', orderId: order.id, transfer });
assert.equal((await marketCommand<MarketActivity>(first.base, market.info, bob, { type: 'activity' })).orders[0].status, 'delivery-pending');
assert.equal((await marketCommand<MarketActivityV3>(first.base, market.info, bob, { type: 'activity' })).orders[0].status, 'delivery-pending');
const pending = { id: transferId(transfer), status: 'pending',
message: 'Ownership changes only when this transaction is mined.' };
assert.deepEqual(await json(second.base, '/api/transfer', 202, transfer), pending);
@@ -330,10 +338,10 @@ test('native RandomX HTTP: three blocks, mined transfer, peer gossip/sync and du
assert.deepEqual(canonical.blocks, [block1, block2, block3]);
assert.deepEqual(canonical.transfers, [transfer]);
assert.deepEqual(canonical.pendingTransfers, []);
const settling = await marketCommand<MarketActivity>(first.base, market.info, bob, { type: 'activity' });
const settling = await marketCommand<MarketActivityV3>(first.base, market.info, bob, { type: 'activity' });
assert.equal(settling.orders[0].deliveryConfirmations, 1);
assert.equal(settling.orders[0].payout, 'not-started');
assert.equal((await json<{ listings: MarketListing[] }>(second.base, '/api/market')).listings.length, 0);
assert.equal((await json<{ listings: MarketListingV3[] }>(second.base, '/api/market')).listings.length, 0);
assert.equal(canonical.cumulativeWork, (3n * workFor(MAX_TARGET)).toString());
for (const node of [first, second]) {
const replay = await json<{ error: string }>(node.base, '/api/transfer', 400, transfer);
@@ -383,7 +391,7 @@ test('native RandomX HTTP: three blocks, mined transfer, peer gossip/sync and du
assert.deepEqual(await pagedClient.load(),restored);
const restoredMarket = await json<{ info: MarketInfo }>(first.base, '/api/market');
assert.equal(restoredMarket.info.marketId, market.info.marketId);
assert.equal((await marketCommand<MarketActivity>(first.base, market.info, bob, { type: 'activity' })).orders[0].id, order.id);
assert.equal((await marketCommand<MarketActivityV3>(first.base, market.info, bob, { type: 'activity' })).orders[0].id, order.id);
assert.deepEqual(restored, { ...canonical, peers: 0 });
assert.deepEqual(await json(first.base, `/api/nfts/${block1.tokenId}`), {
network: NETWORK, chainId: CHAIN_ID, tokenId: block1.tokenId, owner: bob.owner, sequence: 1,