http: refactor to make separation easier.

This commit is contained in:
Christopher Jeffrey 2017-10-20 17:09:05 -07:00
parent fc3b31836b
commit fe2a6eb793
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 600 additions and 111 deletions

View File

@ -8,18 +8,12 @@
'use strict';
const assert = require('assert');
const path = require('path');
const EventEmitter = require('events');
const fs = require('fs');
const URL = require('url');
const path = require('path');
const crypto = require('crypto');
const EventEmitter = require('events');
const {StringDecoder} = require('string_decoder');
const AsyncObject = require('../utils/asyncobject');
const util = require('../utils/util');
const co = require('../utils/co');
const Validator = require('../utils/validator');
const {List, ListItem} = require('../utils/list');
const fs = require('../utils/fs');
const digest = require('../crypto/digest');
const ccmp = require('../crypto/ccmp');
/**
* HTTPBase
@ -33,14 +27,14 @@ function HTTPBase(options) {
if (!(this instanceof HTTPBase))
return new HTTPBase(options);
AsyncObject.call(this);
EventEmitter.call(this);
this.config = new HTTPBaseOptions(options);
this.config.load();
this.server = null;
this.io = null;
this.sockets = new List();
this.sockets = new Set();
this.channels = new Map();
this.routes = new Routes();
this.mounts = [];
@ -50,7 +44,7 @@ function HTTPBase(options) {
this._init();
}
Object.setPrototypeOf(HTTPBase.prototype, AsyncObject.prototype);
Object.setPrototypeOf(HTTPBase.prototype, EventEmitter.prototype);
/**
* Initialize server.
@ -97,10 +91,18 @@ HTTPBase.prototype._init = function _init() {
HTTPBase.prototype._initRouter = function _initRouter() {
this.server.on('request', async (hreq, hres) => {
const req = new Request(hreq, hres, hreq.url);
const res = new Response(hreq, hres);
let req = null;
let res = null;
req.on('error', () => {});
try {
req = new Request(hreq, hres, hreq.url);
res = new Response(hreq, hres);
req.on('error', () => {});
} catch (e) {
this.emit('error', e);
return;
}
try {
req.pause();
@ -190,14 +192,14 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
if (user != null) {
assert(typeof user === 'string');
assert(user.length <= 255, 'Username too long.');
assert(util.isAscii(user), 'Username must be ASCII.');
user = digest.hash256(Buffer.from(user, 'ascii'));
assert(isAscii(user), 'Username must be ASCII.');
user = sha256(user, 'ascii');
}
assert(typeof pass === 'string');
assert(pass.length <= 255, 'Password too long.');
assert(util.isAscii(pass), 'Password must be ASCII.');
pass = digest.hash256(Buffer.from(pass, 'ascii'));
assert(isAscii(pass), 'Password must be ASCII.');
pass = sha256(pass, 'ascii');
if (!realm)
realm = 'server';
@ -249,8 +251,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return;
}
const raw = Buffer.from(username, 'ascii');
const hash = digest.hash256(raw);
const hash = sha256(username, 'ascii');
if (!ccmp(hash, user)) {
fail(res);
@ -263,8 +264,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return;
}
const raw = Buffer.from(password, 'ascii');
const hash = digest.hash256(raw);
const hash = sha256(password, 'ascii');
if (!ccmp(hash, pass)) {
fail(res);
@ -442,6 +442,9 @@ HTTPBase.prototype.jsonRPC = function jsonRPC(rpc) {
let json = await rpc.call(req.body, req.query);
if (json == null)
json = null;
json = JSON.stringify(json);
json += '\n';
@ -560,17 +563,15 @@ HTTPBase.prototype._initSockets = function _initSockets() {
*/
HTTPBase.prototype.to = function to(name, ...args) {
const list = this.channels.get(name);
const sockets = this.channels.get(name);
if (!list)
if (!sockets)
return;
assert(list.size > 0);
assert(sockets.size > 0);
for (let item = list.head; item; item = item.next) {
const socket = item.value;
for (const socket of sockets)
socket.emit(...args);
}
};
/**
@ -581,9 +582,7 @@ HTTPBase.prototype.to = function to(name, ...args) {
*/
HTTPBase.prototype.all = function all() {
const list = this.sockets;
for (let socket = list.head; socket; socket = socket.next)
for (const socket of this.sockets)
socket.emit.apply(socket, arguments);
};
@ -612,7 +611,7 @@ HTTPBase.prototype.addSocket = function addSocket(ws) {
this.leaveChannel(socket, name);
});
this.sockets.push(socket);
this.sockets.add(socket);
for (const route of this.mounts)
route.handler.addSocket(ws);
@ -627,10 +626,10 @@ HTTPBase.prototype.addSocket = function addSocket(ws) {
*/
HTTPBase.prototype.removeSocket = function removeSocket(socket) {
for (const key of socket.channels.keys())
for (const key of socket.channels)
this.leaveChannel(socket, key);
assert(this.sockets.remove(socket));
assert(this.sockets.delete(socket));
};
/**
@ -641,22 +640,18 @@ HTTPBase.prototype.removeSocket = function removeSocket(socket) {
*/
HTTPBase.prototype.joinChannel = function joinChannel(socket, name) {
let item = socket.channels.get(name);
if (socket.channels.has(name))
return false;
if (item)
return;
if (!this.channels.has(name))
this.channels.set(name, new Set());
let list = this.channels.get(name);
const sockets = this.channels.get(name);
if (!list) {
list = new List();
this.channels.set(name, list);
}
sockets.add(socket);
socket.channels.add(name);
item = new ListItem(socket);
list.push(item);
socket.channels.set(name, item);
return true;
};
/**
@ -667,20 +662,20 @@ HTTPBase.prototype.joinChannel = function joinChannel(socket, name) {
*/
HTTPBase.prototype.leaveChannel = function leaveChannel(socket, name) {
const item = socket.channels.get(name);
if (!socket.channels.has(name))
return false;
if (!item)
return;
const sockets = this.channels.get(name);
const list = this.channels.get(name);
assert(sockets);
assert(sockets.delete(socket));
assert(list);
assert(list.remove(item));
if (list.size === 0)
if (sockets.size === 0)
this.channels.delete(name);
socket.channels.delete(name);
return true;
};
/**
@ -690,14 +685,14 @@ HTTPBase.prototype.leaveChannel = function leaveChannel(socket, name) {
*/
HTTPBase.prototype.channel = function channel(name) {
const list = this.channels.get(name);
const sockets = this.channels.get(name);
if (!list)
if (!sockets)
return null;
assert(list.size > 0);
assert(sockets.size > 0);
return list;
return sockets;
};
/**
@ -706,7 +701,7 @@ HTTPBase.prototype.channel = function channel(name) {
* @returns {Promise}
*/
HTTPBase.prototype._open = function _open() {
HTTPBase.prototype.open = function open() {
return this.listen(this.config.port, this.config.host);
};
@ -716,7 +711,7 @@ HTTPBase.prototype._open = function _open() {
* @returns {Promise}
*/
HTTPBase.prototype._close = function _close() {
HTTPBase.prototype.close = function close() {
return new Promise((resolve, reject) => {
if (this.io) {
this.server.once('close', resolve);
@ -895,7 +890,7 @@ HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) {
}
if (options.port != null) {
assert(util.isU16(options.port), 'Port must be a number.');
assert((options.port & 0xffff) === options.port, 'Port must be a number.');
this.port = options.port;
}
@ -906,9 +901,8 @@ HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) {
if (options.prefix != null) {
assert(typeof options.prefix === 'string');
this.prefix = options.prefix;
this.keyFile = path.join(this.prefix, 'key.pem');
this.certFile = path.join(this.prefix, 'cert.pem');
this.keyFile = path.join(options.prefix, 'key.pem');
this.certFile = path.join(options.prefix, 'cert.pem');
}
if (options.ssl != null) {
@ -949,6 +943,16 @@ HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate http server options from object.
* @param {Object} options
* @returns {HTTPBaseOptions}
*/
HTTPBaseOptions.fromOptions = function fromOptions(options) {
return new HTTPBaseOptions().fromOptions(options);
};
/**
* Load key and cert file.
* @private
@ -965,16 +969,6 @@ HTTPBaseOptions.prototype.load = function load() {
this.cert = fs.readFileSync(this.certFile);
};
/**
* Instantiate http server options from object.
* @param {Object} options
* @returns {HTTPBaseOptions}
*/
HTTPBaseOptions.fromOptions = function fromOptions(options) {
return new HTTPBaseOptions().fromOptions(options);
};
/**
* Get HTTP server backend.
* @private
@ -1143,6 +1137,9 @@ Route.prototype.hasPrefix = function hasPrefix(pathname) {
if (!this.path)
return true;
if (pathname.startsWith)
return pathname.startsWith(this.path);
return pathname.indexOf(this.path) === 0;
};
@ -1526,11 +1523,9 @@ function WebSocket(socket, ctx) {
this.socket = socket;
this.remoteAddress = socket.conn.remoteAddress;
this.hooks = Object.create(null);
this.channels = new Map();
this.channels = new Set();
this.auth = false;
this.filter = null;
this.prev = null;
this.next = null;
this.init();
}
@ -1618,7 +1613,13 @@ WebSocket.prototype.emit = function emit() {
WebSocket.prototype.call = function call(...args) {
const socket = this.socket;
return new Promise((resolve, reject) => {
args.push(co.wrap(resolve, reject));
args.push((err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
socket.emit(...args);
});
};
@ -1702,8 +1703,8 @@ function getType(type) {
}
}
function parseType(type) {
type = type || '';
function parseType(hdr) {
let type = hdr || '';
type = type.split(';')[0];
type = type.toLowerCase();
type = type.trim();
@ -1731,6 +1732,18 @@ function parseType(type) {
}
}
function isAscii(str) {
return typeof str === 'string' && /^[\t\n\r -~]*$/.test(str);
}
function sha256(data, enc) {
return crypto.createHash('sha256').update(data, enc).digest();
}
function ccmp(a, b) {
return crypto.timingSafeEqual(a, b);
}
/*
* Expose
*/

View File

@ -1,3 +1,458 @@
/*!
* request.js - http request for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
exports.unsupported = true;
const assert = require('assert');
const EventEmitter = require('events');
const URL = require('url');
const qs = require('querystring');
const fetch = global.fetch;
const FetchHeaders = global.Headers;
/*
* Constants
*/
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36';
/**
* Request Options
* @constructor
* @ignore
* @param {Object} options
*/
function RequestOptions(options) {
if (!(this instanceof RequestOptions))
return new RequestOptions(options);
this.uri = 'http://localhost:80/';
this.host = 'localhost';
this.path = '/';
this.port = 80;
this.ssl = false;
this.method = 'GET';
this.strictSSL = true;
this.agent = USER_AGENT;
this.type = null;
this.expect = null;
this.query = null;
this.body = null;
this.auth = null;
this.limit = 10 << 20;
this.timeout = 5000;
this.buffer = false;
if (options)
this.fromOptions(options);
}
RequestOptions.prototype.setURI = function setURI(uri) {
assert(typeof uri === 'string');
if (!/:\/\//.test(uri))
uri = (this.ssl ? 'https://' : 'http://') + uri;
uri = URL.parse(uri);
assert(uri.protocol === 'http:' || uri.protocol === 'https:');
this.uri = uri;
this.ssl = uri.protocol === 'https:';
if (uri.search)
this.query = qs.parse(uri.search);
this.host = uri.hostname;
this.path = uri.pathname;
this.port = uri.port || (this.ssl ? 443 : 80);
if (uri.auth) {
const parts = uri.auth.split(':');
this.auth = {
username: parts[0] || '',
password: parts[1] || ''
};
}
};
RequestOptions.prototype.fromOptions = function fromOptions(options) {
if (typeof options === 'string')
options = { uri: options };
if (options.ssl != null) {
assert(typeof options.ssl === 'boolean');
this.ssl = options.ssl;
}
if (options.uri != null)
this.setURI(options.uri);
if (options.url != null)
this.setURI(options.url);
if (options.method != null) {
assert(typeof options.method === 'string');
this.method = options.method.toUpperCase();
}
if (options.strictSSL != null) {
assert(typeof options.strictSSL === 'boolean');
this.strictSSL = options.strictSSL;
}
if (options.agent != null) {
assert(typeof options.agent === 'string');
this.agent = options.agent;
}
if (options.auth != null) {
assert(typeof options.auth === 'object');
assert(typeof options.auth.username === 'string');
assert(typeof options.auth.password === 'string');
this.auth = options.auth;
}
if (options.query != null) {
if (typeof options.query === 'string') {
this.query = qs.stringify(options.query);
} else {
assert(typeof options.query === 'object');
this.query = options.query;
}
}
if (options.json != null) {
assert(typeof options.json === 'object');
this.body = Buffer.from(JSON.stringify(options.json), 'utf8');
this.type = 'json';
}
if (options.form != null) {
assert(typeof options.form === 'object');
this.body = Buffer.from(qs.stringify(options.form), 'utf8');
this.type = 'form';
}
if (options.type != null) {
assert(typeof options.type === 'string');
assert(getType(options.type));
this.type = options.type;
}
if (options.expect != null) {
assert(typeof options.expect === 'string');
assert(getType(options.expect));
this.expect = options.expect;
}
if (options.body != null) {
if (typeof options.body === 'string') {
this.body = Buffer.from(options.body, 'utf8');
} else {
assert(Buffer.isBuffer(options.body));
this.body = options.body;
}
}
if (options.timeout != null) {
assert(typeof options.timeout === 'number');
this.timeout = options.timeout;
}
if (options.limit != null) {
assert(typeof options.limit === 'number');
this.limit = options.limit;
}
if (options.buffer != null) {
assert(typeof options.buffer === 'boolean');
this.buffer = options.buffer;
}
};
RequestOptions.prototype.isExpected = function isExpected(type) {
if (!this.expect)
return true;
return this.expect === type;
};
RequestOptions.prototype.isOverflow = function isOverflow(hdr) {
if (!hdr)
return false;
if (!this.buffer)
return false;
const length = parseInt(hdr, 10);
if (!isFinite(length))
return true;
return length > this.limit;
};
RequestOptions.prototype.getHeaders = function getHeaders() {
const headers = new FetchHeaders();
headers.append('User-Agent', this.agent);
if (this.type)
headers.append('Content-Type', getType(this.type));
if (this.body)
headers.append('Content-Length', this.body.length.toString(10));
if (this.auth) {
const auth = `${this.auth.username}:${this.auth.password}`;
const data = Buffer.from(auth, 'utf8');
headers.append('Authorization', `Basic ${data.toString('base64')}`);
}
return headers;
};
RequestOptions.prototype.toURL = function toURL() {
let url = '';
if (this.ssl)
url += 'https://';
else
url += 'http://';
url += this.host;
url += ':' + this.port;
url += this.path;
if (this.query)
url += '?' + qs.stringify(this.query);
return url;
};
RequestOptions.prototype.toHTTP = function toHTTP() {
return {
method: this.method,
headers: this.getHeaders(),
body: this.body.buffer,
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
redirect: 'follow',
referrer: 'no-referrer'
};
};
/**
* Response
* @constructor
* @ignore
*/
function Response() {
this.statusCode = 0;
this.headers = Object.create(null);
this.type = 'bin';
this.body = null;
}
Response.fromFetch = function fromFetch(response) {
const res = new Response();
res.statusCode = response.status;
for (const [key, value] of response.headers.entries())
res.headers[key.toLowerCase()] = value;
const contentType = res.headers['content-type'];
res.type = parseType(contentType);
return res;
};
/**
* Make an HTTP request.
* @private
* @param {Object} options
* @returns {Promise}
*/
async function _request(options) {
if (typeof fetch !== 'function')
throw new Error('Fetch API not available.');
const opt = new RequestOptions(options);
const response = await fetch(opt.toURL(), opt.toHTTP());
const res = Response.fromFetch(response);
if (!opt.isExpected(res.type))
throw new Error('Wrong content-type for response.');
const length = res.headers['content-length'];
if (opt.isOverflow(length))
throw new Error('Response exceeded limit.');
if (opt.buffer) {
switch (res.type) {
case 'bin': {
const data = await response.arrayBuffer();
res.body = Buffer.from(data.buffer);
if (opt.limit && res.body.length > opt.limit)
throw new Error('Response exceeded limit.');
break;
}
case 'json': {
res.body = await response.json();
break;
}
case 'form': {
const data = await response.formData();
res.body = Object.create(null);
for (const [key, value] of data.entries())
res.body[key] = value;
break;
}
default: {
res.body = await response.text();
if (opt.limit && res.body.length > opt.limit)
throw new Error('Response exceeded limit.');
break;
}
}
} else {
res.body = await response.arrayBuffer();
}
return res;
}
/**
* Make an HTTP request.
* @alias module:http.request
* @param {Object} options
* @param {String} options.uri
* @param {Object?} options.query
* @param {Object?} options.body
* @param {Object?} options.json
* @param {Object?} options.form
* @param {String?} options.type - One of `"json"`,
* `"form"`, `"text"`, or `"bin"`.
* @param {String?} options.agent - User agent string.
* @param {Object?} [options.strictSSL=true] - Whether to accept bad certs.
* @param {Object?} options.method - HTTP method.
* @param {Object?} options.auth
* @param {String?} options.auth.username
* @param {String?} options.auth.password
* @param {String?} options.expect - Type to expect (see options.type).
* Error will be returned if the response is not of this type.
* @param {Number?} options.limit - Byte limit on response.
* @returns {Promise}
*/
async function request(options) {
if (typeof options === 'string')
options = { uri: options };
options.buffer = true;
return _request(options);
}
request.stream = function stream(options) {
const s = new EventEmitter();
s.write = (data) => {
options.body = data;
return true;
};
s.end = () => {
_request(options).then((res) => {
s.emit('headers', res.headers);
s.emit('type', res.type);
s.emit('response', res);
s.emit('data', res.body);
s.emit('end');
s.emit('close');
}).catch((err) => {
s.emit('error', err);
});
return true;
};
return s;
};
/*
* Helpers
*/
function parseType(hdr) {
let type = hdr || '';
type = type.split(';')[0];
type = type.toLowerCase();
type = type.trim();
switch (type) {
case 'text/x-json':
case 'application/json':
return 'json';
case 'application/x-www-form-urlencoded':
return 'form';
case 'text/html':
case 'application/xhtml+xml':
return 'html';
case 'text/xml':
case 'application/xml':
return 'xml';
case 'text/javascript':
case 'application/javascript':
return 'js';
case 'text/css':
return 'css';
case 'text/plain':
return 'txt';
case 'application/octet-stream':
return 'bin';
default:
return 'bin';
}
}
function getType(type) {
switch (type) {
case 'json':
return 'application/json; charset=utf-8';
case 'form':
return 'application/x-www-form-urlencoded; charset=utf-8';
case 'html':
return 'text/html; charset=utf-8';
case 'xml':
return 'application/xml; charset=utf-8';
case 'js':
return 'application/javascript; charset=utf-8';
case 'css':
return 'text/css; charset=utf-8';
case 'txt':
return 'text/plain; charset=utf-8';
case 'bin':
return 'application/octet-stream';
default:
throw new Error(`Unknown type: ${type}.`);
}
}
/*
* Expose
*/
module.exports = request;

View File

@ -1,4 +1,4 @@
/*
/*!
* request.js - http request for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
@ -6,9 +6,18 @@
'use strict';
const Stream = require('stream').Stream;
const assert = require('assert');
let url, qs, http, https, StringDecoder;
const {Stream} = require('stream');
/*
* Lazily Loaded
*/
let url = null;
let qs = null;
let http = null;
let https = null;
let StringDecoder = null;
/*
* Constants
@ -584,8 +593,8 @@ request.stream = function stream(options) {
* Helpers
*/
function parseType(type) {
type = type || '';
function parseType(hdr) {
let type = hdr || '';
type = type.split(';')[0];
type = type.toLowerCase();
type = type.trim();

View File

@ -33,6 +33,7 @@ const consensus = require('../protocol/consensus');
const Validator = require('../utils/validator');
const RPCBase = require('./rpcbase');
const pkg = require('../pkg');
const Lock = require('../utils/lock');
const RPCError = RPCBase.RPCError;
const errs = RPCBase.errors;
const MAGIC_STRING = RPCBase.MAGIC_STRING;
@ -61,6 +62,7 @@ function RPC(node) {
this.fees = node.fees;
this.miner = node.miner;
this.logger = node.logger.context('rpc');
this.locker = new Lock();
this.mining = false;
this.procLimit = 0;
@ -155,6 +157,28 @@ RPC.prototype.init = function init() {
this.add('getmemoryinfo', this.getMemoryInfo);
this.add('setloglevel', this.setLogLevel);
this.on('error', (err) => {
this.logger.error('RPC internal error.');
this.logger.error(err);
});
this.on('call', (cmd, query) => {
if (cmd.method !== 'getwork'
&& cmd.method !== 'getblocktemplate'
&& cmd.method !== 'getbestblockhash') {
this.logger.debug('Handling RPC call: %s.', cmd.method);
if (cmd.method !== 'submitblock'
&& cmd.method !== 'getmemorypool') {
this.logger.debug(cmd.params);
}
}
if (cmd.method === 'getwork') {
if (query.longpoll)
cmd.method = 'getworklp';
}
});
};
/*

View File

@ -8,8 +8,6 @@
const assert = require('assert');
const EventEmitter = require('events');
const Lock = require('../utils/lock');
const Logger = require('../node/logger');
/**
* JSON RPC
@ -23,10 +21,8 @@ function RPCBase() {
EventEmitter.call(this);
this.logger = Logger.global;
this.calls = Object.create(null);
this.mounts = [];
this.locker = new Lock();
}
Object.setPrototypeOf(RPCBase.prototype, EventEmitter.prototype);
@ -105,6 +101,9 @@ RPCBase.prototype.call = async function call(body, query) {
let out = [];
let array = true;
if (!query)
query = {};
if (!Array.isArray(cmds)) {
cmds = [cmds];
array = false;
@ -165,20 +164,7 @@ RPCBase.prototype.call = async function call(body, query) {
continue;
}
if (cmd.method !== 'getwork'
&& cmd.method !== 'getblocktemplate'
&& cmd.method !== 'getbestblockhash') {
this.logger.debug('Handling RPC call: %s.', cmd.method);
if (cmd.method !== 'submitblock'
&& cmd.method !== 'getmemorypool') {
this.logger.debug(cmd.params);
}
}
if (cmd.method === 'getwork') {
if (query.longpoll)
cmd.method = 'getworklp';
}
this.emit('call', cmd, query);
let result;
try {
@ -201,8 +187,7 @@ RPCBase.prototype.call = async function call(body, query) {
break;
default:
code = RPCBase.errors.INTERNAL_ERROR;
this.logger.error('RPC internal error.');
this.logger.error(err);
this.emit('error', err);
break;
}
@ -254,7 +239,7 @@ RPCBase.prototype.execute = async function execute(json, help) {
`Method not found: ${json.method}.`);
}
return await func.call(this, json.params, help);
return func.call(this, json.params, help);
};
/**

View File

@ -618,12 +618,12 @@ Account.prototype.syncDepth = async function syncDepth(b, receive, change, neste
for (let i = depth; i < receive + this.lookahead; i++) {
const key = this.deriveReceive(i);
await this.saveKey(b, key);
result = key;
}
this.receiveDepth = receive;
derived = true;
result = this.receive;
}
if (change > this.changeDepth) {
@ -649,6 +649,7 @@ Account.prototype.syncDepth = async function syncDepth(b, receive, change, neste
for (let i = depth; i < nested + this.lookahead; i++) {
const key = this.deriveNested(i);
await this.saveKey(b, key);
result = key;
}
this.nestedDepth = nested;

View File

@ -23,6 +23,7 @@ const encoding = require('../utils/encoding');
const RPCBase = require('../http/rpcbase');
const pkg = require('../pkg');
const Validator = require('../utils/validator');
const Lock = require('../utils/lock');
const common = require('./common');
const RPCError = RPCBase.RPCError;
const errs = RPCBase.errors;
@ -47,6 +48,7 @@ function RPC(wdb) {
this.network = wdb.network;
this.logger = wdb.logger.context('rpc');
this.client = wdb.client;
this.locker = new Lock();
this.wallet = null;

View File

@ -645,7 +645,7 @@ Wallet.prototype._createAccount = async function _createAccount(options, passphr
account.name,
account.accountIndex);
this.accountDepth++;
this.accountDepth += 1;
this.save(b);
await b.write();