V2.1 (#21)
Working version Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
+15
-12
@@ -3,8 +3,8 @@ import { request as httpsRequest } from 'node:https';
|
||||
import dns from 'node:dns/promises';
|
||||
import { BlockList, isIP } from 'node:net';
|
||||
import { z } from 'zod';
|
||||
import { NETWORK, type TestnetBlock, type NftTransfer } from '../../shared/testnet.ts';
|
||||
import { CHAIN_ID, blockSchema, transferId, transferSchema } from './consensus.ts';
|
||||
import { NETWORK, type TestnetBlock, type TestnetTransaction } from '../../shared/testnet.ts';
|
||||
import { CHAIN_ID, blockSchema, transactionId, transactionSchema } from './consensus.ts';
|
||||
import type { Ledger } from './ledger.ts';
|
||||
|
||||
const MAX_BODY = 2 * 1024 * 1024;
|
||||
@@ -24,14 +24,14 @@ function publicAddress(ip: string): boolean {
|
||||
return family === 6 && globalV6.check(ip, 'ipv6') && !excluded.check(ip, 'ipv6');
|
||||
}
|
||||
const remoteStatus = z.object({ network: z.literal(NETWORK), chainId: z.literal(CHAIN_ID),
|
||||
size: z.number().int().min(0).max(10_000), tip: z.string().regex(/^[a-f0-9]{64}$/).optional() }).strict();
|
||||
size: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER), tip: z.string().regex(/^[a-f0-9]{64}$/).optional() }).strict();
|
||||
const pageSchema = z.object({ blocks: z.array(blockSchema).max(32),
|
||||
next: z.number().int().min(0).max(10_000).nullable() }).strict();
|
||||
const poolSchema = z.object({ transactions: z.array(transferSchema).max(256) }).strict();
|
||||
next: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable() }).strict();
|
||||
const poolSchema = z.object({ transactions: z.array(transactionSchema).max(256) }).strict();
|
||||
type Peer = { url: string; connected: boolean; error?: string; offset: number; busy: boolean };
|
||||
|
||||
/** Outbound-only fixed allowlist. HTTP routes are deliberately left to the caller.
|
||||
* allowPrivate is an explicit operator permission for local/Docker networks, not peer input.
|
||||
* allowPrivate is an explicit operator permission for local/Docker/cluster networks, not peer input.
|
||||
*/
|
||||
export class PeerNetwork {
|
||||
private readonly peers: Peer[];
|
||||
@@ -90,10 +90,13 @@ export class PeerNetwork {
|
||||
if (!known) throw new Error('Cannot broadcast an unaccepted block');
|
||||
await this.broadcast('/p2p/block', known);
|
||||
}
|
||||
async broadcastTransfer(tx: NftTransfer): Promise<void> {
|
||||
const id = transferId(transferSchema.parse(tx));
|
||||
async broadcastTransfer(tx: TestnetTransaction): Promise<void> {
|
||||
return this.broadcastTransaction(tx);
|
||||
}
|
||||
async broadcastTransaction(tx: TestnetTransaction): Promise<void> {
|
||||
const id = transactionId(transactionSchema.parse(tx));
|
||||
const snapshot = this.ledger.snapshot();
|
||||
const known = [...snapshot.pendingTransfers, ...snapshot.transfers].find(t => transferId(t) === id);
|
||||
const known = [...snapshot.pendingTransfers, ...snapshot.transfers].find(t => transactionId(t) === id);
|
||||
if (!known) throw new Error('Cannot broadcast an unaccepted transfer');
|
||||
await this.broadcast('/p2p/transaction', known);
|
||||
}
|
||||
@@ -121,8 +124,8 @@ export class PeerNetwork {
|
||||
if (peer.offset < status.size) {
|
||||
const page = pageSchema.parse(await this.request(peer, `/p2p/blocks?offset=${peer.offset}`));
|
||||
const end = peer.offset + page.blocks.length;
|
||||
if (!page.blocks.length || end > 10_000 ||
|
||||
(page.next !== null && (page.next !== end || end >= 10_000)) ||
|
||||
if (!page.blocks.length || !Number.isSafeInteger(end) ||
|
||||
(page.next !== null && page.next !== end) ||
|
||||
(page.next === null && end < status.size)) throw new Error('Invalid peer pagination');
|
||||
for (const block of page.blocks) {
|
||||
if (this.closed) throw new Error('Peer network closed');
|
||||
@@ -134,7 +137,7 @@ export class PeerNetwork {
|
||||
for (const tx of pool.transactions) {
|
||||
if (this.closed) throw new Error('Peer network closed');
|
||||
// Valid remote transactions may be immature/conflicting on our current tip.
|
||||
try { await this.ledger.submitTransfer(tx); } catch { /* Independently reject; do not relay. */ }
|
||||
try { await this.ledger.submitTransaction(tx); } catch { /* Independently reject; do not relay. */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user