wip
This commit is contained in:
parent
1dbba6708d
commit
98ea052405
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var BaseService = require('../../service');
|
||||
var inherits = require('util').inherits;
|
||||
var async = require('async');
|
||||
@ -24,12 +23,92 @@ inherits(AddressService, BaseService);
|
||||
AddressService.dependencies = [
|
||||
'bitcoind',
|
||||
'db',
|
||||
'block',
|
||||
'transaction'
|
||||
];
|
||||
|
||||
// ---- public function prototypes
|
||||
AddressService.prototype.getBalance = function(address, queryMempool, callback) {
|
||||
this.getUtxos(address, queryMempool, function(err, outputs) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var satoshis = outputs.map(function(output) {
|
||||
return output.satoshis;
|
||||
});
|
||||
|
||||
var sum = satoshis.reduce(function(a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
return callback(null, sum);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) {
|
||||
var self = this;
|
||||
|
||||
if(!Array.isArray(addresses)) {
|
||||
addresses = [addresses];
|
||||
}
|
||||
|
||||
var utxos = [];
|
||||
|
||||
async.eachSeries(addresses, function(address, next) {
|
||||
self.getUtxosForAddress(address, queryMempool, function(err, unspents) {
|
||||
if(err && err instanceof errors.NoOutputs) {
|
||||
return next();
|
||||
} else if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
utxos = utxos.concat(unspents);
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
callback(err, utxos);
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.getUtxosForAddress = function(address, queryMempool, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var stream = self.db.createReadStream({
|
||||
gte: self._encoding.encodeUtxoIndexKey(address),
|
||||
lt: self._encoding.encodeUtxoIndexKey(utils.getTerminalKey(new Buffer(address)))
|
||||
});
|
||||
|
||||
var utxos = [];
|
||||
stream.on('data', function(data) {
|
||||
var key = self._encoding.decodeUtxoIndexKey(data.key);
|
||||
var value = self._encoding.decodeUtxoIndexValue(data.value);
|
||||
utxos.push({
|
||||
address: key.address,
|
||||
txid: key.txid,
|
||||
outputIndex: key.outputIndex,
|
||||
satoshis: value.satoshis,
|
||||
height: value.height,
|
||||
script: value.script
|
||||
});
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
return callback(null, utxos);
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.start = function(callback) {
|
||||
|
||||
var self = this;
|
||||
self._setListeners();
|
||||
|
||||
this.db = this.node.services.db;
|
||||
this.db.getPrefix(this.name, function(err, prefix) {
|
||||
@ -60,23 +139,26 @@ AddressService.prototype.getAPIMethods = function() {
|
||||
AddressService.prototype.getAddressBalance = function(addresses, options, callback) {
|
||||
|
||||
var self = this;
|
||||
var addresses = self._normalizeAddressArg(addressArg);
|
||||
var cacheKey = addresses.join('');
|
||||
var balance = self.balanceCache.get(cacheKey);
|
||||
addresses = utils.normalizeAddressArg(addresses);
|
||||
var balance = 0;
|
||||
|
||||
async.eachLimit(addresses, 4, function(address, next) {
|
||||
|
||||
var start = self._encoding.encodeUtxoIndexKey(address);
|
||||
var criteria = {
|
||||
gte: start,
|
||||
lte: Buffer.concat([ start.slice(-36), new Buffer(new Array(73).join('f'), 'hex') ])
|
||||
};
|
||||
|
||||
var stream = this._db.createReadStream(criteria);
|
||||
stream.on('data', function(data) {
|
||||
|
||||
if (balance) {
|
||||
return setImmediate(function() {
|
||||
callback(null, balance);
|
||||
});
|
||||
} else {
|
||||
this.client.getAddressBalance({addresses: addresses}, function(err, response) {
|
||||
if (err) {
|
||||
return callback(self._wrapRPCError(err));
|
||||
}
|
||||
self.balanceCache.set(cacheKey, response.result);
|
||||
callback(null, response.result);
|
||||
stream.on('error', function(err) {
|
||||
});
|
||||
}
|
||||
stream.on('end', function() {
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@ -271,6 +353,87 @@ AddressService.prototype.getAddressUnspentOutputs = function(address, options, c
|
||||
};
|
||||
|
||||
AddressService.prototype.syncPercentage = function(callback) {
|
||||
return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%');
|
||||
};
|
||||
|
||||
|
||||
AddressService.prototype.getAddressTxidsWithHeights = function(address, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var opts = options || {};
|
||||
var txids = {};
|
||||
|
||||
var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0);
|
||||
var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]);
|
||||
|
||||
var stream = self.db.createKeyStream({
|
||||
gte: start,
|
||||
lt: end
|
||||
});
|
||||
|
||||
var streamErr = null;
|
||||
|
||||
stream.on('data', function(buffer) {
|
||||
var key = self._encoding.decodeAddressIndexKey(buffer);
|
||||
txids[key.txid] = key.height;
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
callback(streamErr, txids);
|
||||
});
|
||||
|
||||
stream.on('error', function(err) {
|
||||
streamErr = err;
|
||||
});
|
||||
};
|
||||
|
||||
// ---- private function prototypes
|
||||
AddressService.prototype._setListeners = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._db.on('error', self._onDbError.bind(self));
|
||||
self.on('reorg', self._handleReorg.bind(self));
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._startSubscriptions = function() {
|
||||
|
||||
if (this._subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._subscribed = true;
|
||||
if (!this._bus) {
|
||||
this._bus = this.node.openBus({remoteAddress: 'localhost'});
|
||||
}
|
||||
|
||||
this._bus.on('block/block', this._onBlock.bind(this));
|
||||
this._bus.subscribe('block/block');
|
||||
};
|
||||
|
||||
AddressService.prototype._onBlock = function(block) {
|
||||
var self = this;
|
||||
|
||||
var operations = [];
|
||||
|
||||
block.transactions.forEach(function(tx) {
|
||||
operations.concat(self._processTransaction(tx, { block: block, connect: connect }));
|
||||
});
|
||||
|
||||
if (operations && operations.length > 0) {
|
||||
|
||||
self._db.batch(operations, function(err) {
|
||||
|
||||
if(err) {
|
||||
log.error('Address Service: Error saving block with hash: ' + block.hash);
|
||||
this._db.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('Address Service: Success saving block hash ' + block.hash);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AddressService.prototype._processInput = function(opts, input) {
|
||||
@ -290,7 +453,6 @@ AddressService.prototype._processInput = function(opts, input) {
|
||||
}];
|
||||
|
||||
// prev utxo
|
||||
// TODO: ensure this is a good link backward
|
||||
var rec = {
|
||||
type: opts.action,
|
||||
key: this._encoding.encodeUtxoIndexKey(address, input.prevTxId.toString('hex'), input.outputIndex)
|
||||
@ -362,140 +524,6 @@ AddressService.prototype._processTransaction = function(opts, tx) {
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype.onBlock = function(block, connect) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var operations = [];
|
||||
|
||||
block.transactions.forEach(function(tx) {
|
||||
operations.concat(self._processTransaction(tx, { block: block, connect: connect }));
|
||||
});
|
||||
|
||||
if (operations && operations.length > 0) {
|
||||
|
||||
self._db.batch(operations, function(err) {
|
||||
|
||||
if(err) {
|
||||
log.error('Address Service: Error saving block with hash: ' + block.hash);
|
||||
this._db.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('Address Service: Success saving block hash ' + block.hash);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype.getBalance = function(address, queryMempool, callback) {
|
||||
this.getUtxos(address, queryMempool, function(err, outputs) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var satoshis = outputs.map(function(output) {
|
||||
return output.satoshis;
|
||||
});
|
||||
|
||||
var sum = satoshis.reduce(function(a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
return callback(null, sum);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) {
|
||||
var self = this;
|
||||
|
||||
if(!Array.isArray(addresses)) {
|
||||
addresses = [addresses];
|
||||
}
|
||||
|
||||
var utxos = [];
|
||||
|
||||
async.eachSeries(addresses, function(address, next) {
|
||||
self.getUtxosForAddress(address, queryMempool, function(err, unspents) {
|
||||
if(err && err instanceof errors.NoOutputs) {
|
||||
return next();
|
||||
} else if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
utxos = utxos.concat(unspents);
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
callback(err, utxos);
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.getUtxosForAddress = function(address, queryMempool, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var stream = self.db.createReadStream({
|
||||
gte: self._encoding.encodeUtxoIndexKey(address),
|
||||
lt: self._encoding.encodeUtxoIndexKey(utils.getTerminalKey(new Buffer(address)))
|
||||
});
|
||||
|
||||
var utxos = [];
|
||||
stream.on('data', function(data) {
|
||||
var key = self._encoding.decodeUtxoIndexKey(data.key);
|
||||
var value = self._encoding.decodeUtxoIndexValue(data.value);
|
||||
utxos.push({
|
||||
address: key.address,
|
||||
txid: key.txid,
|
||||
outputIndex: key.outputIndex,
|
||||
satoshis: value.satoshis,
|
||||
height: value.height,
|
||||
script: value.script
|
||||
});
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
return callback(null, utxos);
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressTxidsWithHeights = function(address, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var opts = options || {};
|
||||
var txids = {};
|
||||
|
||||
var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0); //the start and end must be the same length
|
||||
var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]);
|
||||
|
||||
var stream = self.db.createKeyStream({
|
||||
gte: start,
|
||||
lt: end
|
||||
});
|
||||
|
||||
var streamErr = null;
|
||||
|
||||
stream.on('data', function(buffer) {
|
||||
var key = self._encoding.decodeAddressIndexKey(buffer);
|
||||
assert(key.txid.length === 64, 'AddressService, Txid: ' + key.txid + ' with length: ' + key.txid.length + ' does not resemble a txid.');
|
||||
txids[key.txid] = key.height;
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
callback(streamErr, txids);
|
||||
});
|
||||
|
||||
stream.on('error', function(err) {
|
||||
streamErr = err;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = AddressService;
|
||||
|
||||
@ -29,7 +29,7 @@ var BlockService = function(options) {
|
||||
// meta is [{ chainwork: chainwork, hash: hash }]
|
||||
this._meta = [];
|
||||
|
||||
// this is the in-memory full/raw block cache
|
||||
// in-memory full/raw block cache
|
||||
this._blockQueue = LRU({
|
||||
max: 50 * (1 * 1024 * 1024), // 50 MB of blocks,
|
||||
length: function(n) {
|
||||
@ -39,6 +39,7 @@ var BlockService = function(options) {
|
||||
|
||||
// keep track of out-of-order blocks, this is a list of chains (which are lists themselves)
|
||||
// e.g. [ [ block5, block4 ], [ block8, block7 ] ];
|
||||
// TODO: persist this to disk, we can't hold too many blocks in memory
|
||||
this._incompleteChains = [];
|
||||
// list of all chain tips, including main chain and any chains that were orphaned after a reorg
|
||||
this._chainTips = [];
|
||||
@ -61,14 +62,13 @@ BlockService.prototype.getAPIMethods = function() {
|
||||
['getRawBlock', this, this.getRawBlock, 1],
|
||||
['getBlockHeader', this, this.getBlockHeader, 1],
|
||||
['getBlockOverview', this, this.getBlockOverview, 1],
|
||||
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
|
||||
['getBestBlockHash', this, this.getBestBlockHash, 0]
|
||||
];
|
||||
return methods;
|
||||
};
|
||||
|
||||
BlockService.prototype.getBestBlockHash = function(callback) {
|
||||
callback(this._meta[this._meta.length - 1].hash);
|
||||
BlockService.prototype.getBestBlockHash = function() {
|
||||
return this._meta[this._meta.length - 1].hash;
|
||||
};
|
||||
|
||||
BlockService.prototype.getBlock = function(hash, callback) {
|
||||
@ -81,27 +81,10 @@ BlockService.prototype.getBlock = function(hash, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
BlockService.prototype.getBlockHashesByTimestamp = function(high, low, options, callback) {
|
||||
|
||||
var self = this;
|
||||
if (_.isFunction(options)) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
self.client.getBlockHashes(high, low, options, function(err, response) {
|
||||
if (err) {
|
||||
return callback(self._wrapRPCError(err));
|
||||
}
|
||||
callback(null, response.result);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._getHash = function(blockArg) {
|
||||
|
||||
(_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) &&
|
||||
this._meta[blockArg] ? meta.hash : null;
|
||||
return (_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) &&
|
||||
this._meta[blockArg] ? this._meta[blockArg] : null;
|
||||
|
||||
};
|
||||
|
||||
@ -109,8 +92,11 @@ BlockService.prototype.getBlockHeader = function(blockArg, callback) {
|
||||
|
||||
blockArg = this._getHash(blockArg);
|
||||
|
||||
// by hash
|
||||
this._getBlock(meta.hash, function(err, block) {
|
||||
if (!blockArg) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
this._getBlock(blockArg, function(err, block) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
@ -126,49 +112,40 @@ BlockService.prototype.getBlockHeader = function(blockArg, callback) {
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype.getBlockOverview = function(hash, callback) {
|
||||
var self = this;
|
||||
BlockService.prototype._getBlock = function(hash, callback) {
|
||||
var block = this._blockQueue(hash);
|
||||
if (block) {
|
||||
return callback(null, block);
|
||||
}
|
||||
this._db.get(this._encoding.encodeBlockKey(hash), callback);
|
||||
};
|
||||
|
||||
BlockService.prototype.getBlockOverview = function(hash, callback) {
|
||||
|
||||
this._getBlock(hash, function(err, block) {
|
||||
|
||||
function queryBlock(err, blockhash) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var cachedBlock = self.blockOverviewCache.get(blockhash);
|
||||
if (cachedBlock) {
|
||||
return setImmediate(function() {
|
||||
callback(null, cachedBlock);
|
||||
});
|
||||
} else {
|
||||
self._tryAllClients(function(client, done) {
|
||||
client.getBlock(blockhash, true, function(err, response) {
|
||||
if (err) {
|
||||
return done(self._wrapRPCError(err));
|
||||
}
|
||||
var result = response.result;
|
||||
var blockOverview = {
|
||||
hash: result.hash,
|
||||
version: result.version,
|
||||
confirmations: result.confirmations,
|
||||
height: result.height,
|
||||
chainWork: result.chainwork,
|
||||
prevHash: result.previousblockhash,
|
||||
nextHash: result.nextblockhash,
|
||||
merkleRoot: result.merkleroot,
|
||||
time: result.time,
|
||||
medianTime: result.mediantime,
|
||||
nonce: result.nonce,
|
||||
bits: result.bits,
|
||||
difficulty: result.difficulty,
|
||||
txids: result.tx
|
||||
};
|
||||
self.blockOverviewCache.set(blockhash, blockOverview);
|
||||
done(null, blockOverview);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
}
|
||||
|
||||
self._maybeGetBlockHash(blockArg, queryBlock);
|
||||
var blockOverview = {
|
||||
hash: block.hash,
|
||||
version: block.header.version,
|
||||
confirmations: null,
|
||||
height: null,
|
||||
chainWork: null,
|
||||
prevHash: utils.reverseBufferToString(block.header.prevHash),
|
||||
nextHash: null,
|
||||
merkleRoot: block.header.merkleroot,
|
||||
time: null,
|
||||
medianTime: null,
|
||||
nonce: block.header.nonce,
|
||||
bits: block.header.bits,
|
||||
difficulty: null,
|
||||
txids: null
|
||||
};
|
||||
callback(null, blockOverview);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@ -415,7 +392,6 @@ BlockService.prototype._getIncompleteChainIndexes = function(block) {
|
||||
};
|
||||
|
||||
BlockService.prototype._handleReorg = function(block) {
|
||||
|
||||
this._reorging = true;
|
||||
log.warn('Chain reorganization detected! Our current block tip is: ' +
|
||||
this._tip.hash + ' the current block: ' + block.hash + '.');
|
||||
@ -431,9 +407,12 @@ BlockService.prototype._handleReorg = function(block) {
|
||||
}
|
||||
|
||||
log.warn('A common ancestor block was found to at hash: ' + commonAncestor + '.');
|
||||
this._broadcast(this.subscriptions.reorg, 'block/reorg', [block, commonAncestor]);
|
||||
this._broadcast(this.subscriptions.reorg, 'block/reorg', [commonAncestor, [block]]);
|
||||
this._onReorg(commonAncestor, [block]);
|
||||
this._reorging = false;
|
||||
};
|
||||
|
||||
BlockService.prototype._onReorg = function(commonAncestor, newBlockList) {
|
||||
};
|
||||
|
||||
BlockService.prototype._isChainReorganizing = function(block) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
var BaseService = require('../../service');
|
||||
var util = require('util');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Encoding = require('./encoding');
|
||||
var index = require('../../index');
|
||||
var log = index.log;
|
||||
@ -49,9 +48,9 @@ MempoolService.prototype._setListeners = function() {
|
||||
};
|
||||
|
||||
MempoolService.prototype._startSubscriptions = function() {
|
||||
var bus = self.node.openBus({ remoteAddress: 'localhost' });
|
||||
bus.on('transaction/transaction', this._onTransaction.bind(self));
|
||||
bus.subscribe('tranaction/transaction');
|
||||
var bus = this.node.openBus({ remoteAddress: 'localhost' });
|
||||
bus.on('p2p/transaction', this._onTransaction.bind(this));
|
||||
bus.subscribe('p2p/transaction');
|
||||
};
|
||||
|
||||
MempoolService.prototype._onTransaction = function(tx) {
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var Encoding = require('./encoding');
|
||||
var BaseService = require('../../service');
|
||||
|
||||
var inherits = require('util').inherits;
|
||||
var LRU = require('lru-cache');
|
||||
var utils = require('../../../lib/utils');
|
||||
|
||||
function TimestampService(options) {
|
||||
BaseService.call(this, options);
|
||||
this.currentBlock = null;
|
||||
this.currentTimestamp = null;
|
||||
this._createConcurrencyCache();
|
||||
this._concurrencyCache.set(new Array(65).join('0'), { valueItem: 0 });
|
||||
this._db = this.node.services.db;
|
||||
this._tip = null;
|
||||
}
|
||||
|
||||
inherits(TimestampService, BaseService);
|
||||
@ -20,69 +18,111 @@ TimestampService.dependencies = [ 'db', 'block' ];
|
||||
|
||||
TimestampService.prototype.getAPIMethods = function() {
|
||||
return [
|
||||
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
|
||||
['syncPercentage', this, this.syncPercentage, 0]
|
||||
];
|
||||
};
|
||||
|
||||
TimestampService.prototype.syncPercentage = function(callback) {
|
||||
};
|
||||
|
||||
TimestampService.prototype.getBlockHashesByTimestamp = function(callback) {
|
||||
};
|
||||
|
||||
TimestampService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
self._setListeners();
|
||||
|
||||
this.db = this.node.services.db;
|
||||
self._db.getPrefix(self.name, function(err, prefix) {
|
||||
|
||||
this.node.services.db.getPrefix(this.name, function(err, prefix) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
callback();
|
||||
self._prefix = prefix;
|
||||
self._encoding = new Encoding(self._prefix);
|
||||
|
||||
self._db.getServiceTip(self.name, function(err, tip) {
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self._tip = tip;
|
||||
self._startSubscriptions();
|
||||
callback();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
TimestampService.prototype._startSubscriptions = function() {
|
||||
|
||||
if (this._subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._subscribed = true;
|
||||
if (!this._bus) {
|
||||
this._bus = this.node.openBus({remoteAddress: 'localhost'});
|
||||
}
|
||||
|
||||
this._bus.on('block/block', this._onBlock.bind(this));
|
||||
this._bus.subscribe('block/block');
|
||||
};
|
||||
|
||||
BlockService.prototype._sync = function() {
|
||||
|
||||
if (--this._p2pBlockCallsNeeded > 0) {
|
||||
|
||||
log.info('Blocks download progress: ' + this._numCompleted + '/' +
|
||||
this._numNeeded + ' (' + (this._numCompleted/this._numNeeded*100).toFixed(2) + '%)');
|
||||
this._p2p.getBlocks({ startHash: this._latestBlockHash });
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
TimestampService.prototype._setListeners = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._db.on('error', self._onDbError.bind(self));
|
||||
self.on('reorg', self._handleReorg.bind(self));
|
||||
|
||||
};
|
||||
|
||||
TimestampService.prototype._setTip = function(tip) {
|
||||
log.debug('Timestamp Service: Setting tip to height: ' + tip.height);
|
||||
log.debug('Timestamp Service: Setting tip to hash: ' + tip.hash);
|
||||
this._tip = tip;
|
||||
this._db.setServiceTip('block', this._tip);
|
||||
};
|
||||
|
||||
TimestampService.prototype.stop = function(callback) {
|
||||
setImmediate(callback);
|
||||
};
|
||||
|
||||
TimestampService.prototype.concurrentBlockHandler = function(block, connectBlock, callback) {
|
||||
TimestampService.prototype._onBlock = function(block) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var action = connectBlock ? 'put' : 'del';
|
||||
|
||||
var filter = function(newBlockTime, prevBlockTime) {
|
||||
if (newBlockTime <= prevBlockTime) {
|
||||
return prevBlockTime + 1;
|
||||
}
|
||||
return newBlockTime;
|
||||
};
|
||||
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
var hash = block.hash;
|
||||
var queue = self._retrieveCachedItems(hash, block.header.timestamp, prevHash, filter);
|
||||
|
||||
var operations = [];
|
||||
|
||||
if (queue.length === 0) {
|
||||
return callback(null, queue);
|
||||
}
|
||||
|
||||
for(var i = 0; i < queue.length; i++) {
|
||||
|
||||
var item = queue[i];
|
||||
operations = operations.concat([
|
||||
{
|
||||
type: action,
|
||||
key: self.encoding.encodeTimestampBlockKey(item.value),
|
||||
value: self.encoding.encodeTimestampBlockValue(item.key)
|
||||
},
|
||||
{
|
||||
type: action,
|
||||
key: self.encoding.encodeBlockTimestampKey(item.key),
|
||||
value: self.encoding.encodeBlockTimestampValue(item.value)
|
||||
}
|
||||
]);
|
||||
}
|
||||
operations = operations.concat([
|
||||
{
|
||||
type: action,
|
||||
key: self.encoding.encodeTimestampBlockKey(item.value),
|
||||
value: self.encoding.encodeTimestampBlockValue(item.key)
|
||||
},
|
||||
{
|
||||
type: action,
|
||||
key: self.encoding.encodeBlockTimestampKey(item.key),
|
||||
value: self.encoding.encodeBlockTimestampValue(item.value)
|
||||
}
|
||||
]);
|
||||
|
||||
callback(null, operations);
|
||||
|
||||
@ -119,4 +159,18 @@ TimestampService.prototype._getValue = function(key, callback) {
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
TimestampService.prototype._onReorg = function(commonAncestor, newBlockList) {
|
||||
};
|
||||
|
||||
TimestampService.prototype.getBlockHashesByTimestamp = function(high, low, options, callback) {
|
||||
|
||||
var self = this;
|
||||
if (_.isFunction(options)) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = TimestampService;
|
||||
|
||||
@ -17,6 +17,7 @@ inherits(TransactionService, BaseService);
|
||||
|
||||
TransactionService.dependencies = [
|
||||
'db',
|
||||
'block',
|
||||
'timestamp',
|
||||
'mempool'
|
||||
];
|
||||
@ -32,6 +33,9 @@ TransactionService.prototype.getAPIMethods = function() {
|
||||
];
|
||||
};
|
||||
|
||||
TransactionService.prototype.getSpentInfo = function(txid, callback) {
|
||||
};
|
||||
|
||||
TransactionService.prototype.getRawTransaction = function(txid, callback) {
|
||||
this.getTransaction(txid, function(err, tx) {
|
||||
if (err) {
|
||||
@ -45,14 +49,6 @@ TransactionService.prototype.getDetailedTransaction = TransactionService.prototy
|
||||
|
||||
var self = this;
|
||||
|
||||
assert(txid.length === 64, 'Transaction, Txid: ' + txid + ' with length: ' + txid.length + ' does not resemble a txid.');
|
||||
|
||||
if(self.currentTransactions[txid]) {
|
||||
return setImmediate(function() {
|
||||
callback(null, self.currentTransactions[txid]);
|
||||
});
|
||||
}
|
||||
|
||||
var key = self.encoding.encodeTransactionKey(txid);
|
||||
|
||||
async.waterfall([
|
||||
@ -144,8 +140,24 @@ TransactionService.prototype.sendTransaction = function(tx, callback) {
|
||||
this._p2p.sendTransaction(tx, callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype._setListeners = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._db.on('error', self._onDbError.bind(self));
|
||||
self.on('reorg', self._onReorg.bind(self));
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype._onDbError = function(error) {
|
||||
};
|
||||
|
||||
TransactionService.prototype._onReorg = function(commonAncestor, newBlockList) {
|
||||
};
|
||||
|
||||
TransactionService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
self._setListeners();
|
||||
|
||||
self._db.getPrefix(self.name, function(err, prefix) {
|
||||
|
||||
@ -153,7 +165,7 @@ TransactionService.prototype.start = function(callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self._db.getServiceTip('transaction', function(err, tip) {
|
||||
self._db.getServiceTip(self.name, function(err, tip) {
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@ -162,12 +174,31 @@ TransactionService.prototype.start = function(callback) {
|
||||
self._tip = tip;
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
self._startSubscriptions();
|
||||
callback();
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
TransactionService.prototype._onTransaction = function(transaction) {
|
||||
};
|
||||
|
||||
TransactionService.prototype._startSubscriptions = function() {
|
||||
|
||||
if (this._subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._subscribed = true;
|
||||
if (!this._bus) {
|
||||
this._bus = this.node.openBus({remoteAddress: 'localhost'});
|
||||
}
|
||||
|
||||
this._bus.on('block/block', this._onTransaction.bind(this));
|
||||
this._bus.subscribe('block/block');
|
||||
};
|
||||
|
||||
TransactionService.prototype.stop = function(callback) {
|
||||
setImmediate(callback);
|
||||
};
|
||||
|
||||
@ -1,246 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
|
||||
function Encoding(servicePrefix) {
|
||||
this.servicePrefix = servicePrefix;
|
||||
this.subKeyMap = {
|
||||
transaction: {
|
||||
fn: this.encodeWalletTransactionKey,
|
||||
buffer: new Buffer('00', 'hex')
|
||||
},
|
||||
addresses: {
|
||||
fn: this.encodeWalletAddressesKey,
|
||||
buffer: new Buffer('01', 'hex')
|
||||
},
|
||||
utxo: {
|
||||
fn: this.encodeWalletUtxoKey,
|
||||
buffer: new Buffer('02', 'hex')
|
||||
},
|
||||
utxoSat: {
|
||||
fn: this.encodeWalletUtxoSatoshisKey,
|
||||
buffer: new Buffer('03', 'hex')
|
||||
},
|
||||
balance: {
|
||||
fn: this.encodeWalletBalanceKey,
|
||||
buffer: new Buffer('04', 'hex')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Encoding.prototype.encodeWalletTransactionKey = function(walletId, height, txid) {
|
||||
var buffers = [this.servicePrefix, this.subKeyMap.transaction.buffer];
|
||||
|
||||
var walletIdSizeBuffer = new Buffer(1);
|
||||
walletIdSizeBuffer.writeUInt8(walletId.length);
|
||||
var walletIdBuffer = new Buffer(walletId, 'utf8');
|
||||
|
||||
buffers.push(walletIdSizeBuffer);
|
||||
buffers.push(walletIdBuffer);
|
||||
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height || 0);
|
||||
buffers.push(heightBuffer);
|
||||
|
||||
var txidBuffer = new Buffer((txid || new Array(65).join('0')), 'hex');
|
||||
buffers.push(txidBuffer);
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletTransactionKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
|
||||
var walletSize = reader.readUInt8();
|
||||
var walletId = reader.read(walletSize).toString('utf8');
|
||||
var height = reader.readUInt32BE();
|
||||
|
||||
var txid = reader.read(32);
|
||||
|
||||
return {
|
||||
walletId: walletId,
|
||||
height: height,
|
||||
txid: txid
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletUtxoKey = function(walletId, txid, outputIndex) {
|
||||
var buffers = [this.servicePrefix, this.subKeyMap.utxo.buffer];
|
||||
|
||||
var walletIdSizeBuffer = new Buffer(1);
|
||||
walletIdSizeBuffer.writeUInt8(walletId.length);
|
||||
var walletIdBuffer = new Buffer(walletId, 'utf8');
|
||||
|
||||
buffers.push(walletIdSizeBuffer);
|
||||
buffers.push(walletIdBuffer);
|
||||
|
||||
var txidBuffer = new Buffer(txid || new Array(65).join('0'), 'hex');
|
||||
buffers.push(txidBuffer);
|
||||
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex || 0);
|
||||
buffers.push(outputIndexBuffer);
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletUtxoKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
|
||||
var walletIdSize = reader.readUInt8();
|
||||
var walletId = reader.read(walletIdSize).toString('utf8');
|
||||
var txid = reader.read(32).toString('hex');
|
||||
var outputIndex = reader.readUInt32BE();
|
||||
return {
|
||||
walletId: walletId,
|
||||
txid: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletUtxoValue = function(height, satoshis, scriptBuffer) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
var satoshisBuffer = new Buffer(8);
|
||||
satoshisBuffer.writeDoubleBE(satoshis);
|
||||
return Buffer.concat([heightBuffer, satoshisBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletUtxoValue = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
var height = reader.readUInt32BE();
|
||||
var satoshis = buffer.readDoubleBE(4);
|
||||
var scriptBuffer = buffer.slice(12);
|
||||
return {
|
||||
height: height,
|
||||
satoshis: satoshis,
|
||||
script: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletUtxoSatoshisKey = function(walletId, satoshis, txid, outputIndex) {
|
||||
var buffers = [this.servicePrefix, this.subKeyMap.utxoSat.buffer];
|
||||
|
||||
var walletIdSizeBuffer = new Buffer(1);
|
||||
walletIdSizeBuffer.writeUInt8(walletId.length);
|
||||
var walletIdBuffer = new Buffer(walletId, 'utf8');
|
||||
|
||||
buffers.push(walletIdSizeBuffer);
|
||||
buffers.push(walletIdBuffer);
|
||||
|
||||
var satoshisBuffer = new Buffer(8);
|
||||
satoshisBuffer.writeDoubleBE(satoshis || 0);
|
||||
buffers.push(satoshisBuffer);
|
||||
|
||||
var txidBuffer = new Buffer(txid || new Array(65).join('0'), 'hex');
|
||||
buffers.push(txidBuffer);
|
||||
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex || 0);
|
||||
buffers.push(outputIndexBuffer);
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletUtxoSatoshisKey = function(buffer) {
|
||||
var walletIdSize = buffer.readUInt8(3);
|
||||
var walletId = buffer.slice(4, walletIdSize + 4).toString('utf8');
|
||||
var satoshis = buffer.readDoubleBE(walletIdSize + 4);
|
||||
|
||||
var txid = buffer.slice(walletIdSize + 12, walletIdSize + 44).toString('hex');
|
||||
var outputIndex = buffer.readUInt32BE(walletIdSize + 44);
|
||||
return {
|
||||
walletId: walletId,
|
||||
satoshis: satoshis,
|
||||
txid: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletUtxoSatoshisValue = function(height, scriptBuffer) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
return Buffer.concat([heightBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletUtxoSatoshisValue = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
var height = reader.readUInt32BE();
|
||||
var scriptBuffer = reader.read(buffer.length - 4);
|
||||
return {
|
||||
height: height,
|
||||
script: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletAddressesKey = function(walletId) {
|
||||
var prefix = this.subKeyMap.addresses.buffer;
|
||||
var walletIdSizeBuffer = new Buffer(1);
|
||||
walletIdSizeBuffer.writeUInt8(walletId.length);
|
||||
var walletIdBuffer = new Buffer(walletId, 'utf8');
|
||||
return Buffer.concat([this.servicePrefix, prefix, walletIdSizeBuffer, walletIdBuffer]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletAddressesKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
var walletSize = reader.readUInt8();
|
||||
return reader.read(walletSize).toString('utf8');
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletAddressesValue = function(addresses) {
|
||||
var bufferList = [];
|
||||
var addressesLengthBuffer = new Buffer(4);
|
||||
addressesLengthBuffer.writeUInt32BE(addresses.length);
|
||||
bufferList.push(addressesLengthBuffer);
|
||||
for(var i = 0; i < addresses.length; i++) {
|
||||
var addressSizeBuffer = new Buffer(1);
|
||||
addressSizeBuffer.writeUInt8(addresses[i].length);
|
||||
bufferList.push(addressSizeBuffer);
|
||||
bufferList.push(new Buffer(addresses[i], 'utf8'));
|
||||
}
|
||||
|
||||
return Buffer.concat(bufferList);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletAddressesValue = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
var addressesLength = reader.readUInt32BE();
|
||||
var addresses = [];
|
||||
for(var i = 0; i < addressesLength; i++) {
|
||||
var addressSize = reader.readUInt8();
|
||||
addresses.push(reader.read(addressSize).toString('utf8'));
|
||||
}
|
||||
return addresses;
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletBalanceKey = function(walletId) {
|
||||
var prefix = this.subKeyMap.balance.buffer;
|
||||
var walletIdSizeBuffer = new Buffer(1);
|
||||
walletIdSizeBuffer.writeUInt8(walletId.length);
|
||||
var walletIdBuffer = new Buffer(walletId, 'utf8');
|
||||
return Buffer.concat([this.servicePrefix, prefix, walletIdSizeBuffer, walletIdBuffer]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletBalanceKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
var walletSize = reader.readUInt8();
|
||||
return reader.read(walletSize).toString('utf8');
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeWalletBalanceValue = function(balance) {
|
||||
var balanceBuffer = new Buffer(8);
|
||||
balanceBuffer.writeDoubleBE(balance);
|
||||
return balanceBuffer;
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeWalletBalanceValue = function(buffer) {
|
||||
return buffer.readDoubleBE();
|
||||
};
|
||||
|
||||
module.exports = Encoding;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,765 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var Writable = require('stream').Writable;
|
||||
var assert = require('assert');
|
||||
var crypto = require('crypto');
|
||||
var fs = require('fs');
|
||||
var inherits = require('util').inherits;
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var mkdirp = require('mkdirp');
|
||||
var ttyread = require('ttyread');
|
||||
|
||||
var exports = {};
|
||||
|
||||
exports.isInteger = function(value) {
|
||||
return typeof value === 'number' &&
|
||||
isFinite(value) &&
|
||||
Math.floor(value) === value;
|
||||
};
|
||||
|
||||
exports.normalizeTimeStamp = function(value) {
|
||||
if (value > 0xffffffff) {
|
||||
value = Math.round(value/1000);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/**
|
||||
* Will create a directory if it does not already exist.
|
||||
*
|
||||
* @param {String} directory - An absolute path to the directory
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.setupDirectory = function(directory, callback) {
|
||||
fs.access(directory, function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return mkdirp(directory, callback);
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This will split a range of numbers "a" to "b" by sections
|
||||
* of the length "max".
|
||||
*
|
||||
* Example:
|
||||
* > var range = utils.splitRange(1, 10, 3);
|
||||
* > [[1, 3], [4, 6], [7, 9], [10, 10]]
|
||||
*
|
||||
* @param {Number} a - The start index (lesser)
|
||||
* @param {Number} b - The end index (greater)
|
||||
* @param {Number} max - The maximum section length
|
||||
*/
|
||||
exports.splitRange = function(a, b, max) {
|
||||
assert(b > a, '"b" is expected to be greater than "a"');
|
||||
var sections = [];
|
||||
var delta = b - a;
|
||||
var first = a;
|
||||
var last = a;
|
||||
|
||||
var length = Math.floor(delta / max);
|
||||
for (var i = 0; i < length; i++) {
|
||||
last = first + max - 1;
|
||||
sections.push([first, last]);
|
||||
first += max;
|
||||
}
|
||||
|
||||
if (last <= b) {
|
||||
sections.push([first, b]);
|
||||
}
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
/**
|
||||
* getFileStream: Checks for the file's existence and returns a readable stream or stdin
|
||||
* @param {String} path - The path to the file
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.getFileStream = function(filePath, callback) {
|
||||
callback(null, fs.createReadStream(filePath));
|
||||
};
|
||||
|
||||
exports.readWalletDatFile = function(filePath, network, callback) {
|
||||
assert(_.isString(network), 'Network expected to be a string.');
|
||||
var datadir = path.dirname(filePath).replace(/(\/testnet3|\/regtest)$/, '');
|
||||
var name = path.basename(filePath);
|
||||
var options = ['-datadir=' + datadir, '-wallet=' + name];
|
||||
if (network === 'testnet') {
|
||||
options.push('-testnet');
|
||||
} else if (network === 'regtest') {
|
||||
options.push('-regtest');
|
||||
}
|
||||
// TODO use ../node_modules/.bin/wallet-utility
|
||||
var exec = path.resolve(__dirname, '../node_modules/bitcore-node/bin/bitcoin-0.12.1/bin/wallet-utility');
|
||||
var wallet = spawn(exec, options);
|
||||
|
||||
var result = '';
|
||||
|
||||
wallet.stdout.on('data', function(data) {
|
||||
result += data.toString('utf8');
|
||||
});
|
||||
|
||||
var error;
|
||||
|
||||
wallet.stderr.on('data', function(data) {
|
||||
error = data.toString();
|
||||
});
|
||||
|
||||
wallet.on('close', function(code) {
|
||||
if (code === 0) {
|
||||
var addresses;
|
||||
try {
|
||||
addresses = JSON.parse(result);
|
||||
addresses = addresses.map(function(entry) {
|
||||
return entry.addr ? entry.addr : entry;
|
||||
});
|
||||
} catch(err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, addresses);
|
||||
} else if (error) {
|
||||
return callback(new Error(error));
|
||||
} else {
|
||||
var message = 'wallet-utility exited (' + code + '): ' + result;
|
||||
return callback(new Error(message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.readWalletFile = function(filePath, network, callback) {
|
||||
if (/\.dat$/.test(filePath)) {
|
||||
exports.readWalletDatFile(filePath, network, callback);
|
||||
} else {
|
||||
exports.getFileStream(filePath, callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This will split an array into smaller arrays by size
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Number} size - The length of resulting smaller arrays
|
||||
*/
|
||||
exports.splitArray = function(array, size) {
|
||||
var results = [];
|
||||
while (array.length) {
|
||||
results.push(array.splice(0, size));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to get the remote ip address from cloudflare headers.
|
||||
*
|
||||
* @param {Object} req - An express request object
|
||||
*/
|
||||
exports.getRemoteAddress = function(req) {
|
||||
if (req.headers['cf-connecting-ip']) {
|
||||
return req.headers['cf-connecting-ip'];
|
||||
}
|
||||
return req.socket.remoteAddress;
|
||||
};
|
||||
|
||||
/**
|
||||
* A middleware to enable CORS
|
||||
*
|
||||
* @param {Object} req - An express request object
|
||||
* @param {Object} res - An express response object
|
||||
* @param {Function} next
|
||||
*/
|
||||
exports.enableCORS = function(req, res, next) {
|
||||
res.header('access-control-allow-origin', '*');
|
||||
res.header('access-control-allow-methods', 'GET, HEAD, PUT, POST, OPTIONS');
|
||||
var allowed = [
|
||||
'origin',
|
||||
'x-requested-with',
|
||||
'content-type',
|
||||
'accept',
|
||||
'content-length',
|
||||
'cache-control',
|
||||
'cf-connecting-ip'
|
||||
];
|
||||
res.header('access-control-allow-headers', allowed.join(', '));
|
||||
|
||||
var method = req.method && req.method.toUpperCase && req.method.toUpperCase();
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
res.statusCode = 204;
|
||||
res.end();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will send error to express response
|
||||
*
|
||||
* @param {Error} err - error object
|
||||
* @param {Object} res - express response object
|
||||
*/
|
||||
exports.sendError = function(err, res) {
|
||||
if (err.statusCode) {
|
||||
res.status(err.statusCode).send(err.message);
|
||||
} else {
|
||||
console.error(err.stack);
|
||||
res.status(503).send(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will create a writeable logger stream
|
||||
*
|
||||
* @param {Function} logger - Function to log information
|
||||
* @returns {Stream}
|
||||
*/
|
||||
exports.createLogStream = function(logger) {
|
||||
function Log(options) {
|
||||
Writable.call(this, options);
|
||||
}
|
||||
inherits(Log, Writable);
|
||||
|
||||
Log.prototype._write = function (chunk, enc, callback) {
|
||||
logger(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger
|
||||
callback();
|
||||
};
|
||||
var stream = new Log();
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
exports.getWalletId = exports.generateJobId = function() {
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
};
|
||||
|
||||
exports.getClients = function(clientsConfig) {
|
||||
var clients = [];
|
||||
for (var i = 0; i < clientsConfig.length; i++) {
|
||||
var config = clientsConfig[i];
|
||||
var remoteClient = new BitcoinRPC({
|
||||
protocol: config.rpcprotocol || 'http',
|
||||
host: config.rpchost || '127.0.0.1',
|
||||
port: config.rpcport,
|
||||
user: config.rpcuser,
|
||||
pass: config.rpcpassword,
|
||||
rejectUnauthorized: _.isUndefined(config.rpcstrict) ? true : config.rpcstrict
|
||||
});
|
||||
clients.push(remoteClient);
|
||||
}
|
||||
return clients;
|
||||
};
|
||||
|
||||
exports.setClients = function(obj, clients) {
|
||||
obj._clients = clients;
|
||||
obj._clientsIndex = 0;
|
||||
Object.defineProperty(obj, 'clients', {
|
||||
get: function() {
|
||||
var client = obj._clients[obj._clientsIndex];
|
||||
obj._clientsIndex = (obj._clientsIndex + 1) % obj._clients.length;
|
||||
return client;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
};
|
||||
|
||||
exports.tryAllClients = function(obj, func, options, callback) {
|
||||
if (_.isFunction(options)) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
var clientIndex = obj._clientsIndex;
|
||||
var retry = function(done) {
|
||||
var client = obj._clients[clientIndex];
|
||||
clientIndex = (clientIndex + 1) % obj._clients.length;
|
||||
func(client, done);
|
||||
};
|
||||
async.retry({times: obj._clients.length, interval: options.interval || 1000}, retry, callback);
|
||||
};
|
||||
|
||||
exports.wrapRPCError = function(errObj) {
|
||||
var err = new Error(errObj.message);
|
||||
err.code = errObj.code;
|
||||
return err;
|
||||
};
|
||||
|
||||
var PUBKEYHASH = new Buffer('01', 'hex');
|
||||
var SCRIPTHASH = new Buffer('02', 'hex');
|
||||
|
||||
exports.getAddressTypeString = function(bufferArg) {
|
||||
var buffer = bufferArg;
|
||||
if (!Buffer.isBuffer(bufferArg)) {
|
||||
buffer = new Buffer(bufferArg, 'hex');
|
||||
}
|
||||
var type = buffer.slice(0, 1);
|
||||
if (type.compare(PUBKEYHASH) === 0) {
|
||||
return 'pubkeyhash';
|
||||
} else if (type.compare(SCRIPTHASH) === 0) {
|
||||
return 'scripthash';
|
||||
} else {
|
||||
throw new TypeError('Unknown address type');
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAddressTypeBuffer = function(address) {
|
||||
var type;
|
||||
if (address.type === 'pubkeyhash') {
|
||||
type = PUBKEYHASH;
|
||||
} else if (address.type === 'scripthash') {
|
||||
type = SCRIPTHASH;
|
||||
} else {
|
||||
throw new TypeError('Unknown address type');
|
||||
}
|
||||
return type;
|
||||
};
|
||||
|
||||
exports.splitBuffer = function(buffer, size) {
|
||||
var pos = 0;
|
||||
var buffers = [];
|
||||
while (pos < buffer.length) {
|
||||
buffers.push(buffer.slice(pos, pos + size));
|
||||
pos += size;
|
||||
}
|
||||
return buffers;
|
||||
};
|
||||
|
||||
exports.exitWorker = function(worker, timeout, callback) {
|
||||
assert(worker, '"worker" is expected to be defined');
|
||||
var exited = false;
|
||||
worker.once('exit', function(code) {
|
||||
if (!exited) {
|
||||
exited = true;
|
||||
if (code !== 0) {
|
||||
var error = new Error('Worker did not exit cleanly: ' + code);
|
||||
error.code = code;
|
||||
return callback(error);
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
worker.kill('SIGINT');
|
||||
setTimeout(function() {
|
||||
if (!exited) {
|
||||
exited = true;
|
||||
worker.kill('SIGKILL');
|
||||
return callback(new Error('Worker exit timeout, force shutdown'));
|
||||
}
|
||||
}, timeout).unref();
|
||||
};
|
||||
|
||||
exports.timestampToISOString = function(timestamp) {
|
||||
return new Date(this.toIntIfNumberLike(timestamp) * 1000).toISOString();
|
||||
};
|
||||
|
||||
exports.satoshisToBitcoin = function(satoshis) {
|
||||
return satoshis / 100000000;
|
||||
};
|
||||
|
||||
exports.getPassphrase = function(callback) {
|
||||
ttyread('Enter passphrase: ', {silent: true}, callback);
|
||||
};
|
||||
|
||||
exports.acquirePassphrase = function(callback) {
|
||||
var first;
|
||||
var second;
|
||||
async.doWhilst(function(next) {
|
||||
ttyread('Enter passphrase: ', {silent: true}, function(err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
first = result;
|
||||
ttyread('Re-enter passphrase: ', {silent: true}, function(err, result) {
|
||||
second = result;
|
||||
next();
|
||||
});
|
||||
});
|
||||
}, function() {
|
||||
if (first !== second) {
|
||||
console.log('Passphrases do not match, please re-enter.');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, first);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Important notes:
|
||||
|
||||
How the encryption/decryption schemes work.
|
||||
1. The user's passphrase and salt are hashed using scrypt algorithm. You must store the salt.
|
||||
On modern hardware this hashing function should take 1-2 seconds.
|
||||
2. The resulting hash is 48 bytes. The first 32 bytes of this hash is the "key" and the last
|
||||
16 bytes is the "iv" to decrypt the master key using AES256-cbc.
|
||||
3. The plaintext "master key" is always 32 bytes and should be as random as possible.
|
||||
You may pass in the plaintext master key to encryptSecret -or- /dev/random will be consulted.
|
||||
4. The cipherText of the master key must be stored just like the salt. For added security, you
|
||||
might store the cipherText of the master key separate from the cipherText.
|
||||
For example, if an attacker discovers your passphrase and salt (the most likely scenario), they would
|
||||
still require the cipherText of the master key in order to decrypt the cipherText of your private keys.
|
||||
Storing your encrypted master key on another device would be a better choice than keeping your salt,
|
||||
the cipherText of your master key and the cipherText of your private keys on the same computer system.
|
||||
5. The plaintext master key is then used to encrypt/decrypt the bitcoin private keys. The private keys'
|
||||
corresponding public key is used as the IV for the procedure.
|
||||
|
||||
|
||||
Specific notes regarding how private keys are transferred from a traditional "wallet.dat" file used with
|
||||
Bitcoin Core's Wallet:
|
||||
|
||||
1. Bitcoin Core's Wallet uses Berkeley DB version 4.8 to store secp256k1 elliptic curve private keys in WIF format.
|
||||
2. The same Berkeley DB, internally called "main", also stores compressed public keys for the above private keys,
|
||||
the master keys used to encrypt the above private keys and bitcoin transaction details relevant to those private keys
|
||||
3. The underlying data structure for the Berkeley database is the B-Tree (balanced tree). This is a key-value data
|
||||
structure, therefore the database is a key-value database.
|
||||
Berkeley DB documentation also refers to this as "key-record"
|
||||
This means that the data contained in this B-Tree is organized for high speed retrieval based on a key.
|
||||
In other words the database is optimized for lookups.
|
||||
4. The filename for this database file is called "wallet.dat" historically,
|
||||
but you can rename it to whatever suits you
|
||||
|
||||
*/
|
||||
//this function depends on the derivation method and its params that were originally used to hash the passphrase
|
||||
//this could be SHA512, scrypt, etc.
|
||||
exports.sha512KDF = function(passphrase, salt, derivationOptions, callback) {
|
||||
if (!derivationOptions || derivationOptions.method !== 0 || !derivationOptions.rounds) {
|
||||
return callback(new Error('SHA512 KDF method was called for, ' +
|
||||
'yet the derivations options for it were not supplied.'));
|
||||
}
|
||||
var rounds = derivationOptions.rounds || 1;
|
||||
//if salt was sent in as a string, we will have to assume the default encoding type
|
||||
if (!Buffer.isBuffer(salt)) {
|
||||
salt = new Buffer(salt, 'utf-8');
|
||||
}
|
||||
var derivation = Buffer.concat([new Buffer(''), new Buffer(passphrase), salt]);
|
||||
for(var i = 0; i < rounds; i++) {
|
||||
derivation = crypto.createHash('sha512').update(derivation).digest();
|
||||
}
|
||||
callback(null, derivation);
|
||||
};
|
||||
|
||||
exports.hashPassphrase = function() {
|
||||
return exports.sha512KDF;
|
||||
};
|
||||
|
||||
exports.decryptPrivateKey = function(opts, callback) {
|
||||
exports.decryptSecret(opts, function(err, masterKey) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
opts.cipherText = opts.pkCipherText;
|
||||
//decrypt the private here using the plainText master key as the "key"
|
||||
//and the double sha256 compressed pub key as the "IV"
|
||||
opts.key = masterKey;
|
||||
opts.iv = bitcore.crypto.Hash.sha256sha256(new Buffer(opts.pubkey, 'hex'));
|
||||
exports.decrypt(opts, function(err, privateKey) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, privateKey);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//call decryptSecret first
|
||||
exports.encryptPrivateKeys = function(opts, callback) {
|
||||
if (!opts.masterKey || !opts.keys) {
|
||||
return callback(new Error('A decrypted master key, ' +
|
||||
'compressed public keys and private keys are required for encryption.'));
|
||||
}
|
||||
if (!Buffer.isBuffer(opts.masterKey)) { //we'll have to assume the master key is utf-8 encoded
|
||||
opts.masterKey = new Buffer(opts.masterKey);
|
||||
}
|
||||
assert(opts.masterKey.length === 32, 'Master Key must be 32 bytes in length, ' +
|
||||
'if you have a hex string, please pass master key in as a buffer');
|
||||
//if the master key is not 32 bytes, then take the sha256 hash
|
||||
var ret = [];
|
||||
async.mapLimit(opts.keys, 5, function(key, next) {
|
||||
var iv = bitcore.crypto.Hash.sha256sha256(new Buffer(key.pubKey, 'hex')).slice(0, 16);
|
||||
//do we want to encrypt WIF's or RAW private keys or does it matter?
|
||||
exports.encrypt({
|
||||
secret: key.privKey,
|
||||
iv: iv,
|
||||
key: opts.masterKey
|
||||
}, next);
|
||||
}, function(err, results) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
for(var i = 0; i < results.length; i++) {
|
||||
ret.push({
|
||||
cipherText: results[i],
|
||||
checkHash: bitcore.crypto.Hash.sha256(new Buffer(opts.keys[i].pubKey + results[i])).toString('hex'),
|
||||
type: 'encrypted private key',
|
||||
pubKey: opts.keys[i].pubKey
|
||||
});
|
||||
}
|
||||
callback(null, ret);
|
||||
});
|
||||
};
|
||||
|
||||
exports.encrypt = function(opts, callback) {
|
||||
if (!opts.key ||
|
||||
!opts.iv ||
|
||||
!opts.secret ||
|
||||
opts.key.length !== 32 ||
|
||||
opts.iv.length !== 16 ||
|
||||
opts.secret.length < 1) {
|
||||
return callback(new Error('Key, IV, and something to encrypt is required.'));
|
||||
}
|
||||
var cipher = crypto.createCipheriv('aes-256-cbc', opts.key, opts.iv);
|
||||
var cipherText;
|
||||
try {
|
||||
cipherText = Buffer.concat([cipher.update(opts.secret), cipher.final()]).toString('hex');
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
return callback(null, cipherText);
|
||||
|
||||
};
|
||||
exports.encryptSecret = function(opts, callback) {
|
||||
var hashFunc = exports.hashPassphrase(opts.derivationOptions);
|
||||
hashFunc(opts.passphrase, opts.salt, opts.derivationOptions, function(err, hashedPassphrase) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var secret = opts.secret || crypto.randomBytes(32);
|
||||
assert(Buffer.isBuffer(secret), 'secret is expected to be a buffer');
|
||||
secret = bitcore.crypto.Hash.sha256sha256(secret);
|
||||
var firstHalf = hashedPassphrase.slice(0, 32); //AES256-cbc shared key
|
||||
var secondHalf = hashedPassphrase.slice(32, 48); //AES256-cbc IV, for cbc mode, the IV will be 16 bytes
|
||||
exports.encrypt({
|
||||
secret: secret,
|
||||
key: firstHalf,
|
||||
iv: secondHalf
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
exports.decryptSecret = function(opts, callback) {
|
||||
var hashFunc = exports.hashPassphrase(opts.derivationOptions);
|
||||
hashFunc(opts.passphrase, opts.salt, opts.derivationOptions, function(err, hashedPassphrase) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
opts.key = hashedPassphrase;
|
||||
exports.decrypt(opts, callback);
|
||||
});
|
||||
};
|
||||
|
||||
exports.decrypt = function(opts, callback) {
|
||||
if (!Buffer.isBuffer(opts.key)) {
|
||||
opts.key = new Buffer(opts.key, 'hex');
|
||||
}
|
||||
var secondHalf;
|
||||
if (opts.iv) {
|
||||
secondHalf = opts.iv.slice(0, 16);
|
||||
} else {
|
||||
secondHalf = opts.key.slice(32, 48); //AES256-cbc IV
|
||||
}
|
||||
var cipherText = new Buffer(opts.cipherText, 'hex');
|
||||
var firstHalf = opts.key.slice(0, 32); //AES256-cbc shared key
|
||||
var AESDecipher = crypto.createDecipheriv('aes-256-cbc', firstHalf, secondHalf);
|
||||
var plainText;
|
||||
try {
|
||||
plainText = Buffer.concat([AESDecipher.update(cipherText), AESDecipher.final()]).toString('hex');
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
callback(null, plainText);
|
||||
};
|
||||
|
||||
exports.confirm = function(question, callback) {
|
||||
ttyread(question + ' (y/N): ', function(err, answer) {
|
||||
if (err) {
|
||||
return callback(err, false);
|
||||
}
|
||||
if (answer === 'y') {
|
||||
return callback(null, true);
|
||||
}
|
||||
callback(null, false);
|
||||
});
|
||||
};
|
||||
|
||||
exports.encryptSecretWithPassphrase = function(secret, callback) {
|
||||
exports.acquirePassphrase(function(err, passphrase) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var salt = crypto.randomBytes(32).toString('hex');
|
||||
exports.encryptSecret({
|
||||
secret: secret,
|
||||
passphrase: passphrase,
|
||||
salt: salt
|
||||
}, function(err, cipherText) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, cipherText, salt);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.generateNonce = function() {
|
||||
var nonce = new Buffer(new Array(12));
|
||||
nonce.writeDoubleBE(Date.now());
|
||||
nonce.writeUInt32BE(process.hrtime()[1], 8);
|
||||
return nonce;
|
||||
};
|
||||
|
||||
exports.generateHashForRequest = function(method, url, nonce) {
|
||||
nonce = nonce || new Buffer(0);
|
||||
assert(Buffer.isBuffer(nonce), 'nonce must a buffer');
|
||||
var dataToSign = Buffer.concat([nonce, new Buffer(method), new Buffer(url)]);
|
||||
return bitcore.crypto.Hash.sha256sha256(dataToSign);
|
||||
};
|
||||
|
||||
exports.getWalletIdFromName = function(walletName) {
|
||||
if (!Buffer.isBuffer(walletName)) {
|
||||
walletName = new Buffer(walletName, 'utf8');
|
||||
}
|
||||
return bitcore.crypto.Hash.sha256sha256(walletName).toString('hex');
|
||||
};
|
||||
|
||||
exports.isRangeMoreThan = function(a, b) {
|
||||
if (a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a && !b) {
|
||||
return false;
|
||||
}
|
||||
if (!a && b) {
|
||||
return false;
|
||||
}
|
||||
if (a.height > b.height) {
|
||||
return true;
|
||||
} else if (a.height < b.height) {
|
||||
return false;
|
||||
} else {
|
||||
return a.index > b.index;
|
||||
}
|
||||
};
|
||||
|
||||
exports.toHexBuffer = function(a) {
|
||||
if (!Buffer.isBuffer(a)) {
|
||||
a = new Buffer(a, 'hex');
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.toIntIfNumberLike = function(a) {
|
||||
if (!/[^\d]+/.test(a)) {
|
||||
return parseInt(a);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.delimitedStringParse = function(delim, str) {
|
||||
function tryJSONparse(str) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var ret = [];
|
||||
|
||||
if (delim === null) {
|
||||
return tryJSONparse(str);
|
||||
}
|
||||
|
||||
var list = str.split(delim);
|
||||
for(var i = 0; i < list.length; i++) {
|
||||
ret.push(tryJSONparse(list[i]));
|
||||
}
|
||||
ret = _.compact(ret);
|
||||
return ret.length === 0 ? false : ret;
|
||||
|
||||
};
|
||||
|
||||
exports.diffTime = function(time) {
|
||||
var diff = process.hrtime(time);
|
||||
return (diff[0] * 1E9 + diff[1])/(1E9 * 1.0);
|
||||
};
|
||||
|
||||
/*
|
||||
* input: string representing a number + multiple of bytes, e.g. 500MB, 200KB, 100B
|
||||
* output: integer representing the byte count
|
||||
*/
|
||||
exports.parseByteCount = function(byteCountString) {
|
||||
|
||||
function finish(n, m) {
|
||||
var num = parseInt(n);
|
||||
if (num > 0) {
|
||||
return num * m;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_.isString(byteCountString)) {
|
||||
return byteCountString;
|
||||
}
|
||||
var str = byteCountString.replace(/\s+/g, '');
|
||||
var map = { 'MB': 1E6, 'kB': 1000, 'KB': 1000, 'MiB': (1024 * 1024),
|
||||
'KiB': 1024, 'GiB': Math.pow(1024, 3), 'GB': 1E9 };
|
||||
var keys = Object.keys(map);
|
||||
for(var i = 0; i < keys.length; i++) {
|
||||
var re = new RegExp(keys[i] + '$');
|
||||
var match = str.match(re);
|
||||
if (match) {
|
||||
var num = str.slice(0, match.index);
|
||||
return finish(num, map[keys[i]]);
|
||||
}
|
||||
}
|
||||
return finish(byteCountString, 1);
|
||||
};
|
||||
|
||||
/*
|
||||
* input: arguments passed into originating function (whoever called us)
|
||||
* output: bool args are valid for encoding a key to the database
|
||||
*/
|
||||
exports.hasRequiredArgsForEncoding = function(args) {
|
||||
function exists(arg) {
|
||||
return !(arg === null || arg === undefined);
|
||||
}
|
||||
|
||||
if (!exists(args[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var pastArgMissing;
|
||||
|
||||
for(var i = 1; i < args.length; i++) {
|
||||
var argMissing = exists(args[i]);
|
||||
if (argMissing && pastArgMissing) {
|
||||
return false;
|
||||
}
|
||||
pastArgMissing = argMissing;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.toJSONL = function(obj) {
|
||||
//this should be a standard obj that JSON.stringify will handle
|
||||
//general newlines within key values or data values are not permitted
|
||||
//this is intended to be used for bitcoin tx's that don't have newlines
|
||||
//within keys or values themselves
|
||||
var str = JSON.stringify(obj);
|
||||
str = str.replace(/\n/g, '');
|
||||
return str + '\n';
|
||||
};
|
||||
|
||||
module.exports = exports;
|
||||
@ -1,225 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var _ = require('lodash');
|
||||
var utils = require('./utils');
|
||||
|
||||
var MAX_INT = 0xffffffff; // Math.pow(2, 32) - 1
|
||||
|
||||
exports.sanitizeRangeOptions = function(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
options.height = options.height || 0;
|
||||
options.index = options.index || 0;
|
||||
|
||||
if (!options.limit) {
|
||||
options.limit = 10;
|
||||
} else if (options.limit > 500) {
|
||||
throw new Error('Limit exceeds maximum');
|
||||
}
|
||||
|
||||
assert(bitcore.util.js.isNaturalNumber(options.height), '"height" is expected to be a natural number');
|
||||
assert(bitcore.util.js.isNaturalNumber(options.index), '"index" is expected to be a natural number');
|
||||
assert(bitcore.util.js.isNaturalNumber(options.limit), '"limit" is expected to be a natural number');
|
||||
|
||||
assert(options.limit <= 500, '"limit" exceeds maximum');
|
||||
|
||||
if (options.end) {
|
||||
assert(bitcore.util.js.isNaturalNumber(options.end.height), '"end height" is expected to be a natural number');
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
exports.checkRangeParams = function(req, res, next) {
|
||||
assert(req.bitcoinHeight, '"bitcoinHeight" is expected to be set on the request');
|
||||
|
||||
var range = {
|
||||
height: parseInt(req.query.height),
|
||||
index: parseInt(req.query.index),
|
||||
limit: parseInt(req.query.limit),
|
||||
end: {
|
||||
height: req.bitcoinHeight,
|
||||
index: MAX_INT
|
||||
}
|
||||
};
|
||||
|
||||
if (req.query.end) {
|
||||
range.end.height = parseInt(req.query.end) || req.bitcoinHeight;
|
||||
}
|
||||
|
||||
try {
|
||||
range = exports.sanitizeRangeOptions(range);
|
||||
} catch(e) {
|
||||
return utils.sendError({
|
||||
message: 'Invalid params: ' + e.message,
|
||||
statusCode: 400
|
||||
}, res);
|
||||
}
|
||||
|
||||
assert(range.height <= range.end.height, '\'Height\' param required to be less than \'End\' param.');
|
||||
req.range = range;
|
||||
next();
|
||||
};
|
||||
|
||||
exports.checkAddress = function(req, res, next) {
|
||||
var address;
|
||||
var addressStr;
|
||||
|
||||
if (req.body.address) {
|
||||
addressStr = req.body.address;
|
||||
} else {
|
||||
addressStr = req.params.address;
|
||||
}
|
||||
|
||||
if(!addressStr) {
|
||||
return utils.sendError({
|
||||
message: 'Address param is expected',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
}
|
||||
|
||||
assert(req.network, '"network" is expected to be set on the request');
|
||||
|
||||
try {
|
||||
address = new bitcore.Address(addressStr, req.network);
|
||||
} catch(e) {
|
||||
return utils.sendError({
|
||||
message: 'Invalid address: ' + e.message,
|
||||
statusCode: 400
|
||||
}, res);
|
||||
}
|
||||
|
||||
req.address = address;
|
||||
next();
|
||||
};
|
||||
|
||||
exports.checkWalletId = function(req, res, next) {
|
||||
|
||||
if (!req.params.walletId) {
|
||||
return utils.sendError({
|
||||
message: 'Wallet id is expected',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
}
|
||||
|
||||
if (req.params.walletId.length !== 64 || !bitcore.util.js.isHexa(req.params.walletId)) {
|
||||
return utils.sendError({
|
||||
message: 'Wallet id is expected to be a hexadecimal string with length of 64',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
}
|
||||
|
||||
req.walletId = new Buffer(req.params.walletId, 'hex');
|
||||
next();
|
||||
|
||||
};
|
||||
|
||||
exports.checkAddresses = function(req, res, next) {
|
||||
|
||||
if (!req.file && req.body) {
|
||||
req.addresses = req.body;
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!req.file || !req.file.buffer) {
|
||||
generateError(406, 'Content-Type must be set to multipart/form' +
|
||||
' and addresses key and value must be given.');
|
||||
return;
|
||||
}
|
||||
var buf = req.file.buffer;
|
||||
var bufString = buf.toString();
|
||||
if (bufString.slice(-1) === ',') {
|
||||
var bufString = '[' + bufString.slice(0,-1) + ']';
|
||||
}
|
||||
req.addresses = parse(bufString);
|
||||
if (!req.addresses) {
|
||||
generateError(415, 'Could not parse addresses buffer into something meaningful.');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
|
||||
function generateError(status, msg) {
|
||||
res.status(status).jsonp({
|
||||
error: msg
|
||||
});
|
||||
}
|
||||
|
||||
//we are able to deal with json/jsonl, possibly others
|
||||
function parse(string) {
|
||||
var ret = false;
|
||||
var delims = [null, '\n', ' '];
|
||||
for(var i = 0; i < delims.length; i++) {
|
||||
ret = utils.delimitedStringParse(delims[i], string);
|
||||
if (_.isArray(ret)) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
exports.checkAuthHeaders = function(req, res) {
|
||||
var identity = req.header('x-identity');
|
||||
var signature = req.header('x-signature');
|
||||
var nonce = req.header('x-nonce');
|
||||
if (identity && (identity.length > 130 || !bitcore.util.js.isHexa(identity))) {
|
||||
utils.sendError({
|
||||
message: 'x-identity is expected to be a hexadecimal string with length of less than 131',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
return false;
|
||||
}
|
||||
if (signature && (signature.length > 142 || !bitcore.util.js.isHexa(signature))) {
|
||||
utils.sendError({
|
||||
message: 'x-signature is expected to be a hexadecimal string with length of less than 143',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
return false;
|
||||
}
|
||||
if (nonce && (nonce.length > 128 || nonce.length % 2 !== 0 || !bitcore.util.js.isHexa(nonce))) {
|
||||
utils.sendError({
|
||||
message: 'x-nonce is expected to be a hexadecimal string with length of less than 129',
|
||||
statusCode: 400
|
||||
}, res);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.checkDate = function(dateStrings) {
|
||||
var errors = [];
|
||||
if (!Array.isArray(dateStrings)) {
|
||||
dateStrings = [dateStrings];
|
||||
}
|
||||
for(var i = 0; i < dateStrings.length; i++) {
|
||||
internalDateCheck(dateStrings[i]);
|
||||
}
|
||||
|
||||
function internalDateCheck(dateString) {
|
||||
var date = new Date(utils.toIntIfNumberLike(dateString));
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
errors.push('The date supplied: \'' + dateString +
|
||||
'\' is not a valid date string. A valid date could be: \'2016-09-01\'.');
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
exports.checkDateFunction = function(callback) {
|
||||
var self = this;
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var errors = self.checkDate([args[1], args[2]]);
|
||||
if (errors.length > 0) {
|
||||
args.unshift(errors);
|
||||
} else {
|
||||
args.unshift(null);
|
||||
}
|
||||
callback.apply(null, args);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = exports;
|
||||
@ -1,21 +0,0 @@
|
||||
var bcoin = require('bcoin').set('main');
|
||||
|
||||
var node = bcoin.fullnode({
|
||||
checkpoints: true,
|
||||
// Primary wallet passphrase
|
||||
logLevel: 'info'
|
||||
});
|
||||
|
||||
// We get a lot of errors sometimes,
|
||||
// usually from peers hanging up on us.
|
||||
// Just ignore them for now.
|
||||
node.on('error', function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
// Start the node
|
||||
node.open().then(function() {
|
||||
node.connect().then(function() {
|
||||
node.startSync();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user