diff --git a/lib/services/address.js b/lib/services/address.js new file mode 100644 index 00000000..91a68f7c --- /dev/null +++ b/lib/services/address.js @@ -0,0 +1,130 @@ +'use strict'; + +var Promise = require('bluebird'); +var bitcore = require('bitcore'); +var _ = bitcore.deps._; + +var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex'); +var LASTTXHASH = bitcore.util.buffer.fill(bitcore.util.buffer.emptyBuffer(32), -1).toString('hex'); +var MAXOUTPUT = 1 << 31; + +function AddressService(opts) { + opts = _.extend({}, opts); + this.transactionService = opts.transactionService; + this.blockService = opts.blockService; + this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp'))); + this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC'))); +} + +AddressService.prototype.getSummary = function(address, confirmations) { + + var self = this; + var tip, allOutputs, spent; + confirmations = confirmations || 6; + + return Promise.try(function() { + + return self.blockService.getLatest(); + + }).then(function(latest) { + + tip = latest; + return self.getAllOutputs(address); + + }).then(function(outputs) { + + allOutputs = outputs; + return self.getSpent(address); + + }).then(function(spent) { + + return self.buildAddressSummary(address, tip, allOutputs, spent); + + }); +}; + +AddressService.prototype.getAllOutputs = function(address) { + var results = []; + var self = this; + + return new Promise(function(resolve, reject) { + self.db.createReadStream({ + gte: TransactionService.Index.getOutputsForAddress(address, NULLTXHASH, 0), + lte: TransactionService.Index.getOutputsForAddress(address, LASTTXHASH, MAXOUTPUT) + }).on('data', function(element) { + results.push(element.value); + }).on('close', function() { + reject(); + }).on('end', function() { + resolve(results); + }); + }); +}; + +AddressService.prototype.getSpent = function(address) { + var results = []; + var self = this; + + return new Promise(function(resolve, reject) { + self.db.createReadStream({ + gte: TransactionService.Index.getSpentOutputsForAddress(address, NULLTXHASH, 0), + lte: TransactionService.Index.getSpentOutputsForAddress(address, LASTTXHASH, MAXOUTPUT) + }).on('data', function(element) { + results.push(element.value); + }).on('close', function() { + reject(); + }).on('end', function() { + resolve(results); + }); + }); +}; + +AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs, spent) { + + var result = {}; + var transactionsAppended = {}; + + result.address = address.toString(); + result.transactions = []; + + result.confirmed = { + balance: 0, + sent: 0, + received: 0 + }; + result.unconfirmed = { + balance: 0, + sent: 0, + received: 0 + }; + _.each(allOutputs, function(output) { + var value = output.satoshis; + result.unconfirmed.balance += value; + result.unconfirmed.received += value; + if (tip.height - output.heightConfirmed - 1 >= confirmations) { + result.confirmed.balance += value; + result.confirmed.received += value; + } + }); + _.each(spent, function(output) { + var value = output.satoshis; + if (!transactionsAppended[output.spentTx]) { + transactionsAppended[output.spentTx] = true; + result.transactions.push(output.spentTx); + } + if (!transactionsAppended[output.spendInput.prevTxId]) { + transactionsAppended[output.spendInput.prevTxId] = true; + result.transactions.push(output.spendInput.prevTxId); + } + result.unconfirmed.balance -= value; + result.unconfirmed.sent += value; + if (tip.height - output.heightSpent - 1 >= confirmations) { + result.confirmed.balance -= value; + result.confirmed.sent += value; + } + }); + + return result; +}; + +module.exports = AddressService; diff --git a/lib/services/block.js b/lib/services/block.js index 48a38946..a3e1e304 100644 --- a/lib/services/block.js +++ b/lib/services/block.js @@ -168,7 +168,7 @@ BlockService.prototype.getBlockByHeight = function(height) { return Promise.try(function() { - return this.rpc.getBlockHash(height); + return self.rpc.getBlockHash(height); }).then(function(blockHash) { @@ -210,11 +210,11 @@ BlockService.prototype._confirmBlock = function(block) { var ops = []; - this.writeLock().then( + this.writeLock().then(function() { - self._setNextBlock.bind(self, ops, block.header.prevHash, block) + return self._setNextBlock(ops, block.header.prevHash, block); - ).then(function() { + }).then(function() { if (block.header.prevHash.toString('hex') !== NULLBLOCKHASH) { return self.getBlock(block.header.prevHash); @@ -226,11 +226,11 @@ BlockService.prototype._confirmBlock = function(block) { return self._setBlockHeight(ops, block, parent.height + 1); - }).then( + }).then(function() { - self._setBlockByTs.bind(self, ops, block) + return self._setBlockByTs(ops, block); - ).then(function() { + }).then(function() { return Promise.all(block.transactions.map(function(transaction) { return self.transactionService._confirmTransaction(ops, block, transaction); diff --git a/lib/services/transaction.js b/lib/services/transaction.js index c2fc3e49..b774b9ad 100644 --- a/lib/services/transaction.js +++ b/lib/services/transaction.js @@ -80,6 +80,7 @@ function TransactionService (opts) { this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp'))); this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC'))); } +TransactionService.Index = Index; TransactionService.transactionRPCtoBitcore = function(rpcResponse) { if (rpcResponse.error) { diff --git a/test/services/address.js b/test/services/address.js new file mode 100644 index 00000000..584842bb --- /dev/null +++ b/test/services/address.js @@ -0,0 +1,28 @@ +'use strict'; + +var sinon = require('sinon'); +var should = require('chai').should(); +var Promise = require('bluebird'); + +var bitcore = require('bitcore'); +var _ = bitcore.deps._; + +var TransactionService = require('../../lib/services/transaction'); + +describe.only('AddressService', function() { + + it('initializes correctly', function() { + var database = 'mock'; + var rpc = 'mock'; + var blockService = 'mock'; + var transactionService = 'mock'; + var service = new TransactionService({ + database: database, + transactionService: transactionService, + blockService: blockService, + rpc: rpc + }); + should.exist(service); + }); +}); +