Compare commits
1 Commits
master
...
flat-file-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c22d7050f |
63
lib/chain/blockdb-browser.js
Normal file
63
lib/chain/blockdb-browser.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*!
|
||||||
|
* blockdb.js - blockchain data management for bcoin
|
||||||
|
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||||
|
* https://github.com/bcoin-org/bcoin
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockDB
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function BlockDB(chaindb) {
|
||||||
|
this.chaindb = chaindb;
|
||||||
|
this.db = chaindb.db;
|
||||||
|
this.layout = chaindb.layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockDB.prototype.open = function open() {
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.close = function close() {
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.sync = function sync() {
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.saveBlock = function saveBlock(block) {
|
||||||
|
this.chaindb.put(this.layout.b(block.hash()), block.toRaw());
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.readBlock = function readBlock(hash) {
|
||||||
|
return this.db.get(this.layout.b(hash));
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.readBlockEntry = function readBlockEntry(entry) {
|
||||||
|
return this.readBlock(entry.hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.removeBlock = function removeBlock(hash) {
|
||||||
|
this.chaindb.del(this.layout.b(hash));
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.pruneBlock = function pruneBlock(hash) {
|
||||||
|
this.chaindb.del(this.layout.b(hash));
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.pruneBlockEntry = function pruneBlockEntry(entry) {
|
||||||
|
return this.pruneBlock(entry.hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = BlockDB;
|
||||||
196
lib/chain/blockdb.js
Normal file
196
lib/chain/blockdb.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*!
|
||||||
|
* blockdb.js - blockchain data management for bcoin
|
||||||
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
||||||
|
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||||
|
* https://github.com/bcoin-org/bcoin
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var co = require('../utils/co');
|
||||||
|
var Flat = require('../db/flat');
|
||||||
|
var LRU = require('../utils/lru');
|
||||||
|
var FileEntry = Flat.FileEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockDB
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function BlockDB(chaindb) {
|
||||||
|
this.chaindb = chaindb;
|
||||||
|
this.db = chaindb.db;
|
||||||
|
this.layout = chaindb.layout;
|
||||||
|
this.flat = new Flat(this.db);
|
||||||
|
this.cache = new LRU(8192);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockDB.prototype.open = function open() {
|
||||||
|
return this.flat.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.close = function close() {
|
||||||
|
return this.flat.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.sync = co(function* sync() {
|
||||||
|
var entry = yield this.chaindb.getTip();
|
||||||
|
var block, rollback;
|
||||||
|
|
||||||
|
assert(entry);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
block = yield this.readBlock(entry.hash);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.type !== 'ChecksumMismatch')
|
||||||
|
throw e;
|
||||||
|
block = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block)
|
||||||
|
break;
|
||||||
|
|
||||||
|
this.cache.remove(entry.hash);
|
||||||
|
|
||||||
|
entry = yield entry.getPrevious();
|
||||||
|
assert(entry);
|
||||||
|
|
||||||
|
rollback = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rollback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
yield this.chaindb.reset(entry.hash, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.getEntry = co(function* getEntry(hash) {
|
||||||
|
var key = hash;
|
||||||
|
var entry, data;
|
||||||
|
|
||||||
|
if (typeof key !== 'string')
|
||||||
|
key = key.toString('hex');
|
||||||
|
|
||||||
|
entry = this.cache.get(key);
|
||||||
|
|
||||||
|
if (entry)
|
||||||
|
return entry;
|
||||||
|
|
||||||
|
data = yield this.db.get(this.layout.b(hash));
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entry = FileEntry.fromRaw(data);
|
||||||
|
|
||||||
|
this.cache.set(key, entry);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.saveBlock = co(function* saveBlock(block) {
|
||||||
|
var hash = block.hash();
|
||||||
|
var hex = block.hash('hex');
|
||||||
|
var entry = yield this.flat.write(block.toRaw());
|
||||||
|
|
||||||
|
if (block.height === 0)
|
||||||
|
yield this.flat.sync();
|
||||||
|
|
||||||
|
this.cache.set(hex, entry);
|
||||||
|
|
||||||
|
this.chaindb.put(this.layout.b(hash), entry.toRaw());
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.readBlock = co(function* readBlock(hash) {
|
||||||
|
var entry = yield this.getEntry(hash);
|
||||||
|
|
||||||
|
if (!entry)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return yield this.readBlockEntry(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.readBlockEntry = function readBlockEntry(entry) {
|
||||||
|
return this.flat.read(entry.index, entry.pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockDB.prototype.removeBlock = co(function* removeBlock(hash) {
|
||||||
|
var entry = yield this.getEntry(hash);
|
||||||
|
|
||||||
|
if (!entry)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.chaindb.del(this.layout.b(hash));
|
||||||
|
|
||||||
|
if (entry.pos === 0)
|
||||||
|
yield this.flat.remove(entry.index);
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.pruneBlock = co(function* pruneBlock(hash) {
|
||||||
|
var entry = yield this.getEntry(hash);
|
||||||
|
if (!entry)
|
||||||
|
return;
|
||||||
|
return yield this.pruneBlockEntry(hash, entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
BlockDB.prototype.pruneBlockEntry = function pruneBlockEntry(hash, entry) {
|
||||||
|
var index = entry.index;
|
||||||
|
if (index === this.current.index)
|
||||||
|
index -= 1;
|
||||||
|
this.chaindb.del(this.layout.b(hash));
|
||||||
|
return this.flat.remove(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Batch(ffdb) {
|
||||||
|
this.ffdb = ffdb;
|
||||||
|
this.ops = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Batch.prototype.put = function put(block) {
|
||||||
|
this.ops.push(new BatchOp(0, block));
|
||||||
|
};
|
||||||
|
|
||||||
|
Batch.prototype.del = function del(hash) {
|
||||||
|
this.ops.push(new BatchOp(1, hash));
|
||||||
|
};
|
||||||
|
|
||||||
|
Batch.prototype.write = co(function* write() {
|
||||||
|
var i, op;
|
||||||
|
|
||||||
|
for (i = 0; i < this.ops.length; i++) {
|
||||||
|
op = this.ops[i];
|
||||||
|
switch (op.type) {
|
||||||
|
case 0:
|
||||||
|
yield this.ffdb.saveBlock(op.data);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
yield this.ffdb.removeBlock(op.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BatchOp
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function BatchOp(type, data) {
|
||||||
|
this.type = type;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = BlockDB;
|
||||||
@ -17,7 +17,7 @@ var co = require('../utils/co');
|
|||||||
var Network = require('../protocol/network');
|
var Network = require('../protocol/network');
|
||||||
var CoinView = require('./coinview');
|
var CoinView = require('./coinview');
|
||||||
var Coins = require('./coins');
|
var Coins = require('./coins');
|
||||||
var ldb = require('../db/ldb');
|
var LDB = require('../db/ldb');
|
||||||
var LRU = require('../utils/lru');
|
var LRU = require('../utils/lru');
|
||||||
var Block = require('../primitives/block');
|
var Block = require('../primitives/block');
|
||||||
var Coin = require('../primitives/coin');
|
var Coin = require('../primitives/coin');
|
||||||
@ -27,6 +27,7 @@ var Address = require('../primitives/address');
|
|||||||
var ChainEntry = require('./chainentry');
|
var ChainEntry = require('./chainentry');
|
||||||
var U32 = utils.U32;
|
var U32 = utils.U32;
|
||||||
var DUMMY = new Buffer([0]);
|
var DUMMY = new Buffer([0]);
|
||||||
|
var BlockDB = require('./blockdb');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Database Layout:
|
* Database Layout:
|
||||||
@ -173,17 +174,20 @@ function ChainDB(chain) {
|
|||||||
this.logger = chain.logger;
|
this.logger = chain.logger;
|
||||||
this.network = chain.network;
|
this.network = chain.network;
|
||||||
this.options = new ChainOptions(chain.options);
|
this.options = new ChainOptions(chain.options);
|
||||||
|
this.layout = layout;
|
||||||
|
|
||||||
this.db = ldb({
|
this.db = LDB({
|
||||||
location: chain.options.location,
|
location: chain.options.location,
|
||||||
db: chain.options.db,
|
db: chain.options.db,
|
||||||
maxOpenFiles: chain.options.maxFiles,
|
maxOpenFiles: chain.options.maxFiles,
|
||||||
compression: true,
|
compression: false,
|
||||||
cacheSize: 16 << 20,
|
cacheSize: 16 << 20,
|
||||||
writeBufferSize: 8 << 20,
|
writeBufferSize: 8 << 20,
|
||||||
bufferKeys: !utils.isBrowser
|
bufferKeys: !utils.isBrowser
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.blockdb = new BlockDB(this);
|
||||||
|
|
||||||
this.state = new ChainState();
|
this.state = new ChainState();
|
||||||
this.pending = null;
|
this.pending = null;
|
||||||
this.current = null;
|
this.current = null;
|
||||||
@ -198,6 +202,7 @@ function ChainDB(chain) {
|
|||||||
|
|
||||||
// We want to keep the last 5 blocks of unspents in memory.
|
// We want to keep the last 5 blocks of unspents in memory.
|
||||||
this.coinWindow = 25 << 20;
|
this.coinWindow = 25 << 20;
|
||||||
|
this.coinWindow = 100 << 20;
|
||||||
|
|
||||||
this.coinCache = new LRU.Nil();
|
this.coinCache = new LRU.Nil();
|
||||||
this.cacheHash = new LRU(this.cacheWindow);
|
this.cacheHash = new LRU(this.cacheWindow);
|
||||||
@ -228,9 +233,10 @@ ChainDB.prototype._open = co(function* open() {
|
|||||||
this.logger.info('Starting chain load.');
|
this.logger.info('Starting chain load.');
|
||||||
|
|
||||||
yield this.db.open();
|
yield this.db.open();
|
||||||
|
|
||||||
yield this.db.checkVersion('V', 1);
|
yield this.db.checkVersion('V', 1);
|
||||||
|
|
||||||
|
yield this.blockdb.open();
|
||||||
|
|
||||||
state = yield this.getState();
|
state = yield this.getState();
|
||||||
options = yield this.getOptions();
|
options = yield this.getOptions();
|
||||||
|
|
||||||
@ -246,6 +252,7 @@ ChainDB.prototype._open = co(function* open() {
|
|||||||
if (state) {
|
if (state) {
|
||||||
// Grab the chainstate if we have one.
|
// Grab the chainstate if we have one.
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
yield this.blockdb.sync();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise write the genesis block.
|
// Otherwise write the genesis block.
|
||||||
// (We assume this database is fresh).
|
// (We assume this database is fresh).
|
||||||
@ -271,9 +278,10 @@ ChainDB.prototype._open = co(function* open() {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ChainDB.prototype._close = function close() {
|
ChainDB.prototype._close = co(function* close() {
|
||||||
return this.db.close();
|
yield this.blockdb.close();
|
||||||
};
|
yield this.db.close();
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a batch.
|
* Start a batch.
|
||||||
@ -841,7 +849,7 @@ ChainDB.prototype.getBlock = co(function* getBlock(hash) {
|
|||||||
if (!item.hash)
|
if (!item.hash)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
data = yield this.db.get(layout.b(item.hash));
|
data = yield this.blockdb.readBlock(hash);
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
return;
|
return;
|
||||||
@ -869,7 +877,7 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) {
|
|||||||
if (!hash)
|
if (!hash)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return yield this.db.get(layout.b(hash));
|
return yield this.blockdb.readBlock(hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1369,7 +1377,7 @@ ChainDB.prototype._disconnect = co(function* disconnect(entry) {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ChainDB.prototype.reset = co(function* reset(block) {
|
ChainDB.prototype.reset = co(function* reset(block, noData) {
|
||||||
var entry = yield this.get(block);
|
var entry = yield this.get(block);
|
||||||
var tip;
|
var tip;
|
||||||
|
|
||||||
@ -1417,11 +1425,15 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
|||||||
this.del(layout.n(tip.prevBlock));
|
this.del(layout.n(tip.prevBlock));
|
||||||
|
|
||||||
// Disconnect and remove block data.
|
// Disconnect and remove block data.
|
||||||
try {
|
if (!noData) {
|
||||||
yield this.removeBlock(tip.hash);
|
try {
|
||||||
} catch (e) {
|
yield this.removeBlock(tip.hash);
|
||||||
this.drop();
|
} catch (e) {
|
||||||
throw e;
|
this.drop();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.del(layout.b(tip.hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revert chain state to previous tip.
|
// Revert chain state to previous tip.
|
||||||
@ -1489,7 +1501,7 @@ ChainDB.prototype._removeChain = co(function* removeChain(hash) {
|
|||||||
this.del(layout.p(tip.hash));
|
this.del(layout.p(tip.hash));
|
||||||
this.del(layout.h(tip.hash));
|
this.del(layout.h(tip.hash));
|
||||||
this.del(layout.e(tip.hash));
|
this.del(layout.e(tip.hash));
|
||||||
this.del(layout.b(tip.hash));
|
yield this.blockdb.removeBlock(tip.hash);
|
||||||
|
|
||||||
// Queue up hash to be removed
|
// Queue up hash to be removed
|
||||||
// on successful write.
|
// on successful write.
|
||||||
@ -1514,7 +1526,7 @@ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) {
|
|||||||
|
|
||||||
// Write actual block data (this may be
|
// Write actual block data (this may be
|
||||||
// better suited to flat files in the future).
|
// better suited to flat files in the future).
|
||||||
this.put(layout.b(block.hash()), block.toRaw());
|
yield this.blockdb.saveBlock(block);
|
||||||
|
|
||||||
if (!view)
|
if (!view)
|
||||||
return;
|
return;
|
||||||
@ -1540,7 +1552,7 @@ ChainDB.prototype.removeBlock = co(function* removeBlock(hash) {
|
|||||||
if (!block)
|
if (!block)
|
||||||
throw new Error('Block not found.');
|
throw new Error('Block not found.');
|
||||||
|
|
||||||
this.del(layout.b(block.hash()));
|
yield this.blockdb.removeBlock(block.hash());
|
||||||
|
|
||||||
return yield this.disconnectBlock(block);
|
return yield this.disconnectBlock(block);
|
||||||
});
|
});
|
||||||
@ -1761,7 +1773,7 @@ ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) {
|
|||||||
if (!hash)
|
if (!hash)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.del(layout.b(hash));
|
yield this.blockdb.removeBlock(hash);
|
||||||
this.del(layout.u(hash));
|
this.del(layout.u(hash));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
391
lib/db/flat.js
Normal file
391
lib/db/flat.js
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var utils = require('../utils/utils');
|
||||||
|
var co = require('../utils/co');
|
||||||
|
var Locker = require('../utils/locker');
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
var promisify = co.promisify;
|
||||||
|
var fsExists = promisify(fs.exists, fs);
|
||||||
|
var fsMkdir = promisify(fs.mkdir, fs);
|
||||||
|
var fsReaddir = promisify(fs.readdir, fs);
|
||||||
|
var fsOpen = promisify(fs.open, fs);
|
||||||
|
var fsStat = promisify(fs.stat, fs);
|
||||||
|
var fsFstat = promisify(fs.fstat, fs);
|
||||||
|
var fsWrite = promisify(fs.write, fs);
|
||||||
|
var fsRead = promisify(fs.read, fs);
|
||||||
|
var fsClose = promisify(fs.close, fs);
|
||||||
|
var fsFtruncate = promisify(fs.ftruncate, fs);
|
||||||
|
var fsFsync = promisify(fs.fsync, fs);
|
||||||
|
var fsUnlink = promisify(fs.unlink, fs);
|
||||||
|
var fsExists;
|
||||||
|
var assert = utils.assert;
|
||||||
|
var murmur3 = require('../utils/murmur3');
|
||||||
|
|
||||||
|
var MAX_SIZE = 512 << 20;
|
||||||
|
var MAX_FILES = 64;
|
||||||
|
var MAX_ENTRY = 12 << 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Flat(db) {
|
||||||
|
if (!(this instanceof Flat))
|
||||||
|
return new Flat(db);
|
||||||
|
|
||||||
|
this.dir = path.resolve(db.location, '..');
|
||||||
|
this.dir = path.resolve(this.dir, 'blocks');
|
||||||
|
this.locker = new Locker();
|
||||||
|
|
||||||
|
this.fileIndex = -1;
|
||||||
|
this.current = null;
|
||||||
|
this.files = {};
|
||||||
|
this.openFiles = [];
|
||||||
|
this.indexes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Flat.prototype.hash = function hash(data) {
|
||||||
|
return murmur3(data, 0xdeedbeef);
|
||||||
|
};
|
||||||
|
|
||||||
|
Flat.prototype.open = co(function* open() {
|
||||||
|
var index = -1;
|
||||||
|
var i, list, name;
|
||||||
|
|
||||||
|
if (!(yield fsExists(this.dir)))
|
||||||
|
yield fsMkdir(this.dir, 493);
|
||||||
|
|
||||||
|
list = yield fsReaddir(this.dir);
|
||||||
|
|
||||||
|
for (i = 0; i < list.length; i++) {
|
||||||
|
name = list[i];
|
||||||
|
|
||||||
|
if (!/^\d{10}$/.test(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
name = parseInt(name, 10);
|
||||||
|
|
||||||
|
utils.binaryInsert(this.indexes, name, cmp);
|
||||||
|
|
||||||
|
if (name > index)
|
||||||
|
index = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
yield this.allocate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileIndex = index;
|
||||||
|
this.current = yield this.openFile(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.close = co(function* close() {
|
||||||
|
var unlock = yield this.locker.lock();
|
||||||
|
try {
|
||||||
|
return yield this._close();
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype._close = co(function* close() {
|
||||||
|
var i, index, file;
|
||||||
|
|
||||||
|
for (i = this.openFiles.length - 1; i >= 0; i--) {
|
||||||
|
index = this.openFiles[i];
|
||||||
|
file = this.files[index];
|
||||||
|
assert(file);
|
||||||
|
yield this._closeFile(file.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(this.current === null);
|
||||||
|
assert(this.openFiles.length === 0);
|
||||||
|
|
||||||
|
this.fileIndex = -1;
|
||||||
|
this.indexes.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.name = function name(index) {
|
||||||
|
return path.resolve(this.dir, utils.pad32(index));
|
||||||
|
};
|
||||||
|
|
||||||
|
Flat.prototype.openFile = co(function* openFile(index) {
|
||||||
|
var unlock = yield this.locker.lock();
|
||||||
|
try {
|
||||||
|
return yield this._openFile(index);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype._openFile = co(function* _openFile(index) {
|
||||||
|
var file = this.files[index];
|
||||||
|
var name, fd, stat;
|
||||||
|
|
||||||
|
if (file)
|
||||||
|
return file;
|
||||||
|
|
||||||
|
name = this.name(index);
|
||||||
|
|
||||||
|
fd = yield fsOpen(name, 'a+');
|
||||||
|
stat = yield fsFstat(fd);
|
||||||
|
|
||||||
|
file = new File(fd, index, stat.size);
|
||||||
|
|
||||||
|
this.files[index] = file;
|
||||||
|
utils.binaryInsert(this.openFiles, index, cmp);
|
||||||
|
|
||||||
|
yield this.evict(index);
|
||||||
|
|
||||||
|
return file;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.closeFile = co(function* closeFile(index) {
|
||||||
|
var unlock = yield this.locker.lock();
|
||||||
|
try {
|
||||||
|
assert(index !== this.current.index);
|
||||||
|
return yield this._closeFile(index);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype._closeFile = co(function* _closeFile(index) {
|
||||||
|
var file = this.files[index];
|
||||||
|
var result;
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return;
|
||||||
|
|
||||||
|
yield fsClose(file.fd);
|
||||||
|
|
||||||
|
result = utils.binaryRemove(this.openFiles, index, cmp);
|
||||||
|
assert(result);
|
||||||
|
|
||||||
|
delete this.files[index];
|
||||||
|
|
||||||
|
if (file === this.current)
|
||||||
|
this.current = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.remove = co(function* remove(index) {
|
||||||
|
var unlock = yield this.locker.lock();
|
||||||
|
try {
|
||||||
|
return yield this._remove(index);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype._remove = co(function* remove(index) {
|
||||||
|
var result;
|
||||||
|
|
||||||
|
assert(index != null);
|
||||||
|
|
||||||
|
if (!this.files[index])
|
||||||
|
return;
|
||||||
|
|
||||||
|
yield this._closeFile(index);
|
||||||
|
yield fsUnlink(this.name(index));
|
||||||
|
|
||||||
|
result = utils.binaryRemove(this.indexes, index, cmp);
|
||||||
|
assert(result);
|
||||||
|
|
||||||
|
if (!this.current) {
|
||||||
|
index = this.indexes[this.indexes.length - 1];
|
||||||
|
assert(index != null);
|
||||||
|
this.current = yield this._openFile(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.allocate = co(function* allocate() {
|
||||||
|
var index = this.fileIndex + 1;
|
||||||
|
var fd = yield fsOpen(this.name(index), 'a+');
|
||||||
|
var file = new File(fd, index, 0);
|
||||||
|
|
||||||
|
this.files[index] = file;
|
||||||
|
this.current = file;
|
||||||
|
this.fileIndex++;
|
||||||
|
|
||||||
|
utils.binaryInsert(this.openFiles, index, cmp);
|
||||||
|
yield this.evict(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.evict = co(function* evict(not) {
|
||||||
|
var i = 0;
|
||||||
|
var index, file;
|
||||||
|
|
||||||
|
if (this.openFiles.length <= MAX_FILES)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
assert(i < this.openFiles.length);
|
||||||
|
|
||||||
|
index = this.openFiles[i];
|
||||||
|
|
||||||
|
if (this.current) {
|
||||||
|
if (index !== not && index !== this.current.index)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = this.openFiles[i];
|
||||||
|
file = this.files[index];
|
||||||
|
assert(file);
|
||||||
|
|
||||||
|
yield fsClose(file.fd);
|
||||||
|
|
||||||
|
this.openFiles.splice(i, 1);
|
||||||
|
delete this.files[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.write = co(function* write(data) {
|
||||||
|
var unlock = yield this.locker.lock();
|
||||||
|
try {
|
||||||
|
return yield this._write(data);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype._write = co(function* write(data) {
|
||||||
|
var pos, fd, size, chk;
|
||||||
|
var buf = new Buffer(4);
|
||||||
|
var len = 4 + data.length + 4;
|
||||||
|
|
||||||
|
if (data.length > MAX_ENTRY)
|
||||||
|
throw new Error('Size too large.');
|
||||||
|
|
||||||
|
if (this.current.pos + len > MAX_SIZE) {
|
||||||
|
yield this.sync();
|
||||||
|
yield this.allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = this.current.pos;
|
||||||
|
fd = this.current.fd;
|
||||||
|
|
||||||
|
buf.writeUInt32LE(data.length, 0, true);
|
||||||
|
yield fsWrite(fd, buf, 0, 4, pos);
|
||||||
|
|
||||||
|
yield fsWrite(fd, data, 0, data.length, pos + 4);
|
||||||
|
|
||||||
|
buf.writeUInt32LE(this.hash(data), 0, true);
|
||||||
|
yield fsWrite(fd, buf, 0, 4, pos + 4 + data.length);
|
||||||
|
|
||||||
|
this.current.pos += len;
|
||||||
|
|
||||||
|
return new FileEntry(this.current.index, pos, data.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.read = co(function* read(index, offset) {
|
||||||
|
var file = yield this.openFile(index);
|
||||||
|
var buf = new Buffer(4);
|
||||||
|
var size, data, chk, err;
|
||||||
|
|
||||||
|
if (offset + 8 > file.pos)
|
||||||
|
throw new Error('Read is out of bounds.');
|
||||||
|
|
||||||
|
yield fsRead(file.fd, buf, 0, 4, offset);
|
||||||
|
size = buf.readUInt32LE(0, true);
|
||||||
|
|
||||||
|
if (size > MAX_ENTRY)
|
||||||
|
throw new Error('Size too large.');
|
||||||
|
|
||||||
|
if (offset + 4 + size + 4 > file.pos)
|
||||||
|
throw new Error('Read is out of bounds.');
|
||||||
|
|
||||||
|
data = new Buffer(size);
|
||||||
|
yield fsRead(file.fd, data, 0, data.length, offset + 4);
|
||||||
|
|
||||||
|
yield fsRead(file.fd, buf, 0, 4, offset + 4 + data.length);
|
||||||
|
chk = buf.readUInt32LE(0, true);
|
||||||
|
|
||||||
|
if (this.hash(data) !== chk) {
|
||||||
|
err = new Error('Checksum mismatch.');
|
||||||
|
err.type = 'ChecksumMismatch';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flat.prototype.sync = co(function* sync() {
|
||||||
|
yield fsFsync(this.current.fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function File(fd, index, pos) {
|
||||||
|
this.fd = fd;
|
||||||
|
this.index = index;
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FileEntry
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function FileEntry(index, offset, size) {
|
||||||
|
this.index = index;
|
||||||
|
this.offset = offset;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileEntry.prototype.toRaw = function toRaw() {
|
||||||
|
var data = new Buffer(12);
|
||||||
|
data.writeUInt32LE(this.index, 0, true);
|
||||||
|
data.writeUInt32LE(this.offset, 4, true);
|
||||||
|
data.writeUInt32LE(this.size, 8, true);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileEntry.fromRaw = function fromRaw(data) {
|
||||||
|
var entry = new FileEntry(0, 0, 0);
|
||||||
|
entry.index = data.readUInt32LE(0, true);
|
||||||
|
entry.offset = data.readUInt32LE(4, true);
|
||||||
|
entry.size = data.readUInt32LE(8, true);
|
||||||
|
return entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function cmp(a, b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fsExists = co(function* fsExists(name) {
|
||||||
|
var stat;
|
||||||
|
|
||||||
|
try {
|
||||||
|
stat = yield fsStat(name);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ENOENT')
|
||||||
|
return false;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stat.isDirectory())
|
||||||
|
throw new Error('File is not a directory.');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports = Flat;
|
||||||
|
exports.FileEntry = FileEntry;
|
||||||
|
|
||||||
|
module.exports = exports;
|
||||||
@ -67,6 +67,8 @@
|
|||||||
"./lib/http/rpcclient": "./browser/empty.js",
|
"./lib/http/rpcclient": "./browser/empty.js",
|
||||||
"./lib/http/server": "./browser/empty.js",
|
"./lib/http/server": "./browser/empty.js",
|
||||||
"./lib/http/wallet": "./browser/empty.js",
|
"./lib/http/wallet": "./browser/empty.js",
|
||||||
|
"./lib/db/flat": "./browser/empty.js",
|
||||||
|
"./lib/chain/blockdb": "./lib/chain/blockdb-browser.js",
|
||||||
"fs": "./browser/empty.js",
|
"fs": "./browser/empty.js",
|
||||||
"crypto": "./browser/empty.js",
|
"crypto": "./browser/empty.js",
|
||||||
"child_process": "./browser/empty.js",
|
"child_process": "./browser/empty.js",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user