wip
This commit is contained in:
parent
c837456761
commit
b5d67135ae
@ -1,68 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
|
||||
function Encoding(servicePrefix) {
|
||||
this.servicePrefix = servicePrefix;
|
||||
}
|
||||
|
||||
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid) {
|
||||
var prefix = new Buffer('00', 'hex');
|
||||
var buffers = [this.servicePrefix, prefix];
|
||||
|
||||
var addressSizeBuffer = new Buffer(1);
|
||||
addressSizeBuffer.writeUInt8(address.length);
|
||||
var addressBuffer = new Buffer(address, 'utf8');
|
||||
|
||||
buffers.push(addressSizeBuffer);
|
||||
buffers.push(addressBuffer);
|
||||
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height || 0);
|
||||
buffers.push(heightBuffer);
|
||||
|
||||
var txidBuffer = new Buffer(txid || Array(65).join('0'), 'hex');
|
||||
buffers.push(txidBuffer);
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeAddressIndexKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
|
||||
var addressSize = reader.readUInt8();
|
||||
var address = reader.read(addressSize).toString('utf8');
|
||||
var height = reader.readUInt32BE();
|
||||
var txid = reader.read(32).toString('hex');
|
||||
return {
|
||||
address: address,
|
||||
height: height,
|
||||
txid: txid,
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
|
||||
var prefix = new Buffer('01', 'hex');
|
||||
var buffers = [this.servicePrefix, prefix];
|
||||
|
||||
var addressSizeBuffer = new Buffer(1);
|
||||
addressSizeBuffer.writeUInt8(address.length);
|
||||
var addressBuffer = new Buffer(address, 'utf8');
|
||||
|
||||
buffers.push(addressSizeBuffer);
|
||||
buffers.push(addressBuffer);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
module.exports = Encoding;
|
||||
|
||||
@ -1,343 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var BaseService = require('../../service');
|
||||
var inherits = require('util').inherits;
|
||||
var async = require('async');
|
||||
var index = require('../../');
|
||||
var log = index.log;
|
||||
var errors = index.errors;
|
||||
var _ = require('lodash');
|
||||
var Encoding = require('./encoding');
|
||||
var utils = require('../../utils');
|
||||
|
||||
var AddressService = function(options) {
|
||||
BaseService.call(this, options);
|
||||
};
|
||||
|
||||
inherits(AddressService, BaseService);
|
||||
|
||||
AddressService.dependencies = ['transaction'];
|
||||
|
||||
AddressService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
this.db = this.node.services.db;
|
||||
this.db.getPrefix(this.name, function(err, prefix) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.prefix = prefix;
|
||||
|
||||
self._encoding = new Encoding(self.prefix);
|
||||
self._setListeners();
|
||||
callback();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.stop = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
AddressService.prototype.getAPIMethods = function() {
|
||||
return [
|
||||
['getBalance', this, this.getBalance, 2],
|
||||
['getOutputs', this, this.getOutputs, 2],
|
||||
['getUtxos', this, this.getUtxos, 2],
|
||||
['getAddressHistory', this, this.getAddressHistory, 2],
|
||||
['getAddressSummary', this, this.getAddressSummary, 1]
|
||||
];
|
||||
};
|
||||
|
||||
AddressService.prototype.getPublishEvents = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
AddressService.prototype._setupListeners = function() {
|
||||
this._startSubscriptions();
|
||||
};
|
||||
|
||||
AddressService.prototype._startSubscriptions = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (self._subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._subscribed = true;
|
||||
self.bus = self.node.openBus({remoteAddress: 'localhost'});
|
||||
|
||||
self.bus.subscribe('block/reorg');
|
||||
self.bus.on('block/block', self._onBlock.bind(self));
|
||||
self.bus.on('block/reorg', self._onReorg.bind(self));
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._onBlock = function(block) {
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._onReorg = function(commonAncestorBlock) {
|
||||
};
|
||||
|
||||
|
||||
AddressService.prototype.subscribe = function(name, emitter, addresses) {
|
||||
|
||||
for(var i = 0; i < addresses.length; i++) {
|
||||
var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex');
|
||||
if(!this.subscriptions[name][hashHex]) {
|
||||
this.subscriptions[name][hashHex] = [];
|
||||
}
|
||||
this.subscriptions[name][hashHex].push(emitter);
|
||||
}
|
||||
};
|
||||
|
||||
AddressService.prototype.unsubscribe = function(name, emitter, addresses) {
|
||||
|
||||
if(!addresses) {
|
||||
return this.unsubscribeAll(name, emitter);
|
||||
}
|
||||
|
||||
for(var i = 0; i < addresses.length; i++) {
|
||||
var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex');
|
||||
if(this.subscriptions[name][hashHex]) {
|
||||
var emitters = this.subscriptions[name][hashHex];
|
||||
var index = emitters.indexOf(emitter);
|
||||
if(index > -1) {
|
||||
emitters.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddressService.prototype.unsubscribeAll = function(name, emitter) {
|
||||
|
||||
for(var hashHex in this.subscriptions[name]) {
|
||||
var emitters = this.subscriptions[name][hashHex];
|
||||
var index = emitters.indexOf(emitter);
|
||||
if(index > -1) {
|
||||
emitters.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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.isUnspent = function(output, options, callback) {
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var txids = [];
|
||||
|
||||
async.eachLimit(addresses, self.concurrency, function(address, next) {
|
||||
self.getAddressTxids(address, options, function(err, tmpTxids) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
txids = _.union(txids, tmpTxids);
|
||||
return next();
|
||||
});
|
||||
}, function() {
|
||||
async.mapLimit(txids, self.concurrency, function(txid, next) {
|
||||
self.node.services.transaction.getTransaction(txid.toString('hex'), options, function(err, tx) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var txObj = tx.toObject();
|
||||
for(var i = 0; i < txObj.inputs.length; i++) {
|
||||
txObj.inputs[i].satoshis = tx.__inputValues[i];
|
||||
}
|
||||
|
||||
next(null, txObj);
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressTxids = function(address, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var opts = options || { start: 0, end: 0xffffffff, txid: new Array(65).join('0') };
|
||||
var txids = {};
|
||||
|
||||
var start = self._encoding.encodeAddressIndexKey(address, opts.start, opts.txid);
|
||||
var end = self._encoding.encodeAddressIndexKey(address, opts.end, opts.txid);
|
||||
|
||||
var stream = self.db.createKeyStream({
|
||||
gte: start,
|
||||
lt: end
|
||||
});
|
||||
|
||||
var streamErr = null;
|
||||
stream.on('close', function() {
|
||||
});
|
||||
|
||||
stream.on('data', function(buffer) {
|
||||
var key = self._encoding.decodeAddressIndexKey(buffer);
|
||||
txids[key.txid] = true;
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
callback(streamErr, Object.keys(txids));
|
||||
});
|
||||
|
||||
stream.on('error', function(err) {
|
||||
streamErr = 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;
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressSummary = function(addressArg, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var startTime = new Date();
|
||||
var address = new Address(addressArg);
|
||||
|
||||
if (_.isUndefined(options.queryMempool)) {
|
||||
options.queryMempool = true;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self._getAddressConfirmedSummary(address, options, next);
|
||||
},
|
||||
function(result, next) {
|
||||
self._getAddressMempoolSummary(address, options, result, next);
|
||||
},
|
||||
function(result, next) {
|
||||
self._setAndSortTxidsFromAppearanceIds(result, next);
|
||||
}
|
||||
], function(err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var summary = self._transformAddressSummaryFromResult(result, options);
|
||||
|
||||
var timeDelta = new Date() - startTime;
|
||||
if (timeDelta > 5000) {
|
||||
var seconds = Math.round(timeDelta / 1000);
|
||||
log.warn('Slow (' + seconds + 's) getAddressSummary request for address: ' + address.toString());
|
||||
}
|
||||
|
||||
callback(null, summary);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
module.exports = AddressService;
|
||||
@ -1,55 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
|
||||
function Encoding(servicePrefix) {
|
||||
//if you add any more encoding keys, be sure to add a subkey
|
||||
this.servicePrefix = servicePrefix;
|
||||
}
|
||||
|
||||
Encoding.prototype.encodeTransactionKey = function(txid) {
|
||||
return Buffer.concat([this.servicePrefix, new Buffer(txid, 'hex')]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeTransactionKey = function(buffer) {
|
||||
return buffer.slice(2).toString('hex');
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeTransactionValue = function(transaction) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(transaction.__height);
|
||||
|
||||
var timestampBuffer = new Buffer(8);
|
||||
timestampBuffer.writeDoubleBE(transaction.__timestamp);
|
||||
|
||||
var inputValues = transaction.__inputValues;
|
||||
var inputValuesBuffer = new Buffer(8 * inputValues.length);
|
||||
for(var i = 0; i < inputValues.length; i++) {
|
||||
inputValuesBuffer.writeDoubleBE(inputValues[i], i * 8);
|
||||
}
|
||||
|
||||
var inputValuesLengthBuffer = new Buffer(2);
|
||||
inputValuesLengthBuffer.writeUInt16BE(inputValues.length);
|
||||
|
||||
return new Buffer.concat([heightBuffer, timestampBuffer,
|
||||
inputValuesLengthBuffer, inputValuesBuffer, transaction.toBuffer()]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeTransactionValue = function(buffer) {
|
||||
var height = buffer.readUInt32BE();
|
||||
var timestamp = buffer.readDoubleBE(4);
|
||||
|
||||
var inputValuesLength = buffer.readUInt16BE(12);
|
||||
var inputValues = [];
|
||||
for(var i = 0; i < inputValuesLength; i++) {
|
||||
inputValues.push(buffer.readDoubleBE(i * 8 + 14));
|
||||
}
|
||||
var transaction = new bitcore.Transaction(buffer.slice(inputValues.length * 8 + 14));
|
||||
transaction.__height = height;
|
||||
transaction.__inputValues = inputValues;
|
||||
transaction.__timestamp = timestamp;
|
||||
return transaction;
|
||||
};
|
||||
|
||||
module.exports = Encoding;
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var async = require('async');
|
||||
var BaseService = require('../../service');
|
||||
var inherits = require('util').inherits;
|
||||
var Encoding = require('./encoding');
|
||||
var levelup = require('levelup');
|
||||
|
||||
function TransactionService(options) {
|
||||
BaseService.call(this, options);
|
||||
this.concurrency = options.concurrency || 20;
|
||||
this.currentTransactions = {};
|
||||
}
|
||||
|
||||
inherits(TransactionService, BaseService);
|
||||
|
||||
TransactionService.dependencies = [
|
||||
'db',
|
||||
'timestamp',
|
||||
'mempool'
|
||||
];
|
||||
|
||||
TransactionService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
self.db = this.node.services.db;
|
||||
|
||||
self.db.getPrefix(self.name, function(err, prefix) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
self._setListeners();
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
TransactionService.prototype.stop = function(callback) {
|
||||
setImmediate(callback);
|
||||
};
|
||||
|
||||
|
||||
TransactionService.prototype._setListeners = function() {
|
||||
this._startSubscriptions();
|
||||
};
|
||||
|
||||
TransactionService.prototype._startSubscriptions = function() {
|
||||
var self = this;
|
||||
|
||||
if (self._subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._subscribed = true;
|
||||
self.bus = self.node.openBus({remoteAddress: 'localhost'});
|
||||
|
||||
self.bus.on('block/block', self._onBlock.bind(self));
|
||||
self.bus.subscribe('block/block');
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype._onBlock = function(block) {
|
||||
};
|
||||
|
||||
TransactionService.prototype._getMissingInputValues = function(tx, callback) {
|
||||
var self = this;
|
||||
|
||||
if (tx.isCoinbase()) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
async.eachOf(tx.inputs, function(input, index, next) {
|
||||
if (tx.__inputValues[index]) {
|
||||
return next();
|
||||
}
|
||||
self.getTransaction(input.prevTxId.toString('hex'), {}, function(err, prevTx) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
if (!prevTx) {
|
||||
return next(new Error('previous Tx missing.'));
|
||||
}
|
||||
if (!prevTx.outputs[input.outputIndex]) {
|
||||
return next(new Error('Input did not have utxo.'));
|
||||
}
|
||||
var satoshis = prevTx.outputs[input.outputIndex].satoshis;
|
||||
tx.__inputValues[index] = satoshis;
|
||||
next();
|
||||
});
|
||||
}, callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype._getInputValues = function(tx, callback) {
|
||||
var self = this;
|
||||
|
||||
if (tx.isCoinbase()) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
async.mapLimit(tx.inputs, this.concurrency, function(input, next) {
|
||||
self.getTransaction(input.prevTxId.toString('hex'), {}, function(err, prevTx) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
if (!prevTx.outputs[input.outputIndex]) {
|
||||
return next(new Error('Input did not have utxo: ' + prevTx.id + ' for tx: ' + tx.id));
|
||||
}
|
||||
var satoshis = prevTx.outputs[input.outputIndex].satoshis;
|
||||
next(null, satoshis);
|
||||
});
|
||||
}, callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype.getTransaction = function(txid, options, callback) {
|
||||
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([
|
||||
function(next) {
|
||||
self.node.services.db.get(key, function(err, buffer) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return next(null, false);
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var tx = self.encoding.decodeTransactionValue(buffer);
|
||||
next(null, tx);
|
||||
});
|
||||
}, function(tx, next) {
|
||||
if (tx) {
|
||||
return next(null, tx);
|
||||
}
|
||||
if (!options || !options.queryMempool) {
|
||||
return next(new Error('Transaction: ' + txid + ' not found in index'));
|
||||
}
|
||||
self.node.services.mempool.getTransaction(txid, function(err, tx) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return callback(new Error('Transaction: ' + txid + ' not found in index or mempool'));
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self._getMissingInputValues(tx, next);
|
||||
});
|
||||
}], callback);
|
||||
};
|
||||
|
||||
|
||||
module.exports = TransactionService;
|
||||
@ -1,131 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
|
||||
function Encoding(servicePrefix) {
|
||||
this.servicePrefix = servicePrefix;
|
||||
this.nonP2PKPrefix = new Buffer('00', 'hex');
|
||||
this.P2PKPrefix = new Buffer('01', 'hex');
|
||||
}
|
||||
|
||||
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
|
||||
|
||||
assert(address, 'address is required');
|
||||
var buffers = [this.servicePrefix, this.nonP2PKPrefix];
|
||||
|
||||
var addressSizeBuffer = new Buffer(1);
|
||||
addressSizeBuffer.writeUInt8(address.length);
|
||||
var addressBuffer = new Buffer(address, 'utf8');
|
||||
|
||||
buffers.push(addressSizeBuffer);
|
||||
buffers.push(addressBuffer);
|
||||
|
||||
if (txid) {
|
||||
var txidBuffer = new Buffer(txid, 'hex');
|
||||
buffers.push(txidBuffer);
|
||||
}
|
||||
|
||||
if (outputIndex >= 0) {
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
buffers.push(outputIndexBuffer);
|
||||
}
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeUtxoIndexKey = function(buffer) {
|
||||
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
|
||||
var addressSize = reader.readUInt8();
|
||||
var address = reader.read(addressSize).toString('utf8');
|
||||
var txid = reader.read(32).toString('hex');
|
||||
var outputIndex = reader.readUInt32BE(4);
|
||||
|
||||
return {
|
||||
address: address,
|
||||
txid: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeUtxoIndexValue = 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.decodeUtxoIndexValue = function(buffer) {
|
||||
|
||||
var height = buffer.readUInt32BE();
|
||||
var satoshis = buffer.readDoubleBE(4);
|
||||
var scriptBuffer = buffer.slice(12);
|
||||
return {
|
||||
height: height,
|
||||
satoshis: satoshis,
|
||||
script: scriptBuffer
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeP2PKUtxoIndexKey = function(txid, outputIndex) {
|
||||
|
||||
assert(txid, 'txid is required');
|
||||
var buffers = [this.servicePrefix, this.P2PKPrefix];
|
||||
|
||||
var txidBuffer = new Buffer(txid);
|
||||
buffers.push(txidBuffer);
|
||||
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
buffers.push(outputIndexBuffer);
|
||||
|
||||
return Buffer.concat(buffers);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeP2PKUtxoIndexKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
reader.read(3);
|
||||
|
||||
var addressSize = reader.readUInt8();
|
||||
var address = reader.read(addressSize).toString('utf8');
|
||||
var txid = reader.read(32).toString('hex');
|
||||
var outputIndex = reader.readUInt32BE(4);
|
||||
|
||||
return {
|
||||
address: address,
|
||||
txid: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeP2PKUtxoIndexValue = 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.decodeP2PKUtxoIndexValue = function(buffer) {
|
||||
var height = buffer.readUInt32BE();
|
||||
var satoshis = buffer.readDoubleBE(4);
|
||||
var scriptBuffer = buffer.slice(12);
|
||||
return {
|
||||
height: height,
|
||||
satoshis: satoshis,
|
||||
script: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Encoding;
|
||||
|
||||
@ -1,209 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var BaseService = require('../../service');
|
||||
var inherits = require('util').inherits;
|
||||
var Encoding = require('./encoding');
|
||||
var utils = require('../../utils');
|
||||
var index = require('../../');
|
||||
var log = index.log;
|
||||
|
||||
function UtxoService(options) {
|
||||
BaseService.call(this, options);
|
||||
this._exclusionIndexes = [];
|
||||
}
|
||||
|
||||
inherits(UtxoService, BaseService);
|
||||
|
||||
UtxoService.dependencies = ['db'];
|
||||
|
||||
UtxoService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
self.db = this.node.services.db;
|
||||
|
||||
self.db.getPrefix(self.name, function(err, prefix) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
UtxoService.prototype.stop = function(callback) {
|
||||
if (callback) {
|
||||
setImmediate(callback);
|
||||
}
|
||||
};
|
||||
|
||||
UtxoService.prototype.blockHandler = function(block, connect, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._exclusionIndexes.length = 0;
|
||||
var operations = [];
|
||||
|
||||
for(var i = 0; i < block.transactions.length; i++) {
|
||||
|
||||
var tx = block.transactions[i];
|
||||
var inputs = tx.inputs;
|
||||
var outputs = tx.outputs;
|
||||
|
||||
if (!tx.isCoinbase()) {
|
||||
operations = self._processInputs(tx, inputs, connect).concat(operations);
|
||||
}
|
||||
|
||||
operations = self._processOutputs(tx, outputs, block, connect).concat(operations);
|
||||
}
|
||||
|
||||
setImmediate(function() {
|
||||
callback(null, operations);
|
||||
});
|
||||
};
|
||||
|
||||
UtxoService.prototype.getUtxosForAddress = function(address, callback) {
|
||||
|
||||
var self = this;
|
||||
var utxos = [];
|
||||
|
||||
var start = self.encoding.encodeUtxoIndexKey(address);
|
||||
|
||||
var stream = self.db.createReadStream({
|
||||
gte: start,
|
||||
lt: utils.getTerminalKey(start)
|
||||
});
|
||||
|
||||
stream.on('data', function(data) {
|
||||
|
||||
var key = self.encoding.decodeUtxoIndexKey(data.key);
|
||||
var value = self.encoding.decodeUtxoIndexValue(data.value);
|
||||
utxos.push({
|
||||
address: address,
|
||||
txId: key.txid,
|
||||
outputIndex: key.outputIndex,
|
||||
height: value.height,
|
||||
satoshis: value.satoshis,
|
||||
script: value.script.toString('hex')
|
||||
});
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
callback(null, utxos);
|
||||
});
|
||||
};
|
||||
|
||||
UtxoService.prototype._processInputs = function(tx, inputs, connect) {
|
||||
|
||||
var operations = [];
|
||||
for(var i = 0; i < inputs.length; i++) {
|
||||
|
||||
var input = inputs[i];
|
||||
|
||||
if (input.prevTxId === tx.hash) {
|
||||
this._exlusionIndexes.push(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = this._getOperationsKey({
|
||||
script: input.script,
|
||||
txid: input.prevTxId.toString('hex'),
|
||||
index: input.outputIndex
|
||||
});
|
||||
|
||||
if (key) {
|
||||
var operation = connect ? {
|
||||
type: 'del',
|
||||
key: key
|
||||
} : {
|
||||
type: 'put',
|
||||
key: key,
|
||||
value: new Buffer('00', 'hex')
|
||||
};
|
||||
|
||||
operations.push(operation);
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
|
||||
};
|
||||
|
||||
UtxoService.prototype._processOutputs = function(tx, outputs, block, connect) {
|
||||
|
||||
var operations = [];
|
||||
for(var i = 0; i < outputs.length; i++) {
|
||||
|
||||
var output = outputs[i];
|
||||
|
||||
if (this._exclusionIndexes.indexOf(i) > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = this._getOperationsKey({
|
||||
script: output.script,
|
||||
txid: tx.id,
|
||||
index: i
|
||||
});
|
||||
|
||||
var value = this._getOperationsValue({
|
||||
height: block.__height,
|
||||
satoshis: output.satoshis,
|
||||
script: output.script
|
||||
});
|
||||
|
||||
if (key && value) {
|
||||
//console.log(this.encoding.decodeUtxoIndexKey(key));
|
||||
var operation = connect ? {
|
||||
type: 'put',
|
||||
key: key,
|
||||
value: value
|
||||
} : {
|
||||
type: 'del',
|
||||
key: key,
|
||||
value: value
|
||||
};
|
||||
|
||||
operations.push(operation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return operations;
|
||||
|
||||
};
|
||||
|
||||
UtxoService.prototype._getOperationsKey = function(io) {
|
||||
|
||||
var address = utils.getAddressStringFromScript(io.script, this.node.network);
|
||||
|
||||
if (!address) {
|
||||
var key = this._tryP2PKOperation(io);
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
log.debug('could not determine address for script: ' + io.script.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
return this.encoding.encodeUtxoIndexKey(address, io.txid, io.index);
|
||||
};
|
||||
|
||||
UtxoService.prototype._tryP2PKOperation = function(io) {
|
||||
|
||||
// checking for a scriptSig that has one signature
|
||||
var sig = io.script.chunks[0];
|
||||
if (sig && (sig.len > 69 && sig.len < 75)) {
|
||||
return this.encoding.encodeP2PKUtxoIndexKey(io.txid, io.index);
|
||||
}
|
||||
};
|
||||
|
||||
UtxoService.prototype._getOperationsValue = function(value) {
|
||||
return this.encoding.encodeUtxoIndexValue(value.height, value.satoshis, value.script.toBuffer());
|
||||
};
|
||||
|
||||
module.exports = UtxoService;
|
||||
Loading…
Reference in New Issue
Block a user