refactor: config, plugins, and walletdb.

This commit is contained in:
Christopher Jeffrey 2017-03-08 16:54:04 -08:00
parent 70a0147080
commit 5cbbdbfb2f
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
22 changed files with 4825 additions and 3674 deletions

File diff suppressed because it is too large Load Diff

View File

@ -108,8 +108,8 @@ HTTPClient.prototype._open = co(function* _open() {
self.emit('balance', balance);
});
yield this._onConnect();
yield this._sendAuth();
yield this.onConnect();
yield this.sendAuth();
});
/**
@ -134,7 +134,7 @@ HTTPClient.prototype._close = function close() {
* @returns {Promise}
*/
HTTPClient.prototype._onConnect = function _onConnect() {
HTTPClient.prototype.onConnect = function onConnect() {
var self = this;
return new Promise(function(resolve, reject) {
self.socket.once('connect', resolve);
@ -147,12 +147,29 @@ HTTPClient.prototype._onConnect = function _onConnect() {
* @returns {Promise}
*/
HTTPClient.prototype._sendAuth = function _sendAuth() {
HTTPClient.prototype.sendAuth = function sendAuth() {
var self = this;
return new Promise(function(resolve, reject) {
self.socket.emit('auth', self.apiKey, function(err) {
if (err)
return reject(new Error(err.error));
return reject(new Error(err.message));
resolve();
});
});
};
/**
* Wait for websocket auth.
* @private
* @returns {Promise}
*/
HTTPClient.prototype.sendWalletAuth = function sendWalletAuth() {
var self = this;
return new Promise(function(resolve, reject) {
self.socket.emit('wallet auth', self.apiKey, function(err) {
if (err)
return reject(new Error(err.message));
resolve();
});
});
@ -220,7 +237,7 @@ HTTPClient.prototype._request = co(function* _request(method, endpoint, json) {
if (res.statusCode !== 200) {
if (res.body.error)
throw new Error(res.body.error);
throw new Error(res.body.error.message);
throw new Error('Status code: ' + res.statusCode);
}
@ -369,7 +386,7 @@ HTTPClient.prototype.broadcast = function broadcast(tx) {
HTTPClient.prototype.rescan = function rescan(height) {
var options = { height: height };
return this._post('/rescan', options);
return this._post('/_admin/rescan', options);
};
/**
@ -380,7 +397,7 @@ HTTPClient.prototype.rescan = function rescan(height) {
HTTPClient.prototype.reset = function reset(block) {
var options = { block: block };
return this._post('/reset', options);
return this._post('/_admin/reset', options);
};
/**
@ -389,7 +406,7 @@ HTTPClient.prototype.reset = function reset(block) {
*/
HTTPClient.prototype.resend = function resend() {
return this._post('/resend', {});
return this._post('/_admin/resend', {});
};
/**
@ -400,7 +417,7 @@ HTTPClient.prototype.resend = function resend() {
HTTPClient.prototype.backup = function backup(path) {
var options = { path: path };
return this._post('/backup', options);
return this._post('/_admin/backup', options);
};
/**
@ -417,7 +434,7 @@ HTTPClient.prototype.join = function join(id, token) {
return new Promise(function(resolve, reject) {
self.socket.emit('wallet join', id, token, function(err) {
if (err)
return reject(new Error(err.error));
return reject(new Error(err.message));
resolve();
});
});
@ -437,7 +454,7 @@ HTTPClient.prototype.leave = function leave(id) {
return new Promise(function(resolve, reject) {
self.socket.emit('wallet leave', id, function(err) {
if (err)
return reject(new Error(err.error));
return reject(new Error(err.message));
resolve();
});
});
@ -475,7 +492,7 @@ HTTPClient.prototype.getWallets = function getWallets() {
*/
HTTPClient.prototype.createWallet = function createWallet(options) {
return this._post('/wallet', options);
return this._put('/wallet/' + options.id, options);
};
/**
@ -934,9 +951,9 @@ HTTPClient.prototype.createAccount = function createAccount(id, options) {
if (typeof options === 'string')
options = { account: options };
path = '/wallet/' + id + '/account';
path = '/wallet/' + id + '/account' + options.account;
return this._post(path, options);
return this._put(path, options);
};
/**

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -115,6 +115,7 @@ HTTPWallet.prototype.open = co(function* open(options) {
assert(this.id, 'No ID provided.');
yield this.client.open();
yield this.client.sendWalletAuth();
yield this.client.join(this.id, this.token);
});
@ -128,6 +129,7 @@ HTTPWallet.prototype.create = co(function* create(options) {
var wallet;
yield this.client.open();
yield this.client.sendWalletAuth();
wallet = yield this.client.createWallet(options);

View File

@ -107,6 +107,9 @@ Miner.prototype._open = co(function* open() {
this.logger.info('Miner loaded (flags=%s).',
this.options.coinbaseFlags.toString('utf8'));
if (this.addresses.length === 0)
this.logger.warning('No reward address is set for miner!');
});
/**
@ -387,7 +390,8 @@ Miner.prototype.addAddress = function addAddress(address) {
*/
Miner.prototype.getAddress = function getAddress() {
assert(this.addresses.length !== 0, 'No address passed in for miner.');
if (this.addresses.length === 0)
return;
return this.addresses[Math.random() * this.addresses.length | 0];
};

View File

@ -14,6 +14,7 @@ var util = require('../utils/util');
var co = require('../utils/co');
var StaticWriter = require('../utils/staticwriter');
var Network = require('../protocol/network');
var Address = require('../primitives/address');
var TX = require('../primitives/tx');
var Block = require('../primitives/block');
var Input = require('../primitives/input');
@ -232,8 +233,10 @@ MinerBlock.prototype._init = function _init() {
input.script.compile();
// Setup output script (variable size).
output.script.clear();
output.script.fromAddress(this.address);
if (this.address) {
output.script.clear();
output.script.fromAddress(this.address);
}
// Update commitments.
this.refresh();
@ -345,6 +348,23 @@ MinerBlock.prototype.extraNonce = function extraNonce() {
return bw.render();
};
/**
* Set the reward output address.
* @param {Address} address
*/
MinerBlock.prototype.setAddress = function setAddress(address) {
var output = this.coinbase.outputs[0];
this.address = Address(address);
output.script.clear();
output.script.fromAddress(this.address);
// Update commitments.
this.refresh();
};
/**
* Add a transaction to the block. Rebuilds the merkle tree,
* updates coinbase and commitment.

View File

@ -268,7 +268,6 @@ Pool.prototype.resetChain = function resetChain() {
Pool.prototype._close = co(function* close() {
yield this.disconnect();
yield this.hosts.close();
});
/**

View File

@ -243,8 +243,8 @@ Config.prototype.str = function str(key, fallback) {
if (value === null)
return fallback;
assert(typeof value === 'string',
'Passed in config option is of wrong type.');
if (typeof value !== 'string')
throw new Error(key + ' must be a string.');
return value;
};
@ -266,15 +266,18 @@ Config.prototype.num = function num(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(typeof value === 'number',
'Passed in config option is of wrong type.');
if (typeof value !== 'number')
throw new Error(key + ' must be a string.');
return value;
}
if (!/^\d+$/.test(value))
throw new Error(key + ' must be a number.');
value = parseInt(value, 10);
if (!isFinite(value))
return fallback;
throw new Error(key + ' must be a number.');
return value;
};
@ -296,8 +299,8 @@ Config.prototype.bool = function bool(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(typeof value === 'boolean',
'Passed in config option is of wrong type.');
if (typeof value !== 'boolean')
throw new Error(key + ' must be a boolean.');
return value;
}
@ -307,7 +310,7 @@ Config.prototype.bool = function bool(key, fallback) {
if (value === 'false' || value === '0')
return false;
return fallback;
throw new Error(key + ' must be a boolean.');
};
/**
@ -319,6 +322,7 @@ Config.prototype.bool = function bool(key, fallback) {
Config.prototype.buf = function buf(key, fallback) {
var value = this.get(key);
var data;
if (fallback === undefined)
fallback = null;
@ -327,12 +331,17 @@ Config.prototype.buf = function buf(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(Buffer.isBuffer(value),
'Passed in config option is of wrong type.');
if (!Buffer.isBuffer(value))
throw new Error(key + ' must be a buffer.');
return value;
}
return new Buffer(value, 'hex');
data = new Buffer(value, 'hex');
if (data.length !== value.length / 2)
throw new Error(key + ' must be a hex string.');
return data;
};
/**
@ -352,8 +361,8 @@ Config.prototype.list = function list(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(Array.isArray(value),
'Passed in config option is of wrong type.');
if (!Array.isArray(value))
throw new Error(key + ' must be an array.');
return value;
}
@ -377,8 +386,8 @@ Config.prototype.obj = function obj(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(value && typeof value === 'object',
'Passed in config option is of wrong type.');
if (!value || typeof value !== 'object')
throw new Error(key + ' must be an object.');
return value;
}
@ -388,8 +397,8 @@ Config.prototype.obj = function obj(key, fallback) {
;
}
assert(value && typeof value === 'object',
'Passed in config option is of wrong type.');
if (!value || typeof value !== 'object')
throw new Error(key + ' must be an object.');
return value;
};
@ -411,13 +420,12 @@ Config.prototype.func = function func(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(value && typeof value === 'function',
'Passed in config option is of wrong type.');
if (!value || typeof value !== 'function')
throw new Error(key + ' must be a function.');
return value;
}
assert(false,
'Passed in config option is of wrong type.');
throw new Error(key + ' must be a function.');
};
/**
@ -486,8 +494,8 @@ Config.prototype.boolpath = function boolpath(key, fallback) {
return fallback;
if (typeof value !== 'string') {
assert(typeof value === 'boolean' || typeof value === 'string',
'Passed in config option is of wrong type.');
if (typeof value !== 'boolean' && typeof value !== 'string')
throw new Error(key + ' must be a boolean or string.');
return value;
}

View File

@ -15,12 +15,11 @@ var Fees = require('../mempool/fees');
var Mempool = require('../mempool/mempool');
var Pool = require('../net/pool');
var Miner = require('../mining/miner');
var WalletDB = require('../wallet/walletdb');
var HTTPServer = require('../http/server');
/**
* Respresents a fullnode complete with a
* chain, mempool, miner, wallet, etc.
* chain, mempool, miner, etc.
* @alias module:node.FullNode
* @extends Node
* @constructor
@ -30,7 +29,6 @@ var HTTPServer = require('../http/server');
* @property {Mempool} mempool
* @property {Pool} pool
* @property {Miner} miner
* @property {WalletDB} walletdb
* @property {HTTPServer} http
* @emits FullNode#block
* @emits FullNode#tx
@ -126,22 +124,6 @@ function FullNode(options) {
reservedSigops: this.config.num('reserved-sigops')
});
// Wallet database needs access to fees.
this.walletdb = new WalletDB({
network: this.network,
logger: this.logger,
client: this.client,
db: this.config.str('db'),
prefix: this.config.prefix,
maxFiles: this.config.num('walletdb-max-files'),
cacheSize: this.config.mb('walletdb-cache-size'),
witness: false,
checkpoints: this.config.bool('checkpoints'),
startHeight: this.config.num('start-height'),
wipeNoReally: this.config.bool('wipe-no-really'),
verify: false
});
// HTTP needs access to the node.
if (!HTTPServer.unsupported) {
this.http = new HTTPServer({
@ -180,7 +162,6 @@ FullNode.prototype._init = function _init() {
this.mempool.on('error', onError);
this.pool.on('error', onError);
this.miner.on('error', onError);
this.walletdb.on('error', onError);
if (this.http)
this.http.on('error', onError);
@ -217,6 +198,8 @@ FullNode.prototype._init = function _init() {
}
self.emit('reset', tip);
}));
this.loadPlugins();
};
/**
@ -231,10 +214,8 @@ FullNode.prototype._open = co(function* open() {
yield this.mempool.open();
yield this.miner.open();
yield this.pool.open();
yield this.walletdb.open();
// Ensure primary wallet.
yield this.openWallet();
yield this.openPlugins();
if (this.http)
yield this.http.open();
@ -252,11 +233,8 @@ FullNode.prototype._close = co(function* close() {
if (this.http)
yield this.http.close();
yield this.wallet.destroy();
yield this.closePlugins();
this.wallet = null;
yield this.walletdb.close();
yield this.pool.close();
yield this.miner.close();
yield this.mempool.close();

View File

@ -13,7 +13,6 @@ var util = require('../utils/util');
var co = require('../utils/co');
var Network = require('../protocol/network');
var Logger = require('./logger');
var NodeClient = require('./nodeclient');
var workerPool = require('../workers/workerpool').pool;
var ec = require('../crypto/ec');
var native = require('../utils/native');
@ -48,8 +47,6 @@ function Node(options) {
this.mempool = null;
this.pool = null;
this.miner = null;
this.walletdb = null;
this.wallet = null;
this.http = null;
this.client = null;
@ -90,15 +87,15 @@ Node.prototype.init = function init() {
var self = this;
this.initOptions();
this.loadPlugins();
// Local client for walletdb
this.client = new NodeClient(this);
this.hook('preopen', function() {
return self.handlePreopen();
});
this.hook('preopen', function() {
return self.handlePreclose();
});
this.hook('open', function() {
return self.handleOpen();
});
@ -117,6 +114,7 @@ Node.prototype.handlePreopen = co(function* handlePreopen() {
var self = this;
yield fs.mkdirp(this.config.prefix);
yield this.logger.open();
this.bind(this.network.time, 'offset', function(offset) {
@ -173,8 +171,14 @@ Node.prototype.handleOpen = co(function* handleOpen() {
this.logger.warning('Warning: worker pool is disabled.');
this.logger.warning('Verification will be slow.');
}
});
yield this.openPlugins();
/**
* Open node. Bind all events.
* @private
*/
Node.prototype.handlePreclose = co(function* handlePreclose() {
});
/**
@ -185,8 +189,6 @@ Node.prototype.handleOpen = co(function* handleOpen() {
Node.prototype.handleClose = co(function* handleClose() {
var i, bound;
yield this.closePlugins();
this.startTime = -1;
for (i = 0; i < this.bound.length; i++) {
@ -264,36 +266,6 @@ Node.prototype.uptime = function uptime() {
return util.now() - this.startTime;
};
/**
* Open and ensure primary wallet.
* @returns {Promise}
*/
Node.prototype.openWallet = co(function* openWallet() {
var options, wallet;
assert(!this.wallet);
options = {
id: 'primary',
passphrase: this.config.str('passphrase')
};
wallet = yield this.walletdb.ensure(options);
this.logger.info(
'Loaded wallet with id=%s wid=%d address=%s',
wallet.id, wallet.wid, wallet.getAddress());
if (this.miner && this.miner.addresses.length === 0)
this.miner.addAddress(wallet.getAddress());
this.wallet = wallet;
if (this.http && this.http.rpc && !this.http.rpc.wallet)
this.http.rpc.wallet = wallet;
});
/**
* Attach a plugin.
* @param {Object} plugin
@ -312,25 +284,24 @@ Node.prototype.use = function use(plugin) {
assert(typeof instance.open === 'function', '`open` must be a function.');
assert(typeof instance.close === 'function', '`close` must be a function.');
if (plugin.name) {
assert(typeof plugin.name === 'string', '`name` must be a string.');
if (plugin.id) {
assert(typeof plugin.id === 'string', '`name` must be a string.');
// Reserved names
switch (plugin.name) {
switch (plugin.id) {
case 'logger':
case 'chain':
case 'fees':
case 'mempool':
case 'miner':
case 'pool':
case 'walletdb':
assert(false, plugin.name + ' is already added.');
assert(false, plugin.id + ' is already added.');
break;
}
assert(!this.plugins[plugin.name], plugin.name + ' is already added.');
assert(!this.plugins[plugin.id], plugin.id + ' is already added.');
this.plugins[plugin.name] = instance;
this.plugins[plugin.id] = instance;
}
this.stack.push(instance);
@ -366,9 +337,6 @@ Node.prototype.require = function require(name) {
case 'pool':
assert(this.pool, 'pool is not loaded.');
return this.pool;
case 'walletdb':
assert(this.walletdb, 'walletdb is not loaded.');
return this.walletdb;
}
plugin = this.plugins[name];
@ -392,9 +360,16 @@ Node.prototype.loadPlugins = function loadPlugins() {
for (i = 0; i < plugins.length; i++) {
name = plugins[i];
assert(typeof name === 'string',
'Plugin name must be a string.');
// Temporary until we separate walletdb out.
if (name === 'walletdb')
name = __dirname + '/../wallet/walletdb';
plugin = loader(name);
this.use(plugin);
}
};

View File

@ -13,12 +13,11 @@ var Lock = require('../utils/lock');
var Node = require('./node');
var Chain = require('../blockchain/chain');
var Pool = require('../net/pool');
var WalletDB = require('../wallet/walletdb');
var HTTPServer = require('../http/server');
/**
* Create an spv node which only maintains
* a chain, a pool, and a wallet database.
* a chain, a pool, and an http server.
* @alias module:node.SPVNode
* @extends Node
* @constructor
@ -27,11 +26,9 @@ var HTTPServer = require('../http/server');
* @param {Buffer?} options.sslCert
* @param {Number?} options.httpPort
* @param {String?} options.httpHost
* @param {Object?} options.wallet - Primary {@link Wallet} options.
* @property {Boolean} loaded
* @property {Chain} chain
* @property {Pool} pool
* @property {WalletDB} walletdb
* @property {HTTPServer} http
* @emits SPVNode#block
* @emits SPVNode#tx
@ -74,22 +71,6 @@ function SPVNode(options) {
listen: false
});
this.walletdb = new WalletDB({
network: this.network,
logger: this.logger,
client: this.client,
db: this.config.str('db'),
prefix: this.config.prefix,
maxFiles: this.config.num('max-files'),
cacheSize: this.config.mb('cache-size'),
witness: false,
checkpoints: this.config.bool('checkpoints'),
startHeight: this.config.num('start-height'),
wipeNoReally: this.config.bool('wipe-no-really'),
verify: true,
spv: true
});
if (!HTTPServer.unsupported) {
this.http = new HTTPServer({
network: this.network,
@ -129,7 +110,6 @@ SPVNode.prototype._init = function _init() {
// Bind to errors
this.chain.on('error', onError);
this.pool.on('error', onError);
this.walletdb.on('error', onError);
if (this.http)
this.http.on('error', onError);
@ -165,6 +145,8 @@ SPVNode.prototype._init = function _init() {
this.chain.on('reset', function(tip) {
self.emit('reset', tip);
});
this.loadPlugins();
};
/**
@ -177,10 +159,8 @@ SPVNode.prototype._init = function _init() {
SPVNode.prototype._open = co(function* open(callback) {
yield this.chain.open();
yield this.pool.open();
yield this.walletdb.open();
// Ensure primary wallet.
yield this.openWallet();
yield this.openPlugins();
if (this.http)
yield this.http.open();
@ -198,11 +178,8 @@ SPVNode.prototype._close = co(function* close() {
if (this.http)
yield this.http.close();
yield this.wallet.destroy();
yield this.closePlugins();
this.wallet = null;
yield this.walletdb.close();
yield this.pool.close();
yield this.chain.close();
});

View File

@ -31,3 +31,4 @@ exports.BufferReader = require('./reader');
exports.StaticWriter = require('./staticwriter');
exports.util = require('./util');
exports.BufferWriter = require('./writer');
exports.Validator = require('./validator');

View File

@ -1037,3 +1037,30 @@ util.promisify = function promisify(func) {
return Promise.resolve(result);
};
};
/**
* Get memory usage info.
* @returns {Object}
*/
util.memoryUsage = function memoryUsage() {
var mem;
if (!process.memoryUsage) {
return {
total: 0,
jsHeap: 0,
jsHeapTotal: 0,
nativeHeap: 0
};
}
mem = process.memoryUsage();
return {
total: util.mb(mem.rss),
jsHeap: util.mb(mem.heapUsed),
jsHeapTotal: util.mb(mem.heapTotal),
nativeHeap: util.mb(mem.rss - mem.heapTotal)
};
};

428
lib/utils/validator.js Normal file
View File

@ -0,0 +1,428 @@
'use strict';
var assert = require('assert');
/**
* Validator
* @alias module:utils.Validator
* @constructor
* @param {Object} options
*/
function Validator(data) {
if (!(this instanceof Validator))
return new Validator(data);
this.data = [];
if (data)
this.init(data);
}
/**
* Test whether a config option is present.
* @param {String} key
* @returns {Boolean}
*/
Validator.prototype.init = function init(data) {
var i, obj;
assert(data && typeof data === 'object');
if (!Array.isArray(data))
data = [data];
for (i = 0; i < data.length; i++) {
obj = data[i];
assert(obj && typeof obj === 'object');
this.data.push(obj);
}
};
/**
* Test whether a config option is present.
* @param {String} key
* @returns {Boolean}
*/
Validator.prototype.has = function has(key) {
var i, map, value;
assert(typeof key === 'string' || typeof key === 'number',
'Key must be a string.');
for (i = 0; i < this.data.length; i++) {
map = this.data[i];
value = map[key];
if (value != null)
return true;
}
return false;
};
/**
* Get a config option.
* @param {String} key
* @param {Object?} fallback
* @returns {Object|null}
*/
Validator.prototype.get = function get(key, fallback) {
var i, keys, value, map;
if (fallback === undefined)
fallback = null;
if (Array.isArray(key)) {
keys = key;
for (i = 0; i < keys.length; i++) {
key = keys[i];
value = this.get(key);
if (value !== null)
return value;
}
return fallback;
}
assert(typeof key === 'string' || typeof key === 'number',
'Key must be a string.');
for (i = 0; i < this.data.length; i++) {
map = this.data[i];
value = map[key];
if (value != null)
return value;
}
return fallback;
};
/**
* Get a config option (as a string).
* @param {String} key
* @param {Object?} fallback
* @returns {String|null}
*/
Validator.prototype.str = function str(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string')
throw new Error(key + ' must be a string.');
return value;
};
/**
* Get a config option (as a number).
* @param {String} key
* @param {Object?} fallback
* @returns {Number|null}
*/
Validator.prototype.num = function num(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (typeof value !== 'number')
throw new Error(key + ' must be a number.');
return value;
}
if (!/^\d+$/.test(value))
throw new Error(key + ' must be a number.');
value = parseInt(value, 10);
if (!isFinite(value))
throw new Error(key + ' must be a number.');
return value;
};
/**
* Get a config option (as a number).
* @param {String} key
* @param {Object?} fallback
* @returns {Number|null}
*/
Validator.prototype.amt = function amt(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (typeof value !== 'number')
throw new Error(key + ' must be a number.');
return value;
}
if (!/^\d+(\.\d{0,8})?$/.test(value))
throw new Error(key + ' must be a number.');
value = parseFloat(value);
if (!isFinite(value))
throw new Error(key + ' must be a number.');
return value * 1e8;
};
/**
* Get a config option (as a number).
* @param {String} key
* @param {Object?} fallback
* @returns {Number|null}
*/
Validator.prototype.hash = function hash(key, fallback) {
var value = this.get(key);
var out = '';
var i;
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (!Buffer.isBuffer(value))
throw new Error(key + ' must be a buffer.');
if (value.length !== 32)
throw new Error(key + ' must be a buffer.');
return value.toString('hex');
}
if (value.length !== 64)
throw new Error(key + ' must be a hex string.');
if (!/^[0-9a-f]+$/i.test(value))
throw new Error(key + ' must be a hex string.');
for (i = 0; i < value.length; i += 2)
out = value.slice(i, i + 2) + out;
return out;
};
/**
* Get a config option (as a number).
* @param {String} key
* @param {Object?} fallback
* @returns {Number|null}
*/
Validator.prototype.numstr = function numstr(key, fallback) {
var value = this.get(key);
var num;
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (typeof value !== 'number')
throw new Error(key + ' must be a number or string.');
return value;
}
num = parseInt(value, 10);
if (!isFinite(num))
return value;
return num;
};
/**
* Get a config option (as a boolean).
* @param {String} key
* @param {Object?} fallback
* @returns {Boolean|null}
*/
Validator.prototype.bool = function bool(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
assert(typeof value === 'boolean',
'Passed in config option is of wrong type.');
return value;
}
if (value === 'true' || value === '1')
return true;
if (value === 'false' || value === '0')
return false;
throw new Error(key + ' must be a boolean.');
};
/**
* Get a config option (as a buffer).
* @param {String} key
* @param {Object?} fallback
* @returns {Buffer|null}
*/
Validator.prototype.buf = function buf(key, fallback) {
var value = this.get(key);
var data;
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
assert(Buffer.isBuffer(value),
'Passed in config option is of wrong type.');
return value;
}
data = new Buffer(value, 'hex');
if (data.length !== value.length / 2)
throw new Error(key + ' must be a hex string.');
return data;
};
/**
* Get a config option (as an array of strings).
* @param {String} key
* @param {Object?} fallback
* @returns {String[]|null}
*/
Validator.prototype.array = function array(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (!Array.isArray(value))
throw new Error(key + ' must be a list/array.');
return value;
}
return value.trim().split(/\s*,\s*/);
};
/**
* Get a config option (as an object).
* @param {String} key
* @param {Object?} fallback
* @returns {Object|null}
*/
Validator.prototype.obj = function obj(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (!value || typeof value !== 'object')
throw new Error(key + ' must be an object.');
return value;
}
try {
value = JSON.parse(value);
} catch (e) {
;
}
if (!value || typeof value !== 'object')
throw new Error(key + ' must be an object.');
return value;
};
/**
* Get a config option (as an object).
* @param {String} key
* @param {Object?} fallback
* @returns {Object|null}
*/
Validator.prototype.next = function next(key, fallback) {
var value = this.obj(key, fallback);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
return new Validator(value);
};
/**
* Get a config option (as a function).
* @param {String} key
* @param {Object?} fallback
* @returns {Function|null}
*/
Validator.prototype.func = function func(key, fallback) {
var value = this.get(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
if (typeof value !== 'string') {
if (typeof value !== 'function')
throw new Error(key + ' must be a function.');
return value;
}
throw new Error(key + ' must be a function.');
};
/*
* Expose
*/
module.exports = Validator;

View File

@ -23,6 +23,9 @@ common.isName = function isName(key) {
if (typeof key !== 'string')
return false;
if (key === '_admin')
return false;
if (key === '__proto__')
return false;

1087
lib/wallet/http.js Normal file

File diff suppressed because it is too large Load Diff

1847
lib/wallet/rpc.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1424,7 +1424,7 @@ Wallet.prototype._fund = co(function* fund(mtx, options) {
rate = options.rate;
if (rate == null)
rate = yield this.db.estimateFee();
rate = yield this.db.estimateFee(options.blocks);
if (options.smart) {
coins = yield this.getSmartCoins(options.account);

View File

@ -26,6 +26,8 @@ var Logger = require('../node/logger');
var Outpoint = require('../primitives/outpoint');
var layouts = require('./layout');
var records = require('./records');
var NodeClient = require('../node/nodeclient');
var HTTP = require('./http');
var layout = layouts.walletdb;
var ChainState = records.ChainState;
var BlockMapRecord = records.BlockMapRecord;
@ -62,6 +64,20 @@ function WalletDB(options) {
this.logger = this.options.logger;
this.client = this.options.client;
this.db = LDB(this.options);
this.primary = null;
this.http = new HTTP({
walletdb: this,
network: this.network,
logger: this.logger,
prefix: this.options.prefix,
apiKey: this.options.apiKey,
walletAuth: this.options.walletAuth,
noAuth: this.options.noAuth,
host: this.options.host,
port: this.options.port,
ssl: this.options.ssl
});
this.state = new ChainState();
this.wallets = Object.create(null);
@ -83,6 +99,54 @@ function WalletDB(options) {
util.inherits(WalletDB, AsyncObject);
/**
* Plugin name.
* @const {String}
*/
WalletDB.id = 'walletdb';
/**
* Plugin initialization.
* @param {Node} node
* @returns {WalletDB}
*/
WalletDB.init = function init(node) {
var config = node.config;
var client = new NodeClient(node);
var wdb;
wdb = new WalletDB({
network: node.network,
logger: node.logger,
client: client,
prefix: config.prefix,
db: config.str(['wallet-db', 'db']),
maxFiles: config.num('wallet-max-files'),
cacheSize: config.mb('wallet-cache-size'),
witness: config.bool('wallet-witness'),
checkpoints: config.bool('wallet-checkpoints'),
startHeight: config.num('wallet-start-height'),
wipeNoReally: config.bool('wallet-wipe-no-really'),
apiKey: config.str(['wallet-api-key', 'api-key']),
walletAuth: config.bool('wallet-auth'),
noAuth: config.bool('no-auth'),
ssl: config.str('wallet-ssl'),
host: config.str('wallet-host'),
port: config.num('wallet-port'),
spv: node.chain.options.spv,
verify: node.chain.options.spv
});
if (node.http) {
wdb.http.attach(node.http);
wdb.http.rpc.attach(node.http.rpc);
}
return wdb;
};
/**
* Database layout.
* @type {Object}
@ -119,6 +183,8 @@ WalletDB.prototype._init = function _init() {
*/
WalletDB.prototype._open = co(function* open() {
var wallet;
yield this.db.open();
yield this.db.checkVersion('V', 6);
@ -134,6 +200,17 @@ WalletDB.prototype._open = co(function* open() {
this.depth,
this.state.height,
this.state.startHeight);
wallet = yield this.ensure({
id: 'primary'
});
this.logger.info(
'Loaded wallet with id=%s wid=%d address=%s',
wallet.id, wallet.wid, wallet.getAddress());
this.primary = wallet;
this.http.rpc.wallet = wallet;
});
/**
@ -2185,6 +2262,12 @@ function WalletOptions(options) {
this.startHeight = 0;
this.keepBlocks = this.network.block.keepBlocks;
this.wipeNoReally = false;
this.apiKey = null;
this.walletAuth = false;
this.noAuth = false;
this.ssl = false;
this.host = '127.0.0.1';
this.port = this.network.rpcPort + 2;
if (options)
this.fromOptions(options);
@ -2201,6 +2284,7 @@ WalletOptions.prototype.fromOptions = function fromOptions(options) {
if (options.network != null) {
this.network = Network.get(options.network);
this.keepBlocks = this.network.block.keepBlocks;
this.port = this.network.rpcPort + 2;
}
if (options.logger != null) {
@ -2270,6 +2354,36 @@ WalletOptions.prototype.fromOptions = function fromOptions(options) {
this.wipeNoReally = options.wipeNoReally;
}
if (options.apiKey != null) {
assert(typeof options.apiKey === 'string');
this.apiKey = options.apiKey;
}
if (options.walletAuth != null) {
assert(typeof options.walletAuth === 'boolean');
this.walletAuth = options.walletAuth;
}
if (options.noAuth != null) {
assert(typeof options.noAuth === 'boolean');
this.noAuth = options.noAuth;
}
if (options.ssl != null) {
assert(typeof options.ssl === 'boolean');
this.ssl = options.ssl;
}
if (options.host != null) {
assert(typeof options.host === 'string');
this.host = options.host;
}
if (options.port != null) {
assert(typeof options.port === 'number');
this.port = options.port;
}
return this;
};

View File

@ -13,13 +13,15 @@ var FullNode = require('../lib/node/fullnode');
var pkg = require('../lib/pkg');
describe('HTTP', function() {
var node, wallet, addr, hash;
var node, wallet, walletdb, addr, hash;
node = new FullNode({
network: 'regtest',
apiKey: 'foo',
walletAuth: true,
db: 'memory'
db: 'memory',
loader: require,
plugins: ['walletdb']
});
wallet = new HTTP.Wallet({
@ -27,6 +29,8 @@ describe('HTTP', function() {
apiKey: 'foo'
});
walletdb = node.require('walletdb');
node.on('error', function() {});
this.timeout(15000);
@ -82,7 +86,7 @@ describe('HTTP', function() {
details = d;
});
yield node.walletdb.addTX(tx);
yield walletdb.addTX(tx);
yield co.timeout(300);
assert(receive);

View File

@ -11,14 +11,19 @@ var MTX = require('../lib/primitives/mtx');
// var Client = require('../lib/wallet/client');
describe('Node', function() {
var node = new FullNode({ db: 'memory', apiKey: 'foo', network: 'regtest' });
var node = new FullNode({
db: 'memory',
apiKey: 'foo',
network: 'regtest',
loader: require,
plugins: ['../lib/wallet/walletdb']
});
var chain = node.chain;
var walletdb = node.walletdb;
var walletdb = node.require('walletdb');
var miner = node.miner;
var wallet, tip1, tip2, cb1, cb2, mineBlock;
// walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
walletdb.options.resolution = false;
node.on('error', function() {});