walletdb: add experimental client.
This commit is contained in:
parent
d75b5d80cc
commit
6c61ad976d
@ -1329,7 +1329,12 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
if (!socket.api)
|
||||
return callback({ error: 'Not authorized.' });
|
||||
|
||||
socket.watchChain();
|
||||
try {
|
||||
socket.watchChain();
|
||||
} catch (e) {
|
||||
return callback({ error: e.message });
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
@ -1337,7 +1342,12 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
if (!socket.api)
|
||||
return callback({ error: 'Not authorized.' });
|
||||
|
||||
socket.unwatchChain();
|
||||
try {
|
||||
socket.unwatchChain();
|
||||
} catch (e) {
|
||||
return callback({ error: e.message });
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
@ -1345,7 +1355,7 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
var data = args[0];
|
||||
var filter;
|
||||
|
||||
if (!util.isHex(data))
|
||||
if (!util.isHex(data) && !Buffer.isBuffer(data))
|
||||
return callback({ error: 'Invalid parameter.' });
|
||||
|
||||
if (!socket.api)
|
||||
@ -1446,7 +1456,7 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
var data = args[0];
|
||||
var tx;
|
||||
|
||||
if (!util.isHex(data))
|
||||
if (!util.isHex(data) && !Buffer.isBuffer(data))
|
||||
return callback({ error: 'Invalid parameter.' });
|
||||
|
||||
try {
|
||||
@ -1615,6 +1625,7 @@ function ClientSocket(server, socket) {
|
||||
this.filter = null;
|
||||
this.api = false;
|
||||
this.raw = false;
|
||||
this.watching = false;
|
||||
|
||||
this.network = this.server.network;
|
||||
this.node = this.server.node;
|
||||
@ -1682,7 +1693,7 @@ ClientSocket.prototype.addFilter = function addFilter(chunks) {
|
||||
for (i = 0; i < chunks.length; i++) {
|
||||
data = chunks[i];
|
||||
|
||||
if (!util.isHex(data))
|
||||
if (!util.isHex(data) && !Buffer.isBuffer(data))
|
||||
throw new Error('Not a hex string.');
|
||||
|
||||
this.filter.add(data, 'hex');
|
||||
@ -1731,6 +1742,11 @@ ClientSocket.prototype.watchChain = function watchChain() {
|
||||
var self = this;
|
||||
var pool = this.mempool || this.pool;
|
||||
|
||||
if (this.watching)
|
||||
throw new Error('Already watching chain.');
|
||||
|
||||
this.watching = true;
|
||||
|
||||
this.bind(this.chain, 'connect', function(entry, block) {
|
||||
self.connectBlock(entry, block);
|
||||
});
|
||||
@ -1755,6 +1771,12 @@ ClientSocket.prototype.onError = function onError(err) {
|
||||
|
||||
ClientSocket.prototype.unwatchChain = function unwatchChain() {
|
||||
var pool = this.mempool || this.pool;
|
||||
|
||||
if (!this.watching)
|
||||
throw new Error('Not watching chain.');
|
||||
|
||||
this.watching = false;
|
||||
|
||||
this.unbind(this.chain, 'connect');
|
||||
this.unbind(this.chain, 'disconnect');
|
||||
this.unbind(this.chain, 'reset');
|
||||
@ -1875,13 +1897,13 @@ ClientSocket.prototype.scanner = function scanner(entry, txs) {
|
||||
|
||||
ClientSocket.prototype.frameEntry = function frameEntry(entry) {
|
||||
if (this.raw)
|
||||
return entry.toRaw().toString('hex');
|
||||
return entry.toRaw();
|
||||
return entry.toJSON();
|
||||
};
|
||||
|
||||
ClientSocket.prototype.frameTX = function frameTX(tx) {
|
||||
if (this.raw)
|
||||
return tx.toRaw().toString('hex');
|
||||
return tx.toRaw();
|
||||
return tx.toJSON(this.network);
|
||||
};
|
||||
|
||||
|
||||
@ -237,6 +237,10 @@ FullNode.prototype._open = co(function* open() {
|
||||
yield this.mempool.open();
|
||||
yield this.miner.open();
|
||||
yield this.pool.open();
|
||||
|
||||
if (this.http)
|
||||
yield this.http.open();
|
||||
|
||||
yield this.walletdb.open();
|
||||
|
||||
// Ensure primary wallet.
|
||||
@ -245,9 +249,6 @@ FullNode.prototype._open = co(function* open() {
|
||||
if (this.options.listen)
|
||||
yield this.pool.listen();
|
||||
|
||||
if (this.http)
|
||||
yield this.http.open();
|
||||
|
||||
this.logger.info('Node is loaded.');
|
||||
});
|
||||
|
||||
|
||||
432
lib/wallet/client.js
Normal file
432
lib/wallet/client.js
Normal file
@ -0,0 +1,432 @@
|
||||
/*!
|
||||
* client.js - http client for wallets
|
||||
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
||||
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||
* https://github.com/bcoin-org/bcoin
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var Network = require('../protocol/network');
|
||||
var AsyncObject = require('../utils/async');
|
||||
var TX = require('../primitives/tx');
|
||||
var BlockMeta = require('./records').BlockMeta;
|
||||
var Headers = require('../primitives/headers');
|
||||
var Amount = require('../btc/amount');
|
||||
var util = require('../utils/util');
|
||||
var BufferReader = require('../utils/reader');
|
||||
var co = require('../utils/co');
|
||||
var IOClient = require('socket.io-client');
|
||||
|
||||
/**
|
||||
* BCoin HTTP client.
|
||||
* @exports WalletClient
|
||||
* @constructor
|
||||
* @param {String} uri
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function WalletClient(options) {
|
||||
if (!(this instanceof WalletClient))
|
||||
return new WalletClient(options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
if (typeof options === 'string')
|
||||
options = { uri: options };
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
this.options = options;
|
||||
this.network = Network.get(options.network);
|
||||
|
||||
this.uri = options.uri || 'http://localhost:' + this.network.rpcPort;
|
||||
this.apiKey = options.apiKey;
|
||||
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
util.inherits(WalletClient, AsyncObject);
|
||||
|
||||
/**
|
||||
* Open the client, wait for socket to connect.
|
||||
* @alias WalletClient#open
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype._open = co(function* _open() {
|
||||
var self = this;
|
||||
|
||||
this.socket = new IOClient(this.uri, {
|
||||
transports: ['websocket'],
|
||||
forceNew: true
|
||||
});
|
||||
|
||||
this.socket.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.socket.on('version', function(info) {
|
||||
if (info.network !== self.network.type)
|
||||
self.emit('error', new Error('Wrong network.'));
|
||||
});
|
||||
|
||||
this.socket.on('block connect', function(entry, txs) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
data = parseBlock(entry, txs);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('block connect', data.entry, data.txs);
|
||||
});
|
||||
|
||||
this.socket.on('block disconnect', function(entry) {
|
||||
var block;
|
||||
|
||||
try {
|
||||
block = parseEntry(entry);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('block disconnect', block);
|
||||
});
|
||||
|
||||
this.socket.on('block rescan', function(entry, txs, cb) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
data = parseBlock(entry, txs);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
return cb();
|
||||
}
|
||||
|
||||
self.emit('block rescan', data.entry, data.txs, cb);
|
||||
});
|
||||
|
||||
this.socket.on('chain reset', function(tip) {
|
||||
var block;
|
||||
|
||||
try {
|
||||
block = parseEntry(tip);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('chain reset', block);
|
||||
});
|
||||
|
||||
this.socket.on('tx', function(tx) {
|
||||
tx = parseTX(tx);
|
||||
self.emit('tx', tx);
|
||||
});
|
||||
|
||||
yield this.onConnect();
|
||||
yield this.sendAuth();
|
||||
yield this.sendOptions({ raw: true });
|
||||
yield this.watchChain();
|
||||
});
|
||||
|
||||
/**
|
||||
* Close the client, wait for the socket to close.
|
||||
* @alias WalletClient#close
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype._close = function close() {
|
||||
if (!this.socket)
|
||||
return Promise.resolve();
|
||||
|
||||
this.socket.disconnect();
|
||||
this.socket = null;
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for websocket connection.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.onConnect = function onConnect() {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.once('connect', resolve);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for websocket auth.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.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));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for websocket options.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.sendOptions = function sendOptions(options) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('options', options, function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for websocket options.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.watchChain = function watchChain() {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('watch chain', function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chain tip.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.getTip = function getTip() {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('get tip', function(err, tip) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve(parseEntry(tip));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chain entry.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.getEntry = function getEntry(block) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (typeof block === 'string')
|
||||
block = util.revHex(block);
|
||||
|
||||
self.socket.emit('get entry', block, function(err, entry) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
|
||||
if (!entry)
|
||||
return resolve(null);
|
||||
|
||||
resolve(parseEntry(entry));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a transaction. Do not wait for promise.
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.send = function send(tx) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var raw = tx.toRaw();
|
||||
self.socket.emit('send', raw, function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set bloom filter.
|
||||
* @param {Bloom} filter
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.setFilter = function setFilter(filter) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var raw = filter.toRaw();
|
||||
self.socket.emit('set filter', raw, function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add data to filter.
|
||||
* @param {Buffer} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.addFilter = function addFilter(chunks) {
|
||||
var self = this;
|
||||
var out = [];
|
||||
var i;
|
||||
|
||||
if (!Array.isArray(chunks))
|
||||
chunks = [chunks];
|
||||
|
||||
for (i = 0; i < chunks.length; i++)
|
||||
out.push(chunks[i]);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('add filter', out, function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset filter.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.resetFilter = function resetFilter() {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('reset filter', function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Esimate smart fee.
|
||||
* @param {Number?} blocks
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.estimateFee = function estimateFee(blocks) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
self.socket.emit('estimate fee', blocks, function(err, rate) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve(Amount.value(rate));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Rescan for any missed transactions.
|
||||
* @param {Number|Hash} start - Start block.
|
||||
* @param {Bloom} filter
|
||||
* @param {Function} iter - Iterator.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletClient.prototype.rescan = function rescan(start) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (typeof start === 'string')
|
||||
start = util.revHex(start);
|
||||
|
||||
self.socket.emit('rescan', start, function(err) {
|
||||
if (err)
|
||||
return reject(new Error(err.error));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function parseEntry(data, enc) {
|
||||
var p, block, hash;
|
||||
|
||||
if (typeof data === 'string')
|
||||
data = new Buffer(data, 'hex');
|
||||
|
||||
p = new BufferReader(data);
|
||||
|
||||
block = Headers.fromAbbr(p);
|
||||
block.height = p.readU32();
|
||||
|
||||
hash = block.hash('hex');
|
||||
|
||||
return new BlockMeta(hash, block.height, block.ts);
|
||||
}
|
||||
|
||||
function parseBlock(entry, txs) {
|
||||
var block = parseEntry(entry);
|
||||
var out = [];
|
||||
var i, tx;
|
||||
|
||||
for (i = 0; i < txs.length; i++) {
|
||||
tx = txs[i];
|
||||
tx = parseTX(tx);
|
||||
tx.block = block.hash;
|
||||
tx.height = block.height;
|
||||
tx.ts = block.ts;
|
||||
tx.index = -1;
|
||||
out.push(tx);
|
||||
}
|
||||
|
||||
return new BlockResult(block, out);
|
||||
}
|
||||
|
||||
function parseTX(data) {
|
||||
return TX.fromRaw(data, 'hex');
|
||||
}
|
||||
|
||||
function toHex(data) {
|
||||
if (typeof data !== 'string')
|
||||
return data.toString('hex');
|
||||
return data;
|
||||
}
|
||||
|
||||
function BlockResult(entry, txs) {
|
||||
this.entry = entry;
|
||||
this.txs = txs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = WalletClient;
|
||||
@ -236,6 +236,7 @@ WalletDB.prototype.connect = co(function* connect() {
|
||||
this.bind();
|
||||
|
||||
yield this.client.open({ raw: true });
|
||||
yield this.setFilter();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -346,7 +347,7 @@ WalletDB.prototype.watch = co(function* watch() {
|
||||
this.logger.info('Added %d hashes to WalletDB filter.', hashes);
|
||||
this.logger.info('Added %d outpoints to WalletDB filter.', outpoints);
|
||||
|
||||
yield this.loadFilter();
|
||||
yield this.setFilter();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -395,7 +396,6 @@ WalletDB.prototype.sync = co(function* sync() {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.scan = co(function* scan(height) {
|
||||
var iter = this._addBlock.bind(this);
|
||||
var tip;
|
||||
|
||||
if (!this.client)
|
||||
@ -414,10 +414,9 @@ WalletDB.prototype.scan = co(function* scan(height) {
|
||||
|
||||
tip = yield this.getTip();
|
||||
|
||||
this.rescanning = true;
|
||||
|
||||
try {
|
||||
yield this.client.rescan(tip.hash, iter);
|
||||
this.rescanning = true;
|
||||
yield this.client.rescan(tip.hash);
|
||||
} finally {
|
||||
this.rescanning = false;
|
||||
}
|
||||
@ -483,13 +482,13 @@ WalletDB.prototype.estimateFee = co(function* estimateFee(blocks) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.loadFilter = function loadFilter() {
|
||||
WalletDB.prototype.setFilter = function setFilter() {
|
||||
if (!this.client) {
|
||||
this.emit('load filter', this.filter);
|
||||
this.emit('set filter', this.filter);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.client.loadFilter(this.filter);
|
||||
return this.client.setFilter(this.filter);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -9,14 +9,16 @@ var assert = require('assert');
|
||||
var opcodes = constants.opcodes;
|
||||
var co = require('../lib/utils/co');
|
||||
var cob = co.cob;
|
||||
// var Client = require('../lib/wallet/client');
|
||||
|
||||
describe('Chain', function() {
|
||||
var chain, wallet, node, miner, walletdb;
|
||||
var tip1, tip2, cb1, cb2;
|
||||
|
||||
this.timeout(15000);
|
||||
this.timeout(5000);
|
||||
|
||||
node = new bcoin.fullnode({ db: 'memory' });
|
||||
node = new bcoin.fullnode({ db: 'memory', apiKey: 'foo' });
|
||||
// node.walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
|
||||
chain = node.chain;
|
||||
walletdb = node.walletdb;
|
||||
walletdb.options.resolution = false;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user