diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index 2ed46ca8..b8dd06c5 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -1,13 +1,10 @@ 'use strict'; -var bitcore = require('bitcore-lib'); -var BufferReader = bitcore.encoding.BufferReader; - function Encoding(servicePrefix) { this.servicePrefix = servicePrefix; } -Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input) { +Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input, timestamp) { var prefix = new Buffer('00', 'hex'); var buffers = [this.servicePrefix, prefix]; @@ -34,29 +31,33 @@ Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index inputBuffer.writeUInt8(input || 0); buffers.push(inputBuffer); + var timestampBuffer = new Buffer(8); + timestampBuffer.writeDoubleBE(timestamp || 0) + buffers.push(timestampBuffer); + return Buffer.concat(buffers); }; Encoding.prototype.decodeAddressIndexKey = function(buffer) { - var reader = new BufferReader(buffer); - reader.read(3); - var addressSize = reader.readUInt8(); - 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(); + var addressSize = buffer.readUInt8(3); + var address = buffer.slice(4, addressSize + 4).toString('utf8'); + var height = buffer.readUInt32BE(addressSize + 4); + var txid = buffer.slice(addressSize + 8, addressSize + 40).toString('hex'); + var index = buffer.readUInt32BE(addressSize + 40); + var input = buffer.readUInt8(addressSize + 44); + var timestamp = buffer.readDoubleBE(addressSize + 45); return { address: address, height: height, txid: txid, index: index, - input: input + input: input, + timestamp: timestamp }; }; -Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { +Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex, timestamp) { var prefix = new Buffer('01', 'hex'); var buffers = [this.servicePrefix, prefix]; @@ -78,13 +79,10 @@ Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { }; Encoding.prototype.decodeUtxoIndexKey = function(buffer) { - var reader = new BufferReader(buffer); - reader.read(3); - - var addressSize = reader.readUInt8(); - var address = reader.read(addressSize).toString('utf8'); - var txid = reader.read(32).toString('hex'); - var outputIndex = reader.readUInt32BE(4); + var addressSize = buffer.readUInt8(3); + var address = buffer.slice(4, addressSize + 4).toString('utf8'); + var txid = buffer.slice(addressSize + 4, addressSize + 36).toString('hex'); + var outputIndex = buffer.readUInt32BE(addressSize + 36); return { address: address, @@ -93,21 +91,25 @@ Encoding.prototype.decodeUtxoIndexKey = function(buffer) { }; }; -Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, scriptBuffer) { +Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, timestamp, scriptBuffer) { var heightBuffer = new Buffer(4); heightBuffer.writeUInt32BE(height); var satoshisBuffer = new Buffer(8); satoshisBuffer.writeDoubleBE(satoshis); - return Buffer.concat([heightBuffer, satoshisBuffer, scriptBuffer]); + var timestampBuffer = new Buffer(8); + timestampBuffer.writeDoubleBE(timestamp || 0) + return Buffer.concat([heightBuffer, satoshisBuffer, timestampBuffer, scriptBuffer]); }; Encoding.prototype.decodeUtxoIndexValue = function(buffer) { var height = buffer.readUInt32BE(); var satoshis = buffer.readDoubleBE(4); - var scriptBuffer = buffer.slice(12); + var timestamp = buffer.readDoubleBE(12); + var scriptBuffer = buffer.slice(20); return { height: height, satoshis: satoshis, + timestamp: timestamp, script: scriptBuffer }; }; diff --git a/lib/services/address/index.js b/lib/services/address/index.js index a13b0967..9ce7bbe2 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -5,7 +5,6 @@ var inherits = require('util').inherits; var async = require('async'); var index = require('../../'); var log = index.log; -var errors = index.errors; var bitcore = require('bitcore-lib'); var Unit = bitcore.Unit; var _ = bitcore.deps._; @@ -15,96 +14,212 @@ var Transform = require('stream').Transform; var AddressService = function(options) { BaseService.call(this, options); - this._txService = this.node.services.transaction; + this._db = this.node.services.db; + this._tx = this.node.services.transaction; this._network = this.node.getNetworkName(); + this._p2p = this.node.services.p2p; }; inherits(AddressService, BaseService); AddressService.dependencies = [ - 'bitcoind', + 'p2p', 'db', 'block', 'transaction' ]; // ---- public function prototypes -AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) { +AddressService.prototype.getAddressHistory = function(addresses, options, callback) { var self = this; - if(!Array.isArray(addresses)) { - addresses = [addresses]; - } + options = options || {}; + options.from = options.from || 0; + options.to = options.to || 0xffffffff; + options.queryMempool = _.isUndefined(options.queryMempool) ? true : false; - var utxos = []; + async.mapLimit(addresses, 4, function(address, next) { - async.eachSeries(addresses, function(address, next) { - self.getUtxosForAddress(address, queryMempool, function(err, unspents) { - if(err && err instanceof errors.NoOutputs) { - return next(); - } else if(err) { - return next(err); + self._getAddressHistory(address, options, next); + + }, function(err, res) { + + if(err) { + return callback(err); + } + + var results = { + totalItems: res.length, + from: options.from, + to: options.to, + items: res + }; + + callback(null, results); + + }); + +}; + +AddressService.prototype.getAddressSummary = function(address, options, callback) { + + var self = this; + + options = options || {}; + options.from = options.from || 0; + options.to = options.to || 0xffffffff; + options.queryMempool = _.isUndefined(options.queryMempool) ? true : false; + + 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: [] + }; + + // txid criteria + var start = self._encoding.encodeAddressIndexKey(address, options.from); + var end = self._encoding.encodeAddressIndexKey(address, options.to); + + var criteria = { + gte: start, + lt: end + }; + + // txid stream + var txidStream = self._db.createKeyStream(criteria); + + txidStream.on('close', function() { + txidStream.unpipe(); + }); + + // 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); + }); + + // 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(err); + txStream.emit('error', err); + return callback(); } - utxos = utxos.concat(unspents); - next(); + if (!tx) { + log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.'); + return callback(); + } + + var confirmations = self._p2p.getBestHeight() - key.height; + result.transactions.push(tx.txid()); + result.txApperances++; + + 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++; + } + + } else { + + 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(); + }); - }, function(err) { - callback(err, utxos); - }); -}; -AddressService.prototype.getUtxosForAddress = function(address, queryMempool, callback) { + }; - var self = this; - - var stream = self.db.createReadStream({ - gte: self._encoding.encodeUtxoIndexKey(address), - lt: self._encoding.encodeUtxoIndexKey(utils.getTerminalKey(new Buffer(address))) + txStream.on('error', function(err) { + log.error(err); + txStream.unpipe(); }); - var utxos = []; - stream.on('data', function(data) { - var key = self._encoding.decodeUtxoIndexKey(data.key); - var value = self._encoding.decodeUtxoIndexValue(data.value); - utxos.push({ - address: key.address, - txid: key.txid, - outputIndex: key.outputIndex, - satoshis: value.satoshis, - height: value.height, - script: value.script - }); - }); - - stream.on('end', function() { - return callback(null, utxos); - }); - stream.on('error', function(err) { - if(err) { - return callback(err); - } - }); -}; - -AddressService.prototype.start = function(callback) { - - var self = this; - self._setListeners(); - - this.db = this.node.services.db; - this.db.getPrefix(this.name, function(err, prefix) { - if(err) { - return callback(err); - } - self.prefix = prefix; - self._encoding = new Encoding(self.prefix); + txStream._flush = function(callback) { + txStream.emit('end'); callback(); - }); + }; }; -AddressService.prototype.stop = function(callback) { - setImmediate(callback); +AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { + + var self = this; + + options = options || {}; + options.from = options.from || 0; + options.to = options.to || 0xffffffff; + options.queryMempool = _.isUndefined(options.queryMempool) ? true : false; + + var results = []; + + var start = self._encoding.encodeUtxoIndexKey(address); + var criteria = { + gte: start, + lt: utils.getTerminalKey(start) + }; + + var utxoStream = self._db.createReadStream(criteria); + + var streamErr; + utxoStream.on('end', function() { + if (streamErr) { + return callback(streamErr); + } + callback(null, results); + }); + + utxoStream.on('error', function(err) { + streamErr = err; + }); + + utxoStream.on('data', function(data) { + var key = self._encoding.decodeUtxoIndexKey(data.key); + var value = self._encoding.decodeUtxoIndexValue(data.value); + results.push({ + address: address, + txid: key.txid, + vout: key.outputIndex, + ts: value.timestamp, + scriptPubKey: value.script.toString('hex'), + amount: Unit.fromSatoshis(value.satoshis).toBTC(), + confirmations: self._p2p.getBestHeight() - value.height, + satoshis: value.satoshis, + confirmationsFromCache: true + }); + }); + }; AddressService.prototype.getAPIMethods = function() { @@ -116,46 +231,45 @@ AddressService.prototype.getAPIMethods = function() { ]; }; -AddressService.prototype.getAddressHistory = function(addresses, options, callback) { +AddressService.prototype.start = function(callback) { + + var self = this; + + this._db.getPrefix(this.name, function(err, prefix) { + if(err) { + return callback(err); + } + self._encoding = new Encoding(prefix); + self._startSubscriptions(); + callback(); + }); +}; + +AddressService.prototype.stop = function(callback) { + setImmediate(callback); +}; + + +// ---- start private function prototypes +AddressService.prototype._getAddressHistory = function(address, options, callback) { + var self = this; options = options || {}; var from = options.from || 0; var to = options.to || 0xffffffff; - 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 - }; - - callback(null, results); - - }); - -}; - -AddressService.prototype._getAddressHistory = function(address, options, callback) { - - var self = this; + if (_.isUndefined(options.queryMempool)) { + options.queryMempool = true; + } var results = []; - var start = self._encoding.encodeAddressIndexKey(address); + var start = self._encoding.encodeAddressIndexKey(address, options.start); + var end = self._encoding.encodeAddressIndexKey(address, options.end); var criteria = { gte: start, - lte: utils.getTerminalKey(start) + lte: end }; // txid stream @@ -181,9 +295,9 @@ AddressService.prototype._getAddressHistory = function(address, options, callbac txStream._transform = function(chunk, enc, callback) { - var key = self._encoding.decodeWalletTransactionKey(chunk); + var key = self._encoding.decodeAddressIndexKey(chunk); - self._tx.getDetailedTransaction(key.txid, options, function(err, tx) { + self._tx.getTransaction(key.txid, options, function(err, tx) { if(err) { log.error(err); @@ -216,177 +330,6 @@ AddressService.prototype._getAddressHistory = function(address, options, callbac }; -AddressService.prototype.getAddressSummary = function(address, options, callback) { - - var self = this; - - if (_.isUndefined(options.queryMempool)) { - options.queryMempool = true; - } - - 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: [] - }; - - // txid criteria - 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 }); - 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); - }); - - // 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(); - }); - - txStream._flush = function(callback) { - txStream.emit('end'); - callback(); - }; -}; - -AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { - - var self = this; - if (_.isUndefined(options.queryMempool)) { - options.queryMempool = true; - } - - var results = []; - - var start = self._encoding.encodeUtxoIndexKey(address); - var criteria = { - gte: start, - lt: utils.getTerminalKey(start) - }; - - var utxoStream = self._db.createReadStream(criteria); - - var streamErr; - utxoStream.on('end', function() { - if (streamErr) { - return callback(streamErr); - } - callback(null, results); - }); - - 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() { - - var self = this; - - self.on('reorg', self._handleReorg.bind(self)); - -}; - AddressService.prototype._startSubscriptions = function() { if (this._subscribed) { @@ -408,12 +351,12 @@ AddressService.prototype._startSubscriptions = function() { AddressService.prototype._onReorg = function(oldBlockList, newBlockList, commonAncestor) { // if the common ancestor block height is greater than our own, then nothing to do for the reorg - if (this._tip.height <= commonAncestor.header.height) { + if (this._tip.height <= commonAncestor.height) { return; } // set the tip to the common ancestor in case something goes wrong with the reorg - var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.header.height }, this.name); + var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.height }, this.name); var removalOps = [{ type: 'put', diff --git a/test/services/address/encoding.unit.js b/test/services/address/encoding.unit.js index 5556dabe..6dfa6b4f 100644 --- a/test/services/address/encoding.unit.js +++ b/test/services/address/encoding.unit.js @@ -14,6 +14,9 @@ describe('Address service encoding', function() { var addressSizeBuf = new Buffer(1); var prefix0 = new Buffer('00', 'hex'); var prefix1 = new Buffer('01', 'hex'); + var ts = Math.floor(new Date('2017-02-28').getTime() / 1000); + var tsBuf = new Buffer(8); + tsBuf.writeDoubleBE(ts); addressSizeBuf.writeUInt8(address.length); var addressIndexKeyBuf = Buffer.concat([ servicePrefix, @@ -23,7 +26,8 @@ describe('Address service encoding', function() { new Buffer('00000001', 'hex'), new Buffer(txid, 'hex'), new Buffer('00000000', 'hex'), - new Buffer('00', 'hex') + new Buffer('00', 'hex'), + tsBuf ]); var outputIndex = 5; var utxoKeyBuf = Buffer.concat([ @@ -38,10 +42,10 @@ describe('Address service encoding', function() { var sats = tx.outputs[0].satoshis; var satsBuf = new Buffer(8); satsBuf.writeDoubleBE(sats); - var utxoValueBuf = Buffer.concat([new Buffer('00000001', 'hex'), satsBuf, tx.outputs[0]._scriptBuffer]); + var utxoValueBuf = Buffer.concat([new Buffer('00000001', 'hex'), satsBuf, tsBuf, tx.outputs[0]._scriptBuffer]); it('should encode address key' , function() { - encoding.encodeAddressIndexKey(address, height, txid).should.deep.equal(addressIndexKeyBuf); + encoding.encodeAddressIndexKey(address, height, txid, 0, 0, ts).should.deep.equal(addressIndexKeyBuf); }); it('should decode address key', function() { @@ -65,6 +69,7 @@ describe('Address service encoding', function() { encoding.encodeUtxoIndexValue( height, tx.outputs[0].satoshis, + ts, tx.outputs[0]._scriptBuffer).should.deep.equal(utxoValueBuf); }); @@ -73,6 +78,7 @@ describe('Address service encoding', function() { utxoValue.height.should.equal(height); utxoValue.satoshis.should.equal(sats); utxoValue.script.should.deep.equal(tx.outputs[0]._scriptBuffer); + utxoValue.timestamp.should.equal(ts); }); }); diff --git a/test/services/address/index.unit.js b/test/services/address/index.unit.js index af42d344..e247654c 100644 --- a/test/services/address/index.unit.js +++ b/test/services/address/index.unit.js @@ -1,48 +1,222 @@ 'use strict'; -var should = require('chai').should(); -var bitcore = require('bitcore-lib'); -var Script = bitcore.Script; -var PrivateKey = bitcore.PrivateKey; - - +var sinon = require('sinon'); var AddressService = require('../../../lib/services/address'); +var Tx = require('bcoin').tx; +var expect = require('chai').expect; +var Encoding = require('../../../lib/services/address/encoding'); +var Readable = require('stream').Readable; +var EventEmitter = require('events').EventEmitter; var utils = require('../../../lib/utils'); describe('Address Service', function() { - var address; - var sig = new Buffer('3045022100e8b654c91770402bf35d207406c7d4967605f99478954c8030cf7060160b5c730220296690debdd354d5fa17a61379cfdce9fdea136a4b234664e41c1c7cd840098901', 'hex'); + var tx = Tx.fromRaw( '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000', 'hex'); + var addressService; + var sandbox; - var pks = [ new PrivateKey(), new PrivateKey() ]; - - var pubKeys = [ pks[0].publicKey, pks[1].publicKey ]; - - var scripts = { - p2pkhIn: Script.buildPublicKeyHashIn(pubKeys[0], sig), - p2pkhOut: Script.buildPublicKeyHashOut(pubKeys[0]), - p2shIn: Script.buildP2SHMultisigIn(pubKeys, 2, [sig, sig]), - p2shOut: Script.buildScriptHashOut(Script.fromAddress(pks[0].toAddress())), - p2pkIn: Script.buildPublicKeyIn(sig), - p2pkOut: Script.buildPublicKeyOut(pubKeys[0]), - p2bmsIn: Script.buildMultisigIn(pubKeys, 2, [sig, sig]), - p2bmsOut: Script.buildMultisigOut(pubKeys, 2) - }; - - - before(function(done) { - address = new AddressService({ node: { name: 'address' } }); - done(); + beforeEach(function() { + sandbox = sinon.sandbox.create(); + addressService = new AddressService({ + node: { + getNetworkName: function() { return 'regtest'; }, + services: [] + } + }); + addressService._encoding = new Encoding(new Buffer('0000', 'hex')); }); - it('should get an address from a script buffer', function() { - var start = process.hrtime(); - for(var key in scripts) { - var ret = address.getAddressString({ script: scripts[key] }); - console.log(ret); - }; + afterEach(function() { + sandbox.restore(); + }); - console.log(utils.diffTime(start)); + describe('#start', function() { + it('should get prefix for database', function(done) { + var startSubs = sandbox.stub(addressService, '_startSubscriptions'); + var getPrefix = sandbox.stub().callsArgWith(1, null, new Buffer('ffee', 'hex')); + addressService._db = { getPrefix: getPrefix }; + addressService.start(function() { + expect(startSubs.calledOnce).to.be.true; + done(); + }); + }); + + }); + + describe('#stop', function() { + it('should stop the service', function(done) { + addressService.stop(function() { + done(); + }); + }); + }); + + + describe('#getAddressHistory', function() { + it('should get the address history', function(done) { + + sandbox.stub(addressService, '_getAddressHistory').callsArgWith(2, null, {}); + addressService.getAddressHistory(['a', 'b', 'c'], { from: 12, to: 14 }, function(err, res) { + + if (err) { + return done(err); + } + + expect(res).to.be.deep.equal({ + totalItems: 3, + from: 12, + to: 14, + items: [ {}, {}, {} ] + }); + + done(); + }); + }); + + }); + + describe('#_getAddresHistory', function() { + it('should get the address history', function(done) { + var encoding = new Encoding(new Buffer('0001', 'hex')); + addressService._encoding = encoding; + var address = 'a'; + var opts = { from: 12, to: 14 }; + var txid = '1c6ea4a55a3edaac0a05e93b52908f607376a8fdc5387c492042f8baa6c05085'; + var data = [ null, encoding.encodeAddressIndexKey(address, 123, txid, 1, 1) ]; + var getTransaction = sandbox.stub().callsArgWith(2, null, {}); + addressService._tx = { getTransaction: getTransaction }; + + var txidStream = new Readable(); + + txidStream._read = function() { + txidStream.push(data.pop()); + } + + var createReadStream = sandbox.stub().returns(txidStream); + addressService._db = { createKeyStream: createReadStream }; + + addressService._getAddressHistory(address, opts, function(err, res) { + if (err) { + return done(err); + } + expect(getTransaction.calledOnce).to.be.true; + expect(res).to.deep.equal([{}]); + done(); + }); + }); + }); + + 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._p2p = { getBestHeight: function() { return 150; } }; + + var txidStream = new Readable(); + + txidStream._read = function() { + txidStream.push(data.pop()); + } + + var createReadStream = sandbox.stub().returns(txidStream); + addressService._db = { createKeyStream: createReadStream }; + + addressService.getAddressSummary(address, {}, 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, + totalSent: 0, + totalSentSat: 0, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 1, + transactions: [ '25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb' ] + }); + done(); + }); + + }); + + }); + describe('#getAddressUnspentOutputs', function() { + it('should get address utxos', function(done) { + + var encoding = new Encoding(new Buffer('0001', 'hex')); + addressService._encoding = encoding; + + var address = 'a'; + var txid = tx.txid(); + var ts = Math.floor(new Date('2019-01-01').getTime() / 1000); + + var data = { + key: encoding.encodeUtxoIndexKey(address, txid, 1), + value: encoding.encodeUtxoIndexValue(123, 120000, ts, tx.outputs[1].script.raw) + }; + + addressService._p2p = { getBestHeight: function() { return 150; } }; + + var txidStream = new EventEmitter(); + + var createReadStream = sandbox.stub().returns(txidStream); + addressService._db = { createReadStream: createReadStream }; + + addressService.getAddressUnspentOutputs(address, {}, function(err, res) { + if (err) { + return done(err); + } + expect(res[0]).to.deep.equal({ + address: "a", + amount: 0.0012, + confirmations: 27, + confirmationsFromCache: true, + satoshis: 120000, + scriptPubKey: "76a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac", + ts: 1546300800, + txid: "25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb", + vout: 1 + }); + done(); + }); + + txidStream.emit('data', data); + txidStream.emit('end'); + + }); + + }); + + describe('#_Rrorg', function() { + it('should reorg', function() { + var getAddressString = sandbox.stub().returns('a'); + var encodeTip = sandbox.stub().returns({ key: 'key', value: 'value' }); + var batch = sandbox.stub(); + addressService._tip = { height: 100 }; + addressService._db = { batch: batch }; + utils.getAddressString = getAddressString; + utils.encodeTip = encodeTip; + var common = { height: 90 }; + var oldList = []; + var newList = []; + addressService._onReorg(oldList, newList, common); + expect(batch.calledOnce).to.be.true; + expect(getAddressString.calledOnce).to.be.true; + expect(encodeTip.calledOnce).to.be.true; + }); }); });