Skip to content

Commit

Permalink
Fix bitpay#377; db must contain hash type, not just hash.
Browse files Browse the repository at this point in the history
Prevents erroneous crediting of all transactions to both the
p2pkh and the corresponding p2sh address.
  • Loading branch information
STRML committed Dec 18, 2015
1 parent 1f0032a commit c96d26f
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 41 deletions.
5 changes: 3 additions & 2 deletions integration/regtest-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,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;
}
Expand Down
103 changes: 90 additions & 13 deletions lib/services/address/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ 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'
};

AddressService.SPACER_MIN = new Buffer('00', 'hex');
AddressService.SPACER_MAX = new Buffer('ff', 'hex');

Expand Down Expand Up @@ -303,6 +318,7 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
var outKey = Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
addressInfo.hashBuffer,
addressInfo.hashTypeBuffer,
txidBuffer,
outputIndexBuffer
]);
Expand Down Expand Up @@ -355,16 +371,21 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {

// Update input index
var inputHashBuffer;
var inputHashType;
// STRML: this is the bug
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
]);
Expand All @@ -389,7 +410,6 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
}

this.mempoolIndex.batch(operations, callback);

};

/**
Expand All @@ -401,16 +421,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;
Expand All @@ -419,6 +443,7 @@ AddressService.prototype._extractAddressInfoFromScript = function(script) {
}
return {
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
addressType: addressType
};
};
Expand Down Expand Up @@ -474,7 +499,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,
Expand Down Expand Up @@ -514,19 +540,22 @@ 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;
}

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({
Expand Down Expand Up @@ -563,14 +592,15 @@ 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);
outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
heightBuffer,
txidBuffer,
Expand All @@ -583,13 +613,15 @@ 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);
var outputIndex = reader.readUInt32BE();
return {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
txid: txid,
outputIndex: outputIndex
Expand All @@ -611,14 +643,15 @@ 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);
outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN,
heightBuffer,
prevTxIdBuffer,
Expand All @@ -630,13 +663,15 @@ 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);
var outputIndex = reader.readUInt32BE();
return {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
prevTxId: prevTxId,
outputIndex: outputIndex
Expand Down Expand Up @@ -698,6 +733,26 @@ AddressService.prototype._decodeInputValueMap = function(buffer) {
};
};

AddressService.prototype._getAddressInfo = function(addressStr) {
var addrObj = bitcore.Address(addressStr);
var hashbuffer = addrObj.hashBuffer;

// Find our internal encoding for this hash type
var hashTypes = Object.keys(AddressService.HASH_TYPES_READABLE);
var hashTypeBuffer;
for (var i = 0; i < hashTypes.length; i++) {
if (AddressService.HASH_TYPES_READABLE[hashTypes[i]] === addrObj.type) {
hashTypeBuffer = new Buffer(hashTypes[i], 'hex');
break;
}
}
return {
hashBuffer: addrObj.hashBuffer,
hashTypeBuffer: hashTypeBuffer,
hashTypeReadable: addrObj.type
};
}

/**
* This function is responsible for emitting events to any subscribers to the
* `address/transaction` event.
Expand Down Expand Up @@ -921,7 +976,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) {

Expand All @@ -935,20 +995,22 @@ 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
]),
valueEncoding: 'binary',
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]),
Expand All @@ -964,6 +1026,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,
Expand All @@ -988,7 +1051,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);
}
Expand All @@ -1005,19 +1068,21 @@ 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 = [];

var stream = self.mempoolIndex.createReadStream({
gte: Buffer.concat([
AddressService.MEMPREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN
]),
lte: Buffer.concat([
AddressService.MEMPREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MAX
]),
valueEncoding: 'binary',
Expand All @@ -1029,6 +1094,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,
Expand Down Expand Up @@ -1101,7 +1167,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;
Expand All @@ -1117,20 +1188,22 @@ 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
]),
valueEncoding: 'binary',
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]),
Expand All @@ -1146,6 +1219,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,
Expand All @@ -1172,7 +1246,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);
}
Expand All @@ -1188,19 +1262,21 @@ 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 = [];

var stream = self.mempoolIndex.createReadStream({
gte: Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MIN
]),
lte: Buffer.concat([
AddressService.MEMPREFIXES.OUTPUTS,
hashBuffer,
hashTypeBuffer,
AddressService.SPACER_MAX
]),
valueEncoding: 'binary',
Expand All @@ -1214,6 +1290,7 @@ AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, c
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,
Expand Down
Loading

0 comments on commit c96d26f

Please sign in to comment.