Address Service: Fixed many bugs from tests
- Refactored getAddressSummary and added several tests - Fixed bugs revealed from the integration regtests - Updated many unit tests
This commit is contained in:
parent
188ff28ec7
commit
4fcec8755c
|
@ -28,6 +28,7 @@ var Transaction = index.Transaction;
|
|||
var BitcoreNode = index.Node;
|
||||
var AddressService = index.services.Address;
|
||||
var BitcoinService = index.services.Bitcoin;
|
||||
var encoding = require('../lib/services/address/encoding');
|
||||
var DBService = index.services.DB;
|
||||
var testWIF = 'cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG';
|
||||
var testKey;
|
||||
|
@ -43,22 +44,6 @@ describe('Node Functionality', function() {
|
|||
before(function(done) {
|
||||
this.timeout(30000);
|
||||
|
||||
// Add the regtest network
|
||||
bitcore.Networks.remove(bitcore.Networks.testnet);
|
||||
bitcore.Networks.add({
|
||||
name: 'regtest',
|
||||
alias: 'regtest',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0xfabfb5da,
|
||||
port: 18444,
|
||||
dnsSeeds: [ ]
|
||||
});
|
||||
regtest = bitcore.Networks.get('regtest');
|
||||
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
testKey = bitcore.PrivateKey(testWIF);
|
||||
|
@ -93,6 +78,9 @@ describe('Node Functionality', function() {
|
|||
|
||||
node = new BitcoreNode(configuration);
|
||||
|
||||
regtest = bitcore.Networks.get('regtest');
|
||||
should.exist(regtest);
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
@ -208,7 +196,7 @@ describe('Node Functionality', function() {
|
|||
|
||||
// We need to add a transaction to the mempool so that the next block will
|
||||
// have a different hash as the hash has been invalidated.
|
||||
client.sendToAddress(testKey.toAddress().toString(), 10, function(err) {
|
||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -250,7 +238,7 @@ describe('Node Functionality', function() {
|
|||
var address;
|
||||
var unspentOutput;
|
||||
before(function() {
|
||||
address = testKey.toAddress().toString();
|
||||
address = testKey.toAddress(regtest).toString();
|
||||
});
|
||||
it('should be able to get the balance of the test address', function(done) {
|
||||
node.services.address.getBalance(address, false, function(err, balance) {
|
||||
|
@ -333,19 +321,19 @@ describe('Node Functionality', function() {
|
|||
/* jshint maxstatements: 50 */
|
||||
|
||||
testKey2 = bitcore.PrivateKey.fromWIF('cNfF4jXiLHQnFRsxaJyr2YSGcmtNYvxQYSakNhuDGxpkSzAwn95x');
|
||||
address2 = testKey2.toAddress().toString();
|
||||
address2 = testKey2.toAddress(regtest).toString();
|
||||
|
||||
testKey3 = bitcore.PrivateKey.fromWIF('cVTYQbaFNetiZcvxzXcVMin89uMLC43pEBMy2etgZHbPPxH5obYt');
|
||||
address3 = testKey3.toAddress().toString();
|
||||
address3 = testKey3.toAddress(regtest).toString();
|
||||
|
||||
testKey4 = bitcore.PrivateKey.fromWIF('cPNQmfE31H2oCUFqaHpfSqjDibkt7XoT2vydLJLDHNTvcddCesGw');
|
||||
address4 = testKey4.toAddress().toString();
|
||||
address4 = testKey4.toAddress(regtest).toString();
|
||||
|
||||
testKey5 = bitcore.PrivateKey.fromWIF('cVrzm9gCmnzwEVMGeCxY6xLVPdG3XWW97kwkFH3H3v722nb99QBF');
|
||||
address5 = testKey5.toAddress().toString();
|
||||
address5 = testKey5.toAddress(regtest).toString();
|
||||
|
||||
testKey6 = bitcore.PrivateKey.fromWIF('cPfMesNR2gsQEK69a6xe7qE44CZEZavgMUak5hQ74XDgsRmmGBYF');
|
||||
address6 = testKey6.toAddress().toString();
|
||||
address6 = testKey6.toAddress(regtest).toString();
|
||||
|
||||
var tx = new Transaction();
|
||||
tx.from(unspentOutput);
|
||||
|
@ -726,7 +714,7 @@ describe('Node Functionality', function() {
|
|||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
|
||||
setImmediate(function() {
|
||||
var addrObj = node.services.address._getAddressInfo(address);
|
||||
var addrObj = encoding.getAddressInfo(address);
|
||||
node.services.address._getOutputsMempool(address, addrObj.hashBuffer,
|
||||
addrObj.hashTypeBuffer, function(err, outs) {
|
||||
if (err) {
|
||||
|
|
|
@ -36,6 +36,8 @@ exports.HASH_TYPES_MAP = {
|
|||
|
||||
exports.SPACER_MIN = new Buffer('00', 'hex');
|
||||
exports.SPACER_MAX = new Buffer('ff', 'hex');
|
||||
exports.SPACER_HEIGHT_MIN = new Buffer('0000000000', 'hex');
|
||||
exports.SPACER_HEIGHT_MAX = new Buffer('ffffffffff', 'hex');
|
||||
exports.TIMESTAMP_MIN = new Buffer('0000000000000000', 'hex');
|
||||
exports.TIMESTAMP_MAX = new Buffer('ffffffffffffffff', 'hex');
|
||||
|
||||
|
|
|
@ -61,6 +61,12 @@ exports.encodeOutputValue = function(satoshis, scriptBuffer) {
|
|||
return Buffer.concat([satoshisBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
exports.encodeOutputMempoolValue = function(satoshis, timestampBuffer, scriptBuffer) {
|
||||
var satoshisBuffer = new Buffer(8);
|
||||
satoshisBuffer.writeDoubleBE(satoshis);
|
||||
return Buffer.concat([satoshisBuffer, timestampBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
exports.decodeOutputValue = function(buffer) {
|
||||
var satoshis = buffer.readDoubleBE(0);
|
||||
var scriptBuffer = buffer.slice(8, buffer.length);
|
||||
|
@ -70,6 +76,17 @@ exports.decodeOutputValue = function(buffer) {
|
|||
};
|
||||
};
|
||||
|
||||
exports.decodeOutputMempoolValue = function(buffer) {
|
||||
var satoshis = buffer.readDoubleBE(0);
|
||||
var timestamp = buffer.readDoubleBE(8);
|
||||
var scriptBuffer = buffer.slice(16, buffer.length);
|
||||
return {
|
||||
satoshis: satoshis,
|
||||
timestamp: timestamp,
|
||||
scriptBuffer: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
|
@ -175,7 +192,8 @@ exports.decodeSummaryCacheKey = function(buffer, network) {
|
|||
return address;
|
||||
};
|
||||
|
||||
exports.encodeSummaryCacheValue = function(cache, tipHeight) {
|
||||
exports.encodeSummaryCacheValue = function(cache, tipHeight, tipHash) {
|
||||
var tipHashBuffer = new Buffer(tipHash, 'hex');
|
||||
var buffer = new Buffer(new Array(20));
|
||||
buffer.writeUInt32BE(tipHeight);
|
||||
buffer.writeDoubleBE(cache.result.totalReceived, 4);
|
||||
|
@ -189,21 +207,22 @@ exports.encodeSummaryCacheValue = function(cache, tipHeight) {
|
|||
txidBuffers.push(buf);
|
||||
}
|
||||
var txidsBuffer = Buffer.concat(txidBuffers);
|
||||
var value = Buffer.concat([buffer, txidsBuffer]);
|
||||
var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
exports.decodeSummaryCacheValue = function(buffer) {
|
||||
|
||||
var height = buffer.readUInt32BE();
|
||||
var totalReceived = buffer.readDoubleBE(4);
|
||||
var balance = buffer.readDoubleBE(12);
|
||||
var hash = buffer.slice(0, 32).toString('hex');
|
||||
var height = buffer.readUInt32BE(32);
|
||||
var totalReceived = buffer.readDoubleBE(36);
|
||||
var balance = buffer.readDoubleBE(44);
|
||||
|
||||
// read 32 byte chunks until exhausted
|
||||
var appearanceIds = {};
|
||||
var txids = [];
|
||||
var pos = 20;
|
||||
var pos = 52;
|
||||
while(pos < buffer.length) {
|
||||
var txid = buffer.slice(pos, pos + 32).toString('hex');
|
||||
var txidHeight = buffer.readUInt32BE(pos + 32);
|
||||
|
@ -214,6 +233,7 @@ exports.decodeSummaryCacheValue = function(buffer) {
|
|||
|
||||
var cache = {
|
||||
height: height,
|
||||
hash: hash,
|
||||
result: {
|
||||
appearanceIds: appearanceIds,
|
||||
txids: txids,
|
||||
|
|
|
@ -64,8 +64,7 @@ AddressHistory.prototype._mergeAndSortTxids = function(summaries) {
|
|||
// Unconfirmed are sorted by timestamp
|
||||
return unconfirmedAppearanceIds[a] - unconfirmedAppearanceIds[b];
|
||||
});
|
||||
var txids = confirmedTxids.concat(unconfirmedTxids);
|
||||
return txids;
|
||||
return confirmedTxids.concat(unconfirmedTxids);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -81,7 +80,7 @@ AddressHistory.prototype.get = function(callback) {
|
|||
return callback(new Error('Maximum number of addresses (' + this.maxAddressQuery + ') exceeded'));
|
||||
}
|
||||
|
||||
if (this.addresses.length === 0) {
|
||||
if (this.addresses.length === 1) {
|
||||
var address = this.addresses[0];
|
||||
self.node.services.address.getAddressSummary(address, this.options, function(err, summary) {
|
||||
if (err) {
|
||||
|
@ -111,9 +110,14 @@ AddressHistory.prototype.get = function(callback) {
|
|||
totalCount = allTxids.length;
|
||||
|
||||
// Slice the page starting with the most recent
|
||||
var fromOffset = totalCount - self.options.from;
|
||||
var toOffset = totalCount - self.options.to;
|
||||
var txids = allTxids.slice(toOffset, fromOffset);
|
||||
var txids;
|
||||
if (self.options.from >= 0 && self.options.to >= 0) {
|
||||
var fromOffset = totalCount - self.options.from;
|
||||
var toOffset = totalCount - self.options.to;
|
||||
txids = allTxids.slice(toOffset, fromOffset);
|
||||
} else {
|
||||
txids = allTxids;
|
||||
}
|
||||
|
||||
// Verify that this query isn't too long
|
||||
if (txids.length > self.maxHistoryQueryLength) {
|
||||
|
@ -212,16 +216,19 @@ AddressHistory.prototype.getAddressDetailsForTransaction = function(transaction)
|
|||
continue;
|
||||
}
|
||||
var inputAddress = input.script.toAddress(this.node.network);
|
||||
if (inputAddress && this.addressStrings.indexOf(inputAddress.toString()) > 0) {
|
||||
if (!result.addresses[inputAddress]) {
|
||||
result.addresses[inputAddress] = {
|
||||
inputIndexes: [],
|
||||
outputIndexes: []
|
||||
};
|
||||
} else {
|
||||
result.addresses[inputAddress].inputIndexes.push(inputIndex);
|
||||
if (inputAddress) {
|
||||
var inputAddressString = inputAddress.toString();
|
||||
if (this.addressStrings.indexOf(inputAddressString) >= 0) {
|
||||
if (!result.addresses[inputAddressString]) {
|
||||
result.addresses[inputAddressString] = {
|
||||
inputIndexes: [inputIndex],
|
||||
outputIndexes: []
|
||||
};
|
||||
} else {
|
||||
result.addresses[inputAddressString].inputIndexes.push(inputIndex);
|
||||
}
|
||||
result.satoshis -= input.output.satoshis;
|
||||
}
|
||||
result.satoshis -= input.output.satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,16 +238,19 @@ AddressHistory.prototype.getAddressDetailsForTransaction = function(transaction)
|
|||
continue;
|
||||
}
|
||||
var outputAddress = output.script.toAddress(this.node.network);
|
||||
if (outputAddress && this.addressStrings.indexOf(outputAddress.toString()) > 0) {
|
||||
if (!result.addresses[outputAddress]) {
|
||||
result.addresses[outputAddress] = {
|
||||
inputIndexes: [],
|
||||
outputIndexes: []
|
||||
};
|
||||
} else {
|
||||
result.addresses[outputAddress].inputIndexes.push(outputIndex);
|
||||
if (outputAddress) {
|
||||
var outputAddressString = outputAddress.toString();
|
||||
if (this.addressStrings.indexOf(outputAddressString) >= 0) {
|
||||
if (!result.addresses[outputAddressString]) {
|
||||
result.addresses[outputAddressString] = {
|
||||
inputIndexes: [],
|
||||
outputIndexes: [outputIndex]
|
||||
};
|
||||
} else {
|
||||
result.addresses[outputAddressString].outputIndexes.push(outputIndex);
|
||||
}
|
||||
result.satoshis += output.satoshis;
|
||||
}
|
||||
result.satoshis += output.satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -328,12 +328,15 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
|
|||
constants.MEMPREFIXES.OUTPUTS,
|
||||
addressInfo.hashBuffer,
|
||||
addressInfo.hashTypeBuffer,
|
||||
timestampBuffer,
|
||||
txidBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
|
||||
var outValue = encoding.encodeOutputValue(output.satoshis, output._scriptBuffer);
|
||||
var outValue = encoding.encodeOutputMempoolValue(
|
||||
output.satoshis,
|
||||
timestampBuffer,
|
||||
output._scriptBuffer
|
||||
);
|
||||
|
||||
operations.push({
|
||||
type: action,
|
||||
|
@ -395,13 +398,13 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
|
|||
constants.MEMPREFIXES.SPENTS,
|
||||
inputHashBuffer,
|
||||
inputHashType,
|
||||
timestampBuffer,
|
||||
input.prevTxId,
|
||||
inputOutputIndexBuffer
|
||||
]);
|
||||
var inputValue = Buffer.concat([
|
||||
txidBuffer,
|
||||
inputIndexBuffer
|
||||
inputIndexBuffer,
|
||||
timestampBuffer
|
||||
]);
|
||||
operations.push({
|
||||
type: action,
|
||||
|
@ -768,13 +771,17 @@ AddressService.prototype.getInputForOutput = function(txid, outputIndex, options
|
|||
* @param {Function} callback
|
||||
*/
|
||||
AddressService.prototype.createInputsStream = function(addressStr, options) {
|
||||
|
||||
var inputStream = new InputsTransformStream({
|
||||
address: new Address(addressStr, this.node.network),
|
||||
tipHeight: this.node.services.db.tip.__height
|
||||
});
|
||||
|
||||
var stream = this.createInputsDBStream(addressStr, options).pipe(inputStream);
|
||||
var stream = this.createInputsDBStream(addressStr, options)
|
||||
.on('error', function(err) {
|
||||
// Forward the error
|
||||
inputStream.emit('error', err);
|
||||
inputStream.end();
|
||||
}).pipe(inputStream);
|
||||
|
||||
return stream;
|
||||
|
||||
|
@ -786,23 +793,27 @@ AddressService.prototype.createInputsDBStream = function(addressStr, options) {
|
|||
var hashBuffer = addrObj.hashBuffer;
|
||||
var hashTypeBuffer = addrObj.hashTypeBuffer;
|
||||
|
||||
if (options.start && options.end) {
|
||||
if (options.start >= 0 && options.end >= 0) {
|
||||
|
||||
var endBuffer = new Buffer(4);
|
||||
endBuffer.writeUInt32BE(options.end);
|
||||
endBuffer.writeUInt32BE(options.end, 0);
|
||||
|
||||
var startBuffer = new Buffer(4);
|
||||
startBuffer.writeUInt32BE(options.start + 1);
|
||||
// Because the key has additional data following it, we don't have an ability
|
||||
// to use "gte" or "lte" we can only use "gt" and "lt", we therefore need to adjust the number
|
||||
// to be one value larger to include it.
|
||||
var adjustedStart = options.start + 1;
|
||||
startBuffer.writeUInt32BE(adjustedStart, 0);
|
||||
|
||||
stream = this.node.services.db.store.createReadStream({
|
||||
gte: Buffer.concat([
|
||||
gt: Buffer.concat([
|
||||
constants.PREFIXES.SPENTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.SPACER_MIN,
|
||||
endBuffer
|
||||
]),
|
||||
lte: Buffer.concat([
|
||||
lt: Buffer.concat([
|
||||
constants.PREFIXES.SPENTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
|
@ -815,8 +826,8 @@ AddressService.prototype.createInputsDBStream = function(addressStr, options) {
|
|||
} else {
|
||||
var allKey = Buffer.concat([constants.PREFIXES.SPENTS, hashBuffer, hashTypeBuffer]);
|
||||
stream = this.node.services.db.store.createReadStream({
|
||||
gte: Buffer.concat([allKey, constants.SPACER_MIN]),
|
||||
lte: Buffer.concat([allKey, constants.SPACER_MAX]),
|
||||
gt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MIN]),
|
||||
lt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MAX]),
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
});
|
||||
|
@ -903,27 +914,28 @@ AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, ha
|
|||
constants.MEMPREFIXES.SPENTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.TIMESTAMP_MIN
|
||||
constants.SPACER_MIN
|
||||
]),
|
||||
lte: Buffer.concat([
|
||||
constants.MEMPREFIXES.SPENTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.TIMESTAMP_MAX
|
||||
constants.SPACER_MAX
|
||||
]),
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
});
|
||||
|
||||
stream.on('data', function(data) {
|
||||
var timestamp = data.key.readDoubleBE(22);
|
||||
var txid = data.value.slice(0, 32);
|
||||
var inputIndex = data.value.readUInt32BE(32);
|
||||
var timestamp = data.value.readDoubleBE(36);
|
||||
var input = {
|
||||
address: addressStr,
|
||||
hashType: constants.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')],
|
||||
txid: txid.toString('hex'), //TODO use a buffer
|
||||
inputIndex: inputIndex,
|
||||
timestamp: timestamp,
|
||||
height: -1,
|
||||
confirmations: 0
|
||||
};
|
||||
|
@ -979,7 +991,13 @@ AddressService.prototype.createOutputsStream = function(addressStr, options) {
|
|||
tipHeight: this.node.services.db.tip.__height
|
||||
});
|
||||
|
||||
var stream = this.createOutputsDBStream(addressStr, options).pipe(outputStream);
|
||||
var stream = this.createOutputsDBStream(addressStr, options)
|
||||
.on('error', function(err) {
|
||||
// Forward the error
|
||||
outputStream.emit('error', err);
|
||||
outputStream.end();
|
||||
})
|
||||
.pipe(outputStream);
|
||||
|
||||
return stream;
|
||||
|
||||
|
@ -992,22 +1010,27 @@ AddressService.prototype.createOutputsDBStream = function(addressStr, options) {
|
|||
var hashTypeBuffer = addrObj.hashTypeBuffer;
|
||||
var stream;
|
||||
|
||||
if (options.start && options.end) {
|
||||
if (options.start >= 0 && options.end >= 0) {
|
||||
|
||||
var endBuffer = new Buffer(4);
|
||||
endBuffer.writeUInt32BE(options.end, 0);
|
||||
|
||||
var startBuffer = new Buffer(4);
|
||||
startBuffer.writeUInt32BE(options.start + 1);
|
||||
var endBuffer = new Buffer(4);
|
||||
endBuffer.writeUInt32BE(options.end);
|
||||
// Because the key has additional data following it, we don't have an ability
|
||||
// to use "gte" or "lte" we can only use "gt" and "lt", we therefore need to adjust the number
|
||||
// to be one value larger to include it.
|
||||
var startAdjusted = options.start + 1;
|
||||
startBuffer.writeUInt32BE(startAdjusted, 0);
|
||||
|
||||
stream = this.node.services.db.store.createReadStream({
|
||||
gte: Buffer.concat([
|
||||
gt: Buffer.concat([
|
||||
constants.PREFIXES.OUTPUTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.SPACER_MIN,
|
||||
endBuffer
|
||||
]),
|
||||
lte: Buffer.concat([
|
||||
lt: Buffer.concat([
|
||||
constants.PREFIXES.OUTPUTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
|
@ -1020,8 +1043,8 @@ AddressService.prototype.createOutputsDBStream = function(addressStr, options) {
|
|||
} else {
|
||||
var allKey = Buffer.concat([constants.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer]);
|
||||
stream = this.node.services.db.store.createReadStream({
|
||||
gte: Buffer.concat([allKey, constants.SPACER_MIN]),
|
||||
lte: Buffer.concat([allKey, constants.SPACER_MAX]),
|
||||
gt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MIN]),
|
||||
lt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MAX]),
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
});
|
||||
|
@ -1113,13 +1136,13 @@ AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, h
|
|||
constants.MEMPREFIXES.OUTPUTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.TIMESTAMP_MIN
|
||||
constants.SPACER_MIN
|
||||
]),
|
||||
lte: Buffer.concat([
|
||||
constants.MEMPREFIXES.OUTPUTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.TIMESTAMP_MAX
|
||||
constants.SPACER_MAX
|
||||
]),
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
|
@ -1127,18 +1150,17 @@ AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, h
|
|||
|
||||
stream.on('data', function(data) {
|
||||
// Format of data:
|
||||
// prefix: 1, hashBuffer: 20, hashTypeBuffer: 1, timestamp: 8, txid: 32, outputIndex: 4
|
||||
var timestamp = data.key.readDoubleBE(22);
|
||||
var txid = data.key.slice(30, 62);
|
||||
var outputIndex = data.key.readUInt32BE(62);
|
||||
var value = encoding.decodeOutputValue(data.value);
|
||||
// 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 = encoding.decodeOutputMempoolValue(data.value);
|
||||
var output = {
|
||||
address: addressStr,
|
||||
hashType: constants.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')],
|
||||
txid: txid.toString('hex'), //TODO use a buffer
|
||||
outputIndex: outputIndex,
|
||||
height: -1,
|
||||
timestamp: timestamp,
|
||||
timestamp: value.timestamp,
|
||||
satoshis: value.satoshis,
|
||||
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
|
||||
confirmations: 0
|
||||
|
@ -1325,43 +1347,25 @@ AddressService.prototype.getAddressSummary = function(addressArg, options, callb
|
|||
var self = this;
|
||||
|
||||
var startTime = new Date();
|
||||
|
||||
var address = new Address(addressArg);
|
||||
var tipHeight = this.node.services.db.tip.__height;
|
||||
|
||||
if (_.isUndefined(options.queryMempool)) {
|
||||
options.queryMempool = true;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self._getAddressSummaryCache(address, next);
|
||||
self._getAddressConfirmedSummary(address, options, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._getAddressInputsSummary(address, cache, tipHeight, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._getAddressOutputsSummary(address, cache, tipHeight, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._sortTxids(cache, tipHeight, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._saveAddressSummaryCache(address, cache, tipHeight, next);
|
||||
self._getAddressMempoolSummary(address, options, cache, next);
|
||||
}
|
||||
], function(err, cache) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var result = cache.result;
|
||||
var confirmedTxids = result.txids;
|
||||
var unconfirmedTxids = Object.keys(result.unconfirmedAppearanceIds);
|
||||
|
||||
var summary = {
|
||||
totalReceived: result.totalReceived,
|
||||
totalSpent: result.totalReceived - result.balance,
|
||||
balance: result.balance,
|
||||
appearances: confirmedTxids.length,
|
||||
unconfirmedBalance: result.unconfirmedBalance,
|
||||
unconfirmedAppearances: unconfirmedTxids.length
|
||||
};
|
||||
var summary = self._transformAddressSummaryFromCache(cache, options);
|
||||
|
||||
var timeDelta = new Date() - startTime;
|
||||
if (timeDelta > 5000) {
|
||||
|
@ -1370,52 +1374,30 @@ AddressService.prototype.getAddressSummary = function(addressArg, options, callb
|
|||
log.warn('Address Summary:', summary);
|
||||
}
|
||||
|
||||
if (options.fullTxList) {
|
||||
summary.appearanceIds = result.appearanceIds;
|
||||
summary.unconfirmedAppearanceIds = result.unconfirmedAppearanceIds;
|
||||
} else if (!options.noTxList) {
|
||||
summary.txids = confirmedTxids.concat(unconfirmedTxids);
|
||||
}
|
||||
|
||||
callback(null, summary);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._sortTxids = function(cache, tipHeight, callback) {
|
||||
if (cache.height === tipHeight) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
cache.result.txids = Object.keys(cache.result.appearanceIds);
|
||||
cache.result.txids.sort(function(a, b) {
|
||||
return cache.result.appearanceIds[a] - cache.result.appearanceIds[b];
|
||||
AddressService.prototype._getAddressConfirmedSummary = function(address, options, callback) {
|
||||
var self = this;
|
||||
var tipHeight = this.node.services.db.tip.__height;
|
||||
|
||||
self._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Immediately give cache is already current, otherwise update
|
||||
if (cache && cache.height === tipHeight) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
self._updateAddressConfirmedSummaryCache(address, options, cache, tipHeight, callback);
|
||||
});
|
||||
callback(null, cache);
|
||||
};
|
||||
|
||||
AddressService.prototype._saveAddressSummaryCache = function(address, cache, tipHeight, callback) {
|
||||
if (cache.height === tipHeight) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
var transactionLength = cache.result.txids.length;
|
||||
var exceedsCacheThreshold = (transactionLength > this.summaryCacheThreshold);
|
||||
if (exceedsCacheThreshold) {
|
||||
log.info('Saving address summary cache for: ' + address.toString() + 'at height: ' + tipHeight);
|
||||
var key = encoding.encodeSummaryCacheKey(address);
|
||||
var value = encoding.encodeSummaryCacheValue(cache, tipHeight);
|
||||
this.summaryCache.put(key, value, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, cache);
|
||||
});
|
||||
} else {
|
||||
callback(null, cache);
|
||||
}
|
||||
};
|
||||
|
||||
AddressService.prototype._getAddressSummaryCache = function(address, callback) {
|
||||
AddressService.prototype._getAddressConfirmedSummaryCache = function(address, options, callback) {
|
||||
var self = this;
|
||||
var baseCache = {
|
||||
result: {
|
||||
appearanceIds: {},
|
||||
|
@ -1425,6 +1407,11 @@ AddressService.prototype._getAddressSummaryCache = function(address, callback) {
|
|||
unconfirmedBalance: 0
|
||||
}
|
||||
};
|
||||
// Use the base cache if the "start" and "end" options have been used
|
||||
// We only save and retrieve a cache for the summary of all history
|
||||
if (options.start >= 0 || options.end >= 0) {
|
||||
return callback(null, baseCache);
|
||||
}
|
||||
var key = encoding.encodeSummaryCacheKey(address);
|
||||
this.summaryCache.get(key, {
|
||||
valueEncoding: 'binary',
|
||||
|
@ -1436,25 +1423,64 @@ AddressService.prototype._getAddressSummaryCache = function(address, callback) {
|
|||
return callback(err);
|
||||
}
|
||||
var cache = encoding.decodeSummaryCacheValue(buffer);
|
||||
|
||||
// Use base cache if the cached tip/height doesn't match (e.g. there has been a reorg)
|
||||
var blockIndex = self.node.services.bitcoind.getBlockIndex(cache.height);
|
||||
if (cache.hash !== blockIndex.hash) {
|
||||
return callback(null, baseCache);
|
||||
}
|
||||
|
||||
callback(null, cache);
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype._getAddressInputsSummary = function(address, cache, tipHeight, callback) {
|
||||
if (cache.height === tipHeight) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
$.checkArgument(address instanceof Address);
|
||||
AddressService.prototype._updateAddressConfirmedSummaryCache = function(address, options, cache, tipHeight, callback) {
|
||||
var self = this;
|
||||
|
||||
var optionsPartial = _.clone(options);
|
||||
var isHeightQuery = (options.start >= 0 || options.end >= 0);
|
||||
if (!isHeightQuery) {
|
||||
// We will pick up from the last point cached and query for all blocks
|
||||
// proceeding the cache
|
||||
var cacheHeight = _.isUndefined(cache.height) ? 0 : cache.height + 1;
|
||||
optionsPartial.start = tipHeight;
|
||||
optionsPartial.end = cacheHeight;
|
||||
} else {
|
||||
$.checkState(_.isUndefined(cache.height));
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self._getAddressConfirmedInputsSummary(address, cache, optionsPartial, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._getAddressConfirmedOutputsSummary(address, cache, optionsPartial, next);
|
||||
},
|
||||
function(cache, next) {
|
||||
self._setAndSortTxidsFromAppearanceIds(cache, next);
|
||||
}
|
||||
], function(err, cache) {
|
||||
|
||||
// Skip saving the cache if the "start" or "end" options have been used, or
|
||||
// if the transaction length does not exceed the caching threshold.
|
||||
// We only want to cache full history results for addresses that have a large
|
||||
// number of transactions.
|
||||
var exceedsCacheThreshold = (cache.result.txids.length > self.summaryCacheThreshold);
|
||||
if (exceedsCacheThreshold && !isHeightQuery) {
|
||||
self._saveAddressConfirmedSummaryCache(address, cache, tipHeight, callback);
|
||||
} else {
|
||||
callback(null, cache);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype._getAddressConfirmedInputsSummary = function(address, cache, options, callback) {
|
||||
$.checkArgument(address instanceof Address);
|
||||
var self = this;
|
||||
var error = null;
|
||||
|
||||
var opts = {
|
||||
start: _.isUndefined(cache.height) ? 0 : cache.height + 1,
|
||||
end: tipHeight
|
||||
};
|
||||
|
||||
var inputsStream = self.createInputsStream(address, opts);
|
||||
var inputsStream = self.createInputsStream(address, options);
|
||||
inputsStream.on('data', function(input) {
|
||||
var txid = input.txid;
|
||||
cache.result.appearanceIds[txid] = input.height;
|
||||
|
@ -1465,28 +1491,14 @@ AddressService.prototype._getAddressInputsSummary = function(address, cache, tip
|
|||
});
|
||||
|
||||
inputsStream.on('end', function() {
|
||||
|
||||
var addressStr = address.toString();
|
||||
var hashBuffer = address.hashBuffer;
|
||||
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
||||
|
||||
self._getInputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolInputs) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
for(var i = 0; i < mempoolInputs.length; i++) {
|
||||
var input = mempoolInputs[i];
|
||||
cache.result.unconfirmedAppearanceIds[input.txid] = input.timestamp;
|
||||
}
|
||||
callback(error, cache);
|
||||
});
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
callback(null, cache);
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype._getAddressOutputsSummary = function(address, cache, tipHeight, callback) {
|
||||
if (cache.height === tipHeight) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
AddressService.prototype._getAddressConfirmedOutputsSummary = function(address, cache, options, callback) {
|
||||
$.checkArgument(address instanceof Address);
|
||||
$.checkArgument(!_.isUndefined(cache.result) &&
|
||||
!_.isUndefined(cache.result.appearanceIds) &&
|
||||
|
@ -1494,12 +1506,7 @@ AddressService.prototype._getAddressOutputsSummary = function(address, cache, ti
|
|||
|
||||
var self = this;
|
||||
|
||||
var opts = {
|
||||
start: _.isUndefined(cache.height) ? 0 : cache.height + 1,
|
||||
end: tipHeight
|
||||
};
|
||||
|
||||
var outputStream = self.createOutputsStream(address, opts);
|
||||
var outputStream = self.createOutputsStream(address, options);
|
||||
|
||||
outputStream.on('data', function(output) {
|
||||
|
||||
|
@ -1514,16 +1521,19 @@ AddressService.prototype._getAddressOutputsSummary = function(address, cache, ti
|
|||
if (!spentDB) {
|
||||
cache.result.balance += output.satoshis;
|
||||
}
|
||||
// TODO: subtract if spent (because of cache)?
|
||||
|
||||
// Check to see if this output is spent in the mempool and if so
|
||||
// we will subtract it from the unconfirmedBalance (a.k.a unconfirmedDelta)
|
||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||
new Buffer(txid, 'hex'), // TODO: get buffer directly
|
||||
outputIndex
|
||||
);
|
||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||
if (spentMempool) {
|
||||
cache.result.unconfirmedBalance -= output.satoshis;
|
||||
if (options.queryMempool) {
|
||||
// Check to see if this output is spent in the mempool and if so
|
||||
// we will subtract it from the unconfirmedBalance (a.k.a unconfirmedDelta)
|
||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||
new Buffer(txid, 'hex'), // TODO: get buffer directly
|
||||
outputIndex
|
||||
);
|
||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||
if (spentMempool) {
|
||||
cache.result.unconfirmedBalance -= output.satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -1535,38 +1545,112 @@ AddressService.prototype._getAddressOutputsSummary = function(address, cache, ti
|
|||
});
|
||||
|
||||
outputStream.on('end', function() {
|
||||
|
||||
var addressStr = address.toString();
|
||||
var hashBuffer = address.hashBuffer;
|
||||
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
||||
|
||||
self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
for(var i = 0; i < mempoolOutputs.length; i++) {
|
||||
var output = mempoolOutputs[i];
|
||||
|
||||
cache.result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
|
||||
|
||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||
new Buffer(output.txid, 'hex'), // TODO: get buffer directly
|
||||
output.outputIndex
|
||||
);
|
||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||
// Only add this to the balance if it's not spent in the mempool already
|
||||
if (!spentMempool) {
|
||||
cache.result.unconfirmedBalance += output.satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
callback(error, cache);
|
||||
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
callback(null, cache);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._setAndSortTxidsFromAppearanceIds = function(cache, callback) {
|
||||
cache.result.txids = Object.keys(cache.result.appearanceIds);
|
||||
cache.result.txids.sort(function(a, b) {
|
||||
return cache.result.appearanceIds[a] - cache.result.appearanceIds[b];
|
||||
});
|
||||
callback(null, cache);
|
||||
};
|
||||
|
||||
AddressService.prototype._saveAddressConfirmedSummaryCache = function(address, cache, tipHeight, callback) {
|
||||
|
||||
log.info('Saving address summary cache for: ' + address.toString() + 'at height: ' + tipHeight);
|
||||
var key = encoding.encodeSummaryCacheKey(address);
|
||||
var tipBlockIndex = this.node.services.bitcoind.getBlockIndex(tipHeight);
|
||||
var value = encoding.encodeSummaryCacheValue(cache, tipHeight, tipBlockIndex.hash);
|
||||
this.summaryCache.put(key, value, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, cache);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._getAddressMempoolSummary = function(address, options, cache, callback) {
|
||||
var self = this;
|
||||
|
||||
// Skip if the options do not want to include the mempool
|
||||
if (!options.queryMempool) {
|
||||
return callback(null, cache);
|
||||
}
|
||||
|
||||
var addressStr = address.toString();
|
||||
var hashBuffer = address.hashBuffer;
|
||||
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self._getInputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolInputs) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
for(var i = 0; i < mempoolInputs.length; i++) {
|
||||
var input = mempoolInputs[i];
|
||||
cache.result.unconfirmedAppearanceIds[input.txid] = input.timestamp;
|
||||
}
|
||||
next(null, cache);
|
||||
});
|
||||
|
||||
}, function(cache, next) {
|
||||
self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
for(var i = 0; i < mempoolOutputs.length; i++) {
|
||||
var output = mempoolOutputs[i];
|
||||
|
||||
cache.result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
|
||||
|
||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||
new Buffer(output.txid, 'hex'), // TODO: get buffer directly
|
||||
output.outputIndex
|
||||
);
|
||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||
// Only add this to the balance if it's not spent in the mempool already
|
||||
if (!spentMempool) {
|
||||
cache.result.unconfirmedBalance += output.satoshis;
|
||||
}
|
||||
}
|
||||
next(null, cache);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
AddressService.prototype._transformAddressSummaryFromCache = function(cache, options) {
|
||||
|
||||
var result = cache.result;
|
||||
var confirmedTxids = cache.result.txids;
|
||||
var unconfirmedTxids = Object.keys(result.unconfirmedAppearanceIds);
|
||||
|
||||
var summary = {
|
||||
totalReceived: result.totalReceived,
|
||||
totalSpent: result.totalReceived - result.balance,
|
||||
balance: result.balance,
|
||||
appearances: confirmedTxids.length,
|
||||
unconfirmedBalance: result.unconfirmedBalance,
|
||||
unconfirmedAppearances: unconfirmedTxids.length
|
||||
};
|
||||
|
||||
if (options.fullTxList) {
|
||||
summary.appearanceIds = result.appearanceIds;
|
||||
summary.unconfirmedAppearanceIds = result.unconfirmedAppearanceIds;
|
||||
} else if (!options.noTxList) {
|
||||
summary.txids = confirmedTxids.concat(unconfirmedTxids);
|
||||
}
|
||||
|
||||
return summary;
|
||||
|
||||
};
|
||||
|
||||
module.exports = AddressService;
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
"commander": "^2.8.1",
|
||||
"errno": "^0.1.4",
|
||||
"express": "^4.13.3",
|
||||
"leveldown": "^1.4.2",
|
||||
"levelup": "^1.2.1",
|
||||
"leveldown": "^1.4.3",
|
||||
"levelup": "^1.3.1",
|
||||
"liftoff": "^2.2.0",
|
||||
"memdown": "^1.0.0",
|
||||
"mkdirp": "0.5.0",
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
var sinon = require('sinon');
|
||||
var bitcorenode = require('../../../');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Address = bitcore.Address;
|
||||
var Script = bitcore.Script;
|
||||
var AddressService = bitcorenode.services.Address;
|
||||
var Networks = bitcore.Networks;
|
||||
var encoding = require('../../../lib/services/address/encoding');
|
||||
|
||||
var mockdb = {
|
||||
};
|
||||
|
||||
var mocknode = {
|
||||
network: Networks.testnet,
|
||||
datadir: 'testdir',
|
||||
db: mockdb,
|
||||
services: {
|
||||
bitcoind: {
|
||||
on: sinon.stub()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Address Service Encoding', function() {
|
||||
|
||||
describe('#encodeSpentIndexSyncKey', function() {
|
||||
it('will encode to 36 bytes (string)', function() {
|
||||
var txidBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
|
||||
key.length.should.equal(36);
|
||||
});
|
||||
it('will be able to decode encoded value', function() {
|
||||
var txid = '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7';
|
||||
var txidBuffer = new Buffer(txid, 'hex');
|
||||
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
|
||||
var keyBuffer = new Buffer(key, 'binary');
|
||||
keyBuffer.slice(0, 32).toString('hex').should.equal(txid);
|
||||
var outputIndex = keyBuffer.readUInt32BE(32);
|
||||
outputIndex.should.equal(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_encodeInputKeyMap/#_decodeInputKeyMap roundtrip', function() {
|
||||
var encoded;
|
||||
var outputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
it('encode key', function() {
|
||||
encoded = encoding.encodeInputKeyMap(outputTxIdBuffer, 13);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var key = encoding.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() {
|
||||
encoded = encoding.encodeInputValueMap(inputTxIdBuffer, 7);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var key = encoding.decodeInputValueMap(encoded);
|
||||
key.inputTxId.toString('hex').should.equal(inputTxIdBuffer.toString('hex'));
|
||||
key.inputIndex.should.equal(7);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#extractAddressInfoFromScript', function() {
|
||||
it('pay-to-publickey', function() {
|
||||
var pubkey = new bitcore.PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da');
|
||||
var script = Script.buildPublicKeyOut(pubkey);
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToPublicKeyHash);
|
||||
info.hashBuffer.toString('hex').should.equal('9674af7395592ec5d91573aa8d6557de55f60147');
|
||||
});
|
||||
it('pay-to-publickeyhash', function() {
|
||||
var script = Script('OP_DUP OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG');
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToPublicKeyHash);
|
||||
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
|
||||
});
|
||||
it('pay-to-scripthash', function() {
|
||||
var script = Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL');
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToScriptHash);
|
||||
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
|
||||
});
|
||||
it('non-address script type', function() {
|
||||
var buf = new Buffer(40);
|
||||
buf.fill(0);
|
||||
var script = Script('OP_RETURN 40 0x' + buf.toString('hex'));
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -23,8 +23,6 @@ describe('Address Service History', function() {
|
|||
history.node.should.equal(node);
|
||||
history.options.should.equal(options);
|
||||
history.addresses.should.equal(addresses);
|
||||
history.transactionInfo.should.deep.equal([]);
|
||||
history.combinedArray.should.deep.equal([]);
|
||||
history.detailedArray.should.deep.equal([]);
|
||||
});
|
||||
it('will set addresses an array if only sent a string', function() {
|
||||
|
@ -40,27 +38,29 @@ describe('Address Service History', function() {
|
|||
describe('#get', function() {
|
||||
it('will complete the async each limit series', function(done) {
|
||||
var addresses = [address];
|
||||
var summary = {
|
||||
txids: []
|
||||
};
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: addresses
|
||||
});
|
||||
var expected = [{}];
|
||||
history.detailedArray = expected;
|
||||
history.combinedArray = [{}];
|
||||
history.getTransactionInfo = sinon.stub().callsArg(1);
|
||||
history.combineTransactionInfo = sinon.stub();
|
||||
history.sortAndPaginateCombinedArray = sinon.stub();
|
||||
history.getDetailedInfo = sinon.stub().callsArg(1);
|
||||
history.sortTransactionsIntoArray = sinon.stub();
|
||||
history.get(function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
history.getTransactionInfo.callCount.should.equal(1);
|
||||
history.getDetailedInfo.callCount.should.equal(1);
|
||||
history.combineTransactionInfo.callCount.should.equal(1);
|
||||
history.sortAndPaginateCombinedArray.callCount.should.equal(1);
|
||||
results.should.deep.equal({
|
||||
totalCount: 1,
|
||||
items: expected
|
||||
|
@ -78,149 +78,15 @@ describe('Address Service History', function() {
|
|||
var expected = [{}];
|
||||
history.sortedArray = expected;
|
||||
history.transactionInfo = [{}];
|
||||
history.getTransactionInfo = sinon.stub().callsArg(1);
|
||||
history.paginateSortedArray = sinon.stub();
|
||||
history.getDetailedInfo = sinon.stub().callsArgWith(1, new Error('test'));
|
||||
history.get(function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('handle an error from getTransactionInfo', function(done) {
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {},
|
||||
addresses: addresses
|
||||
});
|
||||
var expected = [{}];
|
||||
history.sortedArray = expected;
|
||||
history.transactionInfo = [{}];
|
||||
history.getTransactionInfo = sinon.stub().callsArgWith(1, new Error('test'));
|
||||
history.get(function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTransactionInfo', function() {
|
||||
it('will handle an error from getInputs', function(done) {
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getOutputs: sinon.stub().callsArgWith(2, null, []),
|
||||
getInputs: sinon.stub().callsArgWith(2, new Error('test'))
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.getTransactionInfo(address, function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will handle an error from getOutputs', function(done) {
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getOutputs: sinon.stub().callsArgWith(2, new Error('test')),
|
||||
getInputs: sinon.stub().callsArgWith(2, null, [])
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.getTransactionInfo(address, function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will call getOutputs and getInputs with the correct options', function() {
|
||||
var startTimestamp = 1438289011844;
|
||||
var endTimestamp = 1438289012412;
|
||||
var expectedArgs = {
|
||||
start: new Date(startTimestamp * 1000),
|
||||
end: new Date(endTimestamp * 1000),
|
||||
queryMempool: true
|
||||
};
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getOutputs: sinon.stub().callsArgWith(2, null, []),
|
||||
getInputs: sinon.stub().callsArgWith(2, null, [])
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
start: new Date(startTimestamp * 1000),
|
||||
end: new Date(endTimestamp * 1000),
|
||||
queryMempool: true
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.transactionInfo = [{}];
|
||||
history.getTransactionInfo(address, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
history.node.services.address.getOutputs.args[0][1].should.deep.equal(expectedArgs);
|
||||
history.node.services.address.getInputs.args[0][1].should.deep.equal(expectedArgs);
|
||||
});
|
||||
});
|
||||
it('will handle empty results from getOutputs and getInputs', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getOutputs: sinon.stub().callsArgWith(2, null, []),
|
||||
getInputs: sinon.stub().callsArgWith(2, null, [])
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.transactionInfo = [{}];
|
||||
history.getTransactionInfo(address, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
history.transactionInfo.length.should.equal(1);
|
||||
history.node.services.address.getOutputs.args[0][0].should.equal(address);
|
||||
});
|
||||
});
|
||||
it('will concatenate outputs and inputs', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
address: {
|
||||
getOutputs: sinon.stub().callsArgWith(2, null, [{}]),
|
||||
getInputs: sinon.stub().callsArgWith(2, null, [{}])
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.transactionInfo = [{}];
|
||||
history.getTransactionInfo(address, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
history.transactionInfo.length.should.equal(3);
|
||||
history.node.services.address.getOutputs.args[0][0].should.equal(address);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@sortByHeight', function() {
|
||||
describe('#_mergeAndSortTxids', function() {
|
||||
it('will sort latest to oldest using height', function() {
|
||||
var transactionInfo = [
|
||||
{
|
||||
|
@ -386,131 +252,6 @@ describe('Address Service History', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#sortAndPaginateCombinedArray', function() {
|
||||
it('from 0 to 2', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {
|
||||
from: 0,
|
||||
to: 2
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.combinedArray = [
|
||||
{
|
||||
height: 13
|
||||
},
|
||||
{
|
||||
height: 14,
|
||||
},
|
||||
{
|
||||
height: 12
|
||||
}
|
||||
];
|
||||
history.sortAndPaginateCombinedArray();
|
||||
history.combinedArray.length.should.equal(2);
|
||||
history.combinedArray[0].height.should.equal(14);
|
||||
history.combinedArray[1].height.should.equal(13);
|
||||
});
|
||||
it('from 0 to 4 (exceeds length)', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {
|
||||
from: 0,
|
||||
to: 4
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.combinedArray = [
|
||||
{
|
||||
height: 13
|
||||
},
|
||||
{
|
||||
height: 14,
|
||||
},
|
||||
{
|
||||
height: 12
|
||||
}
|
||||
];
|
||||
history.sortAndPaginateCombinedArray();
|
||||
history.combinedArray.length.should.equal(3);
|
||||
history.combinedArray[0].height.should.equal(14);
|
||||
history.combinedArray[1].height.should.equal(13);
|
||||
history.combinedArray[2].height.should.equal(12);
|
||||
});
|
||||
it('from 0 to 1', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {
|
||||
from: 0,
|
||||
to: 1
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.combinedArray = [
|
||||
{
|
||||
height: 13
|
||||
},
|
||||
{
|
||||
height: 14,
|
||||
},
|
||||
{
|
||||
height: 12
|
||||
}
|
||||
];
|
||||
history.sortAndPaginateCombinedArray();
|
||||
history.combinedArray.length.should.equal(1);
|
||||
history.combinedArray[0].height.should.equal(14);
|
||||
});
|
||||
it('from 2 to 3', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {
|
||||
from: 2,
|
||||
to: 3
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.combinedArray = [
|
||||
{
|
||||
height: 13
|
||||
},
|
||||
{
|
||||
height: 14,
|
||||
},
|
||||
{
|
||||
height: 12
|
||||
}
|
||||
];
|
||||
history.sortAndPaginateCombinedArray();
|
||||
history.combinedArray.length.should.equal(1);
|
||||
history.combinedArray[0].height.should.equal(12);
|
||||
});
|
||||
it('from 10 to 20 (out of range)', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {
|
||||
from: 10,
|
||||
to: 20
|
||||
},
|
||||
addresses: []
|
||||
});
|
||||
history.combinedArray = [
|
||||
{
|
||||
height: 13
|
||||
},
|
||||
{
|
||||
height: 14,
|
||||
},
|
||||
{
|
||||
height: 12
|
||||
}
|
||||
];
|
||||
history.sortAndPaginateCombinedArray();
|
||||
history.combinedArray.length.should.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDetailedInfo', function() {
|
||||
it('will add additional information to existing this.transactions', function() {
|
||||
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
|
||||
|
@ -602,7 +343,7 @@ describe('Address Service History', function() {
|
|||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
addresses: [txAddress]
|
||||
});
|
||||
var transactionInfo = {
|
||||
addresses: {},
|
||||
|
@ -614,7 +355,7 @@ describe('Address Service History', function() {
|
|||
transactionInfo.addresses[txAddress] = {};
|
||||
transactionInfo.addresses[txAddress].outputIndexes = [1];
|
||||
transactionInfo.addresses[txAddress].inputIndexes = [];
|
||||
history.getDetailedInfo(transactionInfo, function(err) {
|
||||
history.getDetailedInfo(txid, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -653,28 +394,4 @@ describe('Address Service History', function() {
|
|||
history.getConfirmationsDetail(transaction).should.equal(1);
|
||||
});
|
||||
});
|
||||
describe('#getSatoshisDetail', function() {
|
||||
it('subtract inputIndexes satoshis without outputIndexes', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
var transaction = {
|
||||
inputs: [
|
||||
{
|
||||
output: {
|
||||
satoshis: 10000
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
var txInfo = {
|
||||
addresses: {}
|
||||
};
|
||||
txInfo.addresses[address] = {};
|
||||
txInfo.addresses[address].inputIndexes = [0];
|
||||
history.getSatoshisDetail(transaction, txInfo).should.equal(-10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue