From b471857bf0f235affbefa7a308743aa3b36ff77c Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Sat, 15 Jul 2017 15:50:52 -0400 Subject: [PATCH] wip --- lib/services/address/encoding.js | 15 +- lib/services/address/index.js | 457 +++++++++++++++--------------- lib/services/block/index.js | 97 +++++-- lib/services/p2p/index.js | 35 +++ lib/services/timestamp/index.js | 23 ++ lib/services/transaction/index.js | 99 ++++--- 6 files changed, 439 insertions(+), 287 deletions(-) diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index b22fcf58..2ed46ca8 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -7,7 +7,7 @@ function Encoding(servicePrefix) { this.servicePrefix = servicePrefix; } -Encoding.prototype.encodeAddressIndexKey = function(address, height, txid) { +Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input) { var prefix = new Buffer('00', 'hex'); var buffers = [this.servicePrefix, prefix]; @@ -25,6 +25,15 @@ Encoding.prototype.encodeAddressIndexKey = function(address, height, txid) { var txidBuffer = new Buffer(txid || Array(65).join('0'), 'hex'); buffers.push(txidBuffer); + var indexBuffer = new Buffer(4); + indexBuffer.writeUInt32BE(index || 0); + buffers.push(indexBuffer); + + // this is whether the address appears in an input (1) or output (0) + var inputBuffer = new Buffer(1); + inputBuffer.writeUInt8(input || 0); + buffers.push(inputBuffer); + return Buffer.concat(buffers); }; @@ -36,10 +45,14 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) { var address = reader.read(addressSize).toString('utf8'); var height = reader.readUInt32BE(); var txid = reader.read(32).toString('hex'); + var index = reader.readUInt32BE(); + var input = reader.readUInt8(); return { address: address, height: height, txid: txid, + index: index, + input: input }; }; diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 58094249..ee3f74f2 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -7,10 +7,33 @@ var index = require('../../'); var log = index.log; var errors = index.errors; var bitcore = require('bitcore-lib'); +var Unit = bitcore.Unit; var _ = bitcore.deps._; -var Address = bitcore.Address; var Encoding = require('./encoding'); var utils = require('../../utils'); +var Transform = require('stream').Transform; +/* + +1. getAddressSummary +2. getAddressUnspentOutputs +3. bitcoind.height +4. getBlockHeader +5. getDetailedTransaction +6. getTransaction +7. sendTransaction +8. getInfo +9. bitcoind.tiphash +10. getBestBlockHash +11. isSynced +12. getAddressHistory +13. getBlock +14. getRawBlock +15. getBlockHashesByTimestamp +16. estimateFee +17. getBlockOverview +18. syncPercentage + +*/ var AddressService = function(options) { BaseService.call(this, options); @@ -28,25 +51,6 @@ AddressService.dependencies = [ ]; // ---- public function prototypes -AddressService.prototype.getBalance = function(address, queryMempool, callback) { - this.getUtxos(address, queryMempool, function(err, outputs) { - if(err) { - return callback(err); - } - - var satoshis = outputs.map(function(output) { - return output.satoshis; - }); - - var sum = satoshis.reduce(function(a, b) { - return a + b; - }, 0); - - return callback(null, sum); - }); -}; - - AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) { var self = this; @@ -127,266 +131,275 @@ AddressService.prototype.stop = function(callback) { AddressService.prototype.getAPIMethods = function() { return [ - ['getAddressBalance', this, this.getAddressBalance, 2], ['getAddressHistory', this, this.getAddressHistory, 2], ['getAddressSummary', this, this.getAddressSummary, 1], - ['getAddressTxids', this, this.getAddressTxids, 2], ['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1], ['syncPercentage', this, this.syncPercentage, 0] ]; }; -AddressService.prototype.getAddressBalance = function(addresses, options, callback) { - +AddressService.prototype.getAddressHistory = function(addresses, options, callback) { var self = this; - addresses = utils.normalizeAddressArg(addresses); - var balance = 0; - async.eachLimit(addresses, 4, function(address, next) { + options = options || {}; + var from = options.from || 0; + var to = options.to || 0xffffffff; - var start = self._encoding.encodeUtxoIndexKey(address); - var criteria = { - gte: start, - lte: Buffer.concat([ start.slice(-36), new Buffer(new Array(73).join('f'), 'hex') ]) + async.mapLimit(addresses, 4, function(address, next) { + + self._getAddressHistory(address, next); + + }, function(err, res) { + + if(err) { + return callback(err); + } + + var results = { + totalItems: res.length, + from: from, + to: to, + items: res }; - var stream = this._db.createReadStream(criteria); - stream.on('data', function(data) { + callback(null, results); - }); - stream.on('error', function(err) { - }); - stream.on('end', function() { - }); }); }; -AddressService.prototype.getAddressHistory = function(addresses, options, callback) { +AddressService.prototype._getAddressHistory = function(address, options, callback) { var self = this; - var txids = []; - async.eachLimit(addresses, 4, function(address, next) { - self.getAddressTxids(address, options, function(err, tmpTxids) { + var results = []; + var start = self._encoding.encodeAddressIndexKey(address); + + var criteria = { + gte: start, + lte: utils.getTerminalKey(start) + }; + + // txid stream + var txidStream = self._db.createKeyStream(criteria); + + txidStream.on('close', function() { + txidStream.unpipe(); + }); + + // tx stream + var txStream = new Transform({ objectMode: true, highWaterMark: 1000 }); + + var streamErr; + txStream.on('end', function() { + if (streamErr) { + return callback(streamErr); + } + callback(null, results); + }); + + // pipe txids into tx stream for processing + txidStream.pipe(txStream); + + txStream._transform = function(chunk, enc, callback) { + + var key = self._encoding.decodeWalletTransactionKey(chunk); + + self._tx.getDetailedTransaction(key.txid, options, function(err, tx) { + if(err) { - return next(err); + log.error(err); + txStream.emit('error', err); + return callback(); } - txids = _.union(txids, tmpTxids); - return next(); + if (!tx) { + log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.'); + return callback(); + } + + results.push(tx); + + callback(); + }); - }, function() { - async.mapLimit(txids, 4, function(txid, next) { - self.node.services.transaction.getTransaction(txid.toString('hex'), options, function(err, tx) { - if(err) { - return next(err); - } - var txObj = tx.toObject(); - for(var i = 0; i < txObj.inputs.length; i++) { - txObj.inputs[i].satoshis = tx.__inputValues[i]; - } + }; - next(null, txObj); - }); - }, callback); + txStream.on('error', function(err) { + log.error(err); + txStream.unpipe(); }); + + txStream._flush = function(callback) { + txStream.emit('end'); + callback(); + }; + }; -AddressService.prototype.getAddressSummary = function(addressArg, options, callback) { +AddressService.prototype.getAddressSummary = function(address, options, callback) { var self = this; - var startTime = new Date(); - var address = new Address(addressArg); - if (_.isUndefined(options.queryMempool)) { options.queryMempool = true; } - async.waterfall([ - function(next) { - self._getAddressConfirmedSummary(address, options, next); - }, - function(result, next) { - self._getAddressMempoolSummary(address, options, result, next); - }, - function(result, next) { - self._setAndSortTxidsFromAppearanceIds(result, next); - } - ], function(err, result) { - if (err) { - return callback(err); - } + var result = { + addrStr: address, + balance: 0, + balanceSat: 0, + totalReceived: 0, + totalReceivedSat: 0, + totalSent: 0, + totalSentSat: 0, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 0, + transactions: [] + }; - var summary = self._transformAddressSummaryFromResult(result, options); - - var timeDelta = new Date() - startTime; - if (timeDelta > 5000) { - var seconds = Math.round(timeDelta / 1000); - log.warn('Slow (' + seconds + 's) getAddressSummary request for address: ' + address.toString()); - } - - callback(null, summary); - - }); - -}; - -AddressService.prototype.getAddressTxids = function(address, options, callback) { - var self = this; - - var opts = options || { start: 0, end: 0xffffffff, txid: new Array(65).join('0') }; - var txids = {}; - - var start = self._encoding.encodeAddressIndexKey(address, opts.start, opts.txid); - var end = self._encoding.encodeAddressIndexKey(address, opts.end, opts.txid); - - var stream = self.db.createKeyStream({ + // txid criteria + var start = self._encoding.encodeAddressIndexKey(address); + var criteria = { gte: start, - lt: end + lte: utils.getTerminalKey(start) + }; + + // txid stream + var txidStream = self._db.createKeyStream(criteria); + + txidStream.on('close', function() { + txidStream.unpipe(); }); - var streamErr = null; - stream.on('close', function() { + // tx stream + var txStream = new Transform({ objectMode: true, highWaterMark: 1000 }); + txStream.on('end', function() { + result.balance = Unit.fromSatoshis(result.balanceSat).toBTC(); + result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC(); + result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC(); + result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC(); + callback(null, result); }); - stream.on('data', function(buffer) { - var key = self._encoding.decodeAddressIndexKey(buffer); - txids[key.txid] = true; + // pipe txids into tx stream for processing + txidStream.pipe(txStream); + + txStream._transform = function(chunk, enc, callback) { + + var key = self._encoding.decodeWalletTransactionKey(chunk); + + self._tx.getTransaction(key.txid, options, function(err, res) { + + if(err) { + log.error(err); + txStream.emit('error', err); + return callback(); + } + + if (!res) { + log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.'); + return callback(); + } + + var tx = res.tx; + + result.transactions.push(tx.id); + result.txApperances++; + + if (key.input) { + + result.balanceSat -= tx.inputValues[key.index]; + result.totalSentSat += tx.inputValues[key.index]; + + if (res.confirmations === 0) { + + result.unconfirmedBalanceSat -= tx.inputValues[key.index]; + result.unconfirmedTxApperances++; + + } + + } else { + + result.balanceSat += tx.outputs[key.index].satoshis; + result.totalReceivedSat += tx.outputs[key.index].satoshis; + + if (res.confirmations === 0) { + + result.unconfirmedBalanceSat += tx.inputValues[key.index]; + result.unconfirmedTxApperances++; + + } + } + + callback(); + + }); + + }; + + txStream.on('error', function(err) { + log.error(err); + txStream.unpipe(); }); - stream.on('end', function() { - callback(streamErr, Object.keys(txids)); - }); - - stream.on('error', function(err) { - streamErr = err; - }); + txStream._flush = function(callback) { + txStream.emit('end'); + callback(); + }; }; AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { - var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; - var addresses = utils._normalizeAddressArg(address); - var cacheKey = addresses.join(''); - var utxos = this.utxosCache.get(cacheKey); - - function transformUnspentOutput(delta) { - var script = bitcore.Script.fromAddress(delta.address); - return { - address: delta.address, - txid: delta.txid, - outputIndex: delta.index, - script: script.toHex(), - satoshis: delta.satoshis, - timestamp: delta.timestamp - }; - } - - function updateWithMempool(confirmedUtxos, mempoolDeltas) { - if (!mempoolDeltas || !mempoolDeltas.length) { - return confirmedUtxos; - } - var isSpentOutputs = false; - var mempoolUnspentOutputs = []; - var spentOutputs = []; - - for (var i = 0; i < mempoolDeltas.length; i++) { - var delta = mempoolDeltas[i]; - if (delta.prevtxid && delta.satoshis <= 0) { - if (!spentOutputs[delta.prevtxid]) { - spentOutputs[delta.prevtxid] = [delta.prevout]; - } else { - spentOutputs[delta.prevtxid].push(delta.prevout); - } - isSpentOutputs = true; - } else { - mempoolUnspentOutputs.push(transformUnspentOutput(delta)); - } - } - - var utxos = mempoolUnspentOutputs.reverse().concat(confirmedUtxos); - - if (isSpentOutputs) { - return utxos.filter(function(utxo) { - if (!spentOutputs[utxo.txid]) { - return true; - } else { - return (spentOutputs[utxo.txid].indexOf(utxo.outputIndex) === -1); - } - }); - } - - return utxos; - } - - function finish(mempoolDeltas) { - if (utxos) { - return setImmediate(function() { - callback(null, updateWithMempool(utxos, mempoolDeltas)); - }); - } else { - self.client.getAddressUtxos({addresses: addresses}, function(err, response) { - if (err) { - return callback(self._wrapRPCError(err)); - } - var utxos = response.result.reverse(); - self.utxosCache.set(cacheKey, utxos); - callback(null, updateWithMempool(utxos, mempoolDeltas)); - }); - } - } - - if (queryMempool) { - self.client.getAddressMempool({addresses: addresses}, function(err, response) { - if (err) { - return callback(self._wrapRPCError(err)); - } - finish(response.result); - }); - } else { - finish(); - } - -}; - -AddressService.prototype.syncPercentage = function(callback) { - return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%'); -}; - - -AddressService.prototype.getAddressTxidsWithHeights = function(address, options, callback) { var self = this; + if (_.isUndefined(options.queryMempool)) { + options.queryMempool = true; + } - var opts = options || {}; - var txids = {}; + var results = []; - var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0); - var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]); - - var stream = self.db.createKeyStream({ + var start = self._encoding.encodeUtxoIndexKey(address); + var criteria = { gte: start, - lt: end + lt: utils.getTerminalKey(start) + }; + + var utxoStream = self._db.createReadStream(criteria); + + var streamErr; + utxoStream.on('end', function() { + if (streamErr) { + return callback(streamErr); + } + callback(null, results); }); - var streamErr = null; - - stream.on('data', function(buffer) { - var key = self._encoding.decodeAddressIndexKey(buffer); - txids[key.txid] = key.height; - }); - - stream.on('end', function() { - callback(streamErr, txids); - }); - - stream.on('error', function(err) { + utxoStream.on('error', function(err) { streamErr = err; }); + + utxoStream.on('data', function(data) { + var key = self._decodeUtxoIndexKey(data.key); + var value = self._encoding.decodeUtxoIndexValue(data.value); + results.push({ + address: address, + txid: key.txid, + vout: key.oudputIndex, + ts: null, + scriptPubKey: value.scriptBuffer.toString('hex'), + amount: Unit.fromSatoshis(value.satoshis).toBTC(), + confirmations: self._p2p.getBestHeight() - value.height, + satoshis: value.satoshis, + confirmationsFromCache: true + }); + }); + }; + // ---- private function prototypes AddressService.prototype._setListeners = function() { diff --git a/lib/services/block/index.js b/lib/services/block/index.js index 71f2bc84..95cc07a1 100644 --- a/lib/services/block/index.js +++ b/lib/services/block/index.js @@ -14,6 +14,28 @@ var BN = require('bn.js'); var consensus = require('bcoin').consensus; var constants = require('../../constants'); +/* + +1. getAddressSummary +2. getAddressUnspentOutputs +3. bitcoind.height +4. getBlockHeader +5. getDetailedTransaction +6. getTransaction +7. sendTransaction +8. getInfo +9. bitcoind.tiphash +10. getBestBlockHash +11. isSynced +12. getAddressHistory +13. getBlock +14. getRawBlock +15. getBlockHashesByTimestamp +16. estimateFee +17. getBlockOverview +18. syncPercentage + +*/ var BlockService = function(options) { BaseService.call(this, options); @@ -67,27 +89,23 @@ BlockService.prototype.getAPIMethods = function() { return methods; }; -BlockService.prototype.getBestBlockHash = function() { - return this._meta[this._meta.length - 1].hash; +BlockService.prototype.getBestBlockHash = function(callback) { + return callback(null, this._meta[this._meta.length - 1].hash); }; BlockService.prototype.getBlock = function(hash, callback) { - var self = this; - this._db.get(this._encoding.encodeBlockKey(hash), function(err, data) { - if(err) { - return callback(err); - } - callback(null, self._encoding.decodeBlockValue(data)); - }); -}; -BlockService.prototype._getHash = function(blockArg) { + blockArg = this._getHash(blockArg); - return (_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) && - this._meta[blockArg] ? this._meta[blockArg] : null; + if (!blockArg) { + return callback(); + } + + this._getBlock(blockArg, callback); }; + BlockService.prototype.getBlockHeader = function(blockArg, callback) { blockArg = this._getHash(blockArg); @@ -112,14 +130,6 @@ BlockService.prototype.getBlockHeader = function(blockArg, callback) { }; -BlockService.prototype._getBlock = function(hash, callback) { - var block = this._blockQueue(hash); - if (block) { - return callback(null, block); - } - this._db.get(this._encoding.encodeBlockKey(hash), callback); -}; - BlockService.prototype.getBlockOverview = function(hash, callback) { this._getBlock(hash, function(err, block) { @@ -169,14 +179,18 @@ BlockService.prototype.getPublishEvents = function() { }; BlockService.prototype.getRawBlock = function(hash, callback) { - this.getBlock(hash, function(err, data) { + this.getBlock(hash, function(err, block) { if(err) { return callback(err); } - data.toString(); + callback(null, block.toString()); }); }; +BlockService.prototype.isSynced = function(callback) { + callback(null, this._p2p.getBestHeight <= this._tip.height); +}; + BlockService.prototype.start = function(callback) { var self = this; @@ -217,6 +231,12 @@ BlockService.prototype.subscribe = function(name, emitter) { }; +BlockService.prototype.syncPercentage = function(callback) { + var p2pHeight = this._p2p.getBestHeight(); + var percentage = ((p2pHeight / (this._tip.height || p2pHeight)) * 100).toFixed(2); + callback(null, percentage); +}; + BlockService.prototype.unsubscribe = function(name, emitter) { var index = this._subscriptions[name].indexOf(emitter); @@ -327,6 +347,14 @@ BlockService.prototype._findCommonAncestor = function(block) { } }; +BlockService.prototype._getBlock = function(hash, callback) { + var block = this._blockQueue(hash); + if (block) { + return callback(null, block); + } + this._db.get(this._encoding.encodeBlockKey(hash), callback); +}; + BlockService.prototype._getBlockOperations = function(block) { var self = this; @@ -379,6 +407,13 @@ BlockService.prototype._getDelta = function(tip) { }; +BlockService.prototype._getHash = function(blockArg) { + + return (_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) && + this._meta[blockArg] ? this._meta[blockArg] : null; + +}; + BlockService.prototype._getIncompleteChainIndexes = function(block) { var ret = []; for(var i = 0; i < this._incompleteChains.length; i++) { @@ -392,7 +427,9 @@ BlockService.prototype._getIncompleteChainIndexes = function(block) { }; BlockService.prototype._handleReorg = function(block) { + this._reorging = true; + log.warn('Chain reorganization detected! Our current block tip is: ' + this._tip.hash + ' the current block: ' + block.hash + '.'); @@ -407,8 +444,11 @@ BlockService.prototype._handleReorg = function(block) { } log.warn('A common ancestor block was found to at hash: ' + commonAncestor + '.'); + this._broadcast(this.subscriptions.reorg, 'block/reorg', [commonAncestor, [block]]); + this._onReorg(commonAncestor, [block]); + this._reorging = false; }; @@ -446,8 +486,11 @@ BlockService.prototype._loadMeta = function(callback) { gte: self._encoding.encodeMetaKey(0), lte: self._encoding.encodeMetaKey(0xffffffff) }; + var stream = this._db.createReadStream(criteria); + stream.on('error', self._onDbError.bind(self)); + stream.on('end', function() { if (self._meta.length < 1) { self._meta.push({ @@ -475,11 +518,15 @@ BlockService.prototype._onBlock = function(block) { } // 2. log the reception - log.debug('New block received: ' + block.hash); + log.debug2('New block received: ' + block.hash); // 3. store the block for safe keeping this._cacheBlock(block); + // don't process any more blocks if we are currently in a reorg + if (this._reorging) { + return; + } // 4. determine block state, reorg, outoforder, normal var blockState = this._determineBlockState(block); @@ -492,6 +539,7 @@ BlockService.prototype._onBlock = function(block) { // nothing to do, but wait until ancestor blocks come in break; case 'reorg': + this._handleReorg(); this.emit('reorg', block); break; default: @@ -575,7 +623,6 @@ BlockService.prototype._setListeners = function() { self._p2p.once('bestHeight', self._onBestHeight.bind(self)); self._db.on('error', self._onDbError.bind(self)); - self.on('reorg', self._handleReorg.bind(self)); }; diff --git a/lib/services/p2p/index.js b/lib/services/p2p/index.js index 8b733019..8e35c484 100644 --- a/lib/services/p2p/index.js +++ b/lib/services/p2p/index.js @@ -9,6 +9,28 @@ var BaseService = require('../../service'); var assert = require('assert'); var Bcoin = require('./bcoin'); +/* + +1. getAddressSummary +2. getAddressUnspentOutputs +3. bitcoind.height +4. getBlockHeader +5. getDetailedTransaction +6. getTransaction +7. sendTransaction +8. getInfo +9. bitcoind.tiphash +10. getBestBlockHash +11. isSynced +12. getAddressHistory +13. getBlock +14. getRawBlock +15. getBlockHashesByTimestamp +16. estimateFee +17. getBlockOverview +18. syncPercentage + +*/ var P2P = function(options) { if (!(this instanceof P2P)) { @@ -61,6 +83,19 @@ P2P.prototype.getHeaders = function(filter) { }; +P2P.prototype.getInfo = function(callback) { + callback(null, { + version: '4.0', + protocolversion: 'latest', + blocks: this._getBestHeight(), + timeoffset: 0, + connections: this._pool.numberConnected, + difficulty: 0, + testnet: false, + relayfee: 0 + }); +}; + P2P.prototype.getMempool = function(filter) { var peer = this._getPeer(); diff --git a/lib/services/timestamp/index.js b/lib/services/timestamp/index.js index e255030a..77f2c234 100644 --- a/lib/services/timestamp/index.js +++ b/lib/services/timestamp/index.js @@ -6,6 +6,28 @@ var inherits = require('util').inherits; var LRU = require('lru-cache'); var utils = require('../../../lib/utils'); +/* + +1. getAddressSummary +2. getAddressUnspentOutputs +3. bitcoind.height +4. getBlockHeader +5. getDetailedTransaction +6. getTransaction +7. sendTransaction +8. getInfo +9. bitcoind.tiphash +10. getBestBlockHash +11. isSynced +12. getAddressHistory +13. getBlock +14. getRawBlock +15. getBlockHashesByTimestamp +16. estimateFee +17. getBlockOverview +18. syncPercentage + +*/ function TimestampService(options) { BaseService.call(this, options); this._db = this.node.services.db; @@ -24,6 +46,7 @@ TimestampService.prototype.getAPIMethods = function() { }; TimestampService.prototype.syncPercentage = function(callback) { + return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%'); }; TimestampService.prototype.getBlockHashesByTimestamp = function(callback) { diff --git a/lib/services/transaction/index.js b/lib/services/transaction/index.js index 530e7d84..395ef6a0 100644 --- a/lib/services/transaction/index.js +++ b/lib/services/transaction/index.js @@ -10,77 +10,97 @@ var levelup = require('levelup'); function TransactionService(options) { BaseService.call(this, options); this._db = this.node.services.db; - this.currentTransactions = {}; + this._mempool = this.node.services._mempool; + this._block = this.node.services.block; + this._p2p = this.node.services.p2p; } inherits(TransactionService, BaseService); TransactionService.dependencies = [ + 'p2p', 'db', 'block', 'timestamp', 'mempool' ]; +/* + +1. getAddressSummary +2. getAddressUnspentOutputs +3. bitcoind.height +4. getBlockHeader +5. getDetailedTransaction +6. getTransaction +7. sendTransaction +8. getInfo +9. bitcoind.tiphash +10. getBestBlockHash +11. isSynced +12. getAddressHistory +13. getBlock +14. getRawBlock +15. getBlockHashesByTimestamp +16. estimateFee +17. getBlockOverview +18. syncPercentage + +*/ TransactionService.prototype.getAPIMethods = function() { return [ ['getRawTransaction', this, this.getRawTransaction, 1], ['getTransaction', this, this.getTransaction, 1], ['getDetailedTransaction', this, this.getDetailedTransaction, 1], ['sendTransaction', this, this.sendTransaction, 1], - ['getSpentInfo', this, this.getSpentInfo, 1], ['syncPercentage', this, this.syncPercentage, 0] ]; }; -TransactionService.prototype.getSpentInfo = function(txid, callback) { -}; -TransactionService.prototype.getRawTransaction = function(txid, callback) { - this.getTransaction(txid, function(err, tx) { - if (err) { - return callback(err); - } - return tx.serialize(); - }); -}; - -TransactionService.prototype.getDetailedTransaction = TransactionService.prototype.getTransaction = function(txid, options, callback) { +TransactionService.prototype.getTransaction = function(txid, options, callback) { var self = this; - var key = self.encoding.encodeTransactionKey(txid); + var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; - async.waterfall([ - function(next) { - self.node.services.db.get(key, function(err, buffer) { - if (err instanceof levelup.errors.NotFoundError) { - return next(null, false); - } else if (err) { + var key = self.encoding.encodeTransactionKey(txid); + this._db.get(key, function(err, tx) { + + if(err) { + return callback(err); + } + + if (queryMempool && !tx) { + + this._mempool.getTransaction(tx, function(err, memTx) { + + if(err) { return callback(err); } - var tx = self.encoding.decodeTransactionValue(buffer); - next(null, tx); + + if (memTx) { + return callback(null, { tx: memTx, confirmations: 0}); + } + return callback(); + }); - }, function(tx, next) { + + } else { + if (tx) { - return next(null, tx); + return callback(null, { tx: tx, confirmations: this._p2p.getBestHeight - tx.__height }); } - if (!options || !options.queryMempool) { - return next(new Error('Transaction: ' + txid + ' not found in index')); - } - self.node.services.mempool.getTransaction(txid, function(err, tx) { - if (err instanceof levelup.errors.NotFoundError) { - return callback(new Error('Transaction: ' + txid + ' not found in index or mempool')); - } else if (err) { - return callback(err); - } - self._getMissingInputValues(tx, next); - }); - }], callback); + return callback(); + + } + + }); + + }; -TransactionService.prototype.onBlock = function(block, connectBlock, callback) { +TransactionService.prototype._onBlock = function(block, connectBlock, callback) { var self = this; var action = 'put'; var reverseAction = 'del'; @@ -156,6 +176,7 @@ TransactionService.prototype._onReorg = function(commonAncestor, newBlockList) { }; TransactionService.prototype.start = function(callback) { + var self = this; self._setListeners(); @@ -173,7 +194,7 @@ TransactionService.prototype.start = function(callback) { self._tip = tip; self.prefix = prefix; - self.encoding = new Encoding(self.prefix); + self._encoding = new Encoding(self.prefix); self._startSubscriptions(); callback();