fix open and close for all async objects.

This commit is contained in:
Christopher Jeffrey 2016-06-30 17:29:00 -07:00
parent d346b36754
commit 1a32e66468
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
18 changed files with 1143 additions and 706 deletions

179
lib/bcoin/async.js Normal file
View File

@ -0,0 +1,179 @@
/*!
* async.js - async object class for bcoin
* Copyright (c) 2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var utils = require('./utils');
var assert = utils.assert;
var EventEmitter = require('events').EventEmitter;
/**
* An abstract object that handles state and
* provides recallable open and close methods.
* @constructor
* @property {Boolean} loading
* @property {Boolean} closing
* @property {Boolean} loaded
*/
function AsyncObject() {
assert(this instanceof AsyncObject);
EventEmitter.call(this);
this.loading = false;
this.closing = false;
this.loaded = false;
this.locker = null;
}
utils.inherits(AsyncObject, EventEmitter);
/**
* Open the object (recallable).
* @param {Function} callback
*/
AsyncObject.prototype.open = function open(callback) {
var self = this;
var unlock;
callback = utils.ensure(callback);
assert(!this.closing, 'Cannot open while closing.');
if (this.loaded)
return utils.nextTick(callback);
if (this.loading)
return this.once('open', callback);
if (this.locker) {
unlock = this.locker.lock(utils.nop, []);
assert(unlock, 'Cannot call methods before load.');
callback = utils.wrap(callback, unlock);
}
this.loading = true;
this._open(function(err) {
utils.nextTick(function() {
if (err) {
self.loading = false;
self._error('open', err);
return callback(err);
}
self.loading = false;
self.loaded = true;
self.emit('open');
callback();
});
});
};
/**
* Close the object (recallable).
* @param {Function} callback
*/
AsyncObject.prototype.close = function close(callback) {
var self = this;
var unlock;
callback = utils.ensure(callback);
assert(!this.loading, 'Cannot close while loading.');
if (!this.loaded)
return utils.nextTick(callback);
if (this.closing)
return this.on('close', callback);
if (this.locker) {
unlock = this.locker.lock(close, [callback]);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
}
this.closing = true;
this.loaded = false;
this._close(function(err) {
utils.nextTick(function() {
if (err) {
self.closing = false;
self._error('close', err);
return callback(err);
}
self.closing = false;
self.emit('close');
callback();
});
});
};
/**
* Close the object (recallable).
* @method
* @param {Function} callback
*/
AsyncObject.prototype.destroy = AsyncObject.prototype.close;
/**
* Emit an error for `open` or `close` listeners.
* @private
* @param {String} event
* @param {Error} err
*/
AsyncObject.prototype._error = function _error(event, err) {
var listeners = this.listeners(event);
var i, callback;
this.removeAllListeners(event);
for (i = 0; i < listeners.length; i++)
listeners[i](err);
this.emit('error', err);
};
/**
* Initialize the object.
* @private
* @param {Function} callback
*/
AsyncObject.prototype._open = function _open(callback) {
throw new Error('Abstract method.');
};
/**
* Close the object.
* @private
* @param {Function} callback
*/
AsyncObject.prototype._close = function _close(callback) {
throw new Error('Abstract method.');
};
/*
* Expose
*/
module.exports = AsyncObject;

View File

@ -9,6 +9,7 @@
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var constants = bcoin.protocol.constants;
var utils = require('./utils');
var assert = utils.assert;
@ -55,17 +56,18 @@ var VerifyError = bcoin.errors.VerifyError;
*/
function Chain(options) {
var self = this;
if (!(this instanceof Chain))
return new Chain(options);
EventEmitter.call(this);
AsyncObject.call(this);
if (!options)
options = {};
this.options = options;
this.loaded = false;
this.network = bcoin.network.get(options.network);
this.db = new bcoin.chaindb(this, options);
this.total = 0;
@ -91,7 +93,12 @@ function Chain(options) {
this._init();
}
utils.inherits(Chain, EventEmitter);
utils.inherits(Chain, AsyncObject);
/**
* Initialize the chain.
* @private
*/
Chain.prototype._init = function _init() {
var self = this;
@ -192,20 +199,30 @@ Chain.prototype._init = function _init() {
this.db.on('remove block', function(block) {
self.emit('remove block', block);
});
};
/**
* Open the chain, wait for the database to load.
* @alias Chain#open
* @param {Function} callback
*/
Chain.prototype._open = function open(callback) {
var self = this;
bcoin.debug('Chain is loading.');
self.db.open(function(err) {
this.db.open(function(err) {
if (err)
return self.emit('error', err);
return callback(err);
self.preload(function(err) {
if (err)
return self.emit('error', err);
return callback(err);
self.db.getTip(function(err, tip) {
if (err)
return self.emit('error', err);
return callback(err);
assert(tip);
@ -221,7 +238,7 @@ Chain.prototype._init = function _init() {
self.getInitialState(function(err) {
if (err)
return self.emit('error', err);
return callback(err);
if (self.csvActive)
bcoin.debug('CSV is active.');
@ -231,41 +248,28 @@ Chain.prototype._init = function _init() {
bcoin.profiler.snapshot();
self.loaded = true;
self.emit('open');
self.emit('tip', tip);
if (!self.synced && self.isFull()) {
self.synced = true;
self.emit('full');
}
callback();
});
});
});
});
};
/**
* Open the chain, wait for the database to load.
* @param {Function} callback
*/
Chain.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the chain, wait for the database to close.
* @method
* @alias Chain#close
* @param {Function} callback
*/
Chain.prototype.close =
Chain.prototype.destroy = function destroy(callback) {
this.db.close(utils.ensure(callback));
Chain.prototype._close = function close(callback) {
this.db.close(callback);
};
/**

View File

@ -9,6 +9,7 @@
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var constants = bcoin.protocol.constants;
var utils = require('./utils');
var assert = utils.assert;
@ -174,12 +175,22 @@ function ChainDB(chain, options) {
if (!options)
options = {};
EventEmitter.call(this);
AsyncObject.call(this);
this.options = options;
this.chain = chain;
this.network = this.chain.network;
this.db = bcoin.ldb({
network: this.network,
name: this.options.name || (this.options.spv ? 'spvchain' : 'chain'),
location: this.options.location,
db: this.options.db,
compression: true,
cacheSize: 16 << 20,
writeBufferSize: 8 << 20
});
this.keepBlocks = options.keepBlocks || 288;
this.prune = !!options.prune;
@ -207,83 +218,59 @@ function ChainDB(chain, options) {
this.coinCache = new NullCache(this.coinWindow);
this.cacheHash = new bcoin.lru(this.cacheWindow, 1);
this.cacheHeight = new bcoin.lru(this.cacheWindow, 1);
this._init();
}
utils.inherits(ChainDB, EventEmitter);
utils.inherits(ChainDB, AsyncObject);
ChainDB.prototype._init = function _init() {
/**
* Open the chain db, wait for the database to load.
* @alias ChainDB#open
* @param {Function} callback
*/
ChainDB.prototype._open = function open(callback) {
var self = this;
var genesis, block;
if (this.loaded)
return;
this.db = bcoin.ldb({
network: this.network,
name: this.options.name || (this.options.spv ? 'spvchain' : 'chain'),
location: this.options.location,
db: this.options.db,
compression: true,
cacheSize: 16 << 20,
writeBufferSize: 8 << 20
});
bcoin.debug('Starting chain load.');
function done(err) {
if (err)
return callback(err);
bcoin.debug('Chain successfully loaded.');
callback();
}
this.db.open(function(err) {
if (err)
return self.emit('error', err);
function finish(err) {
if (err)
return self.emit('error', err);
bcoin.debug('Chain successfully loaded.');
self.loaded = true;
self.emit('open');
}
return done(err);
self.db.has(layout.h(self.network.genesis.hash), function(err, exists) {
if (err)
return self.emit('error', err);
return done(err);
if (exists)
return finish();
return done();
block = bcoin.block.fromRaw(self.network.genesisBlock, 'hex');
block.setHeight(0);
genesis = bcoin.chainentry.fromBlock(self.chain, block);
self.save(genesis, block, null, true, finish);
self.save(genesis, block, null, true, done);
});
});
};
/**
* Open the chain, wait for the database to load.
* Close the chain db, wait for the database to close.
* @alias ChainDB#close
* @param {Function} callback
*/
ChainDB.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the chain, wait for the database to close.
* @method
* @param {Function} callback
*/
ChainDB.prototype.close =
ChainDB.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
ChainDB.prototype._close = function close(callback) {
this.db.close(callback);
};

View File

@ -10,6 +10,7 @@
var bcoin = require('./env');
var utils = require('./utils');
var assert = utils.assert;
var Node = bcoin.node;
/**
* Create a fullnode complete with a chain,
@ -47,20 +48,7 @@ function Fullnode(options) {
if (!(this instanceof Fullnode))
return new Fullnode(options);
bcoin.node.call(this, options);
this.loaded = false;
this._init();
}
utils.inherits(Fullnode, bcoin.node);
Fullnode.prototype._init = function _init() {
var self = this;
var options;
this.wallet = null;
Node.call(this, options);
this.chain = new bcoin.chain({
network: this.network,
@ -119,6 +107,19 @@ Fullnode.prototype._init = function _init() {
});
}
this._init();
}
utils.inherits(Fullnode, Node);
/**
* Initialize the node.
* @private
*/
Fullnode.prototype._init = function _init() {
var self = this;
// Bind to errors
this.mempool.on('error', function(err) {
self.emit('error', err);
@ -196,14 +197,26 @@ Fullnode.prototype._init = function _init() {
this.miner.on('block', function(block) {
self.pool.broadcast(block.toInv());
});
};
function load(err) {
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias Fullnode#open
* @param {Function} callback
*/
Fullnode.prototype._open = function open(callback) {
var self = this;
var options;
function done(err) {
if (err)
return self.emit('error', err);
return callback(err);
self.loaded = true;
self.emit('open');
bcoin.debug('Node is loaded.');
callback();
}
options = utils.merge({
@ -256,7 +269,30 @@ Fullnode.prototype._init = function _init() {
return next();
self.http.open(next);
}
], load);
], done);
};
/**
* Close the node, wait for the database to close.
* @alias Fullnode#close
* @param {Function} callback
*/
Fullnode.prototype._close = function close(callback) {
var self = this;
utils.serial([
function(next) {
if (!self.http)
return next();
self.http.close(next);
},
this.walletdb.close.bind(this.walletdb),
this.pool.close.bind(this.pool),
this.miner.close.bind(this.miner),
this.mempool.close.bind(this.mempool),
this.chain.close.bind(this.chain)
], callback);
};
/**
@ -340,45 +376,6 @@ Fullnode.prototype.stopSync = function stopSync() {
return this.pool.stopSync();
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @param {Function} callback
*/
Fullnode.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the node, wait for the database to close.
* @method
* @param {Function} callback
*/
Fullnode.prototype.close =
Fullnode.prototype.destroy = function destroy(callback) {
var self = this;
this.wallet.destroy();
utils.serial([
function(next) {
if (!self.http)
return next();
self.http.close(next);
},
this.walletdb.close.bind(this.walletdb),
this.pool.close.bind(this.pool),
this.miner.close.bind(this.miner),
this.mempool.close.bind(this.mempool),
this.chain.close.bind(this.chain)
], callback);
};
/**
* Create a {@link Wallet} in the wallet database.
* @param {Object} options - See {@link Wallet}.

View File

@ -8,7 +8,9 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('../async');
var utils = require('../utils');
var assert = utils.assert;
/**
* HTTPBase
@ -25,6 +27,8 @@ function HTTPBase(options) {
if (!options)
options = {};
AsyncObject.call(this);
this.options = options;
this.io = null;
@ -44,7 +48,12 @@ function HTTPBase(options) {
this._init();
}
utils.inherits(HTTPBase, EventEmitter);
utils.inherits(HTTPBase, AsyncObject);
/**
* Initialize server.
* @private
*/
HTTPBase.prototype._init = function _init() {
var self = this;
@ -69,30 +78,10 @@ HTTPBase.prototype._init = function _init() {
});
};
HTTPBase.prototype._initIO = function _initIO() {
var self = this;
var IOServer;
if (!this.options.sockets)
return;
try {
IOServer = require('socket.io');
} catch (e) {
;
}
if (!IOServer)
return;
this.io = new IOServer();
this.io.attach(this.server);
this.io.on('connection', function(socket) {
self.emit('websocket', socket);
});
};
/**
* Initialize router.
* @private
*/
HTTPBase.prototype._initRouter = function _initRouter() {
var self = this;
@ -185,6 +174,66 @@ HTTPBase.prototype._initRouter = function _initRouter() {
});
};
/**
* Initialize websockets.
* @private
*/
HTTPBase.prototype._initIO = function _initIO() {
var self = this;
var IOServer;
if (!this.options.sockets)
return;
try {
IOServer = require('socket.io');
} catch (e) {
;
}
if (!IOServer)
return;
this.io = new IOServer();
this.io.attach(this.server);
this.io.on('connection', function(socket) {
self.emit('websocket', socket);
});
};
/**
* Open the server.
* @alias HTTPBase#open
* @param {Function} callback
*/
HTTPBase.prototype._open = function open(callback) {
assert(typeof this.options.port === 'number', 'Port required.');
this.listen(this.options.port, this.options.host, callback);
};
/**
* Close the server.
* @alias HTTPBase#close
* @param {Function} callback
*/
HTTPBase.prototype._close = function close(callback) {
this.server.close(callback);
};
/**
* Handle middleware stack.
* @param {HTTPRequest} req
* @param {HTTPResponse} res
* @param {Function} _send
* @param {Function} callback
* @private
*/
HTTPBase.prototype._handle = function _handle(req, res, _send, callback) {
var self = this;
var i = 0;
@ -339,15 +388,6 @@ HTTPBase.prototype.listen = function listen(port, host, callback) {
});
};
/**
* Close the the server.
* @param {Function} callback
*/
HTTPBase.prototype.close = function close(callback) {
this.server.close(callback);
};
/*
* Helpers
*/

View File

@ -9,6 +9,7 @@
var bcoin = require('../env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('../async');
var utils = require('../utils');
var assert = utils.assert;
var request = require('./request');
@ -31,19 +32,28 @@ function HTTPClient(options) {
if (typeof options === 'string')
options = { uri: options };
EventEmitter.call(this);
AsyncObject.call(this);
this.uri = options.uri;
this.loaded = false;
this.id = null;
this.options = options;
this.network = bcoin.network.get(options.network);
this._init();
this.uri = options.uri;
this.id = null;
this.socket = null;
// Open automatically.
this.open();
}
utils.inherits(HTTPClient, EventEmitter);
utils.inherits(HTTPClient, AsyncObject);
HTTPClient.prototype._init = function _init() {
/**
* Open the client, wait for socket to connect.
* @alias HTTPClient#open
* @param {Function} callback
*/
HTTPClient.prototype._open = function _open(callback) {
var self = this;
var IOClient;
@ -132,15 +142,19 @@ HTTPClient.prototype._init = function _init() {
};
/**
* Open the client, wait for the socket to load.
* Close the client, wait for the socket to close.
* @alias HTTPClient#close
* @param {Function} callback
*/
HTTPClient.prototype.open = function open(callback) {
if (this.loaded)
HTTPClient.prototype._close = function close(callback) {
if (!this.socket)
return utils.nextTick(callback);
this.once('open', callback);
this.socket.destroy();
this.socket = null;
return utils.nextTick(callback);
};
/**
@ -183,25 +197,6 @@ HTTPClient.prototype.none = function none() {
this.leave('!all');
};
/**
* Close the client, wait for the socket to close.
* @method
* @param {Function} callback
*/
HTTPClient.prototype.close =
HTTPClient.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
if (!this.socket)
return utils.nextTick(callback);
this.socket.destroy();
this.socket = null;
return utils.nextTick(callback);
};
/**
* Make an http request to endpoint.
* @private

View File

@ -52,6 +52,11 @@ function HTTPServer(options) {
utils.inherits(HTTPServer, EventEmitter);
/**
* Initialize routes.
* @private
*/
HTTPServer.prototype._init = function _init() {
var self = this;
@ -707,34 +712,13 @@ HTTPServer.prototype._init = function _init() {
});
this._initIO();
if (this.options.port != null)
this.listen(this.options.port, this.options.host);
};
/**
* Open the server, wait for socket.
* @param {Function} callback
* Initialize websockets.
* @private
*/
HTTPServer.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the server, wait for server socket to close.
* @method
* @param {Function} callback
*/
HTTPServer.prototype.close =
HTTPServer.prototype.destroy = function destroy(callback) {
this.server.close(callback);
};
HTTPServer.prototype._initIO = function _initIO() {
var self = this;
@ -829,6 +813,24 @@ HTTPServer.prototype._initIO = function _initIO() {
});
};
/**
* Open the server, wait for socket.
* @param {Function} callback
*/
HTTPServer.prototype.open = function open(callback) {
this.server.open(callback);
};
/**
* Close the server, wait for server socket to close.
* @param {Function} callback
*/
HTTPServer.prototype.close = function close(callback) {
this.server.close(callback);
};
/**
* @see HTTPBase#use
*/

View File

@ -33,14 +33,21 @@ function HTTPWallet(options) {
options = { uri: options };
this.options = options;
this.client = new http.client(options);
this.network = bcoin.network.get(options.network);
this.client = new http.client(options);
this.uri = options.uri;
this._init();
}
utils.inherits(HTTPWallet, EventEmitter);
/**
* Initialize the wallet.
* @private
*/
HTTPWallet.prototype._init = function _init() {
var self = this;
@ -72,7 +79,9 @@ HTTPWallet.prototype._init = function _init() {
};
/**
* @see Wallet#open
* Open the client and ensure a wallet.
* @alias HTTPWallet#open
* @param {Function} callback
*/
HTTPWallet.prototype.open = function open(callback) {
@ -85,18 +94,13 @@ HTTPWallet.prototype.open = function open(callback) {
};
/**
* @see Wallet#close
* Close the client, wait for the socket to close.
* @alias HTTPWallet#close
* @param {Function} callback
*/
HTTPWallet.prototype.close =
HTTPWallet.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
if (!this.client)
return utils.nextTick(callback);
this.client.destroy(callback);
this.client = null;
HTTPWallet.prototype.close = function close(callback) {
this.client.close(callback);
};
/**

View File

@ -10,6 +10,7 @@
var utils = require('./utils');
var assert = utils.assert;
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
/**
* Extremely low-level version of levelup.
@ -33,9 +34,11 @@ function LowlevelUp(file, options) {
if (!(this instanceof LowlevelUp))
return new LowlevelUp(file, options);
EventEmitter.call(this);
AsyncObject.call(this);
this.loaded = false;
this.options = options;
this.backend = options.db;
this.location = file;
this.db = new options.db(file);
@ -48,39 +51,60 @@ function LowlevelUp(file, options) {
if (this.db.binding)
this.binding = this.db.binding;
this.binding.open(options, function(err) {
if (err)
return self.emit('error', err);
self.loaded = true;
self.emit('open');
});
}
utils.inherits(LowlevelUp, EventEmitter);
utils.inherits(LowlevelUp, AsyncObject);
/**
* Open the database (recallable).
* @alias LowlevelUp#open
* @param {Function} callback
*/
LowlevelUp.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
LowlevelUp.prototype._open = function open(callback) {
this.binding.open(this.options, callback);
};
/**
* Close the database (not recallable).
* Close the database (recallable).
* @alias LowlevelUp#close
* @param {Function} callback
*/
LowlevelUp.prototype.close = function close(callback) {
assert(this.loaded, 'Cannot use database before it is loaded.');
this.loaded = false;
return this.binding.close(callback);
LowlevelUp.prototype._close = function close(callback) {
this.binding.close(callback);
};
/**
* Destroy the database.
* @param {Function} callback
*/
LowlevelUp.prototype.destroy = function destroy(callback) {
assert(!this.loading);
assert(!this.closing);
assert(!this.loaded);
if (!this.backend.destroy)
return utils.nextTick(callback);
this.backend.destroy(this.location, callback);
};
/**
* Repair the database.
* @param {Function} callback
*/
LowlevelUp.prototype.repair = function repair(callback) {
assert(!this.loading);
assert(!this.closing);
assert(!this.loaded);
if (!this.backend.repair)
return utils.asyncify(callback)(new Error('Cannot repair.'));
this.backend.repair(this.location, callback);
};
/**

View File

@ -14,6 +14,7 @@
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var constants = bcoin.protocol.constants;
var utils = require('./utils');
var assert = utils.assert;
@ -63,7 +64,7 @@ function Mempool(options) {
if (!(this instanceof Mempool))
return new Mempool(options);
EventEmitter.call(this);
AsyncObject.call(this);
if (!options)
options = {};
@ -78,7 +79,13 @@ function Mempool(options) {
this.locker = new bcoin.locker(this, this.addTX);
this.db = null;
this.db = bcoin.ldb({
network: this.network,
name: this.options.name || 'mempool',
location: this.options.location,
db: this.options.db || 'memory'
});
this.size = 0;
this.waiting = {};
this.orphans = {};
@ -105,64 +112,61 @@ function Mempool(options) {
this.minFeeRate = 0;
this.minReasonableFee = constants.tx.MIN_RELAY;
this.minRelayFee = constants.tx.MIN_RELAY;
this._init();
}
utils.inherits(Mempool, EventEmitter);
utils.inherits(Mempool, AsyncObject);
Mempool.prototype._lock = function _lock(func, args, force) {
return this.locker.lock(func, args, force);
};
/**
* Open the chain, wait for the database to load.
* @alias Mempool#open
* @param {Function} callback
*/
Mempool.prototype._init = function _init() {
Mempool.prototype._open = function open(callback) {
var self = this;
var unlock = this._lock(utils.nop, []);
var options = {
network: this.network,
name: this.options.name || 'mempool',
location: this.options.location,
db: this.options.db || 'memory'
};
assert(unlock);
// Clean the database before loading. The only
// reason for using an on-disk db for the mempool
// is not for persistence, but to keep ~300mb of
// txs out of main memory.
bcoin.ldb.destroy(options, function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
self.db = bcoin.ldb(options);
this.db.destroy(function(err) {
if (err)
return callback(err);
self.db.open(function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
if (err)
return callback(err);
self.initialMemoryUsage(function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
self.chain.open(function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
unlock();
self.loaded = true;
self.emit('open');
});
if (err)
return callback(err);
self.chain.open(callback);
});
});
});
};
/**
* Close the chain, wait for the database to close.
* @alias Mempool#close
* @param {Function} callback
*/
Mempool.prototype._close = function destroy(callback) {
this.db.close(callback);
};
/**
* Invoke mutex lock.
* @private
* @returns {Function} unlock
*/
Mempool.prototype._lock = function _lock(func, args, force) {
return this.locker.lock(func, args, force);
};
/**
* Tally up total memory usage from database.
* @param {Function} callback - Returns [Error, Number].
@ -187,29 +191,6 @@ Mempool.prototype.initialMemoryUsage = function initialMemoryUsage(callback) {
});
};
/**
* Open the chain, wait for the database to load.
* @param {Function} callback
*/
Mempool.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
return this.once('open', callback);
};
/**
* Close the chain, wait for the database to close.
* @method
* @param {Function} callback
*/
Mempool.prototype.close =
Mempool.prototype.destroy = function destroy(callback) {
this.db.close(utils.ensure(callback));
};
/**
* Notify the mempool that a new block has come
* in (removes all transactions contained in the

View File

@ -13,6 +13,7 @@ var assert = utils.assert;
var constants = bcoin.protocol.constants;
var bn = require('bn.js');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var BufferReader = require('./reader');
var BufferWriter = require('./writer');
@ -33,7 +34,7 @@ function Miner(options) {
if (!(this instanceof Miner))
return new Miner(options);
EventEmitter.call(this);
AsyncObject.call(this);
if (!options)
options = {};
@ -51,7 +52,6 @@ function Miner(options) {
this.network = this.chain.network;
this.running = false;
this.timeout = null;
this.loaded = false;
this.attempt = null;
this.workerPool = null;
@ -66,31 +66,13 @@ function Miner(options) {
this._init();
}
utils.inherits(Miner, EventEmitter);
utils.inherits(Miner, AsyncObject);
/**
* Open the miner, wait for the chain and mempool to load.
* @param {Function} callback
* Initialize the miner.
* @private
*/
Miner.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
return this.once('open', callback);
};
/**
* Close the miner.
* @method
* @param {Function} callback
*/
Miner.prototype.close =
Miner.prototype.destroy = function destroy(callback) {
return utils.nextTick(callback);
};
Miner.prototype._init = function _init() {
var self = this;
@ -134,18 +116,29 @@ Miner.prototype._init = function _init() {
stat.height,
stat.best);
});
};
function done(err) {
if (err)
return self.emit('error', err);
self.loaded = true;
self.emit('open');
}
/**
* Open the miner, wait for the chain and mempool to load.
* @alias Miner#open
* @param {Function} callback
*/
Miner.prototype._open = function open(callback) {
if (this.mempool)
this.mempool.open(done);
this.mempool.open(callback);
else
this.chain.open(done);
this.chain.open(callback);
};
/**
* Close the miner.
* @alias Miner#close
* @param {Function} callback
*/
Miner.prototype._close = function close(callback) {
return utils.nextTick(callback);
};
/**

View File

@ -9,6 +9,7 @@
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var utils = require('./utils');
/**
@ -24,7 +25,7 @@ function Node(options) {
if (!(this instanceof Node))
return new Node(options);
EventEmitter.call(this);
AsyncObject.call(this);
if (!options)
options = {};
@ -37,9 +38,10 @@ function Node(options) {
this.chain = null;
this.miner = null;
this.walletdb = null;
this.wallet = null;
}
utils.inherits(Node, EventEmitter);
utils.inherits(Node, AsyncObject);
/*
* Expose

View File

@ -8,6 +8,7 @@
'use strict';
var bcoin = require('./env');
var AsyncObject = require('./async');
var EventEmitter = require('events').EventEmitter;
var utils = require('./utils');
var IP = require('./ip');
@ -85,7 +86,7 @@ function Pool(options) {
if (!(this instanceof Pool))
return new Pool(options);
EventEmitter.call(this);
AsyncObject.call(this);
if (!options)
options = {};
@ -129,8 +130,6 @@ function Pool(options) {
});
this.server = null;
this.destroyed = false;
this.loaded = false;
this.size = options.size || 8;
this.maxLeeches = options.maxLeeches || 8;
this.connected = false;
@ -215,67 +214,12 @@ function Pool(options) {
};
this._init();
}
utils.inherits(Pool, EventEmitter);
/**
* Open the pool, wait for the chain to load.
* @param {Function} callback
*/
Pool.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Connect to the network.
*/
Pool.prototype.connect = function connect() {
var self = this;
var i;
assert(this.loaded, 'Pool is not loaded.');
if (this.connected)
return;
if (!this.options.selfish && !this.options.spv) {
if (this.mempool) {
this.mempool.on('tx', function(tx) {
self.announce(tx);
});
}
// Normally we would also broadcast
// competing chains, but we want to
// avoid getting banned if an evil
// miner sends us an invalid competing
// chain that we can't connect and
// verify yet.
this.chain.on('block', function(block) {
if (!self.synced)
return;
self.announce(block);
});
}
assert(this.seeds.length !== 0, 'No seeds available.');
this._addLoader();
for (i = 0; i < this.size - 1; i++)
this._addPeer();
this.connected = true;
};
utils.inherits(Pool, AsyncObject);
/**
* Initialize the pool. Bind to events.
* Initialize the pool.
* @private
*/
@ -328,15 +272,16 @@ Pool.prototype._init = function _init() {
bcoin.debug('Chain is fully synced (height=%d).', self.chain.height);
});
};
function done(err) {
if (err)
return self.emit('error', err);
self.loaded = true;
self.emit('open');
}
/**
* Open the pool, wait for the chain to load.
* @alias Pool#open
* @param {Function} callback
*/
Pool.prototype._open = function open(callback) {
var self = this;
this.getIP(function(err, ip) {
if (err)
bcoin.error(err);
@ -347,12 +292,99 @@ Pool.prototype._init = function _init() {
}
if (self.mempool)
self.mempool.open(done);
self.mempool.open(callback);
else
self.chain.open(done);
self.chain.open(callback);
});
};
/**
* Close and destroy the pool.
* @alias Pool#close
* @param {Function} callback
*/
Pool.prototype._close = function close(callback) {
var i, items, peers, hashes, hash;
this.stopSync();
items = this.inv.items.slice();
for (i = 0; i < items.length; i++)
items[i].finish();
hashes = Object.keys(this.request.map);
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.request.map[hash].finish(new Error('Pool closed.'));
}
if (this.peers.load)
this.peers.load.destroy();
peers = this.peers.regular.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
peers = this.peers.pending.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
peers = this.peers.leeches.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
this.unlisten(callback);
};
/**
* Connect to the network.
*/
Pool.prototype.connect = function connect() {
var self = this;
var i;
assert(this.loaded, 'Pool is not loaded.');
if (this.connected)
return;
if (!this.options.selfish && !this.options.spv) {
if (this.mempool) {
this.mempool.on('tx', function(tx) {
self.announce(tx);
});
}
// Normally we would also broadcast
// competing chains, but we want to
// avoid getting banned if an evil
// miner sends us an invalid competing
// chain that we can't connect and
// verify yet.
this.chain.on('block', function(block) {
if (!self.synced)
return;
self.announce(block);
});
}
assert(this.seeds.length !== 0, 'No seeds available.');
this._addLoader();
for (i = 0; i < this.size - 1; i++)
this._addPeer();
this.connected = true;
};
/**
* Start listening on a server socket.
* @param {Function} callback
@ -524,7 +556,7 @@ Pool.prototype._addLoader = function _addLoader() {
var self = this;
var peer;
if (this.destroyed)
if (!this.loaded)
return;
if (this.peers.load)
@ -861,7 +893,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
block.rhash,
utils.date(block.ts),
self.chain.height,
(Math.floor(self.chain.getProgress() * 10000) / 100) + '%',
(self.chain.getProgress() * 100).toFixed(2) + '%',
self.chain.total,
self.chain.orphan.count,
self.request.activeBlocks,
@ -934,7 +966,7 @@ Pool.prototype._createPeer = function _createPeer(options) {
peer.once('close', function() {
self._removePeer(peer);
if (self.destroyed)
if (!self.loaded)
return;
if (!peer.loader)
@ -1223,7 +1255,7 @@ Pool.prototype._addLeech = function _addLeech(socket) {
var self = this;
var peer;
if (this.destroyed)
if (!this.loaded)
return socket.destroy();
peer = this._createPeer({
@ -1254,7 +1286,7 @@ Pool.prototype._addPeer = function _addPeer() {
var self = this;
var peer, host;
if (this.destroyed)
if (!this.loaded)
return;
if (this.peers.regular.length + this.peers.pending.length >= this.size - 1)
@ -1395,7 +1427,7 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) {
callback = utils.ensure(callback);
if (this.destroyed)
if (!this.loaded)
return callback();
if (options == null)
@ -1664,58 +1696,6 @@ Pool.prototype.setFeeRate = function setFeeRate(rate) {
this.peers.regular[i].setFeeRate(rate);
};
/**
* Close and destroy the pool.
* @method
* @param {Function} callback
*/
Pool.prototype.close =
Pool.prototype.destroy = function destroy(callback) {
var i, items, peers, hashes, hash;
callback = utils.ensure(callback);
if (this.destroyed)
return utils.nextTick(callback);
this.destroyed = true;
this.stopSync();
items = this.inv.items.slice();
for (i = 0; i < items.length; i++)
items[i].finish();
hashes = Object.keys(this.request.map);
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.request.map[hash].finish(new Error('Pool closed.'));
}
if (this.peers.load)
this.peers.load.destroy();
peers = this.peers.regular.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
peers = this.peers.pending.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
peers = this.peers.leeches.slice();
for (i = 0; i < peers.length; i++)
peers[i].destroy();
this.unlisten(callback);
};
/**
* Get peer by host.
* @param {String} addr
@ -1859,7 +1839,7 @@ Pool.prototype.getRandom = function getRandom(hosts, unique) {
Pool.prototype.addHost = function addHost(host) {
if (typeof host === 'string')
host = NetworkAddress.fromHostname(host);
host = NetworkAddress.fromHostname(host, this.network);
if (this.hosts.length > 500)
return;

View File

@ -10,6 +10,7 @@
var bcoin = require('./env');
var utils = require('./utils');
var assert = utils.assert;
var Node = bcoin.node;
/**
* Create an spv node which only maintains
@ -37,20 +38,7 @@ function SPVNode(options) {
if (!(this instanceof SPVNode))
return new SPVNode(options);
bcoin.node.call(this, options);
this.loaded = false;
this._init();
}
utils.inherits(SPVNode, bcoin.node);
SPVNode.prototype._init = function _init() {
var self = this;
var options;
this.wallet = null;
Node.call(this, options);
this.chain = new bcoin.chain({
network: this.network,
@ -84,6 +72,19 @@ SPVNode.prototype._init = function _init() {
});
}
this._init();
}
utils.inherits(SPVNode, Node);
/**
* Initialize the node.
* @private
*/
SPVNode.prototype._init = function _init() {
var self = this;
// Bind to errors
this.pool.on('error', function(err) {
self.emit('error', err);
@ -136,14 +137,26 @@ SPVNode.prototype._init = function _init() {
this.walletdb.on('save address', function(hash) {
self.pool.watch(hash, 'hex');
});
};
function load(err) {
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias SPVNode#open
* @param {Function} callback
*/
SPVNode.prototype._open = function open(callback) {
var self = this;
var options;
function done(err) {
if (err)
return self.emit('error', err);
return callback(err);
self.loaded = true;
self.emit('open');
bcoin.debug('Node is loaded.');
callback();
}
options = utils.merge({
@ -223,7 +236,28 @@ SPVNode.prototype._init = function _init() {
return next();
self.http.open(next);
}
], load);
], done);
};
/**
* Close the node, wait for the database to close.
* @alias SPVNode#close
* @param {Function} callback
*/
SPVNode.prototype._close = function close(callback) {
var self = this;
utils.parallel([
function(next) {
if (!self.http)
return next();
self.http.close(next);
},
this.walletdb.close.bind(this.walletdb),
this.pool.close.bind(this.pool),
this.chain.close.bind(this.chain)
], callback);
};
/**
@ -284,43 +318,6 @@ SPVNode.prototype.stopSync = function stopSync() {
return this.pool.stopSync();
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @param {Function} callback
*/
SPVNode.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the node, wait for the database to close.
* @method
* @param {Function} callback
*/
SPVNode.prototype.close =
SPVNode.prototype.destroy = function destroy(callback) {
var self = this;
this.wallet.destroy();
utils.parallel([
function(next) {
if (!self.http)
return next();
self.http.close(next);
},
this.walletdb.close.bind(this.walletdb),
this.pool.close.bind(this.pool),
this.chain.close.bind(this.chain)
], callback);
};
/**
* Create a {@link Wallet} in the wallet database.
* @param {Object} options - See {@link Wallet}.

View File

@ -76,10 +76,22 @@ function TXDB(db, options) {
utils.inherits(TXDB, EventEmitter);
/**
* Invoke the mutex lock.
* @private
* @returns {Function} unlock
*/
TXDB.prototype._lock = function _lock(func, args, force) {
return this.locker.lock(func, args, force);
};
/**
* Load the bloom filter into memory.
* @private
* @param {Function} callback
*/
TXDB.prototype._loadFilter = function loadFilter(callback) {
var self = this;
@ -96,6 +108,13 @@ TXDB.prototype._loadFilter = function loadFilter(callback) {
}, callback);
};
/**
* Test the bloom filter against an array of address hashes.
* @private
* @param {Hash[]} addresses
* @returns {Boolean}
*/
TXDB.prototype._testFilter = function _testFilter(addresses) {
var i;
@ -1668,6 +1687,10 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
* WalletMap
* @constructor
* @private
* @property {WalletMember[]} inputs
* @property {WalletMember[]} outputs
* @property {WalletMember[]} accounts
* @property {Object} table
*/
function WalletMap() {
@ -1680,6 +1703,13 @@ function WalletMap() {
this.table = {};
}
/**
* Inject properties from table and tx.
* @private
* @param {Object} table
* @param {TX} tx
*/
WalletMap.prototype.fromTX = function fromTX(table, tx) {
var i, members, input, output, key;
@ -1801,10 +1831,24 @@ WalletMap.prototype.fromTX = function fromTX(table, tx) {
return this;
};
/**
* Instantiate wallet map from tx.
* @param {Object} table
* @param {TX} tx
* @returns {WalletMap}
*/
WalletMap.fromTX = function fromTX(table, tx) {
return new WalletMap().fromTX(table, tx);
};
/**
* Test whether the map has paths
* for a given address hash.
* @param {Hash} address
* @returns {Boolean}
*/
WalletMap.prototype.hasPaths = function hasPaths(address) {
var paths;
@ -1816,10 +1860,21 @@ WalletMap.prototype.hasPaths = function hasPaths(address) {
return paths && paths.length !== 0;
};
/**
* Get paths for a given address hash.
* @param {Hash} address
* @returns {Path[]|null}
*/
WalletMap.prototype.getPaths = function getPaths(address) {
return this.table[address];
};
/**
* Convert the map to a json-friendly object.
* @returns {Object}
*/
WalletMap.prototype.toJSON = function toJSON() {
return {
inputs: this.inputs.map(function(input) {
@ -1831,6 +1886,12 @@ WalletMap.prototype.toJSON = function toJSON() {
};
};
/**
* Inject properties from json object.
* @private
* @param {Object}
*/
WalletMap.prototype.fromJSON = function fromJSON(json) {
var i, j, table, account, input, output, path;
var hash, paths, hashes, accounts, values, key;
@ -1904,6 +1965,12 @@ WalletMap.prototype.fromJSON = function fromJSON(json) {
return this;
};
/**
* Instantiate map from json object.
* @param {Object}
* @returns {WalletMap}
*/
WalletMap.fromJSON = function fromJSON(json) {
return new WalletMap().fromJSON(json);
};
@ -1912,6 +1979,11 @@ WalletMap.fromJSON = function fromJSON(json) {
* MapMember
* @constructor
* @private
* @property {WalletID} id
* @property {String} name - Account name.
* @property {Number} account - Account index.
* @property {Path[]} paths
* @property {Amount} value
*/
function MapMember() {
@ -1925,10 +1997,20 @@ function MapMember() {
this.value = 0;
}
/**
* Convert member to a key in the form of (id|account).
* @returns {String}
*/
MapMember.prototype.toKey = function toKey() {
return this.id + '/' + this.account;
};
/**
* Convert the member to a json-friendly object.
* @returns {Object}
*/
MapMember.prototype.toJSON = function toJSON() {
return {
id: this.id,
@ -1941,6 +2023,12 @@ MapMember.prototype.toJSON = function toJSON() {
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
MapMember.prototype.fromJSON = function fromJSON(json) {
var i, path;
@ -1958,10 +2046,22 @@ MapMember.prototype.fromJSON = function fromJSON(json) {
return this;
};
/**
* Instantiate member from json object.
* @param {Object} json
* @returns {MapMember}
*/
MapMember.fromJSON = function fromJSON(json) {
return new MapMember().fromJSON(json);
};
/**
* Inject properties from path.
* @private
* @param {Path} path
*/
MapMember.prototype.fromPath = function fromPath(path) {
this.id = path.id;
this.name = path.name;
@ -1969,6 +2069,12 @@ MapMember.prototype.fromPath = function fromPath(path) {
return this;
};
/**
* Instantiate member from path.
* @param {Path} path
* @returns {MapMember}
*/
MapMember.fromPath = function fromPath(path) {
return new MapMember().fromPath(path);
};

View File

@ -18,6 +18,7 @@
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var AsyncObject = require('./async');
var utils = require('./utils');
var assert = utils.assert;
var constants = bcoin.protocol.constants;
@ -44,17 +45,220 @@ function WalletDB(options) {
if (!options)
options = {};
EventEmitter.call(this);
AsyncObject.call(this);
this.watchers = {};
this.options = options;
this.loaded = false;
this.network = bcoin.network.get(options.network);
this.watchers = {};
this.db = bcoin.ldb({
network: this.network,
name: this.options.name || 'wallet',
location: this.options.location,
db: this.options.db,
cacheSize: 8 << 20,
writeBufferSize: 4 << 20
});
this.tx = new bcoin.txdb(this, {
network: this.network,
verify: this.options.verify,
useFilter: true
});
this._init();
}
utils.inherits(WalletDB, EventEmitter);
utils.inherits(WalletDB, AsyncObject);
/**
* Initialize wallet db.
* @private
*/
WalletDB.prototype._init = function _init() {
var self = this;
this.tx.on('error', function(err) {
self.emit('error', err);
});
this.tx.on('tx', function(tx, map) {
var i, path;
self.emit('tx', tx, map);
for (i = 0; i < map.accounts.length; i++) {
path = map.accounts[i];
self.fire(path.id, 'tx', tx, path.name);
}
});
this.tx.on('conflict', function(tx, map) {
var i, path;
self.emit('conflict', tx, map);
for (i = 0; i < map.accounts.length; i++) {
path = map.accounts[i];
self.fire(path.id, 'conflict', tx, path.name);
}
});
this.tx.on('confirmed', function(tx, map) {
var i, path;
self.emit('confirmed', tx, map);
for (i = 0; i < map.accounts.length; i++) {
path = map.accounts[i];
self.fire(path.id, 'confirmed', tx, path.name);
}
});
this.tx.on('unconfirmed', function(tx, map) {
var i, path;
self.emit('unconfirmed', tx, map);
for (i = 0; i < map.accounts.length; i++) {
path = map.accounts[i];
self.fire(path.id, 'unconfirmed', tx, path.name);
}
});
this.tx.on('updated', function(tx, map) {
var i, path, keys, id;
self.emit('updated', tx, map);
for (i = 0; i < map.accounts.length; i++) {
path = map.accounts[i];
self.fire(path.id, 'updated', tx, path.name);
}
self.updateBalances(tx, map, function(err, balances) {
if (err) {
self.emit('error', err);
return;
}
keys = Object.keys(balances);
for (i = 0; i < keys.length; i++) {
id = keys[i];
self.fire(id, 'balance', balances[id]);
}
self.emit('balances', balances, map);
});
});
};
/**
* Open the walletdb, wait for the database to load.
* @alias WalletDB#open
* @param {Function} callback
*/
WalletDB.prototype._open = function open(callback) {
var self = this;
this.db.open(function(err) {
if (err)
return callback(err);
self.tx._loadFilter(callback);
});
};
/**
* Close the walletdb, wait for the database to close.
* @alias WalletDB#close
* @param {Function} callback
*/
WalletDB.prototype._close = function close(callback) {
var self = this;
var keys = Object.keys(this.watchers);
var watcher;
utils.forEachSerial(keys, function(key, next) {
watcher = self.watchers[key];
watcher.refs = 1;
watcher.object.destroy(next);
}, function(err) {
if (err)
return callback(err);
self.db.close(callback);
});
};
/**
* Emit balance events after a tx is saved.
* @private
* @param {TX} tx
* @param {WalletMap} map
* @param {Function} callback
*/
WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
var self = this;
var balances = {};
var id;
utils.forEachSerial(map.outputs, function(output, next) {
id = output.id;
if (self.listeners('balance').length === 0
&& !self.hasListener(id, 'balance')) {
return next();
}
if (balances[id] != null)
return next();
self.getBalance(id, function(err, balance) {
if (err)
return next(err);
balances[id] = balance;
next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, balances);
});
};
/**
* Derive new addresses after a tx is saved.
* @private
* @param {TX} tx
* @param {WalletMap} map
* @param {Function} callback
*/
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
var self = this;
var id;
utils.forEachSerial(map.outputs, function(output, next) {
id = output.id;
self.syncOutputDepth(id, tx, function(err, receive, change) {
if (err)
return next(err);
self.fire(id, 'address', receive, change);
self.emit('address', receive, change, map);
next();
});
}, callback);
};
/**
* Dump database (for debugging).
@ -99,159 +303,6 @@ WalletDB.prototype.dump = function dump(callback) {
})();
};
WalletDB.prototype._init = function _init() {
var self = this;
if (this.loaded)
return;
this.db = bcoin.ldb({
network: this.network,
name: this.options.name || 'wallet',
location: this.options.location,
db: this.options.db,
cacheSize: 8 << 20,
writeBufferSize: 4 << 20
});
this.tx = new bcoin.txdb(this, {
network: this.network,
verify: this.options.verify,
useFilter: true
});
this.tx.on('error', function(err) {
self.emit('error', err);
});
this.tx.on('tx', function(tx, map) {
self.emit('tx', tx, map);
map.accounts.forEach(function(path) {
self.fire(path.id, 'tx', tx, path.name);
});
});
this.tx.on('conflict', function(tx, map) {
self.emit('conflict', tx, map);
map.accounts.forEach(function(path) {
self.fire(path.id, 'conflict', tx, path.name);
});
});
this.tx.on('confirmed', function(tx, map) {
self.emit('confirmed', tx, map);
map.accounts.forEach(function(path) {
self.fire(path.id, 'confirmed', tx, path.name);
});
});
this.tx.on('unconfirmed', function(tx, map) {
self.emit('unconfirmed', tx, map);
map.accounts.forEach(function(path) {
self.fire(path.id, 'unconfirmed', tx, path.name);
});
});
this.tx.on('updated', function(tx, map) {
self.emit('updated', tx, map);
map.accounts.forEach(function(path) {
self.fire(path.id, 'updated', tx, path.name);
});
self.updateBalances(tx, map, function(err, balances) {
if (err)
return self.emit('error', err);
Object.keys(balances).forEach(function(id) {
self.fire(id, 'balance', balances[id]);
});
self.emit('balances', balances, map);
});
});
this.db.open(function(err) {
if (err)
return self.emit('error', err);
self.tx._loadFilter(function(err) {
if (err)
return self.emit('error', err);
self.emit('open');
self.loaded = true;
});
});
};
WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
var self = this;
var balances = {};
utils.forEachSerial(map.outputs, function(output, next) {
var id = output.id;
if (self.listeners('balance').length === 0
&& !self.hasListener(id, 'balance')) {
return next();
}
if (balances[id] != null)
return next();
self.getBalance(id, function(err, balance) {
if (err)
return next(err);
balances[id] = balance;
next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, balances);
});
};
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
var self = this;
utils.forEachSerial(map.outputs, function(output, next) {
var id = output.id;
self.syncOutputDepth(id, tx, function(err, receive, change) {
if (err)
return next(err);
self.fire(id, 'address', receive, change);
self.emit('address', receive, change, map);
next();
});
}, callback);
};
/**
* Open the walletdb, wait for the database to load.
* @param {Function} callback
*/
WalletDB.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
};
/**
* Close the walletdb, wait for the database to close.
* @method
* @param {Function} callback
*/
WalletDB.prototype.close =
WalletDB.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
this.db.close(callback);
};
/**
* Register an object with the walletdb.
* @param {Object} object
@ -480,7 +531,8 @@ WalletDB.prototype.has = function has(id, callback) {
WalletDB.prototype.ensure = function ensure(options, callback) {
var self = this;
return this.get(options.id, function(err, wallet) {
this.get(options.id, function(err, wallet) {
if (err)
return callback(err);
@ -502,7 +554,7 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
var self = this;
var account;
return this.getAccountIndex(id, name, function(err, index) {
this.getAccountIndex(id, name, function(err, index) {
if (err)
return callback(err);
@ -540,6 +592,7 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
WalletDB.prototype.getAccounts = function getAccounts(id, callback) {
var accounts = [];
this.db.iterate({
gte: 'i/' + id + '/',
lte: 'i/' + id + '/~',
@ -571,7 +624,7 @@ WalletDB.prototype.getAccountIndex = function getAccountIndex(id, name, callback
if (typeof name === 'number')
return callback(null, name);
return this.db.get('i/' + id + '/' + name, function(err, index) {
this.db.get('i/' + id + '/' + name, function(err, index) {
if (err && err.type !== 'NotFoundError')
return callback();
@ -1149,6 +1202,12 @@ WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) {
* Path
* @constructor
* @private
* @property {WalletID} id
* @property {String} name - Account name.
* @property {Number} account - Account index.
* @property {Number} change - Change index.
* @property {Number} index - Address index.
* @property {Address|null} address
*/
function Path() {
@ -1163,6 +1222,12 @@ function Path() {
this.address = null;
}
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Path.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.id = p.readVarString('utf8');
@ -1173,10 +1238,21 @@ Path.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate path from serialized data.
* @param {Buffer} data
* @returns {Path}
*/
Path.fromRaw = function fromRaw(data) {
return new Path().fromRaw(data);
};
/**
* Serialize path.
* @returns {Buffer}
*/
Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
@ -1192,6 +1268,13 @@ Path.prototype.toRaw = function toRaw(writer) {
return p;
};
/**
* Inject properties from keyring.
* @private
* @param {WalletID} id
* @param {KeyRing} address
*/
Path.prototype.fromKeyRing = function fromKeyRing(id, address) {
this.id = id;
this.name = address.name;
@ -1201,22 +1284,32 @@ Path.prototype.fromKeyRing = function fromKeyRing(id, address) {
return this;
};
/**
* Instantiate path from keyring.
* @param {WalletID} id
* @param {KeyRing} address
* @returns {Path}
*/
Path.fromKeyRing = function fromKeyRing(id, address) {
return new Path().fromKeyRing(id, address);
};
/**
* Convert path object to string derivation path.
* @returns {String}
*/
Path.prototype.toPath = function() {
return 'm/' + this.account
+ '\'/' + this.change
+ '/' + this.index;
};
Path.prototype.inspect = function() {
return '<Path: ' + this.id
+ '/' + this.name
+ ': ' + this.toPath()
+ '>';
};
/**
* Convert path to a json-friendly object.
* @returns {Object}
*/
Path.prototype.toJSON = function toJSON() {
return {
@ -1226,6 +1319,12 @@ Path.prototype.toJSON = function toJSON() {
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Path.prototype.fromJSON = function fromJSON(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
@ -1242,14 +1341,30 @@ Path.prototype.fromJSON = function fromJSON(json) {
return this;
};
/**
* Instantiate path from json object.
* @param {Object} json
* @returns {Path}
*/
Path.fromJSON = function fromJSON(json) {
return new Path().fromJSON(json);
};
/**
* Convert path to a key in the form of (id|account).
* @returns {String}
*/
Path.prototype.toKey = function toKey() {
return this.id + '/' + this.account;
};
/**
* Convert path to a compact json object.
* @returns {Object}
*/
Path.prototype.toCompact = function toCompact() {
return {
path: 'm/' + this.change + '/' + this.index,
@ -1257,6 +1372,12 @@ Path.prototype.toCompact = function toCompact() {
};
};
/**
* Inject properties from compact json object.
* @private
* @param {Object} json
*/
Path.prototype.fromCompact = function fromCompact(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
@ -1271,10 +1392,28 @@ Path.prototype.fromCompact = function fromCompact(json) {
return this;
};
/**
* Instantiate path from compact json object.
* @param {Object} json
* @returns {Path}
*/
Path.fromCompact = function fromCompact(json) {
return new Path().fromCompact(json);
};
/**
* Inspect the path.
* @returns {String}
*/
Path.prototype.inspect = function() {
return '<Path: ' + this.id
+ '/' + this.name
+ ': ' + this.toPath()
+ '>';
};
/*
* Helpers
*/

View File

@ -58,7 +58,6 @@ describe('Chain', function() {
function deleteCoins(tx) {
if (tx.txs) {
delete tx.view;
deleteCoins(tx.txs);
return;
}
@ -67,12 +66,16 @@ describe('Chain', function() {
return;
}
tx.inputs.forEach(function(input) {
delete input.coin;
input.coin = null;
});
}
it('should open chain and miner', function(cb) {
miner.open(function(err) {
miner.open(cb);
});
it('should open walletdb', function(cb) {
walletdb.open(function(err) {
assert.ifError(err);
walletdb.create({}, function(err, w) {
assert.ifError(err);

View File

@ -21,7 +21,7 @@ describe('Mempool', function() {
db: 'memory'
});
var wdb = new bcoin.walletdb({
var walletdb = new bcoin.walletdb({
name: 'mempool-wallet-test',
db: 'memory',
verify: true
@ -35,8 +35,12 @@ describe('Mempool', function() {
mempool.open(cb);
});
it('should open walletdb', function(cb) {
walletdb.open(cb);
});
it('should open wallet', function(cb) {
wdb.create({}, function(err, wallet) {
walletdb.create({}, function(err, wallet) {
assert.ifError(err);
w = wallet;
cb();