fcoin/test/blockstore-test.js
2019-06-05 12:11:57 -07:00

1250 lines
34 KiB
JavaScript

/* eslint-env mocha */
/* eslint prefer-arrow-callback: "off" */
'use strict';
const Logger = require('blgr');
const bio = require('bufio');
const assert = require('bsert');
const common = require('./util/common');
const {resolve} = require('path');
const fs = require('bfile');
const {rimraf, testdir} = require('./util/common');
const random = require('bcrypto/lib/random');
const vectors = [
common.readBlock('block300025'),
common.readBlock('block426884'),
common.readBlock('block898352')
];
const extra = [
common.readBlock('block482683')
];
const undos = [
common.readBlock('block300025'),
common.readBlock('block928816'),
common.readBlock('block928828'),
common.readBlock('block928831'),
common.readBlock('block928848'),
common.readBlock('block928849')
];
const {
AbstractBlockStore,
FileBlockStore,
LevelBlockStore
} = require('../lib/blockstore');
const layout = require('../lib/blockstore/layout');
const {types} = require('../lib/blockstore/common');
const {
BlockRecord,
FileRecord
} = require('../lib/blockstore/records');
describe('BlockStore', function() {
describe('Abstract', function() {
let logger = null;
function context(ctx) {
return {info: () => ctx};
}
beforeEach(() => {
logger = Logger.global;
Logger.global = {context};
});
afterEach(() => {
Logger.global = logger;
});
it('construct with custom logger', async () => {
const store = new AbstractBlockStore({logger: {context}});
assert(store.logger);
assert(store.logger.info);
assert.equal(store.logger.info(), 'blockstore');
});
it('construct with default logger', async () => {
const store = new AbstractBlockStore();
assert(store.logger);
assert(store.logger.info);
assert.equal(store.logger.info(), 'blockstore');
});
it('has unimplemented base methods', async () => {
const methods = ['open', 'close', 'write', 'writeUndo',
'read', 'readUndo', 'prune', 'pruneUndo',
'has', 'hasUndo', 'ensure'];
const store = new AbstractBlockStore();
for (const method of methods) {
assert(store[method]);
let err = null;
try {
await store[method]();
} catch (e) {
err = e;
}
assert(err, `Expected unimplemented method ${method}.`);
assert.equal(err.message, 'Abstract method.');
}
});
});
describe('Records', function() {
describe('BlockRecord', function() {
function constructError(options) {
let err = null;
try {
new BlockRecord({
file: options.file,
position: options.position,
length: options.length
});
} catch (e) {
err = e;
}
assert(err);
}
function toAndFromRaw(options) {
const rec1 = new BlockRecord(options);
assert.equal(rec1.file, options.file);
assert.equal(rec1.position, options.position);
assert.equal(rec1.length, options.length);
const raw = rec1.toRaw();
const rec2 = BlockRecord.fromRaw(raw);
assert.equal(rec2.file, options.file);
assert.equal(rec2.position, options.position);
assert.equal(rec2.length, options.length);
}
it('construct with correct options', () => {
const rec = new BlockRecord({
file: 12,
position: 23392,
length: 4194304
});
assert.equal(rec.file, 12);
assert.equal(rec.position, 23392);
assert.equal(rec.length, 4194304);
});
it('construct null record', () => {
const rec = new BlockRecord();
assert.equal(rec.file, 0);
assert.equal(rec.position, 0);
assert.equal(rec.length, 0);
});
it('fail with signed number (file)', () => {
constructError({file: -1, position: 1, length: 1});
});
it('fail with signed number (position)', () => {
constructError({file: 1, position: -1, length: 1});
});
it('fail with signed number (length)', () => {
constructError({file: 1, position: 1, length: -1});
});
it('fail with non-32-bit number (file)', () => {
constructError({file: Math.pow(2, 32), position: 1, length: 1});
});
it('fail with non-32-bit number (position)', () => {
constructError({file: 1, position: Math.pow(2, 32), length: 1});
});
it('fail with non-32-bit number (length)', () => {
constructError({file: 1, position: 1, length: Math.pow(2, 32)});
});
it('construct with max 32-bit numbers', () => {
const max = Math.pow(2, 32) - 1;
const rec = new BlockRecord({
file: max,
position: max,
length: max
});
assert(rec);
assert.equal(rec.file, max);
assert.equal(rec.position, max);
assert.equal(rec.length, max);
});
it('serialize/deserialize file record (min)', () => {
toAndFromRaw({file: 0, position: 0, length: 0});
});
it('serialize/deserialize file record', () => {
toAndFromRaw({file: 12, position: 23392, length: 4194304});
});
it('serialize/deserialize file record (max)', () => {
const max = Math.pow(2, 32) - 1;
toAndFromRaw({file: max, position: max, length: max});
});
});
describe('FileRecord', function() {
function constructError(options) {
let err = null;
try {
new FileRecord({
blocks: options.blocks,
used: options.used,
length: options.length
});
} catch (e) {
err = e;
}
assert(err);
}
function toAndFromRaw(options) {
const rec1 = new FileRecord(options);
assert.equal(rec1.blocks, options.blocks);
assert.equal(rec1.used, options.used);
assert.equal(rec1.length, options.length);
const raw = rec1.toRaw();
const rec2 = FileRecord.fromRaw(raw);
assert.equal(rec2.blocks, options.blocks);
assert.equal(rec2.used, options.used);
assert.equal(rec2.length, options.length);
}
it('construct with correct options', () => {
const rec = new FileRecord({
blocks: 1,
used: 4194304,
length: 20971520
});
assert.equal(rec.blocks, 1);
assert.equal(rec.used, 4194304);
assert.equal(rec.length, 20971520);
});
it('fail to with signed number (blocks)', () => {
constructError({blocks: -1, used: 1, length: 1});
});
it('fail to with signed number (used)', () => {
constructError({blocks: 1, used: -1, length: 1});
});
it('fail to with signed number (length)', () => {
constructError({blocks: 1, used: 1, length: -1});
});
it('fail to with non-32-bit number (blocks)', () => {
constructError({blocks: Math.pow(2, 32), used: 1, length: 1});
});
it('fail to with non-32-bit number (used)', () => {
constructError({blocks: 1, used: Math.pow(2, 32), length: 1});
});
it('fail to with non-32-bit number (length)', () => {
constructError({blocks: 1, used: 1, length: Math.pow(2, 32)});
});
it('serialize/deserialize block record (min)', () => {
toAndFromRaw({blocks: 0, used: 0, length: 0});
});
it('serialize/deserialize block record', () => {
toAndFromRaw({blocks: 10, used: 4194304, length: 20971520});
});
it('serialize/deserialize block record (max)', () => {
const max = Math.pow(2, 32) - 1;
toAndFromRaw({blocks: max, used: max, length: max});
});
});
});
describe('FileBlockStore (Unit)', function() {
const location = '/tmp/.bcoin/blocks';
let store = null;
before(() => {
store = new FileBlockStore({
location: location,
maxFileLength: 1024
});
});
describe('constructor', function() {
it('will pass options to super', () => {
const info = () => 'info';
const logger = {
context: () => {
return {info};
}
};
const store = new FileBlockStore({
location: '/tmp/.bcoin/blocks',
maxFileLength: 1024,
logger: logger
});
assert.strictEqual(store.logger.info, info);
});
it('will error with invalid location', () => {
let err = null;
try {
new FileBlockStore({
location: 'tmp/.bcoin/blocks',
maxFileLength: 1024
});
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Location not absolute.');
});
it('will error with invalid max file length', () => {
let err = null;
try {
new FileBlockStore({
location: location,
maxFileLength: 'notanumber'
});
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Invalid max file length.');
});
});
describe('allocate', function() {
it('will fail with length above file max', async () => {
let err = null;
try {
await store.allocate(types.BLOCK, 1025);
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Block length above max file length.');
});
});
describe('filepath', function() {
it('will give correct path (0)', () => {
const filepath = store.filepath(types.BLOCK, 0);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk00000.dat');
});
it('will give correct path (1)', () => {
const filepath = store.filepath(types.BLOCK, 7);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk00007.dat');
});
it('will give correct path (2)', () => {
const filepath = store.filepath(types.BLOCK, 23);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk00023.dat');
});
it('will give correct path (3)', () => {
const filepath = store.filepath(types.BLOCK, 456);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk00456.dat');
});
it('will give correct path (4)', () => {
const filepath = store.filepath(types.BLOCK, 8999);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk08999.dat');
});
it('will give correct path (5)', () => {
const filepath = store.filepath(types.BLOCK, 99999);
assert.equal(filepath, '/tmp/.bcoin/blocks/blk99999.dat');
});
it('will fail over max size', () => {
let err = null;
try {
store.filepath(types.BLOCK, 100000);
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'File number too large.');
});
it('will give undo type', () => {
const filepath = store.filepath(types.UNDO, 99999);
assert.equal(filepath, '/tmp/.bcoin/blocks/blu99999.dat');
});
it('will fail for unknown prefix', () => {
let err = null;
try {
store.filepath(0, 1234);
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Unknown file prefix.');
});
});
describe('write', function() {
const write = fs.write;
const open = fs.open;
const close = fs.close;
let allocate = null;
let has = null;
beforeEach(() => {
allocate = store.allocate;
has = store.db.has;
});
afterEach(() => {
// Restore stubbed methods.
fs.write = write;
fs.open = open;
fs.close = close;
store.allocate = allocate;
store.db.has = has;
});
it('will error if total magic bytes not written', async () => {
let err = null;
store.allocate = () => {
return {
fileno: 20,
filerecord: {
used: 0
},
filepath: '/tmp/.bcoin/blocks/blk00020.dat'
};
};
store.db.has = () => false;
fs.open = () => 7;
fs.close = () => undefined;
fs.write = () => 0;
try {
const hash = random.randomBytes(128);
const block = random.randomBytes(32);
await store.write(hash, block);
} catch (e) {
err = e;
}
assert(err, 'Expected error.');
assert.equal(err.message, 'Could not write block magic.');
});
it('will error if total block bytes not written', async () => {
let err = 0;
let called = 0;
store.allocate = () => {
return {
fileno: 20,
filerecord: {
used: 0
},
filepath: '/tmp/.bcoin/blocks/blk00020.dat'
};
};
store.db.has = () => false;
fs.open = () => 7;
fs.close = () => undefined;
fs.write = (fd, buffer, offset, length, position) => {
let written = 0;
if (called === 0)
written = length;
called += 1;
return written;
};
try {
const hash = random.randomBytes(128);
const block = random.randomBytes(32);
await store.write(hash, block);
} catch (e) {
err = e;
}
assert(err, 'Expected error.');
assert.equal(err.message, 'Could not write block.');
});
it('will close file if write throws', async () => {
let err = null;
let closed = null;
store.allocate = () => {
return {
fileno: 20,
filerecord: {
used: 0
},
filepath: '/tmp/.bcoin/blocks/blk00020.dat'
};
};
store.db.has = () => false;
fs.open = () => 7;
fs.close = (fd) => {
closed = fd;
};
fs.write = () => {
throw new Error('Test.');
};
try {
const hash = random.randomBytes(128);
const block = random.randomBytes(32);
await store.write(hash, block);
} catch (e) {
err = e;
}
assert(err, 'Expected error.');
assert.equal(err.message, 'Test.');
assert.equal(closed, 7);
});
});
describe('read', function() {
const read = fs.read;
const open = fs.open;
const close = fs.close;
let get = null;
let raw = null;
before(() => {
const record = new BlockRecord({
file: 1,
position: 8,
length: 100
});
raw = record.toRaw();
});
beforeEach(() => {
get = store.db.get;
});
afterEach(() => {
// Restore stubbed methods.
store.db.get = get;
fs.read = read;
fs.open = open;
fs.close = close;
});
it('will error if total read bytes not correct', async () => {
let err = null;
store.db.get = () => raw;
fs.open = () => 7;
fs.close = () => undefined;
fs.read = () => 99;
try {
const hash = random.randomBytes(128);
const block = random.randomBytes(32);
await store.read(hash, block);
} catch (e) {
err = e;
}
assert(err, 'Expected error.');
assert.equal(err.message, 'Wrong number of bytes read.');
});
it('will close file if read throws', async () => {
let err = null;
let closed = null;
store.db.get = () => raw;
fs.open = () => 7;
fs.close = (fd) => {
closed = fd;
};
fs.read = () => {
throw new Error('Test.');
};
try {
const hash = random.randomBytes(128);
const block = random.randomBytes(32);
await store.read(hash, block);
} catch (e) {
err = e;
}
assert(err, 'Expected error.');
assert.equal(err.message, 'Test.');
assert.equal(closed, 7);
});
});
});
describe('FileBlockStore (Integration 1)', function() {
const location = testdir('blockstore');
let store = null;
beforeEach(async () => {
await rimraf(location);
store = new FileBlockStore({
location: location,
maxFileLength: 1024
});
await store.ensure();
await store.open();
});
afterEach(async () => {
await store.close();
});
after(async () => {
await rimraf(location);
});
it('will write and read a block', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const block2 = await store.read(hash);
assert.bufferEqual(block1, block2);
});
it('will write and read block undo coins', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.writeUndo(hash, block1);
const block2 = await store.readUndo(hash);
assert.bufferEqual(block1, block2);
});
it('will read a block w/ offset and length', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const offset = 79;
const size = 15;
const block2 = await store.read(hash, offset, size);
assert.bufferEqual(block1.slice(offset, offset + size), block2);
});
it('will fail to read w/ out-of-bounds length', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const offset = 79;
const size = 50;
let err = null;
try {
await store.read(hash, offset, size);
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Out-of-bounds read.');
});
it('will allocate new files', async () => {
const blocks = [];
for (let i = 0; i < 16; i++) {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
blocks.push({hash, block});
await store.write(hash, block);
const block2 = await store.read(hash);
assert.bufferEqual(block2, block);
}
const first = await fs.stat(store.filepath(types.BLOCK, 0));
const second = await fs.stat(store.filepath(types.BLOCK, 1));
const third = await fs.stat(store.filepath(types.BLOCK, 2));
assert.equal(first.size, 952);
assert.equal(second.size, 952);
assert.equal(third.size, 272);
const magic = (8 * 16);
const len = first.size + second.size + third.size - magic;
assert.equal(len, 128 * 16);
for (let i = 0; i < 16; i++) {
const expect = blocks[i];
const block = await store.read(expect.hash);
assert.bufferEqual(block, expect.block);
}
});
it('will allocate new files with block undo coins', async () => {
const blocks = [];
for (let i = 0; i < 16; i++) {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
blocks.push({hash, block});
await store.writeUndo(hash, block);
const block2 = await store.readUndo(hash);
assert.bufferEqual(block2, block);
}
const first = await fs.stat(store.filepath(types.UNDO, 0));
const second = await fs.stat(store.filepath(types.UNDO, 1));
const third = await fs.stat(store.filepath(types.UNDO, 2));
const magic = (40 * 16);
const len = first.size + second.size + third.size - magic;
assert.equal(len, 128 * 16);
for (let i = 0; i < 16; i++) {
const expect = blocks[i];
const block = await store.readUndo(expect.hash);
assert.bufferEqual(block, expect.block);
}
});
it('will recover from interrupt during block write', async () => {
{
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block);
const block2 = await store.read(hash);
assert.bufferEqual(block2, block);
}
// Manually insert a partially written block to the
// end of file as would be the case of an untimely
// interrupted write of a block. The file record
// would not be updated to include the used bytes and
// thus this data should be overwritten.
{
const filepath = store.filepath(types.BLOCK, 0);
const fd = await fs.open(filepath, 'a');
const bw = bio.write(8);
bw.writeU32(store.network.magic);
bw.writeU32(73);
const magic = bw.render();
const failblock = random.randomBytes(73);
const mwritten = await fs.write(fd, magic, 0, 8);
const bwritten = await fs.write(fd, failblock, 0, 73);
await fs.close(fd);
assert.equal(mwritten, 8);
assert.equal(bwritten, 73);
}
// Now check that this block has the correct position
// in the file and that it can be read correctly.
{
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block);
const block2 = await store.read(hash);
assert.bufferEqual(block2, block);
}
});
it('will not write blocks at the same position', (done) => {
let err = null;
let finished = 0;
for (let i = 0; i < 16; i++) {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
// Accidentally don't use `await` and attempt to
// write multiple blocks in parallel and at the
// same file position.
(async () => {
try {
await store.write(hash, block);
} catch (e) {
err = e;
} finally {
finished += 1;
if (finished >= 16) {
assert(err);
assert(err.message, 'Already writing.');
done();
}
}
})();
}
});
it('will not duplicate a block on disk', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
const first = await store.write(hash, block);
assert.equal(first, true);
const second = await store.write(hash, block);
assert.equal(second, false);
const pruned = await store.prune(hash);
assert.equal(pruned, true);
assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), false);
});
it('will return null if block not found', async () => {
const hash = random.randomBytes(32);
const block = await store.read(hash);
assert.strictEqual(block, null);
});
it('will check if block exists (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.has(hash);
assert.strictEqual(exists, false);
});
it('will check if block exists (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block);
const exists = await store.has(hash);
assert.strictEqual(exists, true);
});
it('will check if block undo coins exists (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.hasUndo(hash);
assert.strictEqual(exists, false);
});
it('will check if block undo coins exists (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.writeUndo(hash, block);
const exists = await store.hasUndo(hash);
assert.strictEqual(exists, true);
});
it('will prune blocks', async () => {
const hashes = [];
for (let i = 0; i < 16; i++) {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
hashes.push(hash);
await store.write(hash, block);
}
const first = await fs.stat(store.filepath(types.BLOCK, 0));
const second = await fs.stat(store.filepath(types.BLOCK, 1));
const third = await fs.stat(store.filepath(types.BLOCK, 2));
const magic = (8 * 16);
const len = first.size + second.size + third.size - magic;
assert.equal(len, 128 * 16);
for (let i = 0; i < 16; i++) {
const pruned = await store.prune(hashes[i]);
assert.strictEqual(pruned, true);
}
assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), false);
assert.equal(await fs.exists(store.filepath(types.BLOCK, 1)), false);
assert.equal(await fs.exists(store.filepath(types.BLOCK, 2)), false);
for (let i = 0; i < 16; i++) {
const exists = await store.has(hashes[i]);
assert.strictEqual(exists, false);
}
const exists = await store.db.has(layout.f.encode(types.BLOCK, 0));
assert.strictEqual(exists, false);
});
it('will prune block undo coins', async () => {
const hashes = [];
for (let i = 0; i < 16; i++) {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
hashes.push(hash);
await store.writeUndo(hash, block);
}
const first = await fs.stat(store.filepath(types.UNDO, 0));
const second = await fs.stat(store.filepath(types.UNDO, 1));
const third = await fs.stat(store.filepath(types.UNDO, 2));
const magic = (40 * 16);
const len = first.size + second.size + third.size - magic;
assert.equal(len, 128 * 16);
for (let i = 0; i < 16; i++) {
const pruned = await store.pruneUndo(hashes[i]);
assert.strictEqual(pruned, true);
}
assert.equal(await fs.exists(store.filepath(types.UNDO, 0)), false);
assert.equal(await fs.exists(store.filepath(types.UNDO, 1)), false);
assert.equal(await fs.exists(store.filepath(types.UNDO, 2)), false);
for (let i = 0; i < 16; i++) {
const exists = await store.hasUndo(hashes[i]);
assert.strictEqual(exists, false);
}
const exists = await store.db.has(layout.f.encode(types.UNDO, 0));
assert.strictEqual(exists, false);
});
});
describe('FileBlockStore (Integration 2)', function() {
const location = testdir('blockstore');
let store = null;
beforeEach(async () => {
await rimraf(location);
store = new FileBlockStore({
location: location,
maxFileLength: 1024 * 1024
});
await store.ensure();
await store.open();
});
afterEach(async () => {
await store.close();
});
after(async () => {
await rimraf(location);
});
it('will import from files (e.g. db corruption)', async () => {
const blocks = [];
for (let i = 0; i < vectors.length; i++) {
const [block] = vectors[i].getBlock();
const hash = block.hash();
const raw = block.toRaw();
blocks.push({hash, block: raw});
await store.write(hash, raw);
}
await store.close();
await rimraf(resolve(location, './index'));
store = new FileBlockStore({
location: location,
maxFileLength: 1024
});
await store.open();
for (let i = 0; i < vectors.length; i++) {
const expect = blocks[i];
const block = await store.read(expect.hash);
assert.equal(block.length, expect.block.length);
assert.bufferEqual(block, expect.block);
}
});
it('will import from files after write interrupt', async () => {
const blocks = [];
for (let i = 0; i < vectors.length; i++) {
const [block] = vectors[i].getBlock();
const hash = block.hash();
const raw = block.toRaw();
blocks.push({hash, block: raw});
await store.write(hash, raw);
}
await store.close();
assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), true);
assert.equal(await fs.exists(store.filepath(types.BLOCK, 1)), true);
assert.equal(await fs.exists(store.filepath(types.BLOCK, 2)), false);
// Write partial block as would be the case in a
// block write interrupt.
const [partial] = extra[0].getBlock();
{
// Include all of the header, but not the block.
let raw = partial.toRaw();
const actual = raw.length;
const part = raw.length - 1;
raw = raw.slice(0, part);
const filepath = store.filepath(types.BLOCK, 1);
const fd = await fs.open(filepath, 'a');
const bw = bio.write(8);
bw.writeU32(store.network.magic);
bw.writeU32(actual);
const magic = bw.render();
const mwritten = await fs.write(fd, magic, 0, 8);
const bwritten = await fs.write(fd, raw, 0, part);
await fs.close(fd);
assert.equal(mwritten, 8);
assert.equal(bwritten, part);
}
await rimraf(resolve(location, './index'));
store = new FileBlockStore({
location: location,
maxFileLength: 1024
});
await store.open();
const incomplete = await store.read(partial.hash());
assert(incomplete === null);
for (let i = 0; i < vectors.length; i++) {
const expect = blocks[i];
const block = await store.read(expect.hash);
assert.equal(block.length, expect.block.length);
assert.bufferEqual(block, expect.block);
}
});
it('will import undo blocks from files', async () => {
const blocks = [];
for (let i = 0; i < undos.length; i++) {
const [block] = undos[i].getBlock();
const raw = undos[i].undoRaw;
const hash = block.hash();
blocks.push({hash, block: raw});
await store.writeUndo(hash, raw);
}
await store.close();
await rimraf(resolve(location, './index'));
store = new FileBlockStore({
location: location,
maxFileLength: 1024
});
await store.open();
for (let i = 0; i < undos.length; i++) {
const expect = blocks[i];
const block = await store.readUndo(expect.hash);
assert.equal(block.length, expect.block.length);
assert.bufferEqual(block, expect.block);
}
});
});
describe('LevelBlockStore', function() {
const location = testdir('blockstore');
let store = null;
beforeEach(async () => {
await rimraf(location);
store = new LevelBlockStore({
location: location
});
await store.ensure();
await store.open();
});
afterEach(async () => {
await store.close();
});
after(async () => {
await rimraf(location);
});
it('will write and read a block', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const block2 = await store.read(hash);
assert.bufferEqual(block1, block2);
});
it('will write and read block undo coins', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.writeUndo(hash, block1);
const block2 = await store.readUndo(hash);
assert.bufferEqual(block1, block2);
});
it('will read a block w/ offset and length', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const offset = 79;
const size = 15;
const block2 = await store.read(hash, offset, size);
assert.bufferEqual(block1.slice(offset, offset + size), block2);
});
it('will fail to read w/ out-of-bounds length', async () => {
const block1 = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block1);
const offset = 79;
const size = 50;
let err = null;
try {
await store.read(hash, offset, size);
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Out-of-bounds read.');
});
it('will check if block exists (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.has(hash);
assert.strictEqual(exists, false);
});
it('will check if block exists (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block);
const exists = await store.has(hash);
assert.strictEqual(exists, true);
});
it('will check if block undo coins exists (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.has(hash);
assert.strictEqual(exists, false);
});
it('will check if block undo coins exists (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.writeUndo(hash, block);
const exists = await store.hasUndo(hash);
assert.strictEqual(exists, true);
});
it('will prune blocks (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.write(hash, block);
const pruned = await store.prune(hash);
assert.strictEqual(pruned, true);
const block2 = await store.read(hash);
assert.strictEqual(block2, null);
});
it('will prune blocks (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.has(hash);
assert.strictEqual(exists, false);
const pruned = await store.prune(hash);
assert.strictEqual(pruned, false);
});
it('will prune block undo coins (true)', async () => {
const block = random.randomBytes(128);
const hash = random.randomBytes(32);
await store.writeUndo(hash, block);
const pruned = await store.pruneUndo(hash);
assert.strictEqual(pruned, true);
const block2 = await store.readUndo(hash);
assert.strictEqual(block2, null);
});
it('will prune block undo coins (false)', async () => {
const hash = random.randomBytes(32);
const exists = await store.hasUndo(hash);
assert.strictEqual(exists, false);
const pruned = await store.pruneUndo(hash);
assert.strictEqual(pruned, false);
});
});
});