get address index to work

This commit is contained in:
Patrick Nagurny 2017-01-19 16:11:04 -05:00
parent df8cf1bbb3
commit efa8480651
6 changed files with 550 additions and 857 deletions

View File

@ -264,6 +264,7 @@ Node.prototype.start = function(callback) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
self.emit('ready'); self.emit('ready');
callback(); callback();
} }

View File

@ -139,6 +139,7 @@ function loadModule(req, service) {
var serviceFile = path.resolve(__dirname, '../services/' + service.name); var serviceFile = path.resolve(__dirname, '../services/' + service.name);
service.module = req(serviceFile); service.module = req(serviceFile);
} catch(e) { } catch(e) {
console.log('Could not load ' + service.name + ' service');
log.error(e.stack); log.error(e.stack);
var servicePackage = req(service.name + '/package.json'); var servicePackage = req(service.name + '/package.json');
var serviceModule = service.name; var serviceModule = service.name;

View File

@ -9,12 +9,17 @@ var $ = bitcore.util.preconditions;
var exports = {}; var exports = {};
exports.encodeAddressIndexKey = function(address, isSpent, height, txidBuffer, index, spending) { function Encoding(prefix) {
this.prefix = prefix;
}
Encoding.prototype.encodeAddressIndexKey = function(address, isSpent, height, txid, index, spending) {
var addressSizeBuffer = new Buffer(1); var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length); addressSizeBuffer.writeUInt8(address.length);
var addressBuffer = new Buffer(address, 'utf8'); var addressBuffer = new Buffer(address, 'utf8');
var heightBuffer = new Buffer(4); var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height); heightBuffer.writeUInt32BE(height);
var txidBuffer = new Buffer(txid, 'hex');
var indexBuffer = new Buffer(4); var indexBuffer = new Buffer(4);
indexBuffer.writeUInt32BE(index); indexBuffer.writeUInt32BE(index);
var spendingBuffer = new Buffer(1); var spendingBuffer = new Buffer(1);
@ -22,8 +27,8 @@ exports.encodeAddressIndexKey = function(address, isSpent, height, txidBuffer, i
var isSpentBuffer = new Buffer(1); var isSpentBuffer = new Buffer(1);
isSpentBuffer.writeUInt8(isSpent); isSpentBuffer.writeUInt8(isSpent);
var key = Buffer.concat({ return Buffer.concat([
constants.PREFIXES.ADDRESS, this.prefix,
addressSizeBuffer, addressSizeBuffer,
addressBuffer, addressBuffer,
isSpentBuffer, isSpentBuffer,
@ -31,38 +36,37 @@ exports.encodeAddressIndexKey = function(address, isSpent, height, txidBuffer, i
txidBuffer, txidBuffer,
indexBuffer, indexBuffer,
spendingBuffer spendingBuffer
}); ]);
}; };
exports.decodeAddressIndexKey = function(buffer) { Encoding.prototype.decodeAddressIndexKey = function(buffer) {
var reader = new BufferReader(buffer); var reader = new BufferReader(buffer);
var prefix = reader.read(1); var prefix = reader.read(2);
var addressSize = reader.readUInt8(); var addressSize = reader.readUInt8();
var address = reader.read(addressSize).toString('utf8'); var address = reader.read(addressSize).toString('utf8');
var isSpent = reader.readUInt8(); var isSpent = reader.readUInt8();
var height = reader.readUInt32BE(); var height = reader.readUInt32BE();
var txid = reader.read(32); var txid = reader.read(32).toString('hex');
var index = reader.readUInt32BE(); var index = reader.readUInt32BE();
var spending = reader.readUInt8(); var spending = reader.readUInt8();
return { return {
prefix: prefix,
address: address, address: address,
isSpent: isSpent ? true : false, isSpent: isSpent ? true : false,
height: height, height: height,
txid: txid, txid: txid,
index: outputIndex, index: index,
spending: spending ? true : false spending: spending ? true : false
}; };
}; };
exports.encodeAddressIndexValue = function(satoshis, scriptBuffer) { Encoding.prototype.encodeAddressIndexValue = function(satoshis, scriptBuffer) {
var satoshisBuffer = new Buffer(8); var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis); satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, scriptBuffer]); return Buffer.concat([satoshisBuffer, scriptBuffer]);
}; };
exports.decodeAddressIndexValue = function(buffer) { Encoding.prototype.decodeAddressIndexValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0); var satoshis = buffer.readDoubleBE(0);
var scriptBuffer = buffer.slice(8, buffer.length); var scriptBuffer = buffer.slice(8, buffer.length);
return { return {
@ -71,354 +75,355 @@ exports.decodeAddressIndexValue = function(buffer) {
}; };
}; };
exports.encodeUnspentIndexKey = function(hashTypeBuffer, hashBuffer, txidBuffer, index) { // exports.encodeUnspentIndexKey = function(hashTypeBuffer, hashBuffer, txid, index) {
var indexBuffer = new Buffer(4); // var indexBuffer = new Buffer(4);
indexBuffer.writeUInt32BE(index); // indexBuffer.writeUInt32BE(index);
// var txidBuffer = new Buffer(txid, 'hex');
var key = Buffer.concat({ // var key = Buffer.concat([
constants.PREFIXES.UNSPENT, // constants.PREFIXES.UNSPENT,
hashTypeBuffer, // hashTypeBuffer,
hashBuffer, // hashBuffer,
constants.SPACER_MIN, // constants.SPACER_MIN,
txidBuffer, // txidBuffer,
indexBuffer // indexBuffer
}); // ]);
}; // };
exports.decodeUnspentIndexKey = function(buffer) { // exports.decodeUnspentIndexKey = function(buffer) {
var reader = new BufferReader(buffer); // var reader = new BufferReader(buffer);
var prefix = reader.read(1); // var prefix = reader.read(1);
var hashTypeBuffer = reader.read(1); // var hashTypeBuffer = reader.read(1);
var hashBuffer = reader.read(20); // var hashBuffer = reader.read(20);
var spacer = reader.read(1); // var spacer = reader.read(1);
var txid = reader.read(32); // var txid = reader.read(32);
var index = reader.readUInt32BE(); // var index = reader.readUInt32BE();
return { // return {
prefix: prefix, // prefix: prefix,
hashTypeBuffer: hashTypeBuffer, // hashTypeBuffer: hashTypeBuffer,
hashBuffer: hashBuffer, // hashBuffer: hashBuffer,
txid: txid, // txid: txid,
index: outputIndex // index: outputIndex
}; // };
}; // };
exports.encodeUnspentIndexValue = function(satoshis, height, scriptBuffer) { // exports.encodeUnspentIndexValue = function(satoshis, height, scriptBuffer) {
var satoshisBuffer = new Buffer(8); // var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis); // satoshisBuffer.writeDoubleBE(satoshis);
var heightBuffer = new Buffer(4); // var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height); // heightBuffer.writeUInt32BE(height);
return Buffer.concat([satoshisBuffer, heightBuffer, scriptBuffer]); // return Buffer.concat([satoshisBuffer, heightBuffer, scriptBuffer]);
}; // };
exports.decodeUnspentIndexValue = function(buffer) { // exports.decodeUnspentIndexValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0); // var satoshis = buffer.readDoubleBE(0);
var height = buffer.readUInt32BE(8); // var height = buffer.readUInt32BE(8);
var scriptBuffer = buffer.slice(12, buffer.length); // var scriptBuffer = buffer.slice(12, buffer.length);
return { // return {
satoshis: satoshis, // satoshis: satoshis,
height: height, // height: height,
scriptBuffer: scriptBuffer // scriptBuffer: scriptBuffer
}; // };
}; // };
exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) { // exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
var outputIndexBuffer = new Buffer(4); // var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex); // outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([ // var key = Buffer.concat([
txidBuffer, // txidBuffer,
outputIndexBuffer // outputIndexBuffer
]); // ]);
return key.toString('binary'); // return key.toString('binary');
}; // };
exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) { // exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) {
var key = Buffer.concat([ // var key = Buffer.concat([
hashBuffer, // hashBuffer,
hashTypeBuffer, // hashTypeBuffer,
]); // ]);
return key.toString('binary'); // return key.toString('binary');
}; // };
exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) { // exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) {
var heightBuffer = new Buffer(4); // var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height); // heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4); // var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex); // outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([ // var key = Buffer.concat([
constants.PREFIXES.OUTPUTS, // constants.PREFIXES.OUTPUTS,
hashBuffer, // hashBuffer,
hashTypeBuffer, // hashTypeBuffer,
constants.SPACER_MIN, // constants.SPACER_MIN,
heightBuffer, // heightBuffer,
txidBuffer, // txidBuffer,
outputIndexBuffer // outputIndexBuffer
]); // ]);
return key; // return key;
}; // };
exports.decodeOutputKey = function(buffer) { // exports.decodeOutputKey = function(buffer) {
var reader = new BufferReader(buffer); // var reader = new BufferReader(buffer);
var prefix = reader.read(1); // var prefix = reader.read(1);
var hashBuffer = reader.read(20); // var hashBuffer = reader.read(20);
var hashTypeBuffer = reader.read(1); // var hashTypeBuffer = reader.read(1);
var spacer = reader.read(1); // var spacer = reader.read(1);
var height = reader.readUInt32BE(); // var height = reader.readUInt32BE();
var txid = reader.read(32); // var txid = reader.read(32);
var outputIndex = reader.readUInt32BE(); // var outputIndex = reader.readUInt32BE();
return { // return {
prefix: prefix, // prefix: prefix,
hashBuffer: hashBuffer, // hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer, // hashTypeBuffer: hashTypeBuffer,
height: height, // height: height,
txid: txid, // txid: txid,
outputIndex: outputIndex // outputIndex: outputIndex
}; // };
}; // };
exports.encodeOutputValue = function(satoshis, scriptBuffer) { // exports.encodeOutputValue = function(satoshis, scriptBuffer) {
var satoshisBuffer = new Buffer(8); // var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis); // satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, scriptBuffer]); // return Buffer.concat([satoshisBuffer, scriptBuffer]);
}; // };
exports.encodeOutputMempoolValue = function(satoshis, timestampBuffer, scriptBuffer) { // exports.encodeOutputMempoolValue = function(satoshis, timestampBuffer, scriptBuffer) {
var satoshisBuffer = new Buffer(8); // var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis); // satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, timestampBuffer, scriptBuffer]); // return Buffer.concat([satoshisBuffer, timestampBuffer, scriptBuffer]);
}; // };
exports.decodeOutputValue = function(buffer) { // exports.decodeOutputValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0); // var satoshis = buffer.readDoubleBE(0);
var scriptBuffer = buffer.slice(8, buffer.length); // var scriptBuffer = buffer.slice(8, buffer.length);
return { // return {
satoshis: satoshis, // satoshis: satoshis,
scriptBuffer: scriptBuffer // scriptBuffer: scriptBuffer
}; // };
}; // };
exports.decodeOutputMempoolValue = function(buffer) { // exports.decodeOutputMempoolValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0); // var satoshis = buffer.readDoubleBE(0);
var timestamp = buffer.readDoubleBE(8); // var timestamp = buffer.readDoubleBE(8);
var scriptBuffer = buffer.slice(16, buffer.length); // var scriptBuffer = buffer.slice(16, buffer.length);
return { // return {
satoshis: satoshis, // satoshis: satoshis,
timestamp: timestamp, // timestamp: timestamp,
scriptBuffer: scriptBuffer // scriptBuffer: scriptBuffer
}; // };
}; // };
exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) { // exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) {
var heightBuffer = new Buffer(4); // var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height); // heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4); // var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex); // outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([ // return Buffer.concat([
constants.PREFIXES.SPENTS, // constants.PREFIXES.SPENTS,
hashBuffer, // hashBuffer,
hashTypeBuffer, // hashTypeBuffer,
constants.SPACER_MIN, // constants.SPACER_MIN,
heightBuffer, // heightBuffer,
prevTxIdBuffer, // prevTxIdBuffer,
outputIndexBuffer // outputIndexBuffer
]); // ]);
}; // };
exports.decodeInputKey = function(buffer) { // exports.decodeInputKey = function(buffer) {
var reader = new BufferReader(buffer); // var reader = new BufferReader(buffer);
var prefix = reader.read(1); // var prefix = reader.read(1);
var hashBuffer = reader.read(20); // var hashBuffer = reader.read(20);
var hashTypeBuffer = reader.read(1); // var hashTypeBuffer = reader.read(1);
var spacer = reader.read(1); // var spacer = reader.read(1);
var height = reader.readUInt32BE(); // var height = reader.readUInt32BE();
var prevTxId = reader.read(32); // var prevTxId = reader.read(32);
var outputIndex = reader.readUInt32BE(); // var outputIndex = reader.readUInt32BE();
return { // return {
prefix: prefix, // prefix: prefix,
hashBuffer: hashBuffer, // hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer, // hashTypeBuffer: hashTypeBuffer,
height: height, // height: height,
prevTxId: prevTxId, // prevTxId: prevTxId,
outputIndex: outputIndex // outputIndex: outputIndex
}; // };
}; // };
exports.encodeInputValue = function(txidBuffer, inputIndex) { // exports.encodeInputValue = function(txidBuffer, inputIndex) {
var inputIndexBuffer = new Buffer(4); // var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex); // inputIndexBuffer.writeUInt32BE(inputIndex);
return Buffer.concat([ // return Buffer.concat([
txidBuffer, // txidBuffer,
inputIndexBuffer // inputIndexBuffer
]); // ]);
}; // };
exports.decodeInputValue = function(buffer) { // exports.decodeInputValue = function(buffer) {
var txid = buffer.slice(0, 32); // var txid = buffer.slice(0, 32);
var inputIndex = buffer.readUInt32BE(32); // var inputIndex = buffer.readUInt32BE(32);
return { // return {
txid: txid, // txid: txid,
inputIndex: inputIndex // inputIndex: inputIndex
}; // };
}; // };
exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) { // exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) {
var outputIndexBuffer = new Buffer(4); // var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex); // outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([ // return Buffer.concat([
constants.PREFIXES.SPENTSMAP, // constants.PREFIXES.SPENTSMAP,
outputTxIdBuffer, // outputTxIdBuffer,
outputIndexBuffer // outputIndexBuffer
]); // ]);
}; // };
exports.decodeInputKeyMap = function(buffer) { // exports.decodeInputKeyMap = function(buffer) {
var txid = buffer.slice(1, 33); // var txid = buffer.slice(1, 33);
var outputIndex = buffer.readUInt32BE(33); // var outputIndex = buffer.readUInt32BE(33);
return { // return {
outputTxId: txid, // outputTxId: txid,
outputIndex: outputIndex // outputIndex: outputIndex
}; // };
}; // };
exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) { // exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) {
var inputIndexBuffer = new Buffer(4); // var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex); // inputIndexBuffer.writeUInt32BE(inputIndex);
return Buffer.concat([ // return Buffer.concat([
inputTxIdBuffer, // inputTxIdBuffer,
inputIndexBuffer // inputIndexBuffer
]); // ]);
}; // };
exports.decodeInputValueMap = function(buffer) { // exports.decodeInputValueMap = function(buffer) {
var txid = buffer.slice(0, 32); // var txid = buffer.slice(0, 32);
var inputIndex = buffer.readUInt32BE(32); // var inputIndex = buffer.readUInt32BE(32);
return { // return {
inputTxId: txid, // inputTxId: txid,
inputIndex: inputIndex // inputIndex: inputIndex
}; // };
}; // };
exports.encodeSummaryCacheKey = function(address) { // exports.encodeSummaryCacheKey = function(address) {
return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]); // return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]);
}; // };
exports.decodeSummaryCacheKey = function(buffer, network) { // exports.decodeSummaryCacheKey = function(buffer, network) {
var hashBuffer = buffer.read(20); // var hashBuffer = buffer.read(20);
var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')]; // var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')];
var address = new Address({ // var address = new Address({
hashBuffer: hashBuffer, // hashBuffer: hashBuffer,
type: type, // type: type,
network: network // network: network
}); // });
return address; // return address;
}; // };
exports.encodeSummaryCacheValue = function(cache, tipHeight, tipHash) { // exports.encodeSummaryCacheValue = function(cache, tipHeight, tipHash) {
var tipHashBuffer = new Buffer(tipHash, 'hex'); // var tipHashBuffer = new Buffer(tipHash, 'hex');
var buffer = new Buffer(new Array(20)); // var buffer = new Buffer(new Array(20));
buffer.writeUInt32BE(tipHeight); // buffer.writeUInt32BE(tipHeight);
buffer.writeDoubleBE(cache.result.totalReceived, 4); // buffer.writeDoubleBE(cache.result.totalReceived, 4);
buffer.writeDoubleBE(cache.result.balance, 12); // buffer.writeDoubleBE(cache.result.balance, 12);
var txidBuffers = []; // var txidBuffers = [];
for (var i = 0; i < cache.result.txids.length; i++) { // for (var i = 0; i < cache.result.txids.length; i++) {
var buf = new Buffer(new Array(36)); // var buf = new Buffer(new Array(36));
var txid = cache.result.txids[i]; // var txid = cache.result.txids[i];
buf.write(txid, 'hex'); // buf.write(txid, 'hex');
buf.writeUInt32BE(cache.result.appearanceIds[txid], 32); // buf.writeUInt32BE(cache.result.appearanceIds[txid], 32);
txidBuffers.push(buf); // txidBuffers.push(buf);
} // }
var txidsBuffer = Buffer.concat(txidBuffers); // var txidsBuffer = Buffer.concat(txidBuffers);
var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]); // var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]);
return value; // return value;
}; // };
exports.decodeSummaryCacheValue = function(buffer) { // exports.decodeSummaryCacheValue = function(buffer) {
var hash = buffer.slice(0, 32).toString('hex'); // var hash = buffer.slice(0, 32).toString('hex');
var height = buffer.readUInt32BE(32); // var height = buffer.readUInt32BE(32);
var totalReceived = buffer.readDoubleBE(36); // var totalReceived = buffer.readDoubleBE(36);
var balance = buffer.readDoubleBE(44); // var balance = buffer.readDoubleBE(44);
// read 32 byte chunks until exhausted // // read 32 byte chunks until exhausted
var appearanceIds = {}; // var appearanceIds = {};
var txids = []; // var txids = [];
var pos = 52; // var pos = 52;
while(pos < buffer.length) { // while(pos < buffer.length) {
var txid = buffer.slice(pos, pos + 32).toString('hex'); // var txid = buffer.slice(pos, pos + 32).toString('hex');
var txidHeight = buffer.readUInt32BE(pos + 32); // var txidHeight = buffer.readUInt32BE(pos + 32);
txids.push(txid); // txids.push(txid);
appearanceIds[txid] = txidHeight; // appearanceIds[txid] = txidHeight;
pos += 36; // pos += 36;
} // }
var cache = { // var cache = {
height: height, // height: height,
hash: hash, // hash: hash,
result: { // result: {
appearanceIds: appearanceIds, // appearanceIds: appearanceIds,
txids: txids, // txids: txids,
totalReceived: totalReceived, // totalReceived: totalReceived,
balance: balance, // balance: balance,
unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache // unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache
unconfirmedBalance: 0 // unconfirmedBalance: 0
} // }
}; // };
return cache; // return cache;
}; // };
exports.getAddressInfo = function(addressStr) { // exports.getAddressInfo = function(addressStr) {
var addrObj = bitcore.Address(addressStr); // var addrObj = bitcore.Address(addressStr);
var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type]; // var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type];
return { // return {
hashBuffer: addrObj.hashBuffer, // hashBuffer: addrObj.hashBuffer,
hashTypeBuffer: hashTypeBuffer, // hashTypeBuffer: hashTypeBuffer,
hashTypeReadable: addrObj.type // hashTypeReadable: addrObj.type
}; // };
}; // };
/** // /**
* This function is optimized to return address information about an output script // * This function is optimized to return address information about an output script
* without constructing a Bitcore Address instance. // * without constructing a Bitcore Address instance.
* @param {Script} - An instance of a Bitcore Script // * @param {Script} - An instance of a Bitcore Script
* @param {Network|String} - The network for the address // * @param {Network|String} - The network for the address
*/ // */
exports.extractAddressInfoFromScript = function(script, network) { // exports.extractAddressInfoFromScript = function(script, network) {
$.checkArgument(network, 'Second argument is expected to be a network'); // $.checkArgument(network, 'Second argument is expected to be a network');
var hashBuffer; // var hashBuffer;
var addressType; // var addressType;
var hashTypeBuffer; // var hashTypeBuffer;
if (script.isPublicKeyHashOut()) { // if (script.isPublicKeyHashOut()) {
hashBuffer = script.chunks[2].buf; // hashBuffer = script.chunks[2].buf;
hashTypeBuffer = constants.HASH_TYPES.PUBKEY; // hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
addressType = Address.PayToPublicKeyHash; // addressType = Address.PayToPublicKeyHash;
} else if (script.isScriptHashOut()) { // } else if (script.isScriptHashOut()) {
hashBuffer = script.chunks[1].buf; // hashBuffer = script.chunks[1].buf;
hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT; // hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT;
addressType = Address.PayToScriptHash; // addressType = Address.PayToScriptHash;
} else if (script.isPublicKeyOut()) { // } else if (script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf; // var pubkey = script.chunks[0].buf;
var address = Address.fromPublicKey(new PublicKey(pubkey), network); // var address = Address.fromPublicKey(new PublicKey(pubkey), network);
hashBuffer = address.hashBuffer; // hashBuffer = address.hashBuffer;
hashTypeBuffer = constants.HASH_TYPES.PUBKEY; // hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
// pay-to-publickey doesn't have an address, however for compatibility // // pay-to-publickey doesn't have an address, however for compatibility
// purposes, we can create an address // // purposes, we can create an address
addressType = Address.PayToPublicKeyHash; // addressType = Address.PayToPublicKeyHash;
} else { // } else {
return false; // return false;
} // }
return { // return {
hashBuffer: hashBuffer, // hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer, // hashTypeBuffer: hashTypeBuffer,
addressType: addressType // addressType: addressType
}; // };
}; // };
module.exports = exports; module.exports = Encoding;

View File

@ -20,7 +20,7 @@ var EventEmitter = require('events').EventEmitter;
var Address = bitcore.Address; var Address = bitcore.Address;
var AddressHistory = require('./history'); var AddressHistory = require('./history');
var constants = require('./constants'); var constants = require('./constants');
var encoding = require('./encoding'); var Encoding = require('./encoding');
var InputsTransformStream = require('./streams/inputs-transform'); var InputsTransformStream = require('./streams/inputs-transform');
var OutputsTransformStream = require('./streams/outputs-transform'); var OutputsTransformStream = require('./streams/outputs-transform');
@ -37,29 +37,23 @@ var OutputsTransformStream = require('./streams/outputs-transform');
var AddressService = function(options) { var AddressService = function(options) {
BaseService.call(this, options); BaseService.call(this, options);
this.subscriptions = {}; // this.subscriptions = {};
this.subscriptions['address/transaction'] = {}; // this.subscriptions['address/transaction'] = {};
this.subscriptions['address/balance'] = {}; // this.subscriptions['address/balance'] = {};
this._bitcoindTransactionListener = this.transactionHandler.bind(this); // this._bitcoindTransactionListener = this.transactionHandler.bind(this);
this._bitcoindTransactionLeaveListener = this.transactionLeaveHandler.bind(this); // this._bitcoindTransactionLeaveListener = this.transactionLeaveHandler.bind(this);
this.node.services.bitcoind.on('tx', this._bitcoindTransactionListener); // this.node.services.bitcoind.on('tx', this._bitcoindTransactionListener);
this.node.services.bitcoind.on('txleave', this._bitcoindTransactionLeaveListener); // this.node.services.bitcoind.on('txleave', this._bitcoindTransactionLeaveListener);
this.maxInputsQueryLength = options.maxInputsQueryLength || constants.MAX_INPUTS_QUERY_LENGTH; this.maxInputsQueryLength = options.maxInputsQueryLength || constants.MAX_INPUTS_QUERY_LENGTH;
this.maxOutputsQueryLength = options.maxOutputsQueryLength || constants.MAX_OUTPUTS_QUERY_LENGTH; this.maxOutputsQueryLength = options.maxOutputsQueryLength || constants.MAX_OUTPUTS_QUERY_LENGTH;
this.concurrency = options.concurrency || 20; this.concurrency = options.concurrency || 20;
this._setMempoolIndexPath(); // this.mempoolIndex = null; // Used for larger mempool indexes
if (options.mempoolMemoryIndex) { // this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups
this.levelupStore = memdown; // this.mempoolAddressIndex = {}; // Used to check if an address is on the spend pool
} else {
this.levelupStore = leveldown;
}
this.mempoolIndex = null; // Used for larger mempool indexes
this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups
this.mempoolAddressIndex = {}; // Used to check if an address is on the spend pool
}; };
inherits(AddressService, BaseService); inherits(AddressService, BaseService);
@ -73,71 +67,27 @@ AddressService.dependencies = [
AddressService.prototype.start = function(callback) { AddressService.prototype.start = function(callback) {
var self = this; var self = this;
async.series([ this.store = this.node.services.db.store;
function(next) { this.node.services.db.getPrefix(this.name, function(err, prefix) {
// Flush any existing mempool index if(err) {
if (fs.existsSync(self.mempoolIndexPath)) { return callback(err);
leveldown.destroy(self.mempoolIndexPath, next);
} else {
setImmediate(next);
}
},
function(next) {
// Setup new mempool index
if (!fs.existsSync(self.mempoolIndexPath)) {
mkdirp(self.mempoolIndexPath, next);
} else {
setImmediate(next);
}
},
function(next) {
self.mempoolIndex = levelup(
self.mempoolIndexPath,
{
db: self.levelupStore,
keyEncoding: 'binary',
valueEncoding: 'binary',
fillCache: false,
maxOpenFiles: 200
},
next
);
} }
], callback);
self.prefix = prefix;
self.encoding = new Encoding(self.prefix);
callback();
});
}; };
AddressService.prototype.stop = function(callback) { AddressService.prototype.stop = function(callback) {
// TODO Keep track of ongoing db requests before shutting down // TODO Keep track of ongoing db requests before shutting down
this.node.services.bitcoind.removeListener('tx', this._bitcoindTransactionListener); // this.node.services.bitcoind.removeListener('tx', this._bitcoindTransactionListener);
this.node.services.bitcoind.removeListener('txleave', this._bitcoindTransactionLeaveListener); // this.node.services.bitcoind.removeListener('txleave', this._bitcoindTransactionLeaveListener);
this.mempoolIndex.close(callback); // this.mempoolIndex.close(callback);
}; setImmediate(callback);
/**
* This function will set `this.mempoolIndexPath` based on `this.node.network`.
* @private
*/
AddressService.prototype._setMempoolIndexPath = function() {
this.mempoolIndexPath = this._getDBPathFor('bitcore-addressmempool.db');
};
AddressService.prototype._getDBPathFor = function(dbname) {
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
var path;
if (this.node.network === Networks.livenet) {
path = this.node.datadir + '/' + dbname;
} else if (this.node.network === Networks.testnet) {
if (this.node.network.regtestEnabled) {
path = this.node.datadir + '/regtest/' + dbname;
} else {
path = this.node.datadir + '/testnet3/' + dbname;
}
} else {
throw new Error('Unknown network: ' + this.network);
}
return path;
}; };
/** /**
@ -160,289 +110,21 @@ AddressService.prototype.getAPIMethods = function() {
* Called by the Bus to get the available events for this service. * Called by the Bus to get the available events for this service.
*/ */
AddressService.prototype.getPublishEvents = function() { AddressService.prototype.getPublishEvents = function() {
return [ return [];
{ // return [
name: 'address/transaction', // {
scope: this, // name: 'address/transaction',
subscribe: this.subscribe.bind(this, 'address/transaction'), // scope: this,
unsubscribe: this.unsubscribe.bind(this, 'address/transaction') // subscribe: this.subscribe.bind(this, 'address/transaction'),
}, // unsubscribe: this.unsubscribe.bind(this, 'address/transaction')
{ // },
name: 'address/balance', // {
scope: this, // name: 'address/balance',
subscribe: this.subscribe.bind(this, 'address/balance'), // scope: this,
unsubscribe: this.unsubscribe.bind(this, 'address/balance') // subscribe: this.subscribe.bind(this, 'address/balance'),
} // unsubscribe: this.unsubscribe.bind(this, 'address/balance')
]; // }
}; // ];
/**
* Will process each output of a transaction from the daemon "tx" event, and construct
* an object with the data for the message to be relayed to any subscribers for an address.
*
* @param {Object} messages - An object to collect messages
* @param {Transaction} tx - Instance of the transaction
* @param {Number} outputIndex - The index of the output in the transaction
* @param {Boolean} rejected - If the transaction was rejected by the mempool
*/
AddressService.prototype.transactionOutputHandler = function(messages, tx, outputIndex, rejected) {
var script = tx.outputs[outputIndex].script;
// If the script is invalid skip
if (!script) {
return;
}
var addressInfo = encoding.extractAddressInfoFromScript(script, this.node.network);
if (!addressInfo) {
return;
}
addressInfo.hashHex = addressInfo.hashBuffer.toString('hex');
// Collect data to publish to address subscribers
if (messages[addressInfo.hashHex]) {
messages[addressInfo.hashHex].outputIndexes.push(outputIndex);
} else {
messages[addressInfo.hashHex] = {
tx: tx,
outputIndexes: [outputIndex],
addressInfo: addressInfo,
rejected: rejected
};
}
};
/**
* This will handle data from the daemon "txleave" that a transaction has left the mempool.
* @param {Object} txInfo - The data from the daemon.on('txleave') event
* @param {Buffer} txInfo.buffer - The transaction buffer
* @param {String} txInfo.hash - The hash of the transaction
*/
AddressService.prototype.transactionLeaveHandler = function(txInfo) {
var tx = bitcore.Transaction().fromBuffer(txInfo.buffer);
this.updateMempoolIndex(tx, false);
};
/**
* This will handle data from the daemon "tx" event, go through each of the outputs
* and send messages by calling `transactionEventHandler` to any subscribers for a
* particular address.
* @param {Object} txInfo - The data from the daemon.on('tx') event
* @param {Buffer} txInfo.buffer - The transaction buffer
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
* @param {String} txInfo.hash - The hash of the transaction
* @param {Function} [callback] - Optional callback
*/
AddressService.prototype.transactionHandler = function(buffer, callback) {
var self = this;
if (!callback) {
callback = function(err) {
if (err) {
return log.error(err);
}
};
}
if (this.node.stopping) {
return callback();
}
// Basic transaction format is handled by the daemon
// and we can safely assume the buffer is properly formatted.
var tx = bitcore.Transaction().fromBuffer(buffer);
var messages = {};
var mempool = false; // txInfo.mempool
var outputsLength = tx.outputs.length;
for (var i = 0; i < outputsLength; i++) {
// TODO missing txInfo.mempool
this.transactionOutputHandler(messages, tx, i, mempool);
}
function finish(err) {
if (err) {
return callback(err);
}
for (var key in messages) {
self.transactionEventHandler(messages[key]);
self.balanceEventHandler(null, messages[key].addressInfo);
}
callback();
}
if (mempool) {
self.updateMempoolIndex(tx, true, finish);
} else {
setImmediate(finish);
}
};
AddressService.prototype._updateAddressIndex = function(key, add) {
var currentValue = this.mempoolAddressIndex[key] || 0;
if(add) {
if (currentValue > 0) {
this.mempoolAddressIndex[key] = currentValue + 1;
} else {
this.mempoolAddressIndex[key] = 1;
}
} else {
if (currentValue <= 1) {
delete this.mempoolAddressIndex[key];
} else {
this.mempoolAddressIndex[key]--;
}
}
};
/**
* This function will update the mempool address index with the necessary
* information for further lookups.
* @param {Transaction} - An instance of a Bitcore Transaction
* @param {Boolean} - Add/remove from the index
*/
AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
/* jshint maxstatements: 100 */
var operations = [];
var timestampBuffer = new Buffer(new Array(8));
timestampBuffer.writeDoubleBE(new Date().getTime());
var action = 'put';
if (!add) {
action = 'del';
}
var txid = tx.hash;
var txidBuffer = new Buffer(txid, 'hex');
var outputLength = tx.outputs.length;
for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) {
var output = tx.outputs[outputIndex];
if (!output.script) {
continue;
}
var addressInfo = encoding.extractAddressInfoFromScript(output.script, this.node.network);
if (!addressInfo) {
continue;
}
var addressIndexKey = encoding.encodeMempoolAddressIndexKey(addressInfo.hashBuffer, addressInfo.hashTypeBuffer);
this._updateAddressIndex(addressIndexKey, add);
// Update output index
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
var outKey = Buffer.concat([
constants.MEMPREFIXES.OUTPUTS,
addressInfo.hashBuffer,
addressInfo.hashTypeBuffer,
txidBuffer,
outputIndexBuffer
]);
var outValue = encoding.encodeOutputMempoolValue(
output.satoshis,
timestampBuffer,
output._scriptBuffer
);
operations.push({
type: action,
key: outKey,
value: outValue
});
}
var inputLength = tx.inputs.length;
for (var inputIndex = 0; inputIndex < inputLength; inputIndex++) {
var input = tx.inputs[inputIndex];
var inputOutputIndexBuffer = new Buffer(4);
inputOutputIndexBuffer.writeUInt32BE(input.outputIndex);
// Add an additional small spent index for fast synchronous lookups
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
input.prevTxId,
input.outputIndex
);
if (add) {
this.mempoolSpentIndex[spentIndexSyncKey] = true;
} else {
delete this.mempoolSpentIndex[spentIndexSyncKey];
}
// Add a more detailed spent index with values
var spentIndexKey = Buffer.concat([
constants.MEMPREFIXES.SPENTSMAP,
input.prevTxId,
inputOutputIndexBuffer
]);
var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex);
var inputIndexValue = Buffer.concat([
txidBuffer,
inputIndexBuffer
]);
operations.push({
type: action,
key: spentIndexKey,
value: inputIndexValue
});
// Update input index
var inputHashBuffer;
var inputHashType;
if (input.script.isPublicKeyHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf);
inputHashType = constants.HASH_TYPES.PUBKEY;
} else if (input.script.isScriptHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
inputHashType = constants.HASH_TYPES.REDEEMSCRIPT;
} else {
continue;
}
var inputKey = Buffer.concat([
constants.MEMPREFIXES.SPENTS,
inputHashBuffer,
inputHashType,
input.prevTxId,
inputOutputIndexBuffer
]);
var inputValue = Buffer.concat([
txidBuffer,
inputIndexBuffer,
timestampBuffer
]);
operations.push({
type: action,
key: inputKey,
value: inputValue
});
var addressIndexKey = encoding.encodeMempoolAddressIndexKey(inputHashBuffer, inputHashType);
this._updateAddressIndex(addressIndexKey, add);
}
if (!callback) {
callback = function(err) {
if (err) {
return log.error(err);
}
};
}
this.mempoolIndex.batch(operations, callback);
}; };
/** /**
@ -453,6 +135,8 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
AddressService.prototype.blockHandler = function(block, connectBlock, callback) { AddressService.prototype.blockHandler = function(block, connectBlock, callback) {
var self = this;
var txs = block.transactions; var txs = block.transactions;
var height = block.__height; var height = block.__height;
@ -480,16 +164,21 @@ AddressService.prototype.blockHandler = function(block, connectBlock, callback)
var output = outputs[outputIndex]; var output = outputs[outputIndex];
var script = output.script; var script = output.script;
var address = script.toAddress();
if(!script) { if(!script) {
log.debug('Invalid script'); log.debug('Invalid script');
continue; continue;
} }
var address = self._getAddressString(script);
var key = encoding.encodeAddressIndexKey(address, false, height, txid, outputIndex); if(!address) {
var value = encoding.encodeOutputValue(output.satoshis, output._scriptBuffer); log.warn('Unsupported script type for output in ' + txid);
continue;
}
var key = self.encoding.encodeAddressIndexKey(address, false, height, txid, outputIndex);
var value = self.encoding.encodeAddressIndexValue(output.satoshis, output._scriptBuffer);
operations.push({ operations.push({
type: action, type: action,
key: key, key: key,
@ -509,40 +198,49 @@ AddressService.prototype.blockHandler = function(block, connectBlock, callback)
}; };
} }
this.balanceEventHandler(block, address); //this.balanceEventHandler(block, address);
} }
// Publish events to any subscribers for this transaction // Publish events to any subscribers for this transaction
for (var addressKey in txmessages) { for (var addressKey in txmessages) {
this.transactionEventHandler(txmessages[addressKey]); //this.transactionEventHandler(txmessages[addressKey]);
} }
if(tx.isCoinbase()) { if(tx.isCoinbase()) {
return next(); return next();
} }
async.eachLimit(inputs, self.concurrency, function(input, next) { async.eachOfLimit(inputs, self.concurrency, function(input, inputIndex, next) {
var input = inputs[inputIndex]; var input = inputs[inputIndex];
var inputHash; var inputHash;
var inputHashType; var inputHashType;
var address = input.script.toAddress(); if(!input.script) {
log.debug('Invalid script');
return next();
}
var inputKey = encoding.encodeAddressIndexKey(address, true, height, txid, inputIndex, true); self.node.services.transaction.getTransaction(input.prevTxId.toString('hex'), function(err, tx) {
self.node.services.transaction.getTransaction(input.prevTxId, function(err, result) {
if(err) { if(err) {
return next(err); return next(err);
} }
var tx = result.transaction;
var height = result.height;
var output = tx.outputs[input.outputIndex]; var output = tx.outputs[input.outputIndex];
var outputKey = encoding.encodeAddressIndexKey(address, true, height, tx.id, input.outputIndex, false);
var outputKeyToDelete = encoding.encodeAddressIndexKey(address, false, height, tx.id, input.outputIndex, false); var address = self._getAddressString(input.script, output);
var outputValue = encoding.encodeAdressIndexValue(output.satoshis, output._scriptBuffer);
var inputValue = encoding.encodeAddressIndexValue(output.satoshis, input._scriptBuffer); if(!address) {
log.warn('Unsupported script for input in transaction ' + txid);
return next();
}
var inputKey = self.encoding.encodeAddressIndexKey(address, true, height, txid, inputIndex, true);
var outputKey = self.encoding.encodeAddressIndexKey(address, true, tx.__height, tx.id, input.outputIndex, false);
var outputKeyToDelete = self.encoding.encodeAddressIndexKey(address, false, tx.__height, tx.id, input.outputIndex, false);
var outputValue = self.encoding.encodeAddressIndexValue(output.satoshis, output._scriptBuffer);
var inputValue = self.encoding.encodeAddressIndexValue(output.satoshis, input._scriptBuffer);
operations = operations.concat([{ operations = operations.concat([{
type: action, type: action,
@ -562,11 +260,33 @@ AddressService.prototype.blockHandler = function(block, connectBlock, callback)
next(); next();
}); });
}, function(err) { }, next);
callback(err, operations); }, function(err) {
}); callback(err, operations);
}); });
};
AddressService.prototype._getAddressString = function(script, output) {
var address = script.toAddress();
if(address) {
return address.toString();
}
try {
var pubkey = script.getPublicKey();
if(pubkey) {
return pubkey.toString('hex');
}
} catch(e) {
// if there is an error, it's because a pubkey can not be extracted from the script
// continue on and return null
}
if(output.script.isPublicKeyOut()) {
return output.script.getPublicKey().toString('hex');
}
return null;
}; };
/** /**

View File

@ -42,7 +42,7 @@ function DB(options) {
// to determine during an upgrade if a reindex is required // to determine during an upgrade if a reindex is required
this.version = 2; this.version = 2;
this.dbPrefix = 0; this.dbPrefix = '\u0000\u0000';
this.tip = null; this.tip = null;
this.genesis = null; this.genesis = null;
@ -105,10 +105,10 @@ DB.prototype._setDataPath = function() {
DB.prototype._checkVersion = function(callback) { DB.prototype._checkVersion = function(callback) {
var self = this; var self = this;
var options = { var options = {
keyEncoding: 'binary', keyEncoding: 'string',
valueEncoding: 'binary' valueEncoding: 'binary'
}; };
self.store.get(DB.PREFIXES.TIP, options, function(err) { self.store.get(self.dbPrefix + 'tip', options, function(err) {
if (err instanceof levelup.errors.NotFoundError) { if (err instanceof levelup.errors.NotFoundError) {
// The database is brand new and doesn't have a tip stored // The database is brand new and doesn't have a tip stored
// we can skip version checking // we can skip version checking
@ -116,7 +116,7 @@ DB.prototype._checkVersion = function(callback) {
} else if (err) { } else if (err) {
return callback(err); return callback(err);
} }
self.store.get(DB.PREFIXES.VERSION, options, function(err, buffer) { self.store.get(self.dbPrefix + 'version', options, function(err, buffer) {
var version; var version;
if (err instanceof levelup.errors.NotFoundError) { if (err instanceof levelup.errors.NotFoundError) {
// The initial version (1) of the database didn't store the version number // The initial version (1) of the database didn't store the version number
@ -143,7 +143,7 @@ DB.prototype._checkVersion = function(callback) {
DB.prototype._setVersion = function(callback) { DB.prototype._setVersion = function(callback) {
var versionBuffer = new Buffer(new Array(4)); var versionBuffer = new Buffer(new Array(4));
versionBuffer.writeUInt32BE(this.version); versionBuffer.writeUInt32BE(this.version);
this.store.put(DB.PREFIXES.VERSION, versionBuffer, callback); this.store.put(this.dbPrefix + 'version', versionBuffer, callback);
}; };
/** /**
@ -158,14 +158,14 @@ DB.prototype.start = function(callback) {
} }
this.genesis = Block.fromBuffer(this.node.services.bitcoind.genesisBuffer); this.genesis = Block.fromBuffer(this.node.services.bitcoind.genesisBuffer);
this.store = levelup(this.dataPath, { db: this.levelupStore, maxOpenFiles: this.maxOpenFiles }); this.store = levelup(this.dataPath, { db: this.levelupStore, maxOpenFiles: this.maxOpenFiles, keyEncoding: 'binary', valueEncoding: 'binary'});
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this)); this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
this.once('ready', function() { this.once('ready', function() {
log.info('Bitcoin Database Ready'); log.info('Bitcoin Database Ready');
// Notify that there is a new tip // Notify that there is a new tip
self.node.services.bitcoind.on('tip', function(height) { self.node.services.bitcoind.on('tip', function() {
if(!self.node.stopping) { if(!self.node.stopping) {
self.sync(); self.sync();
} }
@ -240,12 +240,12 @@ DB.prototype.close = function(callback) {
* @param {String} txInfo.hash - The hash of the transaction * @param {String} txInfo.hash - The hash of the transaction
*/ */
DB.prototype.transactionHandler = function(tx) { DB.prototype.transactionHandler = function(tx) {
for (var i = 0; i < this.subscriptions.transaction.length; i++) { // for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('db/transaction', { // this.subscriptions.transaction[i].emit('db/transaction', {
rejected: !txInfo.mempool, // rejected: !txInfo.mempool,
tx: tx // tx: tx
}); // });
} // }
}; };
/** /**
@ -267,11 +267,11 @@ DB.prototype.loadTip = function(callback) {
var self = this; var self = this;
var options = { var options = {
keyEncoding: 'binary', keyEncoding: 'string',
valueEncoding: 'binary' valueEncoding: 'binary'
}; };
self.store.get(DB.PREFIXES.TIP, options, function(err, tipData) { self.store.get(self.dbPrefix + 'tip', options, function(err, tipData) {
if(err && err instanceof levelup.errors.NotFoundError) { if(err && err instanceof levelup.errors.NotFoundError) {
self.tip = self.genesis; self.tip = self.genesis;
self.tip.__height = 0; self.tip.__height = 0;
@ -288,7 +288,8 @@ DB.prototype.loadTip = function(callback) {
return callback(err); return callback(err);
} }
var hash = tipData.toString('hex'); var hash = tipData.slice(0, 32).toString('hex');
var height = tipData.readUInt32BE(32);
var times = 0; var times = 0;
async.retry({times: 3, interval: self.retryInterval}, function(done) { async.retry({times: 3, interval: self.retryInterval}, function(done) {
@ -311,12 +312,9 @@ DB.prototype.loadTip = function(callback) {
return callback(err); return callback(err);
} }
tip.__height = height;
self.tip = tip; self.tip = tip;
var blockIndex = self.node.services.bitcoind.getBlockIndex(self.tip.hash);
if(!blockIndex) {
return callback(new Error('Could not get height for tip.'));
}
self.tip.__height = blockIndex.height;
callback(); callback();
}); });
}); });
@ -330,55 +328,6 @@ DB.prototype.getBlock = function(hash, callback) {
this.node.services.bitcoind.getBlock(hash, callback); this.node.services.bitcoind.getBlock(hash, callback);
}; };
/**
* Get block hashes between two timestamps
* @param {Number} high - high timestamp, in seconds, inclusive
* @param {Number} low - low timestamp, in seconds, inclusive
* @param {Function} callback
*/
DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
var self = this;
var hashes = [];
var lowKey;
var highKey;
try {
lowKey = this._encodeBlockIndexKey(low);
highKey = this._encodeBlockIndexKey(high);
} catch(e) {
return callback(e);
}
var stream = this.store.createReadStream({
gte: lowKey,
lte: highKey,
reverse: true,
valueEncoding: 'binary',
keyEncoding: 'binary'
});
stream.on('data', function(data) {
hashes.push(self._decodeBlockIndexValue(data.value));
});
var error;
stream.on('error', function(streamError) {
if (streamError) {
error = streamError;
}
});
stream.on('close', function() {
if (error) {
return callback(error);
}
callback(null, hashes);
});
return stream;
};
/** /**
* Will give a Bitcore Transaction from bitcoind by txid * Will give a Bitcore Transaction from bitcoind by txid
* @param {String} txid - A transaction hash * @param {String} txid - A transaction hash
@ -532,23 +481,26 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) {
var operations = []; var operations = [];
// Notify block subscribers // Notify block subscribers
for (var i = 0; i < this.subscriptions.block.length; i++) { // for (var i = 0; i < this.subscriptions.block.length; i++) {
this.subscriptions.block[i].emit('db/block', block.hash); // this.subscriptions.block[i].emit('db/block', block.hash);
} // }
// Update tip // Update tip
var tipHash = add ? new Buffer(block.hash, 'hex') : BufferUtil.reverse(block.header.prevHash); var tipData = new Buffer(36);
var heightBuffer = new Buffer(4);
if(add) {
heightBuffer.writeUInt32BE(block.__height);
tipData = Buffer.concat([new Buffer(block.hash, 'hex'), heightBuffer]);
} else {
heightBuffer.writeUInt32BE(block.__height - 1);
tipData = Buffer.concat([BufferUtil.reverse(block.header.prevHash), heightBuffer]);
}
operations.push({ operations.push({
type: 'put', type: 'put',
key: DB.PREFIXES.TIP, key: self.dbPrefix + 'tip',
value: tipHash value: tipData
});
// Update block index
operations.push({
type: add ? 'put' : 'del',
key: this._encodeBlockIndexKey(block.header.timestamp),
value: this._encodeBlockIndexValue(block.hash)
}); });
async.eachSeries( async.eachSeries(
@ -582,22 +534,6 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) {
); );
}; };
DB.prototype._encodeBlockIndexKey = function(timestamp) {
$.checkArgument(timestamp >= 0 && timestamp <= 4294967295, 'timestamp out of bounds');
var timestampBuffer = new Buffer(4);
timestampBuffer.writeUInt32BE(timestamp);
return Buffer.concat([DB.PREFIXES.BLOCKS, timestampBuffer]);
};
DB.prototype._encodeBlockIndexValue = function(hash) {
return new Buffer(hash, 'hex');
};
DB.prototype._decodeBlockIndexValue = function(value) {
return value.toString('hex');
};
/** /**
* This function will find the common ancestor between the current chain and a forked block, * This function will find the common ancestor between the current chain and a forked block,
* by moving backwards on both chains until there is a meeting point. * by moving backwards on both chains until there is a meeting point.
@ -739,6 +675,7 @@ DB.prototype.sync = function() {
height = self.tip.__height; height = self.tip.__height;
return height < self.node.services.bitcoind.height && !self.node.stopping; return height < self.node.services.bitcoind.height && !self.node.stopping;
}, function(done) { }, function(done) {
console.log('fetching block ' + (height + 1));
self.node.services.bitcoind.getBlock(height + 1, function(err, block) { self.node.services.bitcoind.getBlock(height + 1, function(err, block) {
if (err) { if (err) {
return done(err); return done(err);
@ -805,6 +742,57 @@ DB.prototype.sync = function() {
DB.prototype.getPrefix = function(service, callback) { DB.prototype.getPrefix = function(service, callback) {
var self = this; var self = this;
function getPrefix(next) {
self.store.get(self.dbPrefix + 'prefix-' + service, function(err, buffer) {
if(err) {
if(err.notFound) {
return next();
}
return next(err);
}
// we already have the prefix, call the callback
return callback(null, buffer);
});
}
function getUnused(next) {
self.store.get(self.dbPrefix + 'nextUnused', function(err, buffer) {
if(err) {
if(err.notFound) {
return next(null, new Buffer('0001', 'hex'));
}
return next(err);
}
return next(null, buffer);
});
}
function putPrefix(buffer, next) {
self.store.put(self.dbPrefix + 'prefix-' + service, buffer, function(err) {
if(err) {
return next(err);
}
next(null, buffer);
});
}
function putUnused(buffer, next) {
var prefix = buffer.readUInt16BE();
var nextUnused = new Buffer(2);
nextUnused.writeUInt16BE(prefix + 1);
self.store.put(self.dbPrefix + 'nextUnused', nextUnused, function(err) {
if(err) {
return next(err);
}
return next(null, buffer);
});
}
async.waterfall( async.waterfall(
[ [
getPrefix, getPrefix,
@ -814,56 +802,6 @@ DB.prototype.getPrefix = function(service, callback) {
], ],
callback callback
); );
function getPrefix(next) {
self.store.get(this.dbPrefix + 'prefix-' + service, function(err, buffer) {
if(err && !err.notFound) {
return next(err);
}
if(!buffer || err.notFound) {
return next();
}
// we already have the prefix, call the callback
return callback(null, buffer.readUInt16BE());
};
}
function getUnused(next) {
self.store.get(self.dbPrefix + 'nextUnused', function(err, buffer) {
if(err && !err.notFound) {
return next(err);
}
if(!buffer || err.notFound) {
return next(null, 1);
}
return next(null, buffer.readUInt16BE());
}
}
function putPrefix(prefix, next) {
self.store.put(self.dbPrefix + 'prefix-' + service, prefix, function(err) {
if(err) {
return next(err);
}
next(null, prefix);
}
}
function putUnused(prefix, next) {
self.store.put(self.dbPrefix + 'nextUnused', prefix + 1, function(err) {
if(err) {
return next(err);
}
return next(null, prefix);
});
}
}; };
module.exports = DB; module.exports = DB;

View File

@ -1,10 +1,12 @@
'use strict'; 'use strict';
var BaseService = require('../../service'); var BaseService = require('../service');
var inherits = require('util').inherits; var inherits = require('util').inherits;
var bitcore = require('bitcore-lib');
function TransactionService(options) { function TransactionService(options) {
BaseService.call(this, options); BaseService.call(this, options);
this.currentTransactions = {};
} }
inherits(TransactionService, BaseService); inherits(TransactionService, BaseService);
@ -41,8 +43,13 @@ TransactionService.prototype.blockHandler = function(block, connectBlock, callba
var operations = []; var operations = [];
this.currentTransactions = {};
for(var i = 0; i < block.transactions.length; i++) { for(var i = 0; i < block.transactions.length; i++) {
var tx = block.transactions[i]; var tx = block.transactions[i];
tx.__height = block.__height;
this.currentTransactions[tx.id] = tx;
operations.push({ operations.push({
type: action, type: action,
@ -56,25 +63,46 @@ TransactionService.prototype.blockHandler = function(block, connectBlock, callba
}); });
}; };
TransactionService.prototype.getTransaction = function(txid, callback) {
var self = this;
if(self.currentTransactions[txid]) {
return setImmediate(function() {
callback(null, self.currentTransactions[txid]);
});
}
var key = self._encodeTransactionKey(txid);
self.node.services.db.store.get(key, function(err, buffer) {
if(err) {
return callback(err);
}
var tx = self._decodeTransactionValue(buffer);
callback(null, tx);
});
};
TransactionService.prototype._encodeTransactionKey = function(txid) { TransactionService.prototype._encodeTransactionKey = function(txid) {
return Buffer.concat([this.prefix, new Buffer(txid, 'hex')]); return Buffer.concat([this.prefix, new Buffer(txid, 'hex')]);
}; };
TransactionService.prototype._decodeTransactionKey = function(buffer) { TransactionService.prototype._decodeTransactionKey = function(buffer) {
return buffer.slice(1).toString('hex'); return buffer.slice(2).toString('hex');
}; };
TransactionService.prototype._encodeTransactionValue = function(transaction, height) { TransactionService.prototype._encodeTransactionValue = function(transaction, height) {
var heightBuffer = new Buffer(4); var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height); heightBuffer.writeUInt32BE();
return new Buffer.concat([heightBuffer, transaction.uncheckedSerialize()]); return new Buffer.concat([heightBuffer, transaction.toBuffer()]);
}; };
TransactionService.prototype._decodeTransactionValue = function(buffer) { TransactionService.prototype._decodeTransactionValue = function(buffer) {
return { var height = buffer.readUInt32BE();
height: Buffer.readUInt32BE(height), var transaction = new bitcore.Transaction(buffer.slice(4));
transaction: new bitcore.Transaction(buffer) transaction.__height = height;
}; return transaction;
}; };
module.exports = TransactionService; module.exports = TransactionService;