wallet: get server working.

This commit is contained in:
Christopher Jeffrey 2017-10-26 11:58:50 -07:00
parent d0ed214067
commit 5a23ba96d0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
19 changed files with 469 additions and 326 deletions

55
bin/bwallet Executable file
View File

@ -0,0 +1,55 @@
#!/bin/bash
rl=0
daemon=0
if ! type perl > /dev/null 2>& 1; then
if uname | grep -i 'darwin' > /dev/null; then
echo 'Bcoin requires perl to start on OSX.' >& 2
exit 1
fi
rl=1
fi
if test $rl -eq 1; then
file=$(readlink -f "$0")
else
# Have to do it this way
# because OSX isn't a real OS
file=$(perl -MCwd -e "print Cwd::realpath('$0')")
fi
dir=$(dirname "$file")
if test x"$1" = x'cli'; then
shift
exec "${dir}/cli" "wallet" "$@"
exit 1
fi
for arg in "$@"; do
case "$arg" in
--daemon)
daemon=1
;;
esac
done
if test $daemon -eq 1; then
# And yet again, OSX doesn't support something.
if ! type setsid > /dev/null 2>& 1; then
(
"${dir}/wallet" "$@" > /dev/null 2>& 1 &
echo "$!"
)
exit 0
fi
(
setsid "${dir}/wallet" "$@" > /dev/null 2>& 1 &
echo "$!"
)
exit 0
else
exec "${dir}/wallet" "$@"
exit 1
fi

View File

@ -17,7 +17,11 @@ const ANTIREPLAY = ''
+ '220456c656374726f6e696320436173682053797374656d';
function CLI() {
this.config = new Config('bcoin');
this.config = new Config('bcoin', {
suffix: 'network',
fallback: 'main',
alias: { 'n': 'network' }
});
this.config.load({
argv: true,

View File

@ -2,24 +2,46 @@
'use strict';
process.title = 'bcoin-wallet';
process.title = 'bwallet';
const server = require('../lib/wallet/server');
if (process.argv.indexOf('--help') !== -1
|| process.argv.indexOf('-h') !== -1) {
console.error('See the bcoin wiki at: https://github.com/bcoin-org/bcoin/wiki.');
process.exit(1);
throw new Error('Could not exit.');
}
const wdb = server.create({
if (process.argv.indexOf('--version') !== -1
|| process.argv.indexOf('-v') !== -1) {
const pkg = require('../package.json');
console.log(pkg.version);
process.exit(0);
throw new Error('Could not exit.');
}
const Node = require('../lib/wallet/server');
const node = new Node({
config: true,
argv: true,
env: true,
logFile: true,
logConsole: true,
logLevel: 'debug',
db: 'leveldb'
db: 'leveldb',
workers: true,
listen: true,
loader: require
});
process.on('unhandledRejection', (err, promise) => {
throw err;
});
wdb.open().catch((err) => {
throw err;
(async () => {
await node.ensure();
await node.open();
})().catch((err) => {
console.error(err.stack);
process.exit(1);
});

View File

@ -366,7 +366,7 @@ class HTTPServer extends Server {
socket.join('auth');
this.logger.info('Successful auth from %s.', socket.remoteAddress);
this.logger.info('Successful auth from %s.', socket.host);
this.handleAuth(socket);
return null;
@ -465,7 +465,7 @@ class HTTPServer extends Server {
if (!data)
throw new Error('Bad data chunk.');
this.filter.add(data);
socket.filter.add(data);
if (this.node.spv)
this.pool.watch(data);
@ -655,7 +655,7 @@ class HTTPServer extends Server {
for (const tx of txs)
raw.push(tx.toRaw());
socket.fire('block rescan', block, raw);
return socket.fire('block rescan', block, raw);
});
return null;
}

View File

@ -18,18 +18,21 @@ const HOME = os.homedir ? os.homedir() : '/';
* @alias module:node.Config
* @constructor
* @param {String} module - Module name (e.g. `bcoin`).
* @param {Object?} options
*/
function Config(module) {
function Config(module, options) {
if (!(this instanceof Config))
return new Config(module);
return new Config(module, options);
assert(typeof module === 'string');
assert(module.length > 0);
this.module = module;
this.network = 'main';
this.prefix = Path.join(HOME, `.${module}`);
this.suffix = null;
this.fallback = null;
this.alias = Object.create(null);
this.options = Object.create(null);
this.data = Object.create(null);
@ -39,18 +42,39 @@ function Config(module) {
this.pass = [];
this.query = Object.create(null);
this.hash = Object.create(null);
if (options)
this.init(options);
}
/**
* Option name aliases.
* @const {Object}
* Initialize options.
* @private
* @param {Object} options
*/
Config.alias = {
'seed': 'seeds',
'node': 'nodes',
'n': 'network'
};
Config.prototype.init = function init(options) {
assert(options && typeof options === 'object');
if (options.suffix != null) {
assert(typeof options.suffix === 'string');
this.suffix = options.suffix;
}
if (options.fallback != null) {
assert(typeof options.fallback === 'string');
this.fallback = options.fallback;
}
if (options.alias) {
assert(typeof options.alias === 'object');
for (const key of Object.keys(options.alias)) {
const value = options.alias[key];
assert(typeof value === 'string');
this.alias[key] = value;
}
}
}
/**
* Inject options.
@ -92,7 +116,6 @@ Config.prototype.load = function load(options) {
if (options.argv)
this.parseArg(options.argv);
this.network = this.getNetwork();
this.prefix = this.getPrefix();
};
@ -119,7 +142,6 @@ Config.prototype.open = function open(file) {
this.parseConfig(text);
this.network = this.getNetwork();
this.prefix = this.getPrefix();
};
@ -650,20 +672,19 @@ Config.prototype.mb = function mb(key, fallback) {
};
/**
* Grab network type from config data.
* @private
* Grab suffix from config data.
* @returns {String}
*/
Config.prototype.getNetwork = function getNetwork() {
let network = this.str('network');
Config.prototype.getSuffix = function getSuffix() {
if (!this.suffix)
throw new Error('No suffix presented.');
if (!network)
network = 'main';
const suffix = this.str(this.suffix, this.fallback);
assert(isAlpha(network), 'Bad network.');
assert(isAlpha(suffix), 'Bad suffix.');
return network;
return suffix;
};
/**
@ -678,17 +699,18 @@ Config.prototype.getPrefix = function getPrefix() {
if (prefix) {
if (prefix[0] === '~')
prefix = Path.join(HOME, prefix.substring(1));
return prefix;
} else {
prefix = Path.join(HOME, `.${this.module}`);
}
prefix = Path.join(HOME, `.${this.module}`);
if (this.suffix) {
const suffix = this.str(this.suffix);
const network = this.str('network');
if (network) {
assert(isAlpha(network), 'Bad network.');
if (network !== 'main')
prefix = Path.join(prefix, network);
if (suffix) {
assert(isAlpha(suffix), 'Bad suffix.');
if (this.fallback && suffix !== this.fallback)
prefix = Path.join(prefix, suffix);
}
}
return Path.normalize(prefix);
@ -748,8 +770,6 @@ Config.prototype.parseConfig = function parseConfig(text) {
text = text.replace(/\r/g, '\n');
text = text.replace(/\\\n/g, '');
let colons = true;
let seen = false;
let num = 0;
for (const chunk of text.split('\n')) {
@ -763,29 +783,10 @@ Config.prototype.parseConfig = function parseConfig(text) {
if (line[0] === '#')
continue;
const equal = line.indexOf('=');
const colon = line.indexOf(':');
const index = line.indexOf(':');
let index = -1;
if (colon !== -1 && (colon < equal || equal === -1)) {
if (seen && !colons)
throw new Error(`Expected '=' on line ${num}: "${line}".`);
index = colon;
seen = true;
colons = true;
} else if (equal !== -1) {
if (seen && colons)
throw new Error(`Expected ':' on line ${num}: "${line}".`);
index = equal;
seen = true;
colons = false;
} else {
const symbol = colons ? ':' : '=';
throw new Error(`Expected '${symbol}' on line ${num}: "${line}".`);
}
if (index === -1)
throw new Error(`Expected ':' on line ${num}: "${line}".`);
let key = line.substring(0, index).trim();
@ -799,7 +800,7 @@ Config.prototype.parseConfig = function parseConfig(text) {
if (value.length === 0)
continue;
const alias = Config.alias[key];
const alias = this.alias[key];
if (alias)
key = alias;
@ -874,7 +875,7 @@ Config.prototype.parseArg = function parseArg(argv) {
// Do not allow one-letter aliases.
if (key.length > 1) {
const alias = Config.alias[key];
const alias = this.alias[key];
if (alias)
key = alias;
}
@ -901,7 +902,7 @@ Config.prototype.parseArg = function parseArg(argv) {
throw new Error(`Invalid argument: -${key}.`);
}
const alias = Config.alias[key];
const alias = this.alias[key];
if (alias)
key = alias;
@ -971,7 +972,7 @@ Config.prototype.parseEnv = function parseEnv(env) {
// Do not allow one-letter aliases.
if (key.length > 1) {
const alias = Config.alias[key];
const alias = this.alias[key];
if (alias)
key = alias;
}
@ -1063,7 +1064,7 @@ Config.prototype.parseForm = function parseForm(query, map) {
if (value.length === 0)
continue;
const alias = Config.alias[key];
const alias = this.alias[key];
if (alias)
key = alias;
@ -1098,7 +1099,7 @@ function unescape(str) {
}
function isAlpha(str) {
return /^[a-z0-9]+$/.test(str);
return /^[a-z0-9_\-]+$/i.test(str);
}
function isKey(key) {

View File

@ -41,7 +41,7 @@ function FullNode(options) {
if (!(this instanceof FullNode))
return new FullNode(options);
Node.call(this, options);
Node.call(this, 'bcoin', 'bcoin.conf', 'debug.log', options);
// SPV flag.
this.spv = false;

View File

@ -26,20 +26,25 @@ const Config = require('./config');
* @param {Object} options
*/
function Node(options) {
function Node(module, config, file, options) {
if (!(this instanceof Node))
return new Node(options);
return new Node(module, config, file, options);
AsyncObject.call(this);
this.config = new Config('bcoin');
this.config = new Config(module, {
suffix: 'network',
fallback: 'main',
alias: { 'n': 'network' }
});
this.config.inject(options);
this.config.load(options);
if (options.config)
this.config.open('bcoin.conf');
this.config.open(config);
this.network = Network.get(this.config.network);
this.network = Network.get(this.config.getSuffix());
this.startTime = -1;
this.bound = [];
this.plugins = Object.create(null);
@ -56,7 +61,7 @@ function Node(options) {
this.miner = null;
this.http = null;
this.init();
this.init(file);
}
Object.setPrototypeOf(Node.prototype, AsyncObject.prototype);
@ -67,16 +72,17 @@ Object.setPrototypeOf(Node.prototype, AsyncObject.prototype);
* @param {Object} options
*/
Node.prototype.initOptions = function initOptions() {
let logger = new Logger();
Node.prototype.initOptions = function initOptions(file) {
const config = this.config;
let logger = new Logger();
if (config.has('logger'))
logger = config.obj('logger');
logger.set({
filename: config.bool('log-file')
? config.location('debug.log')
? config.location(file)
: null,
level: config.str('log-level'),
console: config.bool('log-console'),
@ -99,8 +105,8 @@ Node.prototype.initOptions = function initOptions() {
* @param {Object} options
*/
Node.prototype.init = function init() {
this.initOptions();
Node.prototype.init = function init(file) {
this.initOptions(file);
this.on('error', () => {});

View File

@ -38,7 +38,7 @@ function SPVNode(options) {
if (!(this instanceof SPVNode))
return new SPVNode(options);
Node.call(this, options);
Node.call(this, 'bcoin', 'bcoin.conf', 'debug.log', options);
// SPV flag.
this.spv = true;
@ -80,21 +80,19 @@ function SPVNode(options) {
this.rpc = new RPC(this);
if (!HTTPServer.unsupported) {
this.http = new HTTPServer({
network: this.network,
logger: this.logger,
node: this,
prefix: this.config.prefix,
ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'),
port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth')
});
}
this.http = new HTTPServer({
network: this.network,
logger: this.logger,
node: this,
prefix: this.config.prefix,
ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'),
port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth')
});
this.rescanJob = null;
this.scanLock = new Lock();

View File

@ -48,6 +48,7 @@ function Network(options) {
this.addressPrefix = options.addressPrefix;
this.requireStandard = options.requireStandard;
this.rpcPort = options.rpcPort;
this.walletPort = options.walletPort;
this.minRelay = options.minRelay;
this.feeRate = options.feeRate;
this.maxFeeRate = options.maxFeeRate;

View File

@ -436,6 +436,14 @@ main.requireStandard = true;
main.rpcPort = 8332;
/**
* Default wallet port.
* @const {Number}
* @default
*/
main.walletPort = 8334;
/**
* Default min relay rate.
* @const {Rate}
@ -646,6 +654,8 @@ testnet.requireStandard = false;
testnet.rpcPort = 18332;
testnet.walletPort = 18334;
testnet.minRelay = 1000;
testnet.feeRate = 20000;
@ -807,6 +817,8 @@ regtest.requireStandard = false;
regtest.rpcPort = 48332;
regtest.walletPort = 48334;
regtest.minRelay = 1000;
regtest.feeRate = 20000;
@ -970,6 +982,8 @@ simnet.requireStandard = false;
simnet.rpcPort = 18556;
simnet.walletPort = 18558;
simnet.minRelay = 1000;
simnet.feeRate = 20000;

View File

@ -10,7 +10,7 @@
const assert = require('assert');
const {NodeClient} = require('bclient');
const TX = require('../primitives/tx');
const digest = require('bcrypto/lib/digest');
const hash256 = require('bcrypto/lib/hash256');
const util = require('../utils/util');
class WalletClient extends NodeClient {
@ -77,7 +77,7 @@ function parseEntry(data) {
assert(Buffer.isBuffer(data));
assert(data.length >= 84);
const h = digest.hash256(data.slice(0, 80));
const h = hash256.digest(data.slice(0, 80));
return {
hash: h.toString('hex'),
@ -90,10 +90,8 @@ function parseBlock(entry, txs) {
const block = parseEntry(entry);
const out = [];
for (const raw of txs) {
const tx = TX.fromRaw(raw);
out.push(tx);
}
for (const tx of txs)
out.push(TX.fromRaw(tx));
return [block, out];
}

View File

@ -41,8 +41,9 @@ class HTTPServer extends Server {
this.network = this.options.network;
this.logger = this.options.logger.context('http');
this.wdb = this.options.walletdb;
this.rpc = this.wdb.rpc;
this.wdb = this.options.node.wdb;
this.rpc = this.options.node.rpc;
this.plugin = this.options.node.plugin;
this.init();
}
@ -90,7 +91,11 @@ class HTTPServer extends Server {
}));
this.use(this.jsonRPC());
this.use(this.router());
if (!this.plugin)
this.use('/wallet', this.router());
else
this.use(this.router());
this.error((err, req, res) => {
const code = err.statusCode || 500;
@ -118,6 +123,11 @@ class HTTPServer extends Server {
const id = valid.str('id');
const token = valid.buf('token');
if (!id) {
res.json(403);
return;
}
if (!this.options.walletAuth) {
const wallet = await this.wdb.get(id);
@ -893,7 +903,7 @@ class HTTPServer extends Server {
socket.join('wallet auth');
this.logger.info('Successful auth from %s.', socket.remoteAddress);
this.logger.info('Successful auth from %s.', socket.host);
this.handleAuth(socket);
@ -967,7 +977,7 @@ class HTTPOptions {
constructor(options) {
this.network = Network.primary;
this.logger = null;
this.walletdb = null;
this.node = null;
this.apiKey = base58.encode(random.randomBytes(20));
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
this.serviceHash = this.apiHash;
@ -993,13 +1003,13 @@ class HTTPOptions {
fromOptions(options) {
assert(options);
assert(options.walletdb && typeof options.walletdb === 'object',
assert(options.node && typeof options.node === 'object',
'HTTP Server requires a WalletDB.');
this.walletdb = options.walletdb;
this.network = options.walletdb.network;
this.logger = options.walletdb.logger;
this.port = this.network.rpcPort + 2;
this.node = options.node;
this.network = options.node.network;
this.logger = options.node.logger;
this.port = this.network.walletPort;
if (options.logger != null) {
assert(typeof options.logger === 'object');

View File

@ -73,6 +73,7 @@ NodeClient.prototype._init = function _init() {
NodeClient.prototype._open = function _open(options) {
this.listen = true;
this.emit('open');
return Promise.resolve();
};
@ -83,6 +84,7 @@ NodeClient.prototype._open = function _open(options) {
NodeClient.prototype._close = function _close() {
this.listen = false;
this.emit('close');
return Promise.resolve();
};

View File

@ -6,8 +6,11 @@
'use strict';
const EventEmitter = require('events');
const WalletDB = require('./walletdb');
const NodeClient = require('./nodeclient');
const HTTPServer = require('./http');
const RPC = require('./rpc');
/**
* @exports wallet/plugin
@ -15,6 +18,74 @@ const NodeClient = require('./nodeclient');
const plugin = exports;
/**
* Plugin
* @constructor
* @param {Node} node
*/
function Plugin(node) {
if (!(this instanceof Plugin))
return new Plugin(node);
const config = node.config;
this.network = node.network;
this.logger = node.logger;
this.client = new NodeClient(node);
this.plugin = true;
this.wdb = new WalletDB({
network: node.network,
logger: node.logger,
workers: node.workers,
client: this.client,
prefix: config.prefix,
db: config.str(['wallet-db', 'db']),
maxFiles: config.uint('wallet-max-files'),
cacheSize: config.mb('wallet-cache-size'),
witness: config.bool('wallet-witness'),
checkpoints: config.bool('wallet-checkpoints'),
startHeight: config.uint('wallet-start-height'),
wipeNoReally: config.bool('wallet-wipe-no-really'),
spv: node.spv
});
this.rpc = new RPC(this);
this.http = new HTTPServer({
network: node.network,
logger: node.logger,
node: this,
apiKey: config.str(['wallet-api-key', 'api-key']),
walletAuth: config.bool('wallet-auth'),
noAuth: config.bool(['wallet-no-auth', 'no-auth'])
});
this.http.attach('/wallet', node.http);
this.init();
}
Object.setPrototypeOf(Plugin.prototype, EventEmitter.prototype);
Plugin.prototype.init = function init() {
this.client.on('error', err => this.emit('error', err));
this.wdb.on('error', err => this.emit('error', err));
this.http.on('error', err => this.emit('error', err));
};
Plugin.prototype.open = async function open() {
await this.wdb.open();
this.rpc.wallet = this.wdb.primary;
};
Plugin.prototype.close = async function close() {
this.rpc.wallet = this.wdb.primary;
await this.wdb.open();
};
/**
* Plugin name.
* @const {String}
@ -29,35 +100,5 @@ plugin.id = 'walletdb';
*/
plugin.init = function init(node) {
const config = node.config;
const client = new NodeClient(node);
const wdb = new WalletDB({
network: node.network,
logger: node.logger,
workers: node.workers,
client: client,
prefix: config.prefix,
db: config.str(['wallet-db', 'db']),
maxFiles: config.uint('wallet-max-files'),
cacheSize: config.mb('wallet-cache-size'),
witness: config.bool('wallet-witness'),
checkpoints: config.bool('wallet-checkpoints'),
startHeight: config.uint('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(['wallet-no-auth', 'no-auth']),
ssl: config.str('wallet-ssl'),
host: config.str('wallet-host'),
port: config.uint('wallet-port'),
spv: node.spv,
verify: node.spv,
listen: true,
plugin: true
});
wdb.http.attach('/wallet', node.http);
return wdb;
return new Plugin(node);
};

View File

@ -81,15 +81,15 @@ class RPC extends RPCBase {
* @param {WalletDB} wdb
*/
constructor(wdb) {
constructor(node) {
super();
assert(wdb, 'RPC requires a WalletDB.');
assert(node, 'RPC requires a WalletDB.');
this.wdb = wdb;
this.network = wdb.network;
this.logger = wdb.logger.context('rpc');
this.client = wdb.client;
this.wdb = node.wdb;
this.network = node.network;
this.logger = node.logger.context('rpc');
this.client = node.client;
this.locker = new Lock();
this.wallet = null;

View File

@ -6,104 +6,128 @@
'use strict';
const Node = require('../node/node');
const WalletDB = require('./walletdb');
const WorkerPool = require('../workers/workerpool');
const Config = require('../node/config');
const Logger = require('../node/logger');
const HTTPServer = require('./http');
const Client = require('./client');
const RPC = require('./rpc');
/**
* @exports wallet/server
* Wallet Node
* @extends Node
* @constructor
*/
const server = exports;
function WalletNode(options) {
if (!(this instanceof WalletNode))
return new WalletNode(options);
Node.call(this, 'bcoin', 'wallet.conf', 'wallet.log', options);
this.client = new Client({
network: this.network,
url: this.config.str('node-url'),
host: this.config.str('node-host'),
port: this.config.str('node-port', this.network.rpcPort),
ssl: this.config.str('node-ssl'),
apiKey: this.config.str('node-api-key')
});
this.wdb = new WalletDB({
network: this.network,
logger: this.logger,
workers: this.workers,
client: this.client,
prefix: this.config.prefix,
db: this.config.str('db'),
maxFiles: this.config.uint('max-files'),
cacheSize: this.config.mb('cache-size'),
witness: this.config.bool('witness'),
checkpoints: this.config.bool('checkpoints'),
startHeight: this.config.uint('start-height'),
wipeNoReally: this.config.bool('wipe-no-really'),
apiKey: this.config.str('api-key'),
walletAuth: this.config.bool('auth'),
noAuth: this.config.bool('no-auth'),
ssl: this.config.str('ssl'),
host: this.config.str('host'),
port: this.config.uint('port'),
spv: this.config.bool('spv')
});
this.rpc = new RPC(this);
this.http = new HTTPServer({
network: this.network,
logger: this.logger,
node: this,
prefix: this.config.prefix,
ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'),
port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth'),
walletAuth: this.config.bool('wallet-auth')
});
this._init();
}
Object.setPrototypeOf(WalletNode.prototype, Node.prototype);
/**
* Create a wallet server.
* @param {Object} options
* @returns {WalletDB}
* Initialize the node.
* @private
*/
server.create = function create(options) {
const config = new Config('bcoin');
let logger = new Logger('debug');
WalletNode.prototype._init = function _init() {
this.client.on('error', err => this.error(err));
this.wdb.on('error', err => this.error(err));
this.http.on('error', err => this.error(err));
config.inject(options);
config.load(options);
if (options.config)
config.open('wallet.conf');
if (config.has('logger'))
logger = config.obj('logger');
const client = new Client({
network: config.network,
uri: config.str('node-uri'),
apiKey: config.str('node-api-key')
});
logger.set({
filename: config.bool('log-file')
? config.location('wallet.log')
: null,
level: config.str('log-level'),
console: config.bool('log-console'),
shrink: config.bool('log-shrink')
});
const workers = new WorkerPool({
enabled: config.str('workers-enabled'),
size: config.uint('workers-size'),
timeout: config.uint('workers-timeout')
});
const wdb = new WalletDB({
network: config.network,
logger: logger,
workers: workers,
client: client,
prefix: config.prefix,
db: config.str('db'),
maxFiles: config.uint('max-files'),
cacheSize: config.mb('cache-size'),
witness: config.bool('witness'),
checkpoints: config.bool('checkpoints'),
startHeight: config.uint('start-height'),
wipeNoReally: config.bool('wipe-no-really'),
apiKey: config.str('api-key'),
walletAuth: config.bool('auth'),
noAuth: config.bool('no-auth'),
ssl: config.str('ssl'),
host: config.str('host'),
port: config.uint('port'),
spv: config.bool('spv'),
verify: config.bool('spv'),
listen: true
});
wdb.on('error', () => {});
workers.on('spawn', (child) => {
logger.info('Spawning worker process: %d.', child.id);
});
workers.on('exit', (code, child) => {
logger.warning('Worker %d exited: %s.', child.id, code);
});
workers.on('log', (text, child) => {
logger.debug('Worker %d says:', child.id);
logger.debug(text);
});
workers.on('error', (err, child) => {
if (child) {
logger.error('Worker %d error: %s', child.id, err.message);
return;
}
wdb.emit('error', err);
});
return wdb;
this.loadPlugins();
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias WalletNode#open
* @returns {Promise}
*/
WalletNode.prototype._open = async function _open(callback) {
// await this.client.open();
await this.wdb.open();
this.rpc.wallet = this.wdb.primary;
await this.openPlugins();
await this.http.open();
this.logger.info('Node is loaded.');
};
/**
* Close the node, wait for the database to close.
* @alias WalletNode#close
* @returns {Promise}
*/
WalletNode.prototype._close = async function _close() {
await this.http.close();
await this.closePlugins();
this.rpc.wallet = null;
await this.wdb.close();
// await this.client.close();
};
/*
* Expose
*/
module.exports = WalletNode;

View File

@ -27,8 +27,6 @@ const Logger = require('../node/logger');
const Outpoint = require('../primitives/outpoint');
const layouts = require('./layout');
const records = require('./records');
const HTTPServer = require('./http');
const RPC = require('./rpc');
const StaticWriter = require('../utils/staticwriter');
const layout = layouts.walletdb;
const ChainState = records.ChainState;
@ -55,27 +53,11 @@ function WalletDB(options) {
this.network = this.options.network;
this.logger = this.options.logger.context('wallet');
this.workers = this.options.workers;
this.client = this.options.client;
this.feeRate = this.options.feeRate;
this.db = LDB(this.options);
this.rpc = new RPC(this);
this.primary = null;
this.http = new HTTPServer({
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.height = 0;
this.wallets = new Map();
@ -86,6 +68,7 @@ function WalletDB(options) {
this.readLock = new MappedLock();
this.writeLock = new Lock();
this.txLock = new Lock();
this.scanLock = new Lock();
this.filter = new Bloom();
@ -130,9 +113,6 @@ WalletDB.prototype._init = function _init() {
*/
WalletDB.prototype._open = async function _open() {
if (!this.options.plugin)
await this.logger.open();
await this.db.open();
await this.db.checkVersion('V', 7);
@ -158,10 +138,6 @@ WalletDB.prototype._open = async function _open() {
wallet.id, wallet.wid, await wallet.receiveAddress());
this.primary = wallet;
this.rpc.wallet = wallet;
if (this.options.listen)
await this.http.open();
};
/**
@ -173,18 +149,12 @@ WalletDB.prototype._open = async function _open() {
WalletDB.prototype._close = async function _close() {
await this.disconnect();
if (this.options.listen)
await this.http.close();
for (const wallet of this.wallets.values()) {
await wallet.destroy();
this.unregister(wallet);
}
await this.db.close();
if (!this.options.plugin)
await this.logger.close();
};
/**
@ -195,9 +165,23 @@ WalletDB.prototype._close = async function _close() {
WalletDB.prototype.load = async function load() {
const unlock = await this.txLock.lock();
try {
await this.watch();
await this.connect();
await this.init();
await this.watch();
} finally {
unlock();
}
};
/**
* Initialize state with server on every connect.
* @returns {Promise}
*/
WalletDB.prototype.resync = async function resync() {
const unlock = await this.txLock.lock();
try {
await this.setFilter();
await this.sync();
await this.resend();
} finally {
@ -223,6 +207,14 @@ WalletDB.prototype.bind = function bind() {
this.emit('error', err);
});
this.client.on('open', async () => {
try {
await this.resync();
} catch (e) {
this.emit('error', e);
}
});
this.client.on('block connect', async (entry, txs) => {
try {
await this.addBlock(entry, txs);
@ -239,7 +231,7 @@ WalletDB.prototype.bind = function bind() {
}
});
this.client.hook('block rescan', async (entry, txs) => {
this.client.on('block rescan', async (entry, txs) => {
try {
await this.rescanBlock(entry, txs);
} catch (e) {
@ -276,7 +268,6 @@ WalletDB.prototype.connect = async function connect() {
this.bind();
await this.client.open();
await this.setFilter();
};
/**
@ -381,8 +372,6 @@ WalletDB.prototype.watch = async function watch() {
});
this.logger.info('Added %d outpoints to WalletDB filter.', outpoints);
await this.setFilter();
};
/**
@ -1787,6 +1776,8 @@ WalletDB.prototype._addBlock = async function _addBlock(entry, txs) {
return 0;
}
this.logger.debug('Adding block: %d.', entry.height);
if (tip.height === this.state.height) {
// We let blocks of the same height
// through specifically for rescans:
@ -1796,7 +1787,9 @@ WalletDB.prototype._addBlock = async function _addBlock(entry, txs) {
// processed (in the case of a crash).
this.logger.warning('Already saw WalletDB block (%d).', tip.height);
} else if (tip.height !== this.state.height + 1) {
throw new Error('WDB: Bad connection (height mismatch).');
// throw new Error('WDB: Bad connection (height mismatch).');
await this.scan(this.state.height);
return;
}
// Sync the state to the new tip.
@ -1898,11 +1891,33 @@ WalletDB.prototype._removeBlock = async function _removeBlock(entry) {
*/
WalletDB.prototype.rescanBlock = async function rescanBlock(entry, txs) {
const unlock = await this.scanLock.lock();
try {
return await this._rescanBlock(entry, txs);
} finally {
unlock();
}
};
/**
* Rescan a block.
* @private
* @param {ChainEntry} entry
* @param {TX[]} txs
* @returns {Promise}
*/
WalletDB.prototype._rescanBlock = async function _rescanBlock(entry, txs) {
if (!this.rescanning) {
this.logger.warning('Unsolicited rescan block: %s.', entry.height);
return;
}
if (entry.height > this.state.height + 1) {
this.logger.warning('Unsolicited rescan block: %s.', entry.height);
return;
}
try {
await this._addBlock(entry, txs);
} catch (e) {
@ -2034,14 +2049,6 @@ function WalletOptions(options) {
this.checkpoints = false;
this.startHeight = 0;
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;
this.plugin = false;
this.listen = false;
if (options)
this.fromOptions(options);
@ -2137,46 +2144,6 @@ 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;
}
if (options.plugin != null) {
assert(typeof options.plugin === 'boolean');
this.plugin = options.plugin;
}
if (options.listen != null) {
assert(typeof options.listen === 'boolean');
this.listen = options.listen;
}
return this;
};

View File

@ -37,7 +37,7 @@ const wallet = new WalletClient({
apiKey: 'foo'
});
const wdb = node.require('walletdb');
const {wdb} = node.require('walletdb');
let addr = null;
let hash = null;

View File

@ -24,7 +24,7 @@ const node = new FullNode({
const chain = node.chain;
const miner = node.miner;
const wdb = node.require('walletdb');
const {wdb} = node.require('walletdb');
let wallet = null;
let tip1 = null;