http test. fix path parsing.

This commit is contained in:
Christopher Jeffrey 2016-07-13 07:07:14 -07:00
parent 747c1a4949
commit a60a48da9a
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
10 changed files with 445 additions and 133 deletions

View File

@ -133,7 +133,7 @@ function Fullnode(options) {
port: this.options.httpPort || this.network.rpcPort,
host: this.options.httpHost || '0.0.0.0',
apiKey: this.options.apiKey,
auth: this.options.auth
walletAuth: this.options.walletAuth
});
}

View File

@ -221,6 +221,12 @@ HTTPBase.prototype._open = function open(callback) {
*/
HTTPBase.prototype._close = function close(callback) {
if (this.io) {
this.server.once('close', callback);
this.io.close();
return;
}
this.server.close(callback);
};

View File

@ -36,9 +36,19 @@ function HTTPClient(options) {
this.options = options;
this.network = bcoin.network.get(options.network);
this.uri = options.uri || 'localhost:' + this.network.rpcPort;
this.id = null;
this.uri = options.uri || 'http://localhost:' + this.network.rpcPort;
this.socket = null;
this.apiKey = options.apiKey;
this.auth = options.auth;
if (this.apiKey) {
if (typeof this.apiKey === 'string') {
assert(utils.isHex(this.apiKey), 'API key must be a hex string.');
this.apiKey = new Buffer(this.apiKey, 'hex');
}
assert(Buffer.isBuffer(this.apiKey));
assert(this.apiKey.length === 32, 'API key must be 32 bytes.');
}
// Open automatically.
this.open();
@ -63,7 +73,7 @@ HTTPClient.prototype._open = function _open(callback) {
}
if (!IOClient)
return;
return callback();
this.socket = new IOClient(this.uri);
@ -71,70 +81,70 @@ HTTPClient.prototype._open = function _open(callback) {
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('tx', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('tx', tx, map);
});
this.socket.on('confirmed', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('confirmed', tx, map);
});
this.socket.on('updated', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('updated', tx, map);
});
this.socket.on('address', function(receive, change, map) {
receive = receive.map(function(address) {
return bcoin.keyring.fromJSON(address);
});
change = change.map(function(address) {
return bcoin.keyring.fromJSON(address);
});
self.emit('address', receive, change, map);
});
this.socket.on('balance', function(balance, id) {
self.emit('balance', {
confirmed: utils.satoshi(balance.confirmed),
unconfirmed: utils.satoshi(balance.unconfirmed),
total: utils.satoshi(balance.total)
}, id);
});
this.socket.on('balances', function(json) {
var balances = {};
Object.keys(json).forEach(function(id) {
balances[id] = {
confirmed: utils.satoshi(json[id].confirmed),
unconfirmed: utils.satoshi(json[id].unconfirmed),
total: utils.satoshi(json[id].total)
};
});
self.emit('balances', balances);
});
this.socket.on('connect', function() {
self.socket.on('version', function(info) {
assert(info.network === self.network.type, 'Wrong network.');
});
self.socket.on('tx', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('tx', tx, map);
});
self.socket.on('confirmed', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('confirmed', tx, map);
});
self.socket.on('updated', function(tx, map) {
try {
tx = bcoin.tx.fromJSON(tx);
} catch (e) {
return self.emit('error', e);
}
self.emit('updated', tx, map);
});
self.socket.on('balance', function(balance, id) {
self.emit('balance', {
confirmed: utils.satoshi(balance.confirmed),
unconfirmed: utils.satoshi(balance.unconfirmed),
total: utils.satoshi(balance.total)
}, id);
});
self.socket.on('address', function(receive, change, map) {
receive = receive.map(function(address) {
return bcoin.keyring.fromJSON(address);
});
change = change.map(function(address) {
return bcoin.keyring.fromJSON(address);
});
self.emit('address', receive, change, map);
});
self.socket.on('balances', function(json) {
var balances = {};
Object.keys(json).forEach(function(id) {
balances[id] = {
confirmed: utils.satoshi(json[id].confirmed),
unconfirmed: utils.satoshi(json[id].unconfirmed),
total: utils.satoshi(json[id].total)
};
});
self.emit('balances', balances);
});
self.loaded = true;
self.emit('open');
callback();
});
};
@ -159,11 +169,11 @@ HTTPClient.prototype._close = function close(callback) {
* @param {WalletID} id
*/
HTTPClient.prototype.join = function join(id) {
HTTPClient.prototype.join = function join(id, token) {
if (!this.socket)
return;
this.socket.emit('join', id);
this.socket.emit('join', id, token);
};
/**
@ -182,8 +192,8 @@ HTTPClient.prototype.leave = function leave(id) {
* Listen for events on all wallets.
*/
HTTPClient.prototype.all = function all() {
this.join('!all');
HTTPClient.prototype.all = function all(token) {
this.join('!all', token);
};
/**
@ -205,8 +215,7 @@ HTTPClient.prototype.none = function none() {
HTTPClient.prototype._request = function _request(method, endpoint, json, callback) {
var self = this;
var query;
var networkType;
var query, network, height;
if (!callback) {
callback = json;
@ -218,19 +227,36 @@ HTTPClient.prototype._request = function _request(method, endpoint, json, callba
json = true;
}
if (this.apiKey) {
if (method === 'get') {
query = query || {};
query.apiKey = this.apiKey.toString('hex');
} else {
json = json || {};
json.apiKey = this.apiKey.toString('hex');
}
}
request({
method: method,
uri: this.uri + endpoint,
query: query,
json: json,
auth: this.auth,
expect: 'json'
}, function(err, res, body) {
if (err)
return callback(err);
networkType = res.headers['x-bcoin-network'];
assert(networkType === self.network.type, 'Wrong network.');
self.network.updateHeight(+res.headers['x-bcoin-height']);
network = res.headers['x-bcoin-network'];
if (network !== self.network.type)
return callback(new Error('Wrong network.'));
height = +res.headers['x-bcoin-height'];
if (utils.isNumber(height))
self.network.updateHeight(height);
if (res.statusCode === 404)
return callback();
@ -820,6 +846,40 @@ HTTPClient.prototype.walletSend = function walletSend(id, options, callback) {
});
};
/**
* Generate a new token.
* @param {(String|Buffer)?} passphrase
* @param {Function} callback
*/
HTTPClient.prototype.walletRetoken = function walletRetoken(id, passphrase, callback) {
var options = { passphrase: passphrase };
callback = utils.ensure(callback);
return this._post('/wallet/' + id + '/retoken', options, function(err, body) {
if (err)
return callback(err);
return callback(null, body.token);
});
};
/**
* Change or set master key's passphrase.
* @param {(String|Buffer)?} old
* @param {String|Buffer} new_
* @param {Function} callback
*/
HTTPClient.prototype.walletSetPassphrase = function walletSetPassphrase(id, old, new_, callback) {
var options = { old: old, passphrase: new_ };
callback = utils.ensure(callback);
return this._post('/wallet/' + id + '/passphrase', options, callback);
};
/**
* Create a transaction, fill.
* @param {WalletID} id

View File

@ -32,6 +32,8 @@ function HTTPServer(options) {
if (!options)
options = {};
EventEmitter.call(this);
this.options = options;
this.node = options.node;
@ -221,7 +223,16 @@ HTTPServer.prototype._init = function _init() {
if (req.password) {
assert(utils.isHex(req.password), 'API key must be a hex string.');
assert(req.password.length === 64, 'API key must be 32 bytes.');
options.apiKey = new Buffer(req.password, 'hex');
options.token = new Buffer(req.password, 'hex');
}
if (req.headers['x-bcoin-api-key'])
params.apiKey = req.headers['x-bcoin-api-key'];
if (params.apiKey) {
assert(utils.isHex(params.apiKey), 'API key must be a hex string.');
assert(params.apiKey.length === 64, 'API key must be 32 bytes.');
options.apiKey = new Buffer(params.apiKey, 'hex');
}
req.options = options;
@ -230,21 +241,20 @@ HTTPServer.prototype._init = function _init() {
});
this.use(function(req, res, next, send) {
if (req.path.length < 2 || req.path[0] !== 'wallet') {
if (self.apiKey) {
if (!utils.ccmp(req.options.apiKey, self.apiKey)) {
res.setHeader('WWW-Authenticate', 'Basic realm="node"');
send(401, { error: 'Unauthorized.' });
return;
}
if (self.apiKey) {
if (!utils.ccmp(req.options.apiKey, self.apiKey)) {
send(403, { error: 'Forbidden.' });
return;
}
return next();
}
if (!self.options.auth)
if (req.path.length < 2 || req.path[0] !== 'wallet')
return next();
self.walletdb.auth(req.options.id, req.options.apiKey, function(err) {
if (!self.options.walletAuth)
return next();
self.walletdb.auth(req.options.id, req.options.token, function(err) {
if (err) {
if (err.message === 'Wallet not found.')
return next();
@ -255,6 +265,7 @@ HTTPServer.prototype._init = function _init() {
return;
}
self.logger.info('Successful auth for %s.', req.options.id);
next();
});
});
@ -515,16 +526,6 @@ HTTPServer.prototype._init = function _init() {
});
});
// Broadcast TX
this.post('/wallet/:id/broadcast', function(req, res, next, send) {
self.node.sendTX(req.options.tx, function(err) {
if (err)
return next(err);
send(200, { success: true });
});
});
// Send TX
this.post('/wallet/:id/send', function(req, res, next, send) {
var id = req.options.id;
@ -833,25 +834,17 @@ HTTPServer.prototype._initIO = function _initIO() {
self.emit('error', err);
});
socket.on('join', function(id, apiKey) {
if (!self.options.auth) {
socket.on('join', function(id, token) {
if (!self.options.walletAuth) {
socket.join(id);
return;
}
if (id === '!all') {
if (self.apiKey) {
if (!utils.ccmp(apiKey, self.apiKey)) {
self.logger.info('Auth failure for %s.', id);
return;
}
}
return socket.join(id);
}
self.walletdb.auth(id, apiKey, function(err) {
self.walletdb.auth(id, token, function(err) {
if (err) {
self.logger.info('Auth failure for %s: %s.', id, err.message);
return;
}
self.logger.info('Successful auth for %s.', id);
socket.join(id);
});
});
@ -912,17 +905,13 @@ HTTPServer.prototype._initIO = function _initIO() {
this.walletdb.on('address', function(receive, change, map) {
var summary = map.toJSON();
if (receive) {
receive = receive.map(function(address) {
return address.toJSON();
});
}
receive = receive.map(function(address) {
return address.toJSON();
});
if (change) {
change = change.map(function(address) {
return address.toJSON();
});
}
change = change.map(function(address) {
return address.toJSON();
});
map.getWallets().forEach(function(id) {
self.server.io.to(id).emit('address', receive, change, summary);

View File

@ -37,6 +37,8 @@ function HTTPWallet(options) {
this.client = new http.client(options);
this.uri = options.uri;
this.id = null;
this.token = null;
this._init();
}
@ -63,8 +65,8 @@ HTTPWallet.prototype._init = function _init() {
self.emit('updated', tx, map);
});
this.client.on('balance', function(balance, map) {
self.emit('balance', balance, map);
this.client.on('balance', function(balance, id) {
self.emit('balance', balance, id);
});
this.client.on('address', function(receive, change, map) {
@ -74,8 +76,6 @@ HTTPWallet.prototype._init = function _init() {
this.client.on('error', function(err) {
self.emit('error', err);
});
this.client.join(this.id);
};
/**
@ -84,12 +84,39 @@ HTTPWallet.prototype._init = function _init() {
* @param {Function} callback
*/
HTTPWallet.prototype.open = function open(callback) {
HTTPWallet.prototype.open = function open(options, callback) {
var self = this;
if (options.token) {
if (typeof options.token === 'string') {
assert(utils.isHex(options.token), 'API key must be a hex string.');
options.token = new Buffer(options.token, 'hex');
}
assert(Buffer.isBuffer(options.token));
assert(options.token.length === 32, 'API key must be 32 bytes.');
this.id = options.id;
this.client.auth = { username: 'x', password: options.token.toString('hex') };
}
this.client.open(function(err) {
if (err)
return callback(err);
self.createWallet(self.options, callback);
if (options.token) {
self.token = options.token;
self.client.join(options.id, options.token.toString('hex'));
return callback();
}
self.client.createWallet(options, function(err, wallet) {
if (err)
return callback(err);
self.id = wallet.id;
self.client.auth = { username: 'x', password: wallet.token };
self.token = new Buffer(wallet.token, 'hex');
self.client.join(self.id, wallet.token);
callback(null, wallet);
});
});
};
@ -179,16 +206,16 @@ HTTPWallet.prototype.zap = function zap(account, age, callback) {
* @see Wallet#createTX
*/
HTTPWallet.prototype.createTX = function createTX(tx, options, outputs, callback) {
return this.client.walletCreate(this.id, tx, options, outputs, callback);
HTTPWallet.prototype.createTX = function createTX(options, outputs, callback) {
return this.client.walletCreate(this.id, options, outputs, callback);
};
/**
* @see HTTPClient#walletSend
*/
HTTPWallet.prototype.send = function send(tx, options, callback) {
return this.client.walletSend(this.id, tx, options, callback);
HTTPWallet.prototype.send = function send(options, callback) {
return this.client.walletSend(this.id, options, callback);
};
/**
@ -231,6 +258,31 @@ HTTPWallet.prototype.createAccount = function createAccount(options, callback) {
return this.client.createWalletAccount(this.id, options, callback);
};
/**
* @see Wallet#setPassphrase
*/
HTTPWallet.prototype.setPassphrase = function setPassphrase(old, _new, callback) {
return this.client.walletSetPassphrase(this.id, old, _new, callback);
};
/**
* @see Wallet#retoken
*/
HTTPWallet.prototype.retoken = function retoken(passphrase, callback) {
var self = this;
return this.client.walletRetoken(this.id, passphrase, function(err, token) {
if (err)
return callback(err);
self.client.auth = { username: 'x', password: token };
self.token = new Buffer(token, 'hex');
return callback(null, token);
});
};
/*
* Expose
*/

View File

@ -81,7 +81,7 @@ function SPVNode(options) {
port: this.options.httpPort || this.network.rpcPort,
host: this.options.httpHost || '0.0.0.0',
apiKey: this.options.apiKey,
auth: this.options.auth
walletAuth: this.options.walletAuth
});
}

View File

@ -1937,6 +1937,22 @@ WalletMap.prototype.fromTX = function fromTX(table, tx) {
continue;
}
// Already have a member for this account.
// i.e. Different address, but same account.
if (member) {
// Increment value.
if (io.coin)
member.value += io.coin.value;
else if (io.value)
member.value += io.value;
// Set address and add path.
path.address = address;
member.paths.push(path);
continue;
}
// Create a member for this account.
assert(!member);
member = MapMember.fromPath(path);
@ -1966,7 +1982,8 @@ WalletMap.prototype.fromTX = function fromTX(table, tx) {
// Update this guy last so the above if
// clause does not return true while
// we're still iterating over paths.
hashes[hash] = true;
if (paths.length > 0)
hashes[hash] = true;
}
}

View File

@ -377,6 +377,7 @@ Wallet.prototype.unlock = function unlock(passphrase, timeout, callback) {
* It is represented as `m/44` (public) hashed
* and converted to an address with a prefix
* of `0x03be04` (`WLT` in base58).
* @private
* @returns {Base58String}
*/
@ -400,6 +401,8 @@ Wallet.prototype.getID = function getID() {
/**
* Generate the wallet api key if none was passed in.
* It is represented as HASH256(m/44'->public|nonce).
* @private
* @param {HDPrivateKey} master
* @param {Number} nonce
* @returns {Buffer}
*/
@ -779,6 +782,12 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) {
});
};
/**
* Retrieve a single keyring by address hash.
* @param {Hash} hash
* @param {Function} callback
*/
Wallet.prototype.getKeyring = function getKeyring(hash, callback) {
var self = this;
var address;

View File

@ -474,6 +474,7 @@ WalletDB.prototype.save = function save(wallet, callback) {
*/
WalletDB.prototype.auth = function auth(id, token, callback) {
var self = this;
var wallet;
if (!id)
@ -1154,14 +1155,14 @@ WalletDB.prototype.fetchWallet = function fetchWallet(id, callback, handler) {
if (!wallet)
return callback(new Error('No wallet.'));
handler(wallet, function(err, result) {
handler(wallet, function(err, res1, res2) {
// Kill the reference.
wallet.destroy();
if (err)
return callback(err);
callback(null, result);
callback(null, res1, res2);
});
});
};

178
test/http-test.js Normal file
View File

@ -0,0 +1,178 @@
'use strict';
var bn = require('bn.js');
var bcoin = require('../').set('regtest');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = require('assert');
var scriptTypes = constants.scriptTypes;
var dummyInput = {
prevout: {
hash: constants.NULL_HASH,
index: 0
},
coin: {
version: 1,
height: 0,
value: constants.MAX_MONEY,
script: new bcoin.script([]),
coinbase: false,
hash: constants.NULL_HASH,
index: 0
},
script: new bcoin.script([]),
witness: new bcoin.witness([]),
sequence: 0xffffffff
};
describe('HTTP', function() {
var request = bcoin.http.request;
var apiKey = utils.hash256(new Buffer([]));
var w, addr, hash;
this.timeout(15000);
var node = new bcoin.fullnode({
network: 'regtest',
apiKey: apiKey,
walletAuth: true
});
var wallet = new bcoin.http.wallet({
network: 'regtest',
apiKey: apiKey
});
node.on('error', function() {});
it('should open node', function(cb) {
constants.tx.COINBASE_MATURITY = 0;
node.open(cb);
});
it('should create wallet', function(cb) {
wallet.open({ id: 'test' }, function(err, wallet) {
assert.ifError(err);
assert.equal(wallet.id, 'test');
cb();
});
});
it('should get info', function(cb) {
wallet.client.getInfo(function(err, info) {
assert.ifError(err);
assert.equal(info.network, node.network.type);
assert.equal(info.version, constants.USER_VERSION);
assert.equal(info.agent, constants.USER_AGENT);
assert.equal(info.height, 0);
cb();
});
});
it('should get wallet info', function(cb) {
wallet.getInfo(function(err, wallet) {
assert.ifError(err);
assert.equal(wallet.id, 'test');
addr = wallet.account.receiveAddress;
assert.equal(typeof addr, 'string');
cb();
});
});
/*
it('should get internal wallet', function(cb) {
node.walletdb.get('test', function(err, w_) {
assert.ifError(err);
assert(w_);
w = w_;
cb();
});
});
*/
it('should fill with funds', function(cb) {
var balance, receive;
// Coinbase
var t1 = bcoin.mtx()
.addOutput(addr, 50460)
.addOutput(addr, 50460)
.addOutput(addr, 50460)
.addOutput(addr, 50460);
t1.addInput(dummyInput);
wallet.once('balance', function(b, id) {
balance = b;
});
wallet.once('address', function(r, c, map) {
receive = r[0];
});
node.walletdb.addTX(t1, function(err) {
assert.ifError(err);
setTimeout(function() {
return cb();
assert(receive);
assert.equal(receive.id, 'test');
assert.equal(receive.type, 'pubkeyhash');
assert.equal(receive.change, 0);
assert(balance);
assert.equal(balance.confirmed, 0);
assert.equal(balance.unconfirmed, 201840);
assert.equal(balance.total, 201840);
cb();
}, 2000);
});
});
it('should get balance', function(cb) {
wallet.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.confirmed, 0);
assert.equal(balance.unconfirmed, 201840);
assert.equal(balance.total, 201840);
cb();
});
});
it('should send a tx', function(cb) {
var options = {
rate: 10000,
outputs: [{
value: 10000,
address: addr
}]
};
wallet.send(options, function(err, tx) {
assert.ifError(err);
assert(tx);
assert.equal(tx.inputs.length, 1);
assert.equal(tx.outputs.length, 2);
assert.equal(tx.getOutputValue(), 48190);
hash = tx.hash('hex');
node.walletdb.addTX(tx, function(err) {
assert.ifError(err);
cb();
});
});
});
it('should get a tx', function(cb) {
wallet.getTX(hash, function(err, tx) {
assert.ifError(err);
assert(tx);
assert.equal(tx.hash('hex'), hash);
cb();
});
});
it('should cleanup', function(cb) {
constants.tx.COINBASE_MATURITY = 100;
node.close(cb);
});
});