Merge pull request #97 from pnagurny/startnode
Simple RPC/websockets API
This commit is contained in:
commit
c21ff322b7
134
bin/start.js
Normal file
134
bin/start.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BitcoinNode = require('..').Node;
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var socketio = require('socket.io');
|
||||||
|
var log = chainlib.log;
|
||||||
|
log.debug = function() {};
|
||||||
|
|
||||||
|
var configuration = {
|
||||||
|
datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin',
|
||||||
|
network: process.env.BITCOINDJS_NETWORK || 'livenet',
|
||||||
|
port: 3000
|
||||||
|
};
|
||||||
|
|
||||||
|
var node = new BitcoinNode(configuration);
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var interval;
|
||||||
|
|
||||||
|
node.on('ready', function() {
|
||||||
|
|
||||||
|
var io = socketio(configuration.port);
|
||||||
|
|
||||||
|
interval = setInterval(function() {
|
||||||
|
log.info('Sync Status: Tip:', node.chain.tip.hash, 'Height:', node.chain.tip.__height, 'Rate:', count/10, 'blocks per second');
|
||||||
|
count = 0;
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
io.on('connection', function(socket) {
|
||||||
|
|
||||||
|
var bus = node.openBus();
|
||||||
|
|
||||||
|
var methods = node.getAllAPIMethods();
|
||||||
|
var methodsMap = {};
|
||||||
|
|
||||||
|
methods.forEach(function(data) {
|
||||||
|
var name = data[0];
|
||||||
|
var instance = data[1];
|
||||||
|
var method = data[2];
|
||||||
|
var args = data[3];
|
||||||
|
methodsMap[name] = {
|
||||||
|
fn: function() {
|
||||||
|
return method.apply(instance, arguments);
|
||||||
|
},
|
||||||
|
args: args
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('message', function(message, socketCallback) {
|
||||||
|
if (methodsMap[message.method]) {
|
||||||
|
var params = message.params;
|
||||||
|
|
||||||
|
if(!params || !params.length) {
|
||||||
|
params = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(params.length !== methodsMap[message.method].args) {
|
||||||
|
return socketCallback({
|
||||||
|
error: 'Expected ' + methodsMap[message.method].args + ' parameters'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback = function(err, result) {
|
||||||
|
var response = {};
|
||||||
|
if(err) {
|
||||||
|
response.error = {
|
||||||
|
message: err.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result) {
|
||||||
|
if(result.toJSON) {
|
||||||
|
response.result = result.toJSON();
|
||||||
|
} else {
|
||||||
|
response.result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socketCallback(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
params = params.concat(callback);
|
||||||
|
methodsMap[message.method].fn.apply(this, params);
|
||||||
|
} else {
|
||||||
|
socketCallback({
|
||||||
|
error: 'Method Not Found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('subscribe', function(name, params) {
|
||||||
|
bus.subscribe(name, params);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('unsubscribe', function(name, params) {
|
||||||
|
bus.unsubscribe(name, params);
|
||||||
|
});
|
||||||
|
|
||||||
|
var events = node.getAllPublishEvents();
|
||||||
|
|
||||||
|
events.forEach(function(event) {
|
||||||
|
bus.on(event.name, function() {
|
||||||
|
if(socket.connected) {
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
for(var i = 0; i < arguments.length; i++) {
|
||||||
|
if(arguments[i].toJSON) {
|
||||||
|
results.push(arguments[i].toJSON());
|
||||||
|
} else {
|
||||||
|
results.push(arguments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var params = [event.name].concat(results);
|
||||||
|
socket.emit.apply(socket, params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
bus.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on('error', function(err) {
|
||||||
|
log.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
node.chain.on('addblock', function(block) {
|
||||||
|
count++;
|
||||||
|
});
|
||||||
44
example/client.js
Normal file
44
example/client.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var socket = require('socket.io-client')('http://localhost:3000');
|
||||||
|
socket.on('connect', function(){
|
||||||
|
console.log('connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function(){
|
||||||
|
console.log('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
var message = {
|
||||||
|
method: 'getOutputs',
|
||||||
|
params: ['1HTxCVrXuthad6YW5895K98XmVsdMvvBSw', true]
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.send(message, function(response) {
|
||||||
|
if(response.error) {
|
||||||
|
console.log('Error', response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(response.result);
|
||||||
|
});
|
||||||
|
|
||||||
|
var message2 = {
|
||||||
|
method: 'getTransaction',
|
||||||
|
params: ['4f793f67fc7465f14fa3a8d3727fa7d133cdb2f298234548b94a5f08b6f4103e', true]
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.send(message2, function(response) {
|
||||||
|
if(response.error) {
|
||||||
|
console.log('Error', response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(response.result);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('transaction', function(address, block) {
|
||||||
|
console.log(address, block);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('subscribe', 'transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
||||||
@ -60,6 +60,10 @@ Block.prototype.toObject = function() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Block.prototype.toJSON = function() {
|
||||||
|
return JSON.stringify(this.toObject());
|
||||||
|
};
|
||||||
|
|
||||||
Block.prototype.headerToBufferWriter = function(bw) {
|
Block.prototype.headerToBufferWriter = function(bw) {
|
||||||
/* jshint maxstatements: 20 */
|
/* jshint maxstatements: 20 */
|
||||||
|
|
||||||
|
|||||||
12
lib/bus.js
12
lib/bus.js
@ -40,4 +40,16 @@ Bus.prototype.unsubscribe = function(name) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bus.prototype.close = function() {
|
||||||
|
// Unsubscribe from all events
|
||||||
|
for (var i = 0; i < this.db.modules.length; i++) {
|
||||||
|
var mod = this.db.modules[i];
|
||||||
|
var events = mod.getPublishEvents();
|
||||||
|
for (var j = 0; j < events.length; j++) {
|
||||||
|
var event = events[j];
|
||||||
|
event.unsubscribe.call(event.scope, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Bus;
|
module.exports = Bus;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ var levelup = chainlib.deps.levelup;
|
|||||||
var errors = chainlib.errors;
|
var errors = chainlib.errors;
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var PublicKey = bitcore.PublicKey;
|
var PublicKey = bitcore.PublicKey;
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
@ -171,7 +172,11 @@ AddressModule.prototype.subscribe = function(name, emitter, addresses) {
|
|||||||
|
|
||||||
AddressModule.prototype.unsubscribe = function(name, emitter, addresses) {
|
AddressModule.prototype.unsubscribe = function(name, emitter, addresses) {
|
||||||
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
|
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
|
||||||
$.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses');
|
$.checkArgument(Array.isArray(addresses) || _.isUndefined(addresses), 'Second argument is expected to be an Array of addresses or undefined');
|
||||||
|
|
||||||
|
if(!addresses) {
|
||||||
|
return this.unsubscribeAll(name, emitter);
|
||||||
|
}
|
||||||
|
|
||||||
for(var i = 0; i < addresses.length; i++) {
|
for(var i = 0; i < addresses.length; i++) {
|
||||||
if(this.subscriptions[name][addresses[i]]) {
|
if(this.subscriptions[name][addresses[i]]) {
|
||||||
@ -184,6 +189,18 @@ AddressModule.prototype.unsubscribe = function(name, emitter, addresses) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressModule.prototype.unsubscribeAll = function(name, emitter) {
|
||||||
|
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
|
||||||
|
|
||||||
|
for(var address in this.subscriptions[name]) {
|
||||||
|
var emitters = this.subscriptions[name][address];
|
||||||
|
var index = emitters.indexOf(emitter);
|
||||||
|
if(index > -1) {
|
||||||
|
emitters.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
AddressModule.prototype.getBalance = function(address, queryMempool, callback) {
|
AddressModule.prototype.getBalance = function(address, queryMempool, callback) {
|
||||||
this.getUnspentOutputs(address, queryMempool, function(err, outputs) {
|
this.getUnspentOutputs(address, queryMempool, function(err, outputs) {
|
||||||
if(err) {
|
if(err) {
|
||||||
|
|||||||
18
lib/node.js
18
lib/node.js
@ -29,6 +29,24 @@ Node.prototype.openBus = function() {
|
|||||||
return new Bus({db: this.db});
|
return new Bus({db: this.db});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Node.prototype.getAllAPIMethods = function() {
|
||||||
|
var methods = this.db.getAPIMethods();
|
||||||
|
for (var i = 0; i < this.db.modules.length; i++) {
|
||||||
|
var mod = this.db.modules[i];
|
||||||
|
methods = methods.concat(mod.getAPIMethods());
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.getAllPublishEvents = function() {
|
||||||
|
var events = [];
|
||||||
|
for (var i = 0; i < this.db.modules.length; i++) {
|
||||||
|
var mod = this.db.modules[i];
|
||||||
|
events = events.concat(mod.getPublishEvents());
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
};
|
||||||
|
|
||||||
Node.prototype._loadConfiguration = function(config) {
|
Node.prototype._loadConfiguration = function(config) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this._loadBitcoinConf(config);
|
this._loadBitcoinConf(config);
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "./bin/build-libbitcoind",
|
"preinstall": "./bin/build-libbitcoind",
|
||||||
"install": "./bin/build-bindings",
|
"install": "./bin/build-bindings",
|
||||||
"start": "node example",
|
"start": "node bin/start.js",
|
||||||
"test": "NODE_ENV=test mocha --recursive",
|
"test": "NODE_ENV=test mocha --recursive",
|
||||||
"coverage": "istanbul cover _mocha -- --recursive",
|
"coverage": "istanbul cover _mocha -- --recursive",
|
||||||
"libbitcoind": "node bin/start-libbitcoind.js"
|
"libbitcoind": "node bin/start-libbitcoind.js"
|
||||||
@ -45,7 +45,8 @@
|
|||||||
"errno": "^0.1.2",
|
"errno": "^0.1.2",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
"nan": "1.3.0"
|
"nan": "1.3.0",
|
||||||
|
"socket.io": "^1.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"benchmark": "1.0.0",
|
"benchmark": "1.0.0",
|
||||||
|
|||||||
@ -58,4 +58,30 @@ describe('Bus', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#close', function() {
|
||||||
|
it('will unsubscribe from all events', function() {
|
||||||
|
var unsubscribe = sinon.spy();
|
||||||
|
var db = {
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
getPublishEvents: sinon.stub().returns([
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
scope: this,
|
||||||
|
unsubscribe: unsubscribe
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var bus = new Bus({db: db});
|
||||||
|
bus.close();
|
||||||
|
|
||||||
|
unsubscribe.callCount.should.equal(1);
|
||||||
|
unsubscribe.args[0].length.should.equal(1);
|
||||||
|
unsubscribe.args[0][0].should.equal(bus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -295,6 +295,20 @@ describe('AddressModule', function() {
|
|||||||
am.unsubscribe(name, emitter, [address]);
|
am.unsubscribe(name, emitter, [address]);
|
||||||
am.subscriptions.balance[address].should.deep.equal([emitter2]);
|
am.subscriptions.balance[address].should.deep.equal([emitter2]);
|
||||||
});
|
});
|
||||||
|
it('should unsubscribe from all addresses if no addresses are specified', function() {
|
||||||
|
var am = new AddressModule({});
|
||||||
|
var emitter = new EventEmitter();
|
||||||
|
var emitter2 = new EventEmitter();
|
||||||
|
am.subscriptions.balance = {
|
||||||
|
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter, emitter2],
|
||||||
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2, emitter]
|
||||||
|
};
|
||||||
|
am.unsubscribe('balance', emitter);
|
||||||
|
am.subscriptions.balance.should.deep.equal({
|
||||||
|
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter2],
|
||||||
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2]
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getBalance', function() {
|
describe('#getBalance', function() {
|
||||||
|
|||||||
@ -40,6 +40,45 @@ describe('Bitcoind Node', function() {
|
|||||||
bus.db.should.equal(db);
|
bus.db.should.equal(db);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#getAllAPIMethods', function() {
|
||||||
|
it('should return db methods and modules methods', function() {
|
||||||
|
var node = new Node({});
|
||||||
|
var db = {
|
||||||
|
getAPIMethods: sinon.stub().returns(['db1', 'db2']),
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
getAPIMethods: sinon.stub().returns(['mda1', 'mda2'])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getAPIMethods: sinon.stub().returns(['mdb1', 'mdb2'])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
node.db = db;
|
||||||
|
|
||||||
|
var methods = node.getAllAPIMethods();
|
||||||
|
methods.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#getAllPublishEvents', function() {
|
||||||
|
it('should return modules publish events', function() {
|
||||||
|
var node = new Node({});
|
||||||
|
var db = {
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
getPublishEvents: sinon.stub().returns(['mda1', 'mda2'])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getPublishEvents: sinon.stub().returns(['mdb1', 'mdb2'])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
node.db = db;
|
||||||
|
|
||||||
|
var events = node.getAllPublishEvents();
|
||||||
|
events.should.deep.equal(['mda1', 'mda2', 'mdb1', 'mdb2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#_loadConfiguration', function() {
|
describe('#_loadConfiguration', function() {
|
||||||
it('should call the necessary methods', function() {
|
it('should call the necessary methods', function() {
|
||||||
var node = new Node({});
|
var node = new Node({});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user