From efa848065139288717740fe901128efcdd59aa9a Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 19 Jan 2017 16:11:04 -0500 Subject: [PATCH] get address index to work --- lib/node.js | 1 + lib/scaffold/start.js | 1 + lib/services/address/encoding.js | 651 ++++++++++++++++--------------- lib/services/address/index.js | 474 +++++----------------- lib/services/db.js | 236 +++++------ lib/services/transaction.js | 44 ++- 6 files changed, 550 insertions(+), 857 deletions(-) diff --git a/lib/node.js b/lib/node.js index bebc0288..aa46635d 100644 --- a/lib/node.js +++ b/lib/node.js @@ -264,6 +264,7 @@ Node.prototype.start = function(callback) { if (err) { return callback(err); } + self.emit('ready'); callback(); } diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index 57e20131..7be24f00 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -139,6 +139,7 @@ function loadModule(req, service) { var serviceFile = path.resolve(__dirname, '../services/' + service.name); service.module = req(serviceFile); } catch(e) { + console.log('Could not load ' + service.name + ' service'); log.error(e.stack); var servicePackage = req(service.name + '/package.json'); var serviceModule = service.name; diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index fa9959e9..c8abcce9 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -9,12 +9,17 @@ var $ = bitcore.util.preconditions; 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); addressSizeBuffer.writeUInt8(address.length); var addressBuffer = new Buffer(address, 'utf8'); var heightBuffer = new Buffer(4); heightBuffer.writeUInt32BE(height); + var txidBuffer = new Buffer(txid, 'hex'); var indexBuffer = new Buffer(4); indexBuffer.writeUInt32BE(index); var spendingBuffer = new Buffer(1); @@ -22,8 +27,8 @@ exports.encodeAddressIndexKey = function(address, isSpent, height, txidBuffer, i var isSpentBuffer = new Buffer(1); isSpentBuffer.writeUInt8(isSpent); - var key = Buffer.concat({ - constants.PREFIXES.ADDRESS, + return Buffer.concat([ + this.prefix, addressSizeBuffer, addressBuffer, isSpentBuffer, @@ -31,38 +36,37 @@ exports.encodeAddressIndexKey = function(address, isSpent, height, txidBuffer, i txidBuffer, indexBuffer, spendingBuffer - }); + ]); }; -exports.decodeAddressIndexKey = function(buffer) { +Encoding.prototype.decodeAddressIndexKey = function(buffer) { var reader = new BufferReader(buffer); - var prefix = reader.read(1); + var prefix = reader.read(2); var addressSize = reader.readUInt8(); var address = reader.read(addressSize).toString('utf8'); var isSpent = reader.readUInt8(); var height = reader.readUInt32BE(); - var txid = reader.read(32); + var txid = reader.read(32).toString('hex'); var index = reader.readUInt32BE(); var spending = reader.readUInt8(); return { - prefix: prefix, address: address, isSpent: isSpent ? true : false, height: height, txid: txid, - index: outputIndex, + index: index, spending: spending ? true : false }; }; -exports.encodeAddressIndexValue = function(satoshis, scriptBuffer) { +Encoding.prototype.encodeAddressIndexValue = function(satoshis, scriptBuffer) { var satoshisBuffer = new Buffer(8); satoshisBuffer.writeDoubleBE(satoshis); return Buffer.concat([satoshisBuffer, scriptBuffer]); }; -exports.decodeAddressIndexValue = function(buffer) { +Encoding.prototype.decodeAddressIndexValue = function(buffer) { var satoshis = buffer.readDoubleBE(0); var scriptBuffer = buffer.slice(8, buffer.length); return { @@ -71,354 +75,355 @@ exports.decodeAddressIndexValue = function(buffer) { }; }; -exports.encodeUnspentIndexKey = function(hashTypeBuffer, hashBuffer, txidBuffer, index) { - var indexBuffer = new Buffer(4); - indexBuffer.writeUInt32BE(index); +// exports.encodeUnspentIndexKey = function(hashTypeBuffer, hashBuffer, txid, index) { +// var indexBuffer = new Buffer(4); +// indexBuffer.writeUInt32BE(index); +// var txidBuffer = new Buffer(txid, 'hex'); - var key = Buffer.concat({ - constants.PREFIXES.UNSPENT, - hashTypeBuffer, - hashBuffer, - constants.SPACER_MIN, - txidBuffer, - indexBuffer - }); -}; +// var key = Buffer.concat([ +// constants.PREFIXES.UNSPENT, +// hashTypeBuffer, +// hashBuffer, +// constants.SPACER_MIN, +// txidBuffer, +// indexBuffer +// ]); +// }; -exports.decodeUnspentIndexKey = function(buffer) { - var reader = new BufferReader(buffer); - var prefix = reader.read(1); - var hashTypeBuffer = reader.read(1); - var hashBuffer = reader.read(20); +// exports.decodeUnspentIndexKey = function(buffer) { +// var reader = new BufferReader(buffer); +// var prefix = reader.read(1); +// var hashTypeBuffer = reader.read(1); +// var hashBuffer = reader.read(20); - var spacer = reader.read(1); - var txid = reader.read(32); - var index = reader.readUInt32BE(); - return { - prefix: prefix, - hashTypeBuffer: hashTypeBuffer, - hashBuffer: hashBuffer, - txid: txid, - index: outputIndex - }; -}; +// var spacer = reader.read(1); +// var txid = reader.read(32); +// var index = reader.readUInt32BE(); +// return { +// prefix: prefix, +// hashTypeBuffer: hashTypeBuffer, +// hashBuffer: hashBuffer, +// txid: txid, +// index: outputIndex +// }; +// }; -exports.encodeUnspentIndexValue = function(satoshis, height, scriptBuffer) { - var satoshisBuffer = new Buffer(8); - satoshisBuffer.writeDoubleBE(satoshis); +// exports.encodeUnspentIndexValue = function(satoshis, height, scriptBuffer) { +// var satoshisBuffer = new Buffer(8); +// satoshisBuffer.writeDoubleBE(satoshis); - var heightBuffer = new Buffer(4); - heightBuffer.writeUInt32BE(height); +// var heightBuffer = new Buffer(4); +// heightBuffer.writeUInt32BE(height); - return Buffer.concat([satoshisBuffer, heightBuffer, scriptBuffer]); -}; +// return Buffer.concat([satoshisBuffer, heightBuffer, scriptBuffer]); +// }; -exports.decodeUnspentIndexValue = function(buffer) { - var satoshis = buffer.readDoubleBE(0); - var height = buffer.readUInt32BE(8); - var scriptBuffer = buffer.slice(12, buffer.length); +// exports.decodeUnspentIndexValue = function(buffer) { +// var satoshis = buffer.readDoubleBE(0); +// var height = buffer.readUInt32BE(8); +// var scriptBuffer = buffer.slice(12, buffer.length); - return { - satoshis: satoshis, - height: height, - scriptBuffer: scriptBuffer - }; -}; +// return { +// satoshis: satoshis, +// height: height, +// scriptBuffer: scriptBuffer +// }; +// }; -exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) { - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - var key = Buffer.concat([ - txidBuffer, - outputIndexBuffer - ]); - return key.toString('binary'); -}; +// exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) { +// var outputIndexBuffer = new Buffer(4); +// outputIndexBuffer.writeUInt32BE(outputIndex); +// var key = Buffer.concat([ +// txidBuffer, +// outputIndexBuffer +// ]); +// return key.toString('binary'); +// }; -exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) { - var key = Buffer.concat([ - hashBuffer, - hashTypeBuffer, - ]); - return key.toString('binary'); -}; +// exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) { +// var key = Buffer.concat([ +// hashBuffer, +// hashTypeBuffer, +// ]); +// return key.toString('binary'); +// }; -exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) { - var heightBuffer = new Buffer(4); - heightBuffer.writeUInt32BE(height); - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - var key = Buffer.concat([ - constants.PREFIXES.OUTPUTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - heightBuffer, - txidBuffer, - outputIndexBuffer - ]); - return key; -}; +// exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) { +// var heightBuffer = new Buffer(4); +// heightBuffer.writeUInt32BE(height); +// var outputIndexBuffer = new Buffer(4); +// outputIndexBuffer.writeUInt32BE(outputIndex); +// var key = Buffer.concat([ +// constants.PREFIXES.OUTPUTS, +// hashBuffer, +// hashTypeBuffer, +// constants.SPACER_MIN, +// heightBuffer, +// txidBuffer, +// outputIndexBuffer +// ]); +// return key; +// }; -exports.decodeOutputKey = function(buffer) { - var reader = new BufferReader(buffer); - var prefix = reader.read(1); - var hashBuffer = reader.read(20); - var hashTypeBuffer = reader.read(1); - var spacer = reader.read(1); - var height = reader.readUInt32BE(); - var txid = reader.read(32); - var outputIndex = reader.readUInt32BE(); - return { - prefix: prefix, - hashBuffer: hashBuffer, - hashTypeBuffer: hashTypeBuffer, - height: height, - txid: txid, - outputIndex: outputIndex - }; -}; +// exports.decodeOutputKey = function(buffer) { +// var reader = new BufferReader(buffer); +// var prefix = reader.read(1); +// var hashBuffer = reader.read(20); +// var hashTypeBuffer = reader.read(1); +// var spacer = reader.read(1); +// var height = reader.readUInt32BE(); +// var txid = reader.read(32); +// var outputIndex = reader.readUInt32BE(); +// return { +// prefix: prefix, +// hashBuffer: hashBuffer, +// hashTypeBuffer: hashTypeBuffer, +// height: height, +// txid: txid, +// outputIndex: outputIndex +// }; +// }; -exports.encodeOutputValue = function(satoshis, scriptBuffer) { - var satoshisBuffer = new Buffer(8); - satoshisBuffer.writeDoubleBE(satoshis); - return Buffer.concat([satoshisBuffer, scriptBuffer]); -}; +// exports.encodeOutputValue = function(satoshis, scriptBuffer) { +// var satoshisBuffer = new Buffer(8); +// satoshisBuffer.writeDoubleBE(satoshis); +// 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.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); - return { - satoshis: satoshis, - scriptBuffer: scriptBuffer - }; -}; +// exports.decodeOutputValue = function(buffer) { +// var satoshis = buffer.readDoubleBE(0); +// var scriptBuffer = buffer.slice(8, buffer.length); +// return { +// satoshis: satoshis, +// scriptBuffer: scriptBuffer +// }; +// }; -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.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); - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - return Buffer.concat([ - constants.PREFIXES.SPENTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - heightBuffer, - prevTxIdBuffer, - outputIndexBuffer - ]); -}; +// exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) { +// var heightBuffer = new Buffer(4); +// heightBuffer.writeUInt32BE(height); +// var outputIndexBuffer = new Buffer(4); +// outputIndexBuffer.writeUInt32BE(outputIndex); +// return Buffer.concat([ +// constants.PREFIXES.SPENTS, +// hashBuffer, +// hashTypeBuffer, +// constants.SPACER_MIN, +// heightBuffer, +// prevTxIdBuffer, +// outputIndexBuffer +// ]); +// }; -exports.decodeInputKey = function(buffer) { - var reader = new BufferReader(buffer); - var prefix = reader.read(1); - var hashBuffer = reader.read(20); - var hashTypeBuffer = reader.read(1); - var spacer = reader.read(1); - var height = reader.readUInt32BE(); - var prevTxId = reader.read(32); - var outputIndex = reader.readUInt32BE(); - return { - prefix: prefix, - hashBuffer: hashBuffer, - hashTypeBuffer: hashTypeBuffer, - height: height, - prevTxId: prevTxId, - outputIndex: outputIndex - }; -}; +// exports.decodeInputKey = function(buffer) { +// var reader = new BufferReader(buffer); +// var prefix = reader.read(1); +// var hashBuffer = reader.read(20); +// var hashTypeBuffer = reader.read(1); +// var spacer = reader.read(1); +// var height = reader.readUInt32BE(); +// var prevTxId = reader.read(32); +// var outputIndex = reader.readUInt32BE(); +// return { +// prefix: prefix, +// hashBuffer: hashBuffer, +// hashTypeBuffer: hashTypeBuffer, +// height: height, +// prevTxId: prevTxId, +// outputIndex: outputIndex +// }; +// }; -exports.encodeInputValue = function(txidBuffer, inputIndex) { - var inputIndexBuffer = new Buffer(4); - inputIndexBuffer.writeUInt32BE(inputIndex); - return Buffer.concat([ - txidBuffer, - inputIndexBuffer - ]); -}; +// exports.encodeInputValue = function(txidBuffer, inputIndex) { +// var inputIndexBuffer = new Buffer(4); +// inputIndexBuffer.writeUInt32BE(inputIndex); +// return Buffer.concat([ +// txidBuffer, +// inputIndexBuffer +// ]); +// }; -exports.decodeInputValue = function(buffer) { - var txid = buffer.slice(0, 32); - var inputIndex = buffer.readUInt32BE(32); - return { - txid: txid, - inputIndex: inputIndex - }; -}; +// exports.decodeInputValue = function(buffer) { +// var txid = buffer.slice(0, 32); +// var inputIndex = buffer.readUInt32BE(32); +// return { +// txid: txid, +// inputIndex: inputIndex +// }; +// }; -exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) { - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - return Buffer.concat([ - constants.PREFIXES.SPENTSMAP, - outputTxIdBuffer, - outputIndexBuffer - ]); -}; +// exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) { +// var outputIndexBuffer = new Buffer(4); +// outputIndexBuffer.writeUInt32BE(outputIndex); +// return Buffer.concat([ +// constants.PREFIXES.SPENTSMAP, +// outputTxIdBuffer, +// outputIndexBuffer +// ]); +// }; -exports.decodeInputKeyMap = function(buffer) { - var txid = buffer.slice(1, 33); - var outputIndex = buffer.readUInt32BE(33); - return { - outputTxId: txid, - outputIndex: outputIndex - }; -}; +// exports.decodeInputKeyMap = function(buffer) { +// var txid = buffer.slice(1, 33); +// var outputIndex = buffer.readUInt32BE(33); +// return { +// outputTxId: txid, +// outputIndex: outputIndex +// }; +// }; -exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) { - var inputIndexBuffer = new Buffer(4); - inputIndexBuffer.writeUInt32BE(inputIndex); - return Buffer.concat([ - inputTxIdBuffer, - inputIndexBuffer - ]); -}; +// exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) { +// var inputIndexBuffer = new Buffer(4); +// inputIndexBuffer.writeUInt32BE(inputIndex); +// return Buffer.concat([ +// inputTxIdBuffer, +// inputIndexBuffer +// ]); +// }; -exports.decodeInputValueMap = function(buffer) { - var txid = buffer.slice(0, 32); - var inputIndex = buffer.readUInt32BE(32); - return { - inputTxId: txid, - inputIndex: inputIndex - }; -}; +// exports.decodeInputValueMap = function(buffer) { +// var txid = buffer.slice(0, 32); +// var inputIndex = buffer.readUInt32BE(32); +// return { +// inputTxId: txid, +// inputIndex: inputIndex +// }; +// }; -exports.encodeSummaryCacheKey = function(address) { - return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]); -}; +// exports.encodeSummaryCacheKey = function(address) { +// return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]); +// }; -exports.decodeSummaryCacheKey = function(buffer, network) { - var hashBuffer = buffer.read(20); - var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')]; - var address = new Address({ - hashBuffer: hashBuffer, - type: type, - network: network - }); - return address; -}; +// exports.decodeSummaryCacheKey = function(buffer, network) { +// var hashBuffer = buffer.read(20); +// var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')]; +// var address = new Address({ +// hashBuffer: hashBuffer, +// type: type, +// network: network +// }); +// return address; +// }; -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); - buffer.writeDoubleBE(cache.result.balance, 12); - var txidBuffers = []; - for (var i = 0; i < cache.result.txids.length; i++) { - var buf = new Buffer(new Array(36)); - var txid = cache.result.txids[i]; - buf.write(txid, 'hex'); - buf.writeUInt32BE(cache.result.appearanceIds[txid], 32); - txidBuffers.push(buf); - } - var txidsBuffer = Buffer.concat(txidBuffers); - var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]); +// 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); +// buffer.writeDoubleBE(cache.result.balance, 12); +// var txidBuffers = []; +// for (var i = 0; i < cache.result.txids.length; i++) { +// var buf = new Buffer(new Array(36)); +// var txid = cache.result.txids[i]; +// buf.write(txid, 'hex'); +// buf.writeUInt32BE(cache.result.appearanceIds[txid], 32); +// txidBuffers.push(buf); +// } +// var txidsBuffer = Buffer.concat(txidBuffers); +// 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 height = buffer.readUInt32BE(32); - var totalReceived = buffer.readDoubleBE(36); - var balance = buffer.readDoubleBE(44); +// 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 = 52; - while(pos < buffer.length) { - var txid = buffer.slice(pos, pos + 32).toString('hex'); - var txidHeight = buffer.readUInt32BE(pos + 32); - txids.push(txid); - appearanceIds[txid] = txidHeight; - pos += 36; - } +// // read 32 byte chunks until exhausted +// var appearanceIds = {}; +// var txids = []; +// var pos = 52; +// while(pos < buffer.length) { +// var txid = buffer.slice(pos, pos + 32).toString('hex'); +// var txidHeight = buffer.readUInt32BE(pos + 32); +// txids.push(txid); +// appearanceIds[txid] = txidHeight; +// pos += 36; +// } - var cache = { - height: height, - hash: hash, - result: { - appearanceIds: appearanceIds, - txids: txids, - totalReceived: totalReceived, - balance: balance, - unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache - unconfirmedBalance: 0 - } - }; +// var cache = { +// height: height, +// hash: hash, +// result: { +// appearanceIds: appearanceIds, +// txids: txids, +// totalReceived: totalReceived, +// balance: balance, +// unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache +// unconfirmedBalance: 0 +// } +// }; - return cache; -}; +// return cache; +// }; -exports.getAddressInfo = function(addressStr) { - var addrObj = bitcore.Address(addressStr); - var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type]; +// exports.getAddressInfo = function(addressStr) { +// var addrObj = bitcore.Address(addressStr); +// var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type]; - return { - hashBuffer: addrObj.hashBuffer, - hashTypeBuffer: hashTypeBuffer, - hashTypeReadable: addrObj.type - }; -}; +// return { +// hashBuffer: addrObj.hashBuffer, +// hashTypeBuffer: hashTypeBuffer, +// hashTypeReadable: addrObj.type +// }; +// }; -/** - * This function is optimized to return address information about an output script - * without constructing a Bitcore Address instance. - * @param {Script} - An instance of a Bitcore Script - * @param {Network|String} - The network for the address - */ -exports.extractAddressInfoFromScript = function(script, network) { - $.checkArgument(network, 'Second argument is expected to be a network'); - var hashBuffer; - var addressType; - var hashTypeBuffer; - if (script.isPublicKeyHashOut()) { - hashBuffer = script.chunks[2].buf; - hashTypeBuffer = constants.HASH_TYPES.PUBKEY; - addressType = Address.PayToPublicKeyHash; - } else if (script.isScriptHashOut()) { - hashBuffer = script.chunks[1].buf; - hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT; - addressType = Address.PayToScriptHash; - } else if (script.isPublicKeyOut()) { - var pubkey = script.chunks[0].buf; - var address = Address.fromPublicKey(new PublicKey(pubkey), network); - hashBuffer = address.hashBuffer; - hashTypeBuffer = constants.HASH_TYPES.PUBKEY; - // pay-to-publickey doesn't have an address, however for compatibility - // purposes, we can create an address - addressType = Address.PayToPublicKeyHash; - } else { - return false; - } - return { - hashBuffer: hashBuffer, - hashTypeBuffer: hashTypeBuffer, - addressType: addressType - }; -}; +// /** +// * This function is optimized to return address information about an output script +// * without constructing a Bitcore Address instance. +// * @param {Script} - An instance of a Bitcore Script +// * @param {Network|String} - The network for the address +// */ +// exports.extractAddressInfoFromScript = function(script, network) { +// $.checkArgument(network, 'Second argument is expected to be a network'); +// var hashBuffer; +// var addressType; +// var hashTypeBuffer; +// if (script.isPublicKeyHashOut()) { +// hashBuffer = script.chunks[2].buf; +// hashTypeBuffer = constants.HASH_TYPES.PUBKEY; +// addressType = Address.PayToPublicKeyHash; +// } else if (script.isScriptHashOut()) { +// hashBuffer = script.chunks[1].buf; +// hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT; +// addressType = Address.PayToScriptHash; +// } else if (script.isPublicKeyOut()) { +// var pubkey = script.chunks[0].buf; +// var address = Address.fromPublicKey(new PublicKey(pubkey), network); +// hashBuffer = address.hashBuffer; +// hashTypeBuffer = constants.HASH_TYPES.PUBKEY; +// // pay-to-publickey doesn't have an address, however for compatibility +// // purposes, we can create an address +// addressType = Address.PayToPublicKeyHash; +// } else { +// return false; +// } +// return { +// hashBuffer: hashBuffer, +// hashTypeBuffer: hashTypeBuffer, +// addressType: addressType +// }; +// }; -module.exports = exports; +module.exports = Encoding; diff --git a/lib/services/address/index.js b/lib/services/address/index.js index f0592609..ece15f32 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -20,7 +20,7 @@ var EventEmitter = require('events').EventEmitter; var Address = bitcore.Address; var AddressHistory = require('./history'); var constants = require('./constants'); -var encoding = require('./encoding'); +var Encoding = require('./encoding'); var InputsTransformStream = require('./streams/inputs-transform'); var OutputsTransformStream = require('./streams/outputs-transform'); @@ -37,29 +37,23 @@ var OutputsTransformStream = require('./streams/outputs-transform'); var AddressService = function(options) { BaseService.call(this, options); - this.subscriptions = {}; - this.subscriptions['address/transaction'] = {}; - this.subscriptions['address/balance'] = {}; + // this.subscriptions = {}; + // this.subscriptions['address/transaction'] = {}; + // this.subscriptions['address/balance'] = {}; - this._bitcoindTransactionListener = this.transactionHandler.bind(this); - this._bitcoindTransactionLeaveListener = this.transactionLeaveHandler.bind(this); - this.node.services.bitcoind.on('tx', this._bitcoindTransactionListener); - this.node.services.bitcoind.on('txleave', this._bitcoindTransactionLeaveListener); + // this._bitcoindTransactionListener = this.transactionHandler.bind(this); + // this._bitcoindTransactionLeaveListener = this.transactionLeaveHandler.bind(this); + // this.node.services.bitcoind.on('tx', this._bitcoindTransactionListener); + // this.node.services.bitcoind.on('txleave', this._bitcoindTransactionLeaveListener); this.maxInputsQueryLength = options.maxInputsQueryLength || constants.MAX_INPUTS_QUERY_LENGTH; this.maxOutputsQueryLength = options.maxOutputsQueryLength || constants.MAX_OUTPUTS_QUERY_LENGTH; this.concurrency = options.concurrency || 20; - this._setMempoolIndexPath(); - if (options.mempoolMemoryIndex) { - this.levelupStore = memdown; - } 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 + // 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); @@ -73,71 +67,27 @@ AddressService.dependencies = [ AddressService.prototype.start = function(callback) { var self = this; - async.series([ + this.store = this.node.services.db.store; - function(next) { - // Flush any existing mempool index - if (fs.existsSync(self.mempoolIndexPath)) { - 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 - ); + this.node.services.db.getPrefix(this.name, function(err, prefix) { + if(err) { + return callback(err); } - ], callback); + self.prefix = prefix; + + self.encoding = new Encoding(self.prefix); + + callback(); + }); }; AddressService.prototype.stop = function(callback) { // TODO Keep track of ongoing db requests before shutting down - this.node.services.bitcoind.removeListener('tx', this._bitcoindTransactionListener); - this.node.services.bitcoind.removeListener('txleave', this._bitcoindTransactionLeaveListener); - this.mempoolIndex.close(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; + // this.node.services.bitcoind.removeListener('tx', this._bitcoindTransactionListener); + // this.node.services.bitcoind.removeListener('txleave', this._bitcoindTransactionLeaveListener); + // this.mempoolIndex.close(callback); + setImmediate(callback); }; /** @@ -160,289 +110,21 @@ AddressService.prototype.getAPIMethods = function() { * Called by the Bus to get the available events for this service. */ AddressService.prototype.getPublishEvents = function() { - return [ - { - name: 'address/transaction', - scope: this, - subscribe: this.subscribe.bind(this, 'address/transaction'), - unsubscribe: this.unsubscribe.bind(this, 'address/transaction') - }, - { - name: 'address/balance', - scope: this, - 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); + return []; + // return [ + // { + // name: 'address/transaction', + // scope: this, + // subscribe: this.subscribe.bind(this, 'address/transaction'), + // unsubscribe: this.unsubscribe.bind(this, 'address/transaction') + // }, + // { + // name: 'address/balance', + // scope: this, + // subscribe: this.subscribe.bind(this, 'address/balance'), + // unsubscribe: this.unsubscribe.bind(this, 'address/balance') + // } + // ]; }; /** @@ -453,6 +135,8 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) { * @param {Function} callback */ AddressService.prototype.blockHandler = function(block, connectBlock, callback) { + var self = this; + var txs = block.transactions; var height = block.__height; @@ -480,16 +164,21 @@ AddressService.prototype.blockHandler = function(block, connectBlock, callback) var output = outputs[outputIndex]; var script = output.script; - var address = script.toAddress(); if(!script) { log.debug('Invalid script'); continue; } + var address = self._getAddressString(script); - var key = encoding.encodeAddressIndexKey(address, false, height, txid, outputIndex); - var value = encoding.encodeOutputValue(output.satoshis, output._scriptBuffer); + if(!address) { + 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({ type: action, 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 for (var addressKey in txmessages) { - this.transactionEventHandler(txmessages[addressKey]); + //this.transactionEventHandler(txmessages[addressKey]); } if(tx.isCoinbase()) { return next(); } - async.eachLimit(inputs, self.concurrency, function(input, next) { + async.eachOfLimit(inputs, self.concurrency, function(input, inputIndex, next) { var input = inputs[inputIndex]; var inputHash; 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, function(err, result) { + self.node.services.transaction.getTransaction(input.prevTxId.toString('hex'), function(err, tx) { if(err) { return next(err); } - var tx = result.transaction; - var height = result.height; 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 outputValue = encoding.encodeAdressIndexValue(output.satoshis, output._scriptBuffer); - var inputValue = encoding.encodeAddressIndexValue(output.satoshis, input._scriptBuffer); + + var address = self._getAddressString(input.script, output); + + 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([{ type: action, @@ -562,11 +260,33 @@ AddressService.prototype.blockHandler = function(block, connectBlock, callback) next(); }); - }, function(err) { - callback(err, operations); - }); + }, next); + }, 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; }; /** diff --git a/lib/services/db.js b/lib/services/db.js index 8b9c5372..92eed429 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -42,7 +42,7 @@ function DB(options) { // to determine during an upgrade if a reindex is required this.version = 2; - this.dbPrefix = 0; + this.dbPrefix = '\u0000\u0000'; this.tip = null; this.genesis = null; @@ -105,10 +105,10 @@ DB.prototype._setDataPath = function() { DB.prototype._checkVersion = function(callback) { var self = this; var options = { - keyEncoding: 'binary', + keyEncoding: 'string', 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) { // The database is brand new and doesn't have a tip stored // we can skip version checking @@ -116,7 +116,7 @@ DB.prototype._checkVersion = function(callback) { } else if (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; if (err instanceof levelup.errors.NotFoundError) { // 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) { var versionBuffer = new Buffer(new Array(4)); 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.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.once('ready', function() { log.info('Bitcoin Database Ready'); // 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) { self.sync(); } @@ -240,12 +240,12 @@ DB.prototype.close = function(callback) { * @param {String} txInfo.hash - The hash of the transaction */ DB.prototype.transactionHandler = function(tx) { - for (var i = 0; i < this.subscriptions.transaction.length; i++) { - this.subscriptions.transaction[i].emit('db/transaction', { - rejected: !txInfo.mempool, - tx: tx - }); - } + // for (var i = 0; i < this.subscriptions.transaction.length; i++) { + // this.subscriptions.transaction[i].emit('db/transaction', { + // rejected: !txInfo.mempool, + // tx: tx + // }); + // } }; /** @@ -267,11 +267,11 @@ DB.prototype.loadTip = function(callback) { var self = this; var options = { - keyEncoding: 'binary', + keyEncoding: 'string', 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) { self.tip = self.genesis; self.tip.__height = 0; @@ -288,7 +288,8 @@ DB.prototype.loadTip = function(callback) { return callback(err); } - var hash = tipData.toString('hex'); + var hash = tipData.slice(0, 32).toString('hex'); + var height = tipData.readUInt32BE(32); var times = 0; async.retry({times: 3, interval: self.retryInterval}, function(done) { @@ -311,12 +312,9 @@ DB.prototype.loadTip = function(callback) { return callback(err); } + tip.__height = height; 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(); }); }); @@ -330,55 +328,6 @@ DB.prototype.getBlock = function(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 * @param {String} txid - A transaction hash @@ -532,23 +481,26 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) { var operations = []; // Notify block subscribers - for (var i = 0; i < this.subscriptions.block.length; i++) { - this.subscriptions.block[i].emit('db/block', block.hash); - } + // for (var i = 0; i < this.subscriptions.block.length; i++) { + // this.subscriptions.block[i].emit('db/block', block.hash); + // } // 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({ type: 'put', - key: DB.PREFIXES.TIP, - value: tipHash - }); - - // Update block index - operations.push({ - type: add ? 'put' : 'del', - key: this._encodeBlockIndexKey(block.header.timestamp), - value: this._encodeBlockIndexValue(block.hash) + key: self.dbPrefix + 'tip', + value: tipData }); 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, * by moving backwards on both chains until there is a meeting point. @@ -739,6 +675,7 @@ DB.prototype.sync = function() { height = self.tip.__height; return height < self.node.services.bitcoind.height && !self.node.stopping; }, function(done) { + console.log('fetching block ' + (height + 1)); self.node.services.bitcoind.getBlock(height + 1, function(err, block) { if (err) { return done(err); @@ -805,6 +742,57 @@ DB.prototype.sync = function() { DB.prototype.getPrefix = function(service, callback) { 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( [ getPrefix, @@ -814,56 +802,6 @@ DB.prototype.getPrefix = function(service, 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; diff --git a/lib/services/transaction.js b/lib/services/transaction.js index dd17e053..b75879a8 100644 --- a/lib/services/transaction.js +++ b/lib/services/transaction.js @@ -1,10 +1,12 @@ 'use strict'; -var BaseService = require('../../service'); +var BaseService = require('../service'); var inherits = require('util').inherits; +var bitcore = require('bitcore-lib'); function TransactionService(options) { BaseService.call(this, options); + this.currentTransactions = {}; } inherits(TransactionService, BaseService); @@ -41,8 +43,13 @@ TransactionService.prototype.blockHandler = function(block, connectBlock, callba var operations = []; + this.currentTransactions = {}; + for(var i = 0; i < block.transactions.length; i++) { var tx = block.transactions[i]; + tx.__height = block.__height; + + this.currentTransactions[tx.id] = tx; operations.push({ 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) { return Buffer.concat([this.prefix, new Buffer(txid, 'hex')]); }; TransactionService.prototype._decodeTransactionKey = function(buffer) { - return buffer.slice(1).toString('hex'); + return buffer.slice(2).toString('hex'); }; TransactionService.prototype._encodeTransactionValue = function(transaction, height) { var heightBuffer = new Buffer(4); - heightBuffer.writeUInt32BE(height); - return new Buffer.concat([heightBuffer, transaction.uncheckedSerialize()]); + heightBuffer.writeUInt32BE(); + return new Buffer.concat([heightBuffer, transaction.toBuffer()]); }; TransactionService.prototype._decodeTransactionValue = function(buffer) { - return { - height: Buffer.readUInt32BE(height), - transaction: new bitcore.Transaction(buffer) - }; + var height = buffer.readUInt32BE(); + var transaction = new bitcore.Transaction(buffer.slice(4)); + transaction.__height = height; + return transaction; }; module.exports = TransactionService;