diff --git a/lib/services/block/index.js b/lib/services/block/index.js index 3d4a1837..f0a6e00a 100644 --- a/lib/services/block/index.js +++ b/lib/services/block/index.js @@ -570,14 +570,14 @@ BlockService.prototype._onBlock = function(block) { // 3. store the block for safe keeping this._cacheBlock(block); - // don't process any more blocks if we are currently in a reorg + // 4. don't process any more blocks if we are currently in a reorg if (this._reorging) { return; } - // 4. determine block state, reorg, outoforder, normal + // 5. determine block state, reorg, outoforder, normal var blockState = this._determineBlockState(block); - // 5. update internal data structures depending on blockstate + // 6. update internal data structures depending on blockstate this._updateChainInfo(block, blockState); // 7. react to state of block diff --git a/lib/services/mempool/encoding.js b/lib/services/mempool/encoding.js index b793497c..bd35ef5d 100644 --- a/lib/services/mempool/encoding.js +++ b/lib/services/mempool/encoding.js @@ -1,74 +1,28 @@ 'use strict'; -var bitcore = require('bitcore-lib'); -var BufferReader = bitcore.encoding.BufferReader; +var tx = require('bcoin').tx; function Encoding(servicePrefix) { this.servicePrefix = servicePrefix; } -Encoding.prototype.encodeMempoolAddressIndexKey = function(address, txid) { - var prefix = new Buffer('00', 'hex'); - var buffers = [this.servicePrefix, prefix]; - - var addressSizeBuffer = new Buffer(1); - addressSizeBuffer.writeUInt8(address.length); - buffers.push(addressSizeBuffer); - var addressBuffer = new Buffer(address, 'utf8'); - buffers.push(addressBuffer); - - var txidBuffer = new Buffer(txid, 'hex'); - buffers.push(txidBuffer); - - return Buffer.concat(buffers); -}; - -Encoding.prototype.decodeMempoolAddressIndexKey = 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'); - - return { - address: address, - txid: txid - }; -}; - Encoding.prototype.encodeMempoolTransactionKey = function(txid) { - var prefix = new Buffer('01', 'hex'); - var buffers = [this.servicePrefix, prefix]; + var buffers = [this.servicePrefix]; var txidBuffer = new Buffer(txid, 'hex'); buffers.push(txidBuffer); return Buffer.concat(buffers); }; Encoding.prototype.decodeMempoolTransactionKey = function(buffer) { - return buffer.slice(4).toString('hex'); + return buffer.slice(2).toString('hex'); }; Encoding.prototype.encodeMempoolTransactionValue = function(transaction) { - var inputValues = transaction.__inputValues || []; - var inputValuesBuffer = new Buffer(8 * inputValues.length); - for(var i = 0; i < inputValues.length; i++) { - inputValuesBuffer.writeDoubleBE(inputValues[i], i * 8); - } - var inputValuesLengthBuffer = new Buffer(2); - inputValuesLengthBuffer.writeUInt16BE(inputValues.length * 8); - return new Buffer.concat([inputValuesLengthBuffer, inputValuesBuffer, transaction.toBuffer()]); + return transaction.toRaw(); }; Encoding.prototype.decodeMempoolTransactionValue = function(buffer) { - var inputValues = []; - var inputValuesLength = buffer.readUInt16BE(); - for(var i = 0; i < inputValuesLength / 8; i++) { - inputValues.push(buffer.readDoubleBE(i * 8 + 2)); - } - var transaction = new bitcore.Transaction(buffer.slice(inputValues.length * 8 + 2)); - transaction.__inputValues = inputValues; - return transaction; + return tx.fromRaw(buffer); }; module.exports = Encoding; diff --git a/lib/services/mempool/index.js b/lib/services/mempool/index.js index f6afec4c..9d46e803 100644 --- a/lib/services/mempool/index.js +++ b/lib/services/mempool/index.js @@ -4,279 +4,89 @@ var util = require('util'); var Encoding = require('./encoding'); var index = require('../../index'); var log = index.log; -var async = require('async'); var MempoolService = function(options) { BaseService.call(this, options); - this.node = options.node; - this.name = options.name; - this._txIndex = {}; - this._addressIndex = {}; - this.db = this.node.services.db; - this._handleBlocks = false; + this._db = this.node.services.db; + this._tx = this.node.services.transaction; }; util.inherits(MempoolService, BaseService); MempoolService.dependencies = ['db']; - -MempoolService.prototype.getPublishEvents = function() { - return []; -}; - MempoolService.prototype.getAPIMethods = function() { - return []; + var methods = [ + ['getTransaction', this, this.getTransaction, 1] + ]; + return methods; }; MempoolService.prototype.start = function(callback) { var self = this; - self.node.services.db.getPrefix(self.name, function(err, servicePrefix) { + self._db.getPrefix(self.name, function(err, prefix) { if(err) { return callback(err); } - self.servicePrefix = servicePrefix; - self._encoding = new Encoding(self.servicePrefix); - self._setListeners(); + self._encoding = new Encoding(prefix); + self._startSubscriptions(); callback(); }); }; -MempoolService.prototype._setListeners = function() { - this._startSubscriptions(); +MempoolService.prototype._startSubscriptions = function() { + + if (this._subscribed) { + return; + } + + this._subscribed = true; + if (!this._bus) { + this._bus = this.node.openBus({remoteAddress: 'localhost'}); + } + + this._bus.on('p2p/block', this._onBlock.bind(this)); + this._bus.on('p2p/transaction', this._onTransaction.bind(this)); + + this._bus.subscribe('p2p/block'); + this._bus.subscribe('p2p/transaction'); }; -MempoolService.prototype._startSubscriptions = function() { - var bus = this.node.openBus({ remoteAddress: 'localhost' }); - bus.on('p2p/transaction', this._onTransaction.bind(this)); - bus.subscribe('p2p/transaction'); +MempoolService.prototype._onBlock = function(block) { + // remove this block's txs from mempool + var ops = block.transactions.map(function(tx) { + return { + type: 'del', + key: tx.id + }; + }); + this._db.batch(ops); }; MempoolService.prototype._onTransaction = function(tx) { + var txOps = this._getTxOperation(tx); + this._db.batch(txOps); +}; +MempoolService.prototype._getTxOperations = function(tx) { + var inputValues = this._tx.getInputValues(tx); + tx.__inputValues = inputValues; + return { + type: 'put', + key: this._encoding.encodeMempoolTransactionKey(tx.id), + value: this._encoding.encodeMempoolTransactionValue(tx) + }; +}; + +MempoolService.prototype.getTransaction = function(txid, callback) { + this._db.get(this._encoding.encodeMempoolTransactionKey(txid), callback); }; MempoolService.prototype.stop = function(callback) { callback(); }; -MempoolService.prototype.setupRoutes = function() { -}; - -MempoolService.prototype.getRoutePrefix = function() { - return this.name; -}; - -MempoolService.prototype._getTransactionAddressDetailOperations = function(tx, action) { - var self = this; - - if(tx.isCoinbase()) { - return []; - } - var operations = []; - - var txid = tx.id; - var inputs = tx.inputs; - var outputs = tx.outputs; - - var outputLength = outputs.length; - for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) { - var output = outputs[outputIndex]; - - var script = output.script; - - if(!script) { - log.debug('Invalid script'); - continue; - } - - var address = self.getAddressString(script); - - if(!address) { - continue; - } - - var key = self.encoding.encodeMempoolAddressIndexKey(address, txid); - operations.push({ - type: action, - key: key - }); - - } - - //TODO deal with P2PK - for(var i = 0; i < inputs.length; i++) { - var input = inputs[i]; - if(!input.script) { - log.debug('Invalid script'); - continue; - } - - var inputAddress = self.getAddressString(input.script); - - if(!inputAddress) { - continue; - } - - var inputKey = self.encodeMempoolAddressIndexKey(inputAddress, txid); - - operations.push({ - type: action, - key: inputKey - }); - } - return operations; -}; - -MempoolService.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(output && output.script && output.script.isPublicKeyOut()) { - return output.script.getPublicKey().toString('hex'); - } - - return null; -}; - -MempoolService.prototype._getInputValues = function(tx, callback) { - var self = this; - - if (tx.isCoinbase()) { - return callback(null, []); - } - - async.mapLimit(tx.inputs, this.concurrency, function(input, next) { - self.getTransaction(input.prevTxId.toString('hex'), {}, function(err, prevTx) { - if(err) { - return next(err); - } - if (!prevTx) { - return next(null, 0); - } - if (!prevTx.outputs[input.outputIndex]) { - return next(new Error('Input did not have utxo.')); - } - var satoshis = prevTx.outputs[input.outputIndex].satoshis; - next(null, satoshis); - }); - }, callback); -}; - - -MempoolService.prototype._updateCache = function(operation) { - if (operation.type === 'del') { - return this._cache.del(operation.key); - } - this._cache.set(operation.key, operation.value); -}; - -MempoolService.prototype.getTransaction = function(txid, callback) { - var self = this; - var key = self._encoding.encodeMempoolTransactionKey(txid); - var txBuffer = self._cache.get(key); - if (txBuffer) { - return setImmediate(function() { - callback(null, self._encoding.decodeMempoolTransactionValue(txBuffer)); - }); - } - self.db.get(key, function(err, value) { - if(err) { - return callback(err); - } - callback(null, self._encoding.decodeMempoolTransactionValue(value)); - }); -}; - -MempoolService.prototype._getTransactionOperation = function(tx, action, callback) { - var self = this; - self._getInputValues(tx, function(err, inputValues) { - if(err) { - return callback(err); - } - tx.__inputValues = inputValues; - var operation = { - type: action, - key: self._encoding.encodeMempoolTransactionKey(tx.id), - value: self._encoding.encodeMempoolTransactionValue(tx) - }; - callback(null, operation); - }); -}; - -MempoolService.prototype._updateMempool = function(tx, action, callback) { - var self = this; - self._getTransactionOperation(tx, action, function(err, operation) { - if(err) { - return callback(err); - } - var operations = [operation].concat(self._getTransactionAddressDetailOperations(tx, action)); - self.db.batch(operations, function(err) { - if(err) { - log.error('batch operation for updating Mempool failed.'); - return; - } - operations.forEach(self._updateCache); - }); - }); -}; - -MempoolService.prototype.getTransactionsByAddress = function(address, callback) { - var self = this; - var txids = []; - var maxTxid = new Buffer(new Array(65).join('f'), 'hex'); - var start = self._encoding.encodeMempoolAddressKey(address); - var end = Buffer.concat([self._encoding.encodeMempoolAddressKey(address), maxTxid]); - var stream = self.db.createKeyStream({ - gte: start, - lte: end - }); - var streamErr; - stream.on('error', function(err) { - streamErr = err; - }); - stream.on('data', function(data) { - var key = self._encoding.decodeMempoolAddressKey(data); - txids.push(key.txid); - }); - stream.on('end', function() { - async.mapLimit(txids, 10, function(txid, next) { - self.getTransaction(txid, next); - }, callback); - }); -}; - -MempoolService.prototype.getTransactionsByAddresses = function(addresses, callback) { - var self = this; - var transactions = {}; - async.eachLimit(addresses, 10, function(address, next) { - self.getTransactionsByAddress(address, function(err, txs) { - if(err) { - return next(err); - } - txs.forEach(function(tx) { - transactions[tx.id] = tx; - }); - next(); - }); - }, function(err) { - if(err) { - return callback(err); - } - callback(null, Object.values(transactions)); - }); -}; module.exports = MempoolService; diff --git a/lib/services/p2p/index.js b/lib/services/p2p/index.js index 8e35c484..2c57becf 100644 --- a/lib/services/p2p/index.js +++ b/lib/services/p2p/index.js @@ -9,28 +9,6 @@ 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)) { @@ -85,14 +63,8 @@ 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 + connections: this._pool.numberConnected }); }; @@ -245,7 +217,8 @@ P2P.prototype._initPool = function() { if (this._configPeers) { opts.addrs = this._configPeers; } - opts.dnsSeed = false; + // TODO: bcoin stuff goes here + opts.dnsSeed = false; // do not set to true unless you want to be pwned opts.maxPeers = this._maxPeers; opts.network = this.node.getNetworkName(); this._pool = new p2p.Pool(opts); @@ -258,7 +231,6 @@ P2P.prototype._initPubSub = function() { this.subscriptions.transaction = []; }; - P2P.prototype._onPeerBlock = function(peer, message) { this._broadcast(this.subscriptions.block, 'p2p/block', message.block); }; diff --git a/lib/services/timestamp/index.js b/lib/services/timestamp/index.js index d6430f84..49517f5f 100644 --- a/lib/services/timestamp/index.js +++ b/lib/services/timestamp/index.js @@ -206,7 +206,7 @@ TimestampService.prototype._onReorg = function(oldBlockList, newBlockList, commo } // set the tip to the common ancestor in case something goes wrong with the reorg - var tipOps = utils.encodeTip({ height: commonAncestor.header.height }); + var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.header.height }, this.name); var removalOps = [{ type: 'put', diff --git a/lib/services/transaction/index.js b/lib/services/transaction/index.js index 5c0c000a..37fd44aa 100644 --- a/lib/services/transaction/index.js +++ b/lib/services/transaction/index.js @@ -35,7 +35,8 @@ TransactionService.prototype.getAPIMethods = function() { ['getTransaction', this, this.getTransaction, 1], ['getDetailedTransaction', this, this.getDetailedTransaction, 1], ['sendTransaction', this, this.sendTransaction, 1], - ['syncPercentage', this, this.syncPercentage, 0] + ['syncPercentage', this, this.syncPercentage, 0], + ['getInputValues', this, this._getInputValues, 1] ]; }; @@ -140,9 +141,9 @@ TransactionService.prototype._getBlockTimestamp = function(hash) { return this._timestamp.getTimestamp(hash); }; -TransactionService.prototype._getInputValues = function(tx, opts) { +TransactionService.prototype._getInputValues = function(tx) { - return tx.inputs.forEach(function(input) { + return tx.inputs.map(function(input) { var value = this._inputValuesCache.get(input.prevTxId); if (value) { return value[input.outputIndex]; diff --git a/test/services/mempool/encoding.unit.js b/test/services/mempool/encoding.unit.js new file mode 100644 index 00000000..3e1e0a45 --- /dev/null +++ b/test/services/mempool/encoding.unit.js @@ -0,0 +1,41 @@ +'use strict'; + +var should = require('chai').should(); +var tx = require('bcoin').tx; + +var Encoding = require('../../../lib/services/mempool/encoding'); + +describe('Block service encoding', function() { + + var servicePrefix = new Buffer('0000', 'hex'); + + var encoding = new Encoding(servicePrefix); + var hash = '25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb'; + var txString = '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000' + + describe('Mempool', function() { + + it('should encode mempool transaction key', function() { + encoding.encodeMempoolTransactionKey(hash).should.deep.equal(Buffer.concat([ servicePrefix, new Buffer(hash, 'hex') ])); + }); + + it('should decode mempool transaction key', function() { + encoding.decodeMempoolTransactionKey(Buffer.concat([ servicePrefix, new Buffer(hash, 'hex') ])).should.deep.equal(hash); + }); + + it('should encode mempool transaction value', function() { + var mytx = tx.fromRaw(txString, 'hex'); + mytx.__inputValues = [1012955, 447698, 446664, 391348]; + encoding.encodeMempoolTransactionValue(mytx).should.deep.equal(new Buffer(txString, 'hex')); + }); + + it('should decode mempool transaction value', function() { + var mytx = encoding.decodeMempoolTransactionValue(new Buffer(txString, 'hex')); + mytx.should.deep.equal(tx.fromRaw(txString, 'hex')); + }); + + }); + +}); + + diff --git a/test/services/mempool/index.unit.js b/test/services/mempool/index.unit.js new file mode 100644 index 00000000..c9108062 --- /dev/null +++ b/test/services/mempool/index.unit.js @@ -0,0 +1,50 @@ +'use strict'; + +var expect = require('chai').expect; +var MempoolService = require('../../../lib/services/mempool'); +var sinon = require('sinon'); +var Encoding = require('../../../lib/services/block/encoding'); +var Tx = require('bcoin').tx; + +describe('Mempool Service', function() { + + var mempoolService; + var sandbox; + var tx = Tx.fromRaw( '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000', 'hex'); + beforeEach(function() { + sandbox = sinon.sandbox.create(); + mempoolService = new MempoolService({ + node: { + getNetworkName: function() { return 'regtest'; }, + services: [] + } + }); + mempoolService._encoding = new Encoding(new Buffer('0000', 'hex')); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('#start', function() { + + it('should get the db prefix', function(done) { + var getPrefix = sandbox.stub().callsArgWith(1, null, new Buffer('0001', 'hex')); + var startSubs = sandbox.stub(mempoolService, '_startSubscriptions'); + mempoolService._db = { getPrefix: getPrefix }; + + mempoolService.start(function() { + expect(getPrefix.calledOnce).to.be.true; + expect(startSubs.calledOnce).to.be.true; + done(); + }); + }); + + describe('#getTransaction', function(done) { + var get = sandbox.stub().callsArgWith(1, null, tx.toJSON()); + }); + }); + +}); + +