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() {
return [
['getBalance', this, this.getBalance, 2],
['getOutputs', this, this.getOutputs, 2],
['getUtxos', this, this.getUtxos, 2],
['getInputForOutput', this, this.getInputForOutput, 2],
['isSpent', this, this.isSpent, 2],
['getAddressHistory', this, this.getAddressHistory, 2],
['getAddressSummary', this, this.getAddressSummary, 1]
//['getBalance', this, this.getBalance, 2],
//['getOutputs', this, this.getOutputs, 2],
//['getUtxos', this, this.getUtxos, 2],
//['getInputForOutput', this, this.getInputForOutput, 2],
//['getAddressHistory', this, this.getAddressHistory, 2],
//['getAddressSummary', this, this.getAddressSummary, 1],
['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1]
];
};
@ -87,117 +87,104 @@ AddressService.prototype._getAddress = function(opts, item) {
};
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;
AddressService.prototype._processInput = function(opts, input) {
var address = this._getAddress(opts, input);
if (!address) {
return setImemdiate(cb);
return;
}
var action = self._getAddress(opts.connect);
var operations = [];
// 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({
type: action.action,
var operations = [{
type: opts.action,
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) {
log.debug('Error saving tx inputs.');
return self._emit('error', err);
}
operations.push(rec);
var utxo = tx.outputs[input.outputIndex];
// 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;
});
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) {
continue;
return;
}
var addressKey = self._encoding.encodeAddressIndexKey(address, block.height, txid);
var utxoKey = self._encoding.encodeUtxoIndexKey(address, txid, outputIndex);
var utxoValue = self._encoding.encodeUtxoIndexValue(block.height, output.satoshis, output._scriptBuffer);
var txid = tx.id;
var addressKey = this._encoding.encodeAddressIndexKey(address, opts.block.height, txid);
var utxoKey = this._encoding.encodeUtxoIndexKey(address, txid, index);
var utxoValue = this._encoding.encodeUtxoIndexValue(opts.block.height, output.satoshis, output._scriptBuffer);
operations.push({
type: action,
var operations = [{
type: opts.action,
key: addressKey
});
}];
operations.push({
type: action,
type: opts.action,
key: utxoKey,
value: utxoValue
});
};
AddressService.prototype._processTransactions = function(opts, tx) {
AddressService.prototype._processTransaction = function(opts, tx) {
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) {
return self._processOutput(tx, opts);
if (!opts.connect) {
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(tx) {
self._processInput(tx, opts, self._onOperations.bind(self));
var inputOperations = tx.inputs.map(function(input) {
self._processInput(tx, input, _opts);
});
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) {
@ -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) {
var address = script.toAddress(this.node.network.name);
if(address) {
@ -1014,7 +992,7 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
var txids = [];
async.eachLimit(addresses, self.concurrency, function(address, next) {
async.eachLimit(addresses, 4, function(address, next) {
self.getAddressTxids(address, options, function(err, tmpTxids) {
if(err) {
return next(err);
@ -1024,7 +1002,7 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
return next();
});
}, 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) {
if(err) {
return next(err);
@ -1104,22 +1082,94 @@ AddressService.prototype.getAddressTxidsWithHeights = function(address, options,
});
};
/**
* This will give an object with:
* balance - confirmed balance
* unconfirmedBalance - unconfirmed balance
* totalReceived - satoshis received
* totalSpent - satoshis spent
* appearances - number of transactions
* unconfirmedAppearances - number of unconfirmed transactions
* txids - list of txids (unless noTxList is set)
*
* @param {String} address
* @param {Object} options
* @param {Boolean} [options.noTxList] - if set, txid array will not be included
* @param {Function} callback
*/
AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
var addresses = utils._normalizeAddressArg(address);
var cacheKey = addresses.join('');
var utxos = this.utxosCache.get(cacheKey);
function transformUnspentOutput(delta) {
var script = bitcore.Script.fromAddress(delta.address);
return {
address: delta.address,
txid: delta.txid,
outputIndex: delta.index,
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) {
var self = this;
var startTime = new Date();

View File

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