coinlib
Version:
A coin daemon interraction library aiming to support as many crypto-currencies as possible
207 lines (158 loc) • 7.28 kB
JavaScript
//
// coinlib
// Copyright (C) 2013 - 2014, int6ware
// http://www.int6ware.com - https://github.com/int6/coinlib
//
//
var events = require('events');
var http = require('http');
var winston = require('winston');
var errors = require('./errors.js');
var client = module.exports = function (config) {
var _this = this;
// free the limits of http connections.
http.globalAgent.maxSockets = Infinity;
// store the config
_this.config = config;
// api functions.
this.cmd = cmd;
this.batch = batch;
// try to connect the configured daemon
waitDaemonConnectivity(function () {
// wait until block-chain is in sync
waitBlockchainSync(function (error) {
if(error !== null)
_this.emit('online');
});
});
function cmd(method, params, callback) {
var request = JSON.stringify({
method: method,
params: params,
id: Date.now() + Math.floor(Math.random() * 10)
});
makeHttpRequest(request, function (error, result) {
callback(error, result);
});
}
function batch(methods, callback) {
var requests = [];
for (var i = 0; i < methods.length; i++) {
requests.push({
method: methods[i][0],
params: methods[i][1],
id: Date.now() + Math.floor(Math.random() * 10) + i
});
}
var json = JSON.stringify(requests);
makeHttpRequest(json, function (error, results) {
callback(error, results);
});
}
function waitDaemonConnectivity(callback) {
winston.log('debug', 'Checking coin daemon connectivity [%s:%d]', config.host, config.port);
var retryCount = 0;
var check = function () {
cmd('getinfo', [], function (error, response) {
if (error !== null || response === 'undefined' || response.error) {
// case when we can't get a response back from coin daemon.
retryCount++;
winston.log('debug', 'Waiting for coin daemon [%s:%d] to come online - retry: %d', config.host, config.port, retryCount);
setTimeout(check, 1000);
}
else if (response.result.connections === 0) {
// case where coin daemon is not connected to coin network (getinfo.connections = 0)
retryCount++;
winston.log('debug', 'Waiting for coin daemon [%s:%d] to get connected - retry: %d', config.host, config.port, retryCount);
setTimeout(check, 1000);
}
else {
// once we can get a reply back from coin daemon and it's connected to other peers, we are ready to rock
winston.log('debug', 'Daemon connectivity check succesfull [%s:%d]', config.host, config.port);
callback();
}
});
};
check();
}
function waitBlockchainSync(callback) {
winston.log('debug', 'Waiting for block-chain synchronization');
var check = function () {
var calls = [
['getblocktemplate', []],
['getinfo', []],
['getpeerinfo', []]
];
batch(calls, function (error, responses) {
var results = [];
responses.forEach(function (response, index) {
var call = calls[index][0];
results[call] = response;
if (response.error) {
winston.log('debug', 'Initilization failed as rpc call ' + call + ' failed: ' + response.error.message);
callback(error);
}
});
var blocktemplate = results.getblocktemplate;
var synced = !blocktemplate.result.error || blocktemplate.result.error.code !== errors.Rpc.CLIENT_IN_INITIAL_DOWNLOAD;
var blockCount = results.getinfo.result.blocks;
var peers = results.getpeerinfo.result;
if (synced) {
winston.log('debug', 'Block chain is on sync with %d peers [total blocks: %d]', peers.length, blockCount);
callback();
} else {
setTimeout(check, 500);
var sorted = peers.sort(function (a, b) {
return b.startingheight - a.startingheight;
});
var longestChain = sorted[0].startingheight;
var percent = (blockCount / longestChain * 100).toFixed(2);
winston.log('debug', 'Waiting for block chain syncronization, downloaded ' + percent + '% from ' + peers.length + ' peers');
}
});
};
check();
}
function makeHttpRequest(requestData, callback) {
var options = {
hostname: (typeof (_this.config.host) == 'undefined' ? '127.0.0.1' : _this.config.host), // default to 127.0.0.1
port: _this.config.port,
method: 'POST',
auth: _this.config.username + ':' + _this.config.password,
headers: {
'Content-Length': requestData.length
}
}
var request = http.request(options, function (response) {
var data = '';
response.setEncoding('utf8');
response.on('data', function (chunk) {
data += chunk;
});
response.on('end', function () {
parseResponse(response, data);
});
});
request.on('error', function (e) {
callback(e);
});
request.end(requestData);
var parseResponse = function (response, responseData) {
var json;
// handle 401 - Unauthorized
if (response.statusCode === 401) {
winston.log('debug', 'Unauthorized RPC access; invalid username or password');
callback('Invalid username or password');
}
try {
json = JSON.parse(responseData);
} catch (e) {
winston.log('debug', 'Could not parse rpc data from coin daemon; \n request: ' + requestData + '\nresponse: ' + responseData);
callback(e);
}
if (json)
callback(json.error, json);
}
}
}
client.prototype.__proto__ = events.EventEmitter.prototype;