commit
56f375a3b7
|
@ -34,6 +34,7 @@ var testKey;
|
|||
var client;
|
||||
|
||||
var outputForIsSpentTest1;
|
||||
var unspentOutputSpentTxId;
|
||||
|
||||
describe('Node Functionality', function() {
|
||||
|
||||
|
@ -356,6 +357,8 @@ describe('Node Functionality', function() {
|
|||
tx.change(address);
|
||||
tx.sign(testKey);
|
||||
|
||||
unspentOutputSpentTxId = tx.id;
|
||||
|
||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
|
||||
function mineBlock(next) {
|
||||
|
@ -733,9 +736,28 @@ describe('Node Functionality', function() {
|
|||
|
||||
});
|
||||
|
||||
describe('isSpent', function() {
|
||||
describe('#getInputForOutput(db)', function() {
|
||||
it('will get the input txid and input index', function(done) {
|
||||
var txid = outputForIsSpentTest1.txid;
|
||||
var outputIndex = outputForIsSpentTest1.outputIndex;
|
||||
var options = {
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.address.getInputForOutput(txid, outputIndex, options, function(err, result) {
|
||||
result.inputTxId.should.equal(unspentOutputSpentTxId);
|
||||
result.inputIndex.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isSpent and #getInputForOutput(mempool)', function() {
|
||||
var spentOutput;
|
||||
var spentOutputInputTxId;
|
||||
it('will return true if an input is spent in a confirmed transaction', function(done) {
|
||||
var result = node.services.bitcoind.isSpent(outputForIsSpentTest1.txid, outputForIsSpentTest1.outputIndex);
|
||||
var txid = outputForIsSpentTest1.txid;
|
||||
var outputIndex = outputForIsSpentTest1.outputIndex;
|
||||
var result = node.services.bitcoind.isSpent(txid, outputIndex);
|
||||
result.should.equal(true);
|
||||
done();
|
||||
});
|
||||
|
@ -755,6 +777,8 @@ describe('Node Functionality', function() {
|
|||
tx.sign(testKey);
|
||||
|
||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
spentOutput = unspentOutput;
|
||||
spentOutputInputTxId = tx.hash;
|
||||
|
||||
setImmediate(function() {
|
||||
var result = node.services.bitcoind.isSpent(unspentOutput.txid, unspentOutput.outputIndex);
|
||||
|
@ -763,6 +787,21 @@ describe('Node Functionality', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will get the input txid and input index (mempool)', function(done) {
|
||||
var txid = spentOutput.txid;
|
||||
var outputIndex = spentOutput.outputIndex;
|
||||
var options = {
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.address.getInputForOutput(txid, outputIndex, options, function(err, result) {
|
||||
result.inputTxId.should.equal(spentOutputInputTxId);
|
||||
result.inputIndex.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ var log = index.log;
|
|||
var errors = index.errors;
|
||||
var Transaction = require('../../transaction');
|
||||
var bitcore = require('bitcore');
|
||||
var levelup = require('levelup');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
var Hash = bitcore.crypto.Hash;
|
||||
|
@ -49,8 +50,9 @@ AddressService.dependencies = [
|
|||
];
|
||||
|
||||
AddressService.PREFIXES = {
|
||||
OUTPUTS: new Buffer('02', 'hex'),
|
||||
SPENTS: new Buffer('03', 'hex')
|
||||
OUTPUTS: new Buffer('02', 'hex'), // Query outputs by address and/or height
|
||||
SPENTS: new Buffer('03', 'hex'), // Query inputs by address and/or height
|
||||
SPENTSMAP: new Buffer('05', 'hex') // Get the input that spends an output
|
||||
};
|
||||
|
||||
AddressService.SPACER_MIN = new Buffer('00', 'hex');
|
||||
|
@ -65,6 +67,7 @@ AddressService.prototype.getAPIMethods = function() {
|
|||
['getBalance', this, this.getBalance, 2],
|
||||
['getOutputs', this, this.getOutputs, 2],
|
||||
['getUnspentOutputs', this, this.getUnspentOutputs, 2],
|
||||
['getInputForOutput', this, this.getInputForOutput, 2],
|
||||
['isSpent', this, this.isSpent, 2],
|
||||
['getAddressHistory', this, this.getAddressHistory, 2],
|
||||
['getAddressSummary', this, this.getAddressSummary, 1]
|
||||
|
@ -174,13 +177,18 @@ AddressService.prototype.transactionHandler = function(txInfo) {
|
|||
* txid - A hex string of the transaction hash
|
||||
* inputIndex - A number of the corresponding input
|
||||
*
|
||||
* mempoolSpentIndex, an object keyed by <prevTxId>-<outputIndex>
|
||||
* mempoolSpentIndex, an object keyed by <prevTxId>-<outputIndex> with (buffer) values:
|
||||
* inputTxId - A 32 byte buffer of the input txid
|
||||
* inputIndex - 4 bytes stored as UInt32BE
|
||||
*
|
||||
* @param {Transaction} - An instance of a Bitcore Transaction
|
||||
*/
|
||||
AddressService.prototype.updateMempoolIndex = function(tx) {
|
||||
/* jshint maxstatements: 30 */
|
||||
|
||||
var txid = tx.hash;
|
||||
var txidBuffer = new Buffer(txid, 'hex');
|
||||
|
||||
var outputLength = tx.outputs.length;
|
||||
for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) {
|
||||
var output = tx.outputs[outputIndex];
|
||||
|
@ -203,7 +211,7 @@ AddressService.prototype.updateMempoolIndex = function(tx) {
|
|||
}
|
||||
|
||||
this.mempoolOutputIndex[addressStr].push({
|
||||
txid: tx.hash, // TODO use buffer
|
||||
txid: txid,
|
||||
outputIndex: outputIndex,
|
||||
satoshis: output.satoshis,
|
||||
script: output._scriptBuffer.toString('hex') //TODO use a buffer
|
||||
|
@ -217,7 +225,14 @@ AddressService.prototype.updateMempoolIndex = function(tx) {
|
|||
|
||||
// Update spent index
|
||||
var spentIndexKey = [input.prevTxId.toString('hex'), input.outputIndex].join('-');
|
||||
this.mempoolSpentIndex[spentIndexKey] = true;
|
||||
|
||||
var inputIndexBuffer = new Buffer(4);
|
||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||
var inputIndexValue = Buffer.concat([
|
||||
txidBuffer,
|
||||
inputIndexBuffer
|
||||
]);
|
||||
this.mempoolSpentIndex[spentIndexKey] = inputIndexValue;
|
||||
|
||||
var address = input.script.toAddress(this.node.network);
|
||||
if (!address) {
|
||||
|
@ -313,6 +328,7 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
|
|||
|
||||
var tx = txs[i];
|
||||
var txid = tx.id;
|
||||
var txidBuffer = new Buffer(txid, 'hex');
|
||||
var inputs = tx.inputs;
|
||||
var outputs = tx.outputs;
|
||||
|
||||
|
@ -340,7 +356,7 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
|
|||
// can have a time that is previous to the previous block (however not
|
||||
// less than the mean of the 11 previous blocks) and not greater than 2
|
||||
// hours in the future.
|
||||
var key = this._encodeOutputKey(addressInfo.hashBuffer, height, txid, outputIndex);
|
||||
var key = this._encodeOutputKey(addressInfo.hashBuffer, height, txidBuffer, outputIndex);
|
||||
var value = this._encodeOutputValue(output.satoshis, output._scriptBuffer);
|
||||
operations.push({
|
||||
type: action,
|
||||
|
@ -389,15 +405,28 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
|
|||
continue;
|
||||
}
|
||||
|
||||
var prevTxIdBuffer = new Buffer(input.prevTxId, 'hex');
|
||||
|
||||
// To be able to query inputs by address and spent height
|
||||
var inputKey = this._encodeInputKey(inputHash, height, input.prevTxId, input.outputIndex);
|
||||
var inputValue = this._encodeInputValue(txid, inputIndex);
|
||||
var inputKey = this._encodeInputKey(inputHash, height, prevTxIdBuffer, input.outputIndex);
|
||||
var inputValue = this._encodeInputValue(txidBuffer, inputIndex);
|
||||
|
||||
operations.push({
|
||||
type: action,
|
||||
key: inputKey,
|
||||
value: inputValue
|
||||
});
|
||||
|
||||
// To be able to search for an input spending an output
|
||||
var inputKeyMap = this._encodeInputKeyMap(prevTxIdBuffer, input.outputIndex);
|
||||
var inputValueMap = this._encodeInputValueMap(txidBuffer, inputIndex);
|
||||
|
||||
operations.push({
|
||||
type: action,
|
||||
key: inputKeyMap,
|
||||
value: inputValueMap
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,7 +435,7 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txid, outputIndex) {
|
||||
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txidBuffer, outputIndex) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
|
@ -416,7 +445,7 @@ AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txid, o
|
|||
hashBuffer,
|
||||
AddressService.SPACER_MIN,
|
||||
heightBuffer,
|
||||
new Buffer(txid, 'hex'), //TODO get buffer directly from tx
|
||||
txidBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
return key;
|
||||
|
@ -486,11 +515,11 @@ AddressService.prototype._decodeInputKey = function(buffer) {
|
|||
};
|
||||
};
|
||||
|
||||
AddressService.prototype._encodeInputValue = function(txid, inputIndex) {
|
||||
AddressService.prototype._encodeInputValue = function(txidBuffer, inputIndex) {
|
||||
var inputIndexBuffer = new Buffer(4);
|
||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||
return Buffer.concat([
|
||||
new Buffer(txid, 'hex'),
|
||||
txidBuffer,
|
||||
inputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
@ -504,6 +533,43 @@ AddressService.prototype._decodeInputValue = function(buffer) {
|
|||
};
|
||||
};
|
||||
|
||||
AddressService.prototype._encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) {
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
return Buffer.concat([
|
||||
AddressService.PREFIXES.SPENTSMAP,
|
||||
outputTxIdBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
AddressService.prototype._decodeInputKeyMap = function(buffer) {
|
||||
var txid = buffer.slice(1, 33);
|
||||
var outputIndex = buffer.readUInt32BE(33);
|
||||
return {
|
||||
outputTxId: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
AddressService.prototype._encodeInputValueMap = function(inputTxIdBuffer, inputIndex) {
|
||||
var inputIndexBuffer = new Buffer(4);
|
||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||
return Buffer.concat([
|
||||
inputTxIdBuffer,
|
||||
inputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
AddressService.prototype._decodeInputValueMap = function(buffer) {
|
||||
var txid = buffer.slice(0, 32);
|
||||
var inputIndex = buffer.readUInt32BE(32);
|
||||
return {
|
||||
inputTxId: txid,
|
||||
inputIndex: inputIndex
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is responsible for emitting events to any subscribers to the
|
||||
* `address/transaction` event.
|
||||
|
@ -659,6 +725,58 @@ AddressService.prototype.getBalance = function(address, queryMempool, callback)
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give the input that spends an output if it exists with:
|
||||
* inputTxId - The input txid hex string
|
||||
* inputIndex - A number with the spending input index
|
||||
* @param {String|Buffer} txid - The transaction hash with the output
|
||||
* @param {Number} outputIndex - The output index in the transaction
|
||||
* @param {Object} options
|
||||
* @param {Object} options.queryMempool - Include mempool in results
|
||||
* @param {Function} callback
|
||||
*/
|
||||
AddressService.prototype.getInputForOutput = function(txid, outputIndex, options, callback) {
|
||||
$.checkArgument(_.isNumber(outputIndex));
|
||||
$.checkArgument(_.isObject(options));
|
||||
$.checkArgument(_.isFunction(callback));
|
||||
var self = this;
|
||||
var txidBuffer;
|
||||
if (Buffer.isBuffer(txid)) {
|
||||
txidBuffer = txid;
|
||||
} else {
|
||||
txidBuffer = new Buffer(txid, 'hex');
|
||||
}
|
||||
if (options.queryMempool) {
|
||||
var spentIndexKey = [txid.toString('hex'), outputIndex].join('-');
|
||||
if (this.mempoolSpentIndex[spentIndexKey]) {
|
||||
var mempoolValue = this.mempoolSpentIndex[spentIndexKey];
|
||||
var inputTxId = mempoolValue.slice(0, 32);
|
||||
var inputIndex = mempoolValue.readUInt32BE(32);
|
||||
return callback(null, {
|
||||
inputTxId: inputTxId.toString('hex'),
|
||||
inputIndex: inputIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
var key = this._encodeInputKeyMap(txidBuffer, outputIndex);
|
||||
var dbOptions = {
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
};
|
||||
this.node.services.db.store.get(key, dbOptions, function(err, buffer) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return callback(null, false);
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var value = self._decodeInputValueMap(buffer);
|
||||
callback(null, {
|
||||
inputTxId: value.inputTxId.toString('hex'),
|
||||
inputIndex: value.inputIndex
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give inputs that spend previous outputs for an address as an object with:
|
||||
* address - The base58check encoded address
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('Address Service', function() {
|
|||
it('should return the correct methods', function() {
|
||||
var am = new AddressService({node: mocknode});
|
||||
var methods = am.getAPIMethods();
|
||||
methods.length.should.equal(6);
|
||||
methods.length.should.equal(7);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -162,16 +162,19 @@ describe('Address Service', function() {
|
|||
|
||||
am.blockHandler(block, true, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(81);
|
||||
operations.length.should.equal(151);
|
||||
operations[0].type.should.equal('put');
|
||||
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
|
||||
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
|
||||
operations[3].type.should.equal('put');
|
||||
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
|
||||
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
|
||||
operations[64].type.should.equal('put');
|
||||
operations[64].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
|
||||
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||
operations[4].type.should.equal('put');
|
||||
operations[4].key.toString('hex').should.equal('053d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
|
||||
operations[4].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
|
||||
operations[121].type.should.equal('put');
|
||||
operations[121].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
|
||||
operations[121].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -185,16 +188,16 @@ describe('Address Service', function() {
|
|||
};
|
||||
am.blockHandler(block, false, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(81);
|
||||
operations.length.should.equal(151);
|
||||
operations[0].type.should.equal('del');
|
||||
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
|
||||
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
|
||||
operations[3].type.should.equal('del');
|
||||
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
|
||||
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
|
||||
operations[64].type.should.equal('del');
|
||||
operations[64].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
|
||||
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||
operations[121].type.should.equal('del');
|
||||
operations[121].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
|
||||
operations[121].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -208,6 +211,7 @@ describe('Address Service', function() {
|
|||
},
|
||||
transactions: [
|
||||
{
|
||||
id: '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7',
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
|
@ -263,6 +267,36 @@ describe('Address Service', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#_encodeInputKeyMap/#_decodeInputKeyMap roundtrip', function() {
|
||||
var encoded;
|
||||
var outputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
it('encode key', function() {
|
||||
var am = new AddressService({node: mocknode});
|
||||
encoded = am._encodeInputKeyMap(outputTxIdBuffer, 13);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var am = new AddressService({node: mocknode});
|
||||
var key = am._decodeInputKeyMap(encoded);
|
||||
key.outputTxId.toString('hex').should.equal(outputTxIdBuffer.toString('hex'));
|
||||
key.outputIndex.should.equal(13);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_encodeInputValueMap/#_decodeInputValueMap roundtrip', function() {
|
||||
var encoded;
|
||||
var inputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
it('encode key', function() {
|
||||
var am = new AddressService({node: mocknode});
|
||||
encoded = am._encodeInputValueMap(inputTxIdBuffer, 7);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var am = new AddressService({node: mocknode});
|
||||
var key = am._decodeInputValueMap(encoded);
|
||||
key.inputTxId.toString('hex').should.equal(inputTxIdBuffer.toString('hex'));
|
||||
key.inputIndex.should.equal(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#transactionEventHandler', function() {
|
||||
it('will emit a transaction if there is a subscriber', function(done) {
|
||||
var am = new AddressService({node: mocknode});
|
||||
|
|
Loading…
Reference in New Issue