Mainnet
This commit is contained in:
@@ -44,12 +44,13 @@ export class Ledger {
|
||||
private closed = false;
|
||||
private revision = 0;
|
||||
private reorgs = 0;
|
||||
private readonly nowSeconds: () => number;
|
||||
private canonicalCache: { tip: string; blocks: TestnetBlock[] } | undefined;
|
||||
private constructor(file: string, db: DatabaseSync, pow: PowEngine) {
|
||||
this.file = file; this.db = db; this.pow = pow; this.index = new CanonicalIndex(db);
|
||||
private constructor(file: string, db: DatabaseSync, pow: PowEngine, nowSeconds: () => number) {
|
||||
this.file = file; this.db = db; this.pow = pow; this.nowSeconds = nowSeconds; this.index = new CanonicalIndex(db);
|
||||
}
|
||||
|
||||
static async open(dir: string, pow: PowEngine): Promise<Ledger> {
|
||||
static async open(dir: string, pow: PowEngine, nowSeconds: () => number = () => Math.floor(Date.now() / 1000)): Promise<Ledger> {
|
||||
const directory = join(dir, PROFILE);
|
||||
await mkdir(directory, { recursive: true, mode: 0o700 });
|
||||
const file = join(directory, 'ledger.sqlite');
|
||||
@@ -120,17 +121,36 @@ export class Ledger {
|
||||
for (const block of oldDisk.blocks) insertBlock.run(block.hash, block.previousHash, JSON.stringify(block));
|
||||
const insertPending = db.prepare('INSERT INTO pending_transfers(position, id, transaction_json) VALUES(?, ?, ?)');
|
||||
oldDisk.pendingTransfers.forEach((tx, index) => insertPending.run(index, transactionId(tx), JSON.stringify(tx)));
|
||||
insertMeta.run('legacy_json_imported_at', String(nowSeconds()));
|
||||
insertMeta.run('legacy_json_block_count', String(oldDisk.blocks.length));
|
||||
}
|
||||
insertMeta.run('legacy_json_migrated', '1');
|
||||
db.exec('COMMIT');
|
||||
} catch (error) { db.exec('ROLLBACK'); throw error; }
|
||||
}
|
||||
|
||||
const ledger = new Ledger(file, db, pow);
|
||||
for (const row of db.prepare('SELECT block_json FROM blocks ORDER BY sequence').iterate() as Iterable<{ block_json: string }>) {
|
||||
const ledger = new Ledger(file, db, pow, nowSeconds);
|
||||
const importedAtText = (db.prepare('SELECT value FROM metadata WHERE key=?').get('legacy_json_imported_at') as { value: string } | undefined)?.value;
|
||||
const importedCountText = (db.prepare('SELECT value FROM metadata WHERE key=?').get('legacy_json_block_count') as { value: string } | undefined)?.value;
|
||||
const legacyImportedAt = importedAtText !== undefined && /^(0|[1-9]\d{0,10})$/.test(importedAtText)
|
||||
? Number(importedAtText) : undefined;
|
||||
const legacyImportedCount = importedCountText !== undefined && /^(0|[1-9]\d{0,9})$/.test(importedCountText)
|
||||
? Number(importedCountText) : undefined;
|
||||
if ((importedAtText !== undefined && (legacyImportedAt === undefined || !Number.isSafeInteger(legacyImportedAt))) ||
|
||||
(importedCountText !== undefined && (legacyImportedCount === undefined || !Number.isSafeInteger(legacyImportedCount))) ||
|
||||
(legacyImportedAt === undefined) !== (legacyImportedCount === undefined)) {
|
||||
throw new Error('Legacy ledger import timestamp metadata is invalid');
|
||||
}
|
||||
for (const row of db.prepare('SELECT sequence, block_json FROM blocks ORDER BY sequence').iterate() as Iterable<{ sequence: number; block_json: string }>) {
|
||||
const raw = blockSchema.parse(JSON.parse(row.block_json));
|
||||
if (ledger.nodes.has(raw.hash)) throw new Error('Duplicate persisted block');
|
||||
const node = await ledger.validate(raw);
|
||||
const importedLegacyBlock = legacyImportedCount !== undefined && row.sequence <= legacyImportedCount;
|
||||
// Historical SQLite blocks already passed admission; wall-clock rollback must not
|
||||
// strand them. Imported JSON blocks retain their original admission clock so an
|
||||
// untrusted legacy file cannot inject arbitrarily future-dated history.
|
||||
const node = importedLegacyBlock
|
||||
? await ledger.validate(raw, false, legacyImportedAt!)
|
||||
: await ledger.validate(raw, true);
|
||||
ledger.nodes.set(node.block.hash, node);
|
||||
if (node.work > ledger.tipWork()) ledger.tip = node.block.hash;
|
||||
}
|
||||
@@ -297,10 +317,11 @@ export class Ledger {
|
||||
if (this.canonicalCache?.tip !== this.tip) this.canonicalCache = { tip: this.tip, blocks: this.branch(this.tip) };
|
||||
return this.canonicalCache.blocks; // Internal immutable block references only; public APIs clone their slice.
|
||||
}
|
||||
private async validate(raw: TestnetBlock): Promise<Node> {
|
||||
private async validate(raw: TestnetBlock, historicalReplay = false, now = this.nowSeconds()): Promise<Node> {
|
||||
const ancestors = this.branch(raw.previousHash);
|
||||
const parent = this.nodes.get(raw.previousHash);
|
||||
const { block, state } = await validateBlock(raw, ancestors, parent?.state ?? {}, this.pow);
|
||||
const { block, state } = await validateBlock(raw, ancestors, parent?.state ?? {}, this.pow,
|
||||
now, historicalReplay);
|
||||
return { block, state, work: (parent?.work ?? 0n) + workFor(block.target) };
|
||||
}
|
||||
private filterPending(parent: NftStateView, chain: TestnetBlock[], candidates: TestnetTransaction[]):
|
||||
|
||||
Reference in New Issue
Block a user