diff --git a/lib/http/base.js b/lib/http/base.js index 772a34ad..5fe9b45c 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -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 */ diff --git a/lib/http/request-browser.js b/lib/http/request-browser.js index 21227270..b4b96fb0 100644 --- a/lib/http/request-browser.js +++ b/lib/http/request-browser.js @@ -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; diff --git a/lib/http/request.js b/lib/http/request.js index 76b35509..52598fbc 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -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(); diff --git a/lib/http/rpc.js b/lib/http/rpc.js index fe2eb4f3..4f3e2fe7 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -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'; + } + }); }; /* diff --git a/lib/http/rpcbase.js b/lib/http/rpcbase.js index 038c1550..98009e9d 100644 --- a/lib/http/rpcbase.js +++ b/lib/http/rpcbase.js @@ -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); }; /** diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 82d854b8..b5dbb07b 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -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; diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index 8c269f35..854bcab8 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -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; diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 39e9be80..4d62da3c 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -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();