Fix #377; db must contain hash type, not just hash.

Prevents erroneous crediting of all transactions to both the
p2pkh and the corresponding p2sh address.
This commit is contained in:
Samuel Reed 2015-12-18 09:56:34 -06:00
parent 858182a346
commit 3214390d4c
No known key found for this signature in database
GPG Key ID: 011E698EE329F38B
3 changed files with 226 additions and 45 deletions

View File

@ -726,8 +726,9 @@ describe('Node Functionality', function() {
node.services.bitcoind.sendTransaction(tx.serialize());
setImmediate(function() {
var hashBuffer = bitcore.Address(address).hashBuffer;
node.services.address._getOutputsMempool(address, hashBuffer, function(err, outs) {
var addrObj = node.services.address._getAddressInfo(address);
node.services.address._getOutputsMempool(address, addrObj.hashBuffer,
addrObj.hashTypeBuffer, function(err, outs) {
if (err) {
throw err;
}

View File

@ -70,6 +70,27 @@ AddressService.MEMPREFIXES = {
SPENTSMAP: new Buffer('03', 'hex') // Query mempool for the input that spends an output
};
// To save space, we're only storing the PubKeyHash or ScriptHash in our index.
// To avoid intentional unspendable collisions, which have been seen on the blockchain,
// we must store the hash type (PK or Script) as well.
AddressService.HASH_TYPES = {
PUBKEY: new Buffer('01', 'hex'),
REDEEMSCRIPT: new Buffer('02', 'hex')
};
// Translates from our enum type back into the hash types returned by
// bitcore-lib/address.
AddressService.HASH_TYPES_READABLE = {
'01': 'pubkeyhash',
'02': 'scripthash'
};
// Trnaslates from address types to our enum type.
AddressService.HASH_TYPES_MAP = {
'pubkeyhash': AddressService.HASH_TYPES.PUBKEY,
'scripthash': AddressService.HASH_TYPES.REDEEMSCRIPT
};
AddressService.SPACER_MIN = new Buffer('00', 'hex');
AddressService.SPACER_MAX = new Buffer('ff', 'hex');
@ -303,6 +324,7 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
var outKey = Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
addressInfo.hashBuffer,
addressInfo.hashTypeBuffer,
txidBuffer,
outputIndexBuffer
]);
@ -355,16 +377,20 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
// Update input index
var inputHashBuffer;
var inputHashType;
if (input.script.isPublicKeyHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf);
inputHashType = AddressService.HASH_TYPES.PUBKEY;
} else if (input.script.isScriptHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
inputHashType = AddressService.HASH_TYPES.REDEEMSCRIPT;
} else {
continue;
}
var inputKey = Buffer.concat([
AddressService.MEMPREFIXES.SPENTS,
inputHashBuffer,
inputHashType,
input.prevTxId,
inputOutputIndexBuffer
]);
@ -389,7 +415,6 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
}
this.mempoolIndex.batch(operations, callback);
};
/**
@ -401,16 +426,20 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
AddressService.prototype._extractAddressInfoFromScript = function(script) {
var hashBuffer;
var addressType;
var hashTypeBuffer;
if (script.isPublicKeyHashOut()) {
hashBuffer = script.chunks[2].buf;
hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
addressType = Address.PayToPublicKeyHash;
} else if (script.isScriptHashOut()) {
hashBuffer = script.chunks[1].buf;
hashTypeBuffer = AddressService.HASH_TYPES.REDEEMSCRIPT;
addressType = Address.PayToScriptHash;
} else if (script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf;
var address = Address.fromPublicKey(new PublicKey(pubkey), this.node.network);
hashBuffer = address.hashBuffer;
hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
// pay-to-publickey doesn't have an address, however for compatibility
// purposes, we can create an address
addressType = Address.PayToPublicKeyHash;
@ -419,6 +448,7 @@ AddressService.prototype._extractAddressInfoFromScript = function(script) {
}
return {
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
addressType: addressType
};
};
@ -474,7 +504,8 @@ 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, txidBuffer, outputIndex);
var key = this._encodeOutputKey(addressInfo.hashBuffer, addressInfo.hashTypeBuffer,
height, txidBuffer, outputIndex);
var value = this._encodeOutputValue(output.satoshis, output._scriptBuffer);
operations.push({
type: action,
@ -514,11 +545,14 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
var input = inputs[inputIndex];
var inputHash;
var inputHashType;
if (input.script.isPublicKeyHashIn()) {
inputHash = Hash.sha256ripemd160(input.script.chunks[1].buf);
inputHashType = AddressService.HASH_TYPES.PUBKEY;
} else if (input.script.isScriptHashIn()) {
inputHash = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
inputHashType = AddressService.HASH_TYPES.REDEEMSCRIPT;
} else {
continue;
}
@ -526,7 +560,7 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
var prevTxIdBuffer = new Buffer(input.prevTxId, 'hex');
// To be able to query inputs by address and spent height
var inputKey = this._encodeInputKey(inputHash, height, prevTxIdBuffer, input.outputIndex);
var inputKey = this._encodeInputKey(inputHash, inputHashType, height, prevTxIdBuffer, input.outputIndex);
var inputValue = this._encodeInputValue(txidBuffer, inputIndex);
operations.push({
@ -563,7 +597,7 @@ AddressService.prototype._encodeSpentIndexSyncKey = function(txidBuffer, outputI
return key.toString('binary');
};
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txidBuffer, outputIndex) {
AddressService.prototype._encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4);
@ -571,6 +605,7 @@ AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txidBuf
var key = Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
heightBuffer,
txidBuffer,
@ -583,6 +618,7 @@ AddressService.prototype._decodeOutputKey = function(buffer) {
var reader = new BufferReader(buffer);
var prefix = reader.read(1);
var hashBuffer = reader.read(20);
var hashTypeBuffer = reader.read(1);
var spacer = reader.read(1);
var height = reader.readUInt32BE();
var txid = reader.read(32);
@ -590,6 +626,7 @@ AddressService.prototype._decodeOutputKey = function(buffer) {
return {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
txid: txid,
outputIndex: outputIndex
@ -611,7 +648,7 @@ AddressService.prototype._decodeOutputValue = function(buffer) {
};
};
AddressService.prototype._encodeInputKey = function(hashBuffer, height, prevTxIdBuffer, outputIndex) {
AddressService.prototype._encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4);
@ -619,6 +656,7 @@ AddressService.prototype._encodeInputKey = function(hashBuffer, height, prevTxId
return Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
heightBuffer,
prevTxIdBuffer,
@ -630,6 +668,7 @@ AddressService.prototype._decodeInputKey = function(buffer) {
var reader = new BufferReader(buffer);
var prefix = reader.read(1);
var hashBuffer = reader.read(20);
var hashTypeBuffer = reader.read(1);
var spacer = reader.read(1);
var height = reader.readUInt32BE();
var prevTxId = reader.read(32);
@ -637,6 +676,7 @@ AddressService.prototype._decodeInputKey = function(buffer) {
return {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
prevTxId: prevTxId,
outputIndex: outputIndex
@ -698,6 +738,17 @@ AddressService.prototype._decodeInputValueMap = function(buffer) {
};
};
AddressService.prototype._getAddressInfo = function(addressStr) {
var addrObj = bitcore.Address(addressStr);
var hashTypeBuffer = AddressService.HASH_TYPES_MAP[addrObj.type];
return {
hashBuffer: addrObj.hashBuffer,
hashTypeBuffer: hashTypeBuffer,
hashTypeReadable: addrObj.type
};
};
/**
* This function is responsible for emitting events to any subscribers to the
* `address/transaction` event.
@ -902,6 +953,7 @@ AddressService.prototype.getInputForOutput = function(txid, outputIndex, options
/**
* Will give inputs that spend previous outputs for an address as an object with:
* address - The base58check encoded address
* hashType - The type of the address, e.g. 'pubkeyhash' or 'scripthash'
* txid - A string of the transaction hash
* outputIndex - A number of corresponding transaction input
* height - The height of the block the transaction was included, will be -1 for mempool transactions
@ -921,7 +973,12 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
var inputs = [];
var stream;
var hashBuffer = bitcore.Address(addressStr).hashBuffer;
var addrObj = this._getAddressInfo(addressStr);
var hashBuffer = addrObj.hashBuffer;
var hashTypeBuffer = addrObj.hashTypeBuffer;
if (!hashTypeBuffer) {
return callback(new Error('Unknown address type: ' + addrObj.hashTypeReadable + ' for address: ' + addressStr));
}
if (options.start && options.end) {
@ -935,12 +992,14 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
gte: Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
endBuffer
]),
lte: Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
startBuffer
]),
@ -948,7 +1007,7 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
keyEncoding: 'binary'
});
} else {
var allKey = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer]);
var allKey = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, hashTypeBuffer]);
stream = this.node.services.db.store.createReadStream({
gte: Buffer.concat([allKey, AddressService.SPACER_MIN]),
lte: Buffer.concat([allKey, AddressService.SPACER_MAX]),
@ -964,6 +1023,7 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
var input = {
address: addressStr,
hashType: addrObj.hashTypeReadable,
txid: value.txid.toString('hex'),
inputIndex: value.inputIndex,
height: key.height,
@ -988,7 +1048,7 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
}
if(options.queryMempool) {
self._getInputsMempool(addressStr, hashBuffer, function(err, mempoolInputs) {
self._getInputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolInputs) {
if (err) {
return callback(err);
}
@ -1005,7 +1065,7 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
};
AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, callback) {
AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, hashTypeBuffer, callback) {
var self = this;
var mempoolInputs = [];
@ -1013,11 +1073,13 @@ AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, ca
gte: Buffer.concat([
AddressService.MEMPREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN
]),
lte: Buffer.concat([
AddressService.MEMPREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MAX
]),
valueEncoding: 'binary',
@ -1029,6 +1091,7 @@ AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, ca
var inputIndex = data.value.readUInt32BE(32);
var output = {
address: addressStr,
hashType: AddressService.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')],
txid: txid.toString('hex'), //TODO use a buffer
inputIndex: inputIndex,
height: -1,
@ -1082,6 +1145,7 @@ AddressService.prototype._getSpentMempool = function(txidBuffer, outputIndex, ca
/**
* Will give outputs for an address as an object with:
* address - The base58check encoded address
* hashType - The type of the address, e.g. 'pubkeyhash' or 'scripthash'
* txid - A string of the transaction hash
* outputIndex - A number of corresponding transaction output
* height - The height of the block the transaction was included, will be -1 for mempool transactions
@ -1101,7 +1165,12 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
$.checkArgument(_.isObject(options), 'Second argument is expected to be an options object.');
$.checkArgument(_.isFunction(callback), 'Third argument is expected to be a callback function.');
var hashBuffer = bitcore.Address(addressStr).hashBuffer;
var addrObj = this._getAddressInfo(addressStr);
var hashBuffer = addrObj.hashBuffer;
var hashTypeBuffer = addrObj.hashTypeBuffer;
if (!hashTypeBuffer) {
return callback(new Error('Unknown address type: ' + addrObj.hashTypeReadable + ' for address: ' + addressStr));
}
var outputs = [];
var stream;
@ -1117,12 +1186,14 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
gte: Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
endBuffer
]),
lte: Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
startBuffer
]),
@ -1130,7 +1201,7 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
keyEncoding: 'binary'
});
} else {
var allKey = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer]);
var allKey = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer]);
stream = this.node.services.db.store.createReadStream({
gte: Buffer.concat([allKey, AddressService.SPACER_MIN]),
lte: Buffer.concat([allKey, AddressService.SPACER_MAX]),
@ -1146,6 +1217,7 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
var output = {
address: addressStr,
hashType: addrObj.hashTypeReadable,
txid: key.txid.toString('hex'), //TODO use a buffer
outputIndex: key.outputIndex,
height: key.height,
@ -1172,7 +1244,7 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
}
if(options.queryMempool) {
self._getOutputsMempool(addressStr, hashBuffer, function(err, mempoolOutputs) {
self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) {
if (err) {
return callback(err);
}
@ -1188,7 +1260,7 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
};
AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, callback) {
AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, hashTypeBuffer, callback) {
var self = this;
var mempoolOutputs = [];
@ -1196,11 +1268,13 @@ AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, c
gte: Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN
]),
lte: Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MAX
]),
valueEncoding: 'binary',
@ -1208,12 +1282,13 @@ AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, c
});
stream.on('data', function(data) {
// Format of data: prefix: 1, hashBuffer: 20, txid: 32, outputIndex: 4
var txid = data.key.slice(21, 53);
var outputIndex = data.key.readUInt32BE(53);
// Format of data: prefix: 1, hashBuffer: 20, hashTypeBuffer: 1, txid: 32, outputIndex: 4
var txid = data.key.slice(22, 54);
var outputIndex = data.key.readUInt32BE(54);
var value = self._decodeOutputValue(data.value);
var output = {
address: addressStr,
hashType: AddressService.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')],
txid: txid.toString('hex'), //TODO use a buffer
outputIndex: outputIndex,
height: -1,

View File

@ -343,7 +343,9 @@ describe('Address Service', function() {
});
am.node.network = Networks.livenet;
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
var hashHex = bitcore.Address(address).hashBuffer.toString('hex');
var addrObj = bitcore.Address(address);
var hashHex = addrObj.hashBuffer.toString('hex');
var hashType = addrObj.type;
var messages = {};
am.transactionOutputHandler(messages, tx, 0, true);
should.exist(messages[hashHex]);
@ -351,6 +353,7 @@ describe('Address Service', function() {
message.tx.should.equal(tx);
message.outputIndexes.should.deep.equal([0]);
message.addressInfo.hashBuffer.toString('hex').should.equal(hashHex);
message.addressInfo.addressType.should.equal(hashType);
message.addressInfo.hashHex.should.equal(hashHex);
message.rejected.should.equal(true);
});
@ -446,16 +449,16 @@ describe('Address Service', function() {
should.not.exist(err);
operations.length.should.equal(151);
operations[0].type.should.equal('put');
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b0100000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
operations[3].type.should.equal('put');
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e0100000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
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].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c75280100000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
operations[121].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
done();
});
@ -472,13 +475,13 @@ describe('Address Service', function() {
should.not.exist(err);
operations.length.should.equal(151);
operations[0].type.should.equal('del');
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b0100000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
operations[3].type.should.equal('del');
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e0100000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
operations[121].type.should.equal('del');
operations[121].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
operations[121].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c75280100000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
operations[121].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
done();
});
@ -818,6 +821,7 @@ describe('Address Service', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashBuffer = bitcore.Address(address).hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
var db = {
tip: {
__height: 1
@ -866,8 +870,9 @@ describe('Address Service', function() {
end: 12,
queryMempool: true
};
am._getInputsMempool = sinon.stub().callsArgWith(2, null, {
am._getInputsMempool = sinon.stub().callsArgWith(3, null, {
address: address,
hashType: 'pubkeyhash',
height: -1,
confirmations: 0
});
@ -890,9 +895,11 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('000000000c', 'hex')]);
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer,
hashTypeBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('0000000010', 'hex')]);
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer,
hashTypeBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
@ -901,7 +908,7 @@ describe('Address Service', function() {
am.node.services.bitcoind = {
getMempoolInputs: sinon.stub().returns([])
};
am._getInputsMempool = sinon.stub().callsArgWith(2, null, []);
am._getInputsMempool = sinon.stub().callsArgWith(3, null, []);
am.getInputs(address, args, function(err, inputs) {
should.not.exist(err);
inputs.length.should.equal(1);
@ -913,7 +920,7 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b68701000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000000', 'hex')
};
testStream.emit('data', data);
@ -927,9 +934,9 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('00', 'hex')]);
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, hashTypeBuffer, new Buffer('00', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('ff', 'hex')]);
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, hashTypeBuffer, new Buffer('ff', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
@ -949,7 +956,7 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b68701000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000000', 'hex')
};
testStream.emit('data', data);
@ -979,6 +986,7 @@ describe('Address Service', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashBuffer = bitcore.Address(address).hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
var db = {
tip: {
__height: 1
@ -1005,7 +1013,7 @@ describe('Address Service', function() {
am.mempoolIndex = {};
am.mempoolIndex.createReadStream = sinon.stub().returns(testStream);
am._getInputsMempool(address, hashBuffer, function(err, outputs) {
am._getInputsMempool(address, hashBuffer, hashTypeBuffer, function(err, outputs) {
should.exist(err);
err.message.should.equal('readstreamerror');
done();
@ -1021,11 +1029,13 @@ describe('Address Service', function() {
am.mempoolIndex = {};
am.mempoolIndex.createReadStream = sinon.stub().returns(testStream);
am._getInputsMempool(address, hashBuffer, function(err, outputs) {
am._getInputsMempool(address, hashBuffer, hashTypeBuffer, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(1);
outputs[0].address.should.equal(address);
outputs[0].txid.should.equal(txid);
outputs[0].hashType.should.equal('pubkeyhash');
outputs[0].hashType.should.equal(AddressService.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')]);
outputs[0].inputIndex.should.equal(5);
outputs[0].height.should.equal(-1);
outputs[0].confirmations.should.equal(0);
@ -1099,6 +1109,7 @@ describe('Address Service', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashBuffer = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W').hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
var db = {
tip: {
__height: 1
@ -1135,20 +1146,22 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
var gte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, new Buffer('000000000c', 'hex')]);
var gte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, new Buffer('0000000010', 'hex')]);
var lte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
};
am._getOutputsMempool = sinon.stub().callsArgWith(2, null, []);
am._getOutputsMempool = sinon.stub().callsArgWith(3, null, []);
am.getOutputs(address, args, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(1);
outputs[0].address.should.equal(address);
outputs[0].txid.should.equal('125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87');
outputs[0].hashType.should.equal('pubkeyhash');
outputs[0].hashType.should.equal(AddressService.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')]);
outputs[0].outputIndex.should.equal(1);
outputs[0].satoshis.should.equal(4527773864);
outputs[0].script.should.equal('76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac');
@ -1157,7 +1170,7 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68701000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
testStream.emit('data', data);
@ -1170,10 +1183,11 @@ describe('Address Service', function() {
createReadStream: sinon.stub().returns(readStream1)
};
am._getOutputsMempool = sinon.stub().callsArgWith(2, null, [
am._getOutputsMempool = sinon.stub().callsArgWith(3, null, [
{
address: address,
height: -1,
hashType: 'pubkeyhash',
confirmations: 0,
txid: 'aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371',
satoshis: 307627737,
@ -1186,18 +1200,21 @@ describe('Address Service', function() {
should.not.exist(err);
outputs.length.should.equal(3);
outputs[0].address.should.equal(address);
outputs[0].hashType.should.equal('pubkeyhash');
outputs[0].txid.should.equal('125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87');
outputs[0].outputIndex.should.equal(1);
outputs[0].satoshis.should.equal(4527773864);
outputs[0].script.should.equal('76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac');
outputs[0].height.should.equal(345000);
outputs[1].address.should.equal(address);
outputs[1].hashType.should.equal('pubkeyhash');
outputs[1].txid.should.equal('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7');
outputs[1].outputIndex.should.equal(2);
outputs[1].satoshis.should.equal(10000);
outputs[1].script.should.equal('76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac');
outputs[1].height.should.equal(345004);
outputs[2].address.should.equal(address);
outputs[2].hashType.should.equal('pubkeyhash');
outputs[2].txid.should.equal('aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371');
outputs[2].script.should.equal('76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac');
outputs[2].height.should.equal(-1);
@ -1206,12 +1223,12 @@ describe('Address Service', function() {
});
var data1 = {
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b6870100000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
var data2 = {
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'),
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b6870100000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'),
value: new Buffer('40c388000000000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
@ -1237,12 +1254,98 @@ describe('Address Service', function() {
readStream2.emit('close');
});
});
it('should print outputs for a p2sh address', function(done) {
// This address has the redeemScript 0x038a213afdfc551fc658e9a2a58a86e98d69b687,
// which is the same as the pkhash for the address 1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W.
// See https://github.com/bitpay/bitcore-node/issues/377
var address = '321jRYeWBrLBWr2j1KYnAFGico3GUdd5q7';
var hashBuffer = bitcore.Address(address).hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.REDEEMSCRIPT;
var testStream = new EventEmitter();
var args = {
start: 15,
end: 12,
queryMempool: true
};
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
var gte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
};
am._getOutputsMempool = sinon.stub().callsArgWith(3, null, []);
am.getOutputs(address, args, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(1);
outputs[0].address.should.equal(address);
outputs[0].txid.should.equal('125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87');
outputs[0].hashType.should.equal('scripthash');
outputs[0].hashType.should.equal(AddressService.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')]);
outputs[0].outputIndex.should.equal(1);
outputs[0].satoshis.should.equal(4527773864);
outputs[0].script.should.equal('a914038a213afdfc551fc658e9a2a58a86e98d69b68787');
outputs[0].height.should.equal(15);
done();
});
createReadStreamCallCount.should.equal(1);
var data = {
// note '68702', '02' meaning p2sh redeemScript, not p2pkh
// value is also the p2sh script, not p2pkh
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68702000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('41f0de058a800000a914038a213afdfc551fc658e9a2a58a86e98d69b68787', 'hex')
};
testStream.emit('data', data);
testStream.emit('close');
});
it('should not print outputs for a p2pkh address, if the output was sent to a p2sh redeemScript', function(done) {
// This address has the redeemScript 0x038a213afdfc551fc658e9a2a58a86e98d69b687,
// which is the same as the pkhash for the address 1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W.
// See https://github.com/bitpay/bitcore-node/issues/377
var address = '321jRYeWBrLBWr2j1KYnAFGico3GUdd5q7';
var hashBuffer = bitcore.Address(address).hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.REDEEMSCRIPT;
var testStream = new EventEmitter();
var args = {
start: 15,
end: 12,
queryMempool: true
};
var createReadStreamCallCount = 0;
// Verifying that the db query is looking for a redeemScript, *not* a p2pkh
am.node.services.db.store = {
createReadStream: function(ops) {
var gte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
};
am._getOutputsMempool = sinon.stub().callsArgWith(3, null, []);
am.getOutputs(address, args, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(0);
done();
});
createReadStreamCallCount.should.equal(1);
testStream.emit('close');
});
});
describe('#_getOutputsMempool', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashBuffer = bitcore.Address(address).hashBuffer;
var hashTypeBuffer = AddressService.HASH_TYPES.PUBKEY;
var db = {
tip: {
__height: 1
@ -1268,7 +1371,7 @@ describe('Address Service', function() {
var testStream = new EventEmitter();
am.mempoolIndex = {};
am.mempoolIndex.createReadStream = sinon.stub().returns(testStream);
am._getOutputsMempool(address, hashBuffer, function(err, outputs) {
am._getOutputsMempool(address, hashBuffer, hashTypeBuffer, function(err, outputs) {
should.exist(err);
err.message.should.equal('readstreamerror');
done();
@ -1283,12 +1386,13 @@ describe('Address Service', function() {
am.mempoolIndex = {};
am.mempoolIndex.createReadStream = sinon.stub().returns(testStream);
am._getOutputsMempool(address, hashBuffer, function(err, outputs) {
am._getOutputsMempool(address, hashBuffer, hashTypeBuffer, function(err, outputs) {
if (err) {
throw err;
}
outputs.length.should.equal(1);
outputs[0].address.should.equal(address);
outputs[0].hashType.should.equal('pubkeyhash');
outputs[0].txid.should.equal(txid);
outputs[0].outputIndex.should.equal(outputIndex);
outputs[0].height.should.equal(-1);
@ -1304,8 +1408,9 @@ describe('Address Service', function() {
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
var keyData = Buffer.concat([
new Buffer('01', 'hex'),
AddressService.MEMPREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
txidBuffer,
outputIndexBuffer
]);