Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 + AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui (React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and push.sh deploy script for digibyte.x/web.
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
// Milestone A6 demo — persistent, disk-backed header chain.
|
||
// Run once: downloads N headers from network, writes them to disk as
|
||
// they arrive. Run again: adopts the tip from disk in milliseconds
|
||
// and resumes from there.
|
||
//
|
||
// Usage:
|
||
// npm run cache -w @dgb-wallet/p2p [target_height] [cache_dir]
|
||
|
||
import { rm } from 'node:fs/promises';
|
||
import {
|
||
PeerPool,
|
||
HeaderChain,
|
||
encodeGetHeaders,
|
||
defaultCacheDir,
|
||
type BlockHeader,
|
||
} from '../src/index.js';
|
||
|
||
const DGB_GENESIS_HASH = '7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496';
|
||
|
||
const args = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
||
const TARGET = Number(args[0]) || 80_000;
|
||
const CACHE_DIR = args[1] || defaultCacheDir();
|
||
const CLEAR = process.argv.includes('--clear');
|
||
|
||
console.log('\n=== DigiByte Header Cache Demo (A6, disk-backed) ===\n');
|
||
console.log(`Cache dir: ${CACHE_DIR}`);
|
||
console.log(`Target: ${TARGET.toLocaleString()} headers`);
|
||
if (CLEAR) console.log('Flag --clear given → wiping cache first');
|
||
console.log();
|
||
|
||
if (CLEAR) {
|
||
await rm(CACHE_DIR, { recursive: true, force: true });
|
||
}
|
||
|
||
const t0 = Date.now();
|
||
const chain = new HeaderChain({ dir: CACHE_DIR });
|
||
chain.appendGenesis({
|
||
hash: DGB_GENESIS_HASH, prevBlock: '00'.repeat(32), merkleRoot: '',
|
||
version: 0, timestamp: 0, bits: 0, nonce: 0, raw: Buffer.alloc(80),
|
||
}, 0);
|
||
const startTip = chain.tipHeight;
|
||
const openMs = Date.now() - t0;
|
||
console.log(`[chain] opened in ${openMs}ms — tip=${startTip.toLocaleString()} (adopted from disk)`);
|
||
|
||
if (chain.tipHeight >= TARGET) {
|
||
console.log(`\nAlready at or past target height ${TARGET.toLocaleString()}. Nothing to sync.`);
|
||
console.log(`Elapsed: ${(Date.now() - t0)}ms (this launch is entirely disk-served)`);
|
||
chain.close();
|
||
process.exit(0);
|
||
}
|
||
|
||
console.log(`\nSyncing headers from ${chain.tipHeight.toLocaleString()} to ${TARGET.toLocaleString()}...`);
|
||
const pool = new PeerPool({ poolSize: 3, peerOpts: { handshakeTimeoutMs: 10_000 } });
|
||
let peer: ReturnType<typeof pool.pickReady>[number] | null = null;
|
||
const netStart = Date.now();
|
||
|
||
pool.on('connect', () => {
|
||
if (peer) return;
|
||
peer = pool.pickReady()[0];
|
||
if (!peer) return;
|
||
console.log(` peer: ${peer.host}:${peer.port}`);
|
||
|
||
peer.on('headers', (headers: BlockHeader[]) => {
|
||
chain.append(headers); // writes straight to disk, batched per call
|
||
process.stdout.write(`\r height: ${chain.tipHeight.toLocaleString()} `);
|
||
if (chain.tipHeight >= TARGET) {
|
||
const netMs = Date.now() - netStart;
|
||
const added = chain.tipHeight - startTip;
|
||
console.log(`\n\n network sync: ${netMs}ms for ${added.toLocaleString()} new headers`);
|
||
console.log(` disk usage: ${(chain.tipHeight * 80 / 1024 / 1024).toFixed(1)} MiB (80 bytes × ${chain.tipHeight.toLocaleString()} headers)`);
|
||
chain.close();
|
||
pool.stop();
|
||
process.exit(0);
|
||
} else if (headers.length >= 2000) {
|
||
peer!.send('getheaders', encodeGetHeaders(70019, chain.buildLocator()));
|
||
}
|
||
});
|
||
|
||
peer.send('getheaders', encodeGetHeaders(70019, chain.buildLocator()));
|
||
});
|
||
|
||
setTimeout(() => {
|
||
console.error('\ntimed out before reaching target');
|
||
chain.close();
|
||
pool.stop();
|
||
process.exit(1);
|
||
}, 120_000);
|