From 93db6790e23d1d558f44819de5e93bd16a6da7d4 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Wed, 11 Oct 2017 16:38:28 -0400 Subject: [PATCH] Repairs to getAddressSummary. --- lib/services/address/index.js | 126 +++++++------------ lib/services/block/index.js | 21 ++-- lib/services/header/index.js | 13 +- lib/utils.js | 10 ++ package-lock.json | 180 ++++++++++++++++++++++------ package.json | 2 +- test/services/address/index.unit.js | 38 ++---- 7 files changed, 226 insertions(+), 164 deletions(-) diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 37636422..f9023c12 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -82,6 +82,7 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba }; +// this is basically the same as _getAddressHistory apart from the summary AddressService.prototype.getAddressSummary = function(address, options, callback) { var self = this; @@ -106,99 +107,62 @@ AddressService.prototype.getAddressSummary = function(address, options, callback transactions: [] }; + self.getAddressHistory(address, options, function(err, results) { - // txid criteria - var start = self._encoding.encodeAddressIndexKey(address, options.from); - var end = self._encoding.encodeAddressIndexKey(address, options.to); + if (err) { + return callback(err); + } - var criteria = { - gte: start, - lte: end - }; + var txs = results.items; + for(var i = 0; i < txs.length; i++) { - // txid stream - var txidStream = self._db.createKeyStream(criteria); + var tx = txs[i]; - txidStream.on('close', function() { - txidStream.unpipe(); - }); + for(var j = 0; j < tx.outputs.length; j++) { - // tx stream - var txStream = new Transform({ objectMode: true, highWaterMark: 1000 }); + var output = tx.outputs[j]; + if (utils.getAddress(output, self._network) !== address) { + continue; + } + + result.txApperances++; + result.totalReceivedSat += output.value; + result.balanceSat += output.value; + + if (tx.confirmations === 0) { + result.unconfirmedTxApperances++; + result.unconfirmedBalanceSat += output.value; + } + + } + + for(j = 0; j < tx.inputs.length; j++) { + + var input = tx.inputs[j]; + if (utils.getAddress(input, self._network) !== address) { + continue; + } + + result.totalSentSat += tx.__inputValues[j]; + result.balanceSat -= tx.__inputValues[j]; + + if (tx.confirmations === 0) { + result.unconfirmedBalanceSat -= tx.__inputValues[j]; + } + + } + + result.transactions.push(tx.txid()); + + } - txStream.on('end', function() { result.balance = Unit.fromSatoshis(result.balanceSat).toBTC(); - assert(result.balance >= 0, 'Balance can\'t be less than zero.'); result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC(); result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC(); - result.unconfirmedBalance = result.unconfirmedBalanceSat; - result.transactions = _.uniq(result.transactions); + result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC(); callback(null, result); }); - // pipe txids into tx stream for processing - txidStream.pipe(txStream); - - txStream._transform = function(chunk, enc, callback) { - - var key = self._encoding.decodeAddressIndexKey(chunk); - - self._tx.getTransaction(key.txid, options, function(err, tx) { - - if(err) { - log.error('Address Service: gettransaction ' + err); - txStream.emit('error', err); - return; - } - - if (!tx) { - log.error('Address Service: Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.'); - txStream.emit('error', new Error('Txid should map to a tx.')); - return; - } - - var confirmations = self._header.getBestHeight() - key.height + 1; - - result.transactions.push(tx.txid()); - result.txApperances++; - // is this an input? - if (key.input) { - - result.balanceSat -= tx.__inputValues[key.index]; - result.totalSentSat += tx.__inputValues[key.index]; - - if (confirmations < 1) { - result.unconfirmedBalanceSat -= tx.__inputValues[key.index]; - result.unconfirmedTxApperances++; - } - - return callback(); - - } - - result.balanceSat += tx.outputs[key.index].value; - result.totalReceivedSat += tx.outputs[key.index].value; - - if (confirmations < 1) { - result.unconfirmedBalanceSat += tx.__inputValues[key.index]; - result.unconfirmedTxApperances++; - } - - callback(); - - }); - - }; - - txStream.on('error', function(err) { - log.error('Address Service: txstream on error ' + err); - txStream.unpipe(); - }); - - txStream._flush = function(callback) { - txStream.emit('end'); - callback(); - }; }; AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { diff --git a/lib/services/block/index.js b/lib/services/block/index.js index 269b16dd..23e5750c 100644 --- a/lib/services/block/index.js +++ b/lib/services/block/index.js @@ -49,7 +49,6 @@ BlockService.prototype.getAPIMethods = function() { BlockService.prototype.getInfo = function(callback) { var self = this; - callback(null, { blocks: self.getTip().height, connections: self._p2p.getNumberOfPeers(), @@ -830,12 +829,18 @@ BlockService.prototype._syncBlock = function(block) { clearTimeout(self._getBlocksTimer); + if (self._lastBlockSaved === block.rhash()) { + return; + } + self._saveBlock(block, function(err) { if(err) { return self._handleError(err); } + self._lastBlockSaved = block.rhash(); + if (self._tip.height < self._header.getLastHeader().height) { return self.emit('next block'); } @@ -928,12 +933,6 @@ BlockService.prototype._sync = function() { return; } - if (self._currentQuery === self._tip.hash) { - return; - } - - self._currentQuery = self._tip.hash; - log.debug('Block Service: querying header service for next block using tip: ' + self._tip.hash); self._header.getNextHash(self._tip, function(err, targetHash, nextHash) { @@ -942,13 +941,10 @@ BlockService.prototype._sync = function() { return self._handleError(err); } - // this might be because the peer reorg from our tip or the peer went away. - if (self._targetHash === targetHash) { - log.warning('Block Service: we asked for the same hash from the header service more than once.'); + if (!targetHash && !nextHash) { + return self.emit('synced'); } - self._targetHash = targetHash; - // to ensure that we can receive blocks that were previously delivered // this will lead to duplicate transactions being sent self._p2p.clearInventoryCache(); @@ -957,7 +953,6 @@ BlockService.prototype._sync = function() { // then we must assume we've reorg'ed very shortly after // we made this call and we should re-compute where we are self._getBlocksTimer = setTimeout(function() { - self._currentQuery = null; self.emit('next block'); }, 5000); diff --git a/lib/services/header/index.js b/lib/services/header/index.js index a3c9c6ea..e04f09ee 100644 --- a/lib/services/header/index.js +++ b/lib/services/header/index.js @@ -878,13 +878,18 @@ HeaderService.prototype._sync = function() { HeaderService.prototype.getNextHash = function(tip, callback) { var self = this; - var numResultsNeeded = 2; - // if the tip being passed in is the second to last block, then return 0 because there isn't a block - if (tip.height + 1 >= self._tip.height) { - numResultsNeeded = 1; + var numResultsNeeded = Math.min((self._tip.height - tip.height), 2); + + if (numResultsNeeded === 0 && self._tip.hash === tip.hash) { + return callback(); } + if (numResultsNeeded <= 0) { + return callback(new Error('Header Service: block service is mis-aligned ')); + } + + var start = self._encoding.encodeHeaderHeightKey(tip.height + 1); var end = self._encoding.encodeHeaderHeightKey(tip.height + 3); var results = []; diff --git a/lib/utils.js b/lib/utils.js index 41b231f5..dd42ca6e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -165,4 +165,14 @@ utils.orderByConfirmations = function(list) { }); }; +// items is output or input +utils.getAddress = function(item, network) { + var address = item.getAddress(); + if (!address) { + return; + } + address.network = network; + return address.toString(); +}; + module.exports = utils; diff --git a/package-lock.json b/package-lock.json index 8929ced3..6e4df8a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -833,7 +833,8 @@ "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true }, "browserify-aes": { "version": "1.0.6", @@ -1224,11 +1225,6 @@ "fs-exists-sync": "0.1.0" } }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" - }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -1920,10 +1916,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true }, "handlebars": { "version": "4.0.10", @@ -2047,11 +2044,6 @@ "sntp": "1.0.9" } }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -2708,6 +2700,63 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -2718,6 +2767,17 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, "lodash.mapvalues": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", @@ -2880,54 +2940,96 @@ } }, "mocha": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", - "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.2.0.tgz", + "integrity": "sha1-fcT0XlCIB1FxpoiWgU5q6et6heM=", + "dev": true, "requires": { "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "3.1.2" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, "requires": { - "ms": "2.0.0" + "graceful-readlink": "1.0.1" } }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "1.0.0" } } } diff --git a/package.json b/package.json index 42556d84..2b5eafad 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "istanbul": "^0.4.3", "jshint": "^2.9.2", "jshint-stylish": "^2.1.0", - "mocha": "", + "mocha": "3.2.0", "proxyquire": "^1.3.1", "rimraf": "^2.4.2", "sinon": "^1.15.4" diff --git a/test/services/address/index.unit.js b/test/services/address/index.unit.js index f98d49a7..0c7663af 100644 --- a/test/services/address/index.unit.js +++ b/test/services/address/index.unit.js @@ -130,37 +130,22 @@ describe('Address Service', function() { describe('#AddressSummary', function() { - it('should get the address summary', function(done) { - var encoding = new Encoding(new Buffer('0001', 'hex')); - addressService._encoding = encoding; - var address = 'a'; - var txid = tx.txid(); - var data = [ null, encoding.encodeAddressIndexKey(address, 123, txid, 1, 0) ]; - var inputValues = [120, 0, 120, 120]; - tx.__inputValues = inputValues; - var getTransaction = sandbox.stub().callsArgWith(2, null, tx); - addressService._tx = { getTransaction: getTransaction }; - addressService._header = { getBestHeight: function() { return 150; } }; + it('should get the address summary, incoming', function(done) { - var txidStream = new Readable(); + var _tx = tx; + _tx.__inputValues = [ 0, 0, 0, 0 ]; + var results = { items: [_tx] }; - txidStream._read = function() { - txidStream.push(data.pop()); - }; - - var createReadStream = sandbox.stub().returns(txidStream); - addressService._db = { createKeyStream: createReadStream }; - - addressService.getAddressSummary(address, {}, function(err, res) { + sandbox.stub(addressService, 'getAddressHistory').callsArgWith(2, null, results); + addressService.getAddressSummary('1JoSiR4dBcSrGs2AZBP2gCHqCCsgzccsGb', {}, function(err, res) { if (err) { return done(err); } - expect(getTransaction.calledOnce).to.be.true; - expect(res).to.deep.equal({ addrStr: 'a', - balance: 0.01139033, - balanceSat: 1139033, - totalReceived: 0.01139033, - totalReceivedSat: 1139033, + expect(res).to.deep.equal({ addrStr: '1JoSiR4dBcSrGs2AZBP2gCHqCCsgzccsGb', + balance: 0.005, + balanceSat: 500000, + totalReceived: 0.005, + totalReceivedSat: 500000, totalSent: 0, totalSentSat: 0, unconfirmedBalance: 0, @@ -175,6 +160,7 @@ describe('Address Service', function() { }); }); + describe('#getAddressUnspentOutputs', function() { it('should get address utxos', function(done) {