257 lines
5.6 KiB
JavaScript
257 lines
5.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const {tmpdir} = require('os');
|
|
const path = require('path');
|
|
const fs = require('bfile');
|
|
const bio = require('bufio');
|
|
const {randomBytes} = require('bcrypto/lib/random');
|
|
const Block = require('../../lib/primitives/block');
|
|
const MerkleBlock = require('../../lib/primitives/merkleblock');
|
|
const Headers = require('../../lib/primitives/headers');
|
|
const {CompactBlock} = require('../../lib/net/bip152');
|
|
const TX = require('../../lib/primitives/tx');
|
|
const Output = require('../../lib/primitives/output');
|
|
const CoinView = require('../../lib/coins/coinview');
|
|
|
|
const common = exports;
|
|
|
|
common.readFile = function readFile(name, enc) {
|
|
const file = path.resolve(__dirname, '..', 'data', name);
|
|
return fs.readFileSync(file, enc);
|
|
};
|
|
|
|
common.writeFile = function writeFile(name, data) {
|
|
const file = path.resolve(__dirname, '..', 'data', name);
|
|
return fs.writeFileSync(file, data);
|
|
};
|
|
|
|
common.exists = function exists(name) {
|
|
const file = path.resolve(__dirname, '..', 'data', name);
|
|
return fs.existsSync(file);
|
|
};
|
|
|
|
common.readBlock = function readBlock(name) {
|
|
const raw = common.readFile(`${name}.raw`);
|
|
|
|
if (!common.exists(`${name}-undo.raw`))
|
|
return new BlockContext(Block, raw);
|
|
|
|
const undoRaw = common.readFile(`${name}-undo.raw`);
|
|
|
|
return new BlockContext(Block, raw, undoRaw);
|
|
};
|
|
|
|
common.readMerkle = function readMerkle(name) {
|
|
const raw = common.readFile(`${name}.raw`);
|
|
return new BlockContext(MerkleBlock, raw);
|
|
};
|
|
|
|
common.readCompact = function readCompact(name) {
|
|
const raw = common.readFile(`${name}.raw`);
|
|
return new BlockContext(CompactBlock, raw);
|
|
};
|
|
|
|
common.readTX = function readTX(name) {
|
|
const raw = common.readFile(`${name}.raw`);
|
|
|
|
if (!common.exists(`${name}-undo.raw`))
|
|
return new TXContext(raw);
|
|
|
|
const undoRaw = common.readFile(`${name}-undo.raw`);
|
|
|
|
return new TXContext(raw, undoRaw);
|
|
};
|
|
|
|
common.writeBlock = function writeBlock(name, block, view) {
|
|
common.writeFile(`${name}.raw`, block.toRaw());
|
|
|
|
if (!view)
|
|
return;
|
|
|
|
const undo = makeBlockUndo(block, view);
|
|
const undoRaw = serializeUndo(undo);
|
|
|
|
common.writeFile(`${name}-undo.raw`, undoRaw);
|
|
};
|
|
|
|
common.writeTX = function writeTX(name, tx, view) {
|
|
common.writeFile(`${name}.raw`, tx.toRaw());
|
|
|
|
if (!view)
|
|
return;
|
|
|
|
const undo = makeTXUndo(tx, view);
|
|
const undoRaw = serializeUndo(undo);
|
|
|
|
common.writeFile(`${name}-undo.raw`, undoRaw);
|
|
};
|
|
|
|
common.testdir = function(name) {
|
|
assert(/^[a-z]+$/.test(name), 'Invalid name');
|
|
|
|
const uniq = randomBytes(4).toString('hex');
|
|
return path.join(tmpdir(), `bcoin-test-${name}-${uniq}`);
|
|
};
|
|
|
|
common.rimraf = async function(p) {
|
|
const allowed = /bcoin\-test\-[a-z]+\-[a-f0-9]{8}(\/[a-z]+)?$/;
|
|
if (!allowed.test(p))
|
|
throw new Error(`Path not allowed: ${p}.`);
|
|
|
|
return await fs.rimraf(p);
|
|
};
|
|
|
|
common.forValue = async function(obj, key, val, timeout = 30000) {
|
|
assert(typeof obj === 'object');
|
|
assert(typeof key === 'string');
|
|
|
|
const ms = 10;
|
|
let interval = null;
|
|
let count = 0;
|
|
return new Promise((resolve, reject) => {
|
|
interval = setInterval(() => {
|
|
if (obj[key] === val) {
|
|
clearInterval(interval);
|
|
resolve();
|
|
} else if (count * ms >= timeout) {
|
|
clearInterval(interval);
|
|
reject(new Error('Timeout waiting for value.'));
|
|
}
|
|
count += 1;
|
|
}, ms);
|
|
});
|
|
};
|
|
|
|
function parseUndo(data) {
|
|
const br = bio.read(data);
|
|
const items = [];
|
|
|
|
while (br.left()) {
|
|
const output = Output.fromReader(br);
|
|
items.push(output);
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
function serializeUndo(items) {
|
|
const bw = bio.write();
|
|
|
|
for (const item of items) {
|
|
bw.writeI64(item.value);
|
|
bw.writeVarBytes(item.script.toRaw());
|
|
}
|
|
|
|
return bw.render();
|
|
}
|
|
|
|
function applyBlockUndo(block, undo) {
|
|
const view = new CoinView();
|
|
let i = 0;
|
|
|
|
for (const tx of block.txs) {
|
|
if (tx.isCoinbase())
|
|
continue;
|
|
|
|
for (const {prevout} of tx.inputs)
|
|
view.addOutput(prevout, undo[i++]);
|
|
}
|
|
|
|
assert(i === undo.length, 'Undo coins data inconsistency.');
|
|
|
|
return view;
|
|
}
|
|
|
|
function applyTXUndo(tx, undo) {
|
|
const view = new CoinView();
|
|
let i = 0;
|
|
|
|
for (const {prevout} of tx.inputs)
|
|
view.addOutput(prevout, undo[i++]);
|
|
|
|
assert(i === undo.length, 'Undo coins data inconsistency.');
|
|
|
|
return view;
|
|
}
|
|
|
|
function makeBlockUndo(block, view) {
|
|
const items = [];
|
|
|
|
for (const tx of block.txs) {
|
|
if (tx.isCoinbase())
|
|
continue;
|
|
|
|
for (const {prevout} of tx.inputs) {
|
|
const coin = view.getOutput(prevout);
|
|
assert(coin);
|
|
items.push(coin);
|
|
}
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
function makeTXUndo(tx, view) {
|
|
const items = [];
|
|
|
|
for (const {prevout} of tx.inputs) {
|
|
const coin = view.getOutput(prevout);
|
|
assert(coin);
|
|
items.push(coin);
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
class BlockContext {
|
|
constructor(ctor, raw, undoRaw) {
|
|
this.ctor = ctor;
|
|
this.raw = raw;
|
|
this.undoRaw = undoRaw || null;
|
|
}
|
|
getRaw() {
|
|
return this.raw;
|
|
}
|
|
getBlock() {
|
|
const Block = this.ctor;
|
|
const block = Block.fromRaw(this.raw);
|
|
|
|
if (!this.undoRaw) {
|
|
const view = new CoinView();
|
|
return [block, view];
|
|
}
|
|
|
|
const undo = parseUndo(this.undoRaw);
|
|
const view = applyBlockUndo(block, undo);
|
|
|
|
return [block, view];
|
|
}
|
|
getHeaders() {
|
|
return Headers.fromHead(this.raw);
|
|
}
|
|
}
|
|
|
|
class TXContext {
|
|
constructor(raw, undoRaw) {
|
|
this.raw = raw;
|
|
this.undoRaw = undoRaw || null;
|
|
}
|
|
getRaw() {
|
|
return this.raw;
|
|
}
|
|
getTX() {
|
|
const tx = TX.fromRaw(this.raw);
|
|
|
|
if (!this.undoRaw) {
|
|
const view = new CoinView();
|
|
return [tx, view];
|
|
}
|
|
|
|
const undo = parseUndo(this.undoRaw);
|
|
const view = applyTXUndo(tx, undo);
|
|
|
|
return [tx, view];
|
|
}
|
|
}
|