This commit is contained in:
Chris Kleeschulte 2017-07-11 17:16:23 -04:00
parent 5de265e094
commit 5e0b2b1f11
2 changed files with 164 additions and 104 deletions

View File

@ -50,13 +50,13 @@ AddressService.prototype.stop = function(callback) {
AddressService.prototype.getAPIMethods = function() { AddressService.prototype.getAPIMethods = function() {
return [ return [
['getBalance', this, this.getBalance, 2], //['getBalance', this, this.getBalance, 2],
['getOutputs', this, this.getOutputs, 2], //['getOutputs', this, this.getOutputs, 2],
['getUtxos', this, this.getUtxos, 2], //['getUtxos', this, this.getUtxos, 2],
['getInputForOutput', this, this.getInputForOutput, 2], //['getInputForOutput', this, this.getInputForOutput, 2],
['isSpent', this, this.isSpent, 2], //['getAddressHistory', this, this.getAddressHistory, 2],
['getAddressHistory', this, this.getAddressHistory, 2], //['getAddressSummary', this, this.getAddressSummary, 1],
['getAddressSummary', this, this.getAddressSummary, 1] ['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1]
]; ];
}; };
@ -87,117 +87,104 @@ AddressService.prototype._getAddress = function(opts, item) {
}; };
AddressService.prototype._processInput = function(opts, input) {
AddressService.prototype._getActions = function(connect) {
var action = 'put';
var reverseAction = 'del';
if (!connect) {
action = 'del';
reverseAction = 'put';
}
return { action: action, reverseAction: reverseAction };
};
AddressService.prototype._processInput = function(opts, input, cb) {
var self = this;
var address = this._getAddress(opts, input); var address = this._getAddress(opts, input);
if (!address) { if (!address) {
return setImemdiate(cb); return;
} }
var action = self._getAddress(opts.connect);
var operations = [];
// address index // address index
var addressKey = self._encoding.encodeAddressIndexKey(address, opts.block.height, opts.tx.id); var addressKey = this._encoding.encodeAddressIndexKey(address, opts.block.height, opts.tx.id);
operations.push({ var operations = [{
type: action.action, type: opts.action,
key: addressKey key: addressKey
}); }];
var prevTxId = input.prevTxId.toString('hex'); // 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)
};
self._txService.getTransaction(prevTxId, {}, function(err, tx) { // In the event where we are reorg'ing,
// this is where we are putting a utxo back in, we don't know what the original height, sats, or scriptBuffer
// since this only happens on reorg and the utxo that was spent in the chain we are reorg'ing away from will likely
// be spent again sometime soon, we will not add the value back in, just the key
if (err) { operations.push(rec);
log.debug('Error saving tx inputs.');
return self._emit('error', err);
}
var utxo = tx.outputs[input.outputIndex]; return operations;
// utxo index
// prev utxo
var oldUtxoKey = self._encoding.encodeUtxoIndexKey(address, tx.id, input.outputIndex);
// remove the old utxo
operations.push({
type: action.reverseAction,
key: utxaKxey,
value: inputValue
});
return operations;
});
}; };
AddressService.prototype._processOutput = function(txid, output) { AddressService.prototype._processOutput = function(tx, output, index, opts) {
var address = self.getAddressString(script); var address = this.getAddressString(output.script);
if(!address) { if(!address) {
continue; return;
} }
var addressKey = self._encoding.encodeAddressIndexKey(address, block.height, txid); var txid = tx.id;
var utxoKey = self._encoding.encodeUtxoIndexKey(address, txid, outputIndex); var addressKey = this._encoding.encodeAddressIndexKey(address, opts.block.height, txid);
var utxoValue = self._encoding.encodeUtxoIndexValue(block.height, output.satoshis, output._scriptBuffer); var utxoKey = this._encoding.encodeUtxoIndexKey(address, txid, index);
var utxoValue = this._encoding.encodeUtxoIndexValue(opts.block.height, output.satoshis, output._scriptBuffer);
operations.push({ var operations = [{
type: action, type: opts.action,
key: addressKey key: addressKey
}); }];
operations.push({ operations.push({
type: action, type: opts.action,
key: utxoKey, key: utxoKey,
value: utxoValue value: utxoValue
}); });
}; };
AddressService.prototype._processTransactions = function(opts, tx) { AddressService.prototype._processTransaction = function(opts, tx) {
var self = this; var self = this;
var txid = tx.id;
var _opts = { opts.block, connect: connect ? true : false }; var action = 'put';
var reverseAction = 'del';
var outputOperations = tx.outputs.map(function(tx) { if (!opts.connect) {
return self._processOutput(tx, opts); action = 'del';
reverseAction = 'put';
}
var _opts = { block: opts.block, action: action, reverseAction: reverseAction };
var outputOperations = tx.outputs.map(function(output, index) {
return self._processOutput(tx, output, index, _opts);
}); });
opts.outputOperations = outputOperations; outputOperations = _.flatten(_.compact(outputOperations));
// this is async because we need to look up a utxo var inputOperations = tx.inputs.map(function(input) {
var inputOperations = tx.inputs.map(function(tx) { self._processInput(tx, input, _opts);
self._processInput(tx, opts, self._onOperations.bind(self));
}); });
inputOperations = _.flatten(_.compact(inputOperations));
return outputOperations.concat(inputOperations);
}; };
AddressService.prototype._onOperations = function(operations) { 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) { if (operations && operations.length > 0) {
@ -215,15 +202,6 @@ AddressService.prototype._onOperations = function(operations) {
}; };
AddressService.prototype._onBlock = function(block, connect) {
var self = this;
self._processTransactions(txs, { block: block, connect: connect });
};
AddressService.prototype.getAddressString = function(script, output) { AddressService.prototype.getAddressString = function(script, output) {
var address = script.toAddress(this.node.network.name); var address = script.toAddress(this.node.network.name);
if(address) { if(address) {
@ -1014,7 +992,7 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
var txids = []; var txids = [];
async.eachLimit(addresses, self.concurrency, function(address, next) { async.eachLimit(addresses, 4, function(address, next) {
self.getAddressTxids(address, options, function(err, tmpTxids) { self.getAddressTxids(address, options, function(err, tmpTxids) {
if(err) { if(err) {
return next(err); return next(err);
@ -1024,7 +1002,7 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
return next(); return next();
}); });
}, function() { }, function() {
async.mapLimit(txids, self.concurrency, function(txid, next) { async.mapLimit(txids, 4, function(txid, next) {
self.node.services.transaction.getTransaction(txid.toString('hex'), options, function(err, tx) { self.node.services.transaction.getTransaction(txid.toString('hex'), options, function(err, tx) {
if(err) { if(err) {
return next(err); return next(err);
@ -1104,22 +1082,94 @@ AddressService.prototype.getAddressTxidsWithHeights = function(address, options,
}); });
}; };
/** AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
* This will give an object with:
* balance - confirmed balance var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
* unconfirmedBalance - unconfirmed balance var addresses = utils._normalizeAddressArg(address);
* totalReceived - satoshis received var cacheKey = addresses.join('');
* totalSpent - satoshis spent var utxos = this.utxosCache.get(cacheKey);
* appearances - number of transactions
* unconfirmedAppearances - number of unconfirmed transactions function transformUnspentOutput(delta) {
* txids - list of txids (unless noTxList is set) var script = bitcore.Script.fromAddress(delta.address);
* return {
* @param {String} address address: delta.address,
* @param {Object} options txid: delta.txid,
* @param {Boolean} [options.noTxList] - if set, txid array will not be included outputIndex: delta.index,
* @param {Function} callback script: script.toHex(),
*/ satoshis: delta.satoshis,
timestamp: delta.timestamp
};
}
function updateWithMempool(confirmedUtxos, mempoolDeltas) {
if (!mempoolDeltas || !mempoolDeltas.length) {
return confirmedUtxos;
}
var isSpentOutputs = false;
var mempoolUnspentOutputs = [];
var spentOutputs = [];
for (var i = 0; i < mempoolDeltas.length; i++) {
var delta = mempoolDeltas[i];
if (delta.prevtxid && delta.satoshis <= 0) {
if (!spentOutputs[delta.prevtxid]) {
spentOutputs[delta.prevtxid] = [delta.prevout];
} else {
spentOutputs[delta.prevtxid].push(delta.prevout);
}
isSpentOutputs = true;
} else {
mempoolUnspentOutputs.push(transformUnspentOutput(delta));
}
}
var utxos = mempoolUnspentOutputs.reverse().concat(confirmedUtxos);
if (isSpentOutputs) {
return utxos.filter(function(utxo) {
if (!spentOutputs[utxo.txid]) {
return true;
} else {
return (spentOutputs[utxo.txid].indexOf(utxo.outputIndex) === -1);
}
});
}
return utxos;
}
function finish(mempoolDeltas) {
if (utxos) {
return setImmediate(function() {
callback(null, updateWithMempool(utxos, mempoolDeltas));
});
} else {
self.client.getAddressUtxos({addresses: addresses}, function(err, response) {
if (err) {
return callback(self._wrapRPCError(err));
}
var utxos = response.result.reverse();
self.utxosCache.set(cacheKey, utxos);
callback(null, updateWithMempool(utxos, mempoolDeltas));
});
}
}
if (queryMempool) {
self.client.getAddressMempool({addresses: addresses}, function(err, response) {
if (err) {
return callback(self._wrapRPCError(err));
}
finish(response.result);
});
} else {
finish();
}
};
AddressService.prototype.getAddressSummary = function(addressArg, options, callback) { AddressService.prototype.getAddressSummary = function(addressArg, options, callback) {
var self = this; var self = this;
var startTime = new Date(); var startTime = new Date();

View File

@ -3,6 +3,8 @@
var bitcore = require('bitcore-lib'); var bitcore = require('bitcore-lib');
var BufferUtil = bitcore.util.buffer; var BufferUtil = bitcore.util.buffer;
var MAX_SAFE_INTEGER = 0x1fffffffffffff; // 2 ^ 53 - 1 var MAX_SAFE_INTEGER = 0x1fffffffffffff; // 2 ^ 53 - 1
var crypto = require('crypto');
var _ = require('lodash');
var utils = {}; var utils = {};
utils.isHash = function(value) { utils.isHash = function(value) {
@ -93,7 +95,15 @@ utils.toJSONL = function(obj) {
var str = JSON.stringify(obj); var str = JSON.stringify(obj);
str = str.replace(/\n/g, ''); str = str.replace(/\n/g, '');
return str + '\n'; return str + '\n';
} };
utils.normalizeTimeStamp = function(addressArg) {
var addresses = [addressArg];
if (Array.isArray(addressArg)) {
addresses = addressArg;
}
return addresses;
};
utils.normalizeTimeStamp = function(value) { utils.normalizeTimeStamp = function(value) {
if (value > 0xffffffff) { if (value > 0xffffffff) {