diff --git a/bin/node b/bin/node index e6c4b898..2200d37c 100755 --- a/bin/node +++ b/bin/node @@ -9,6 +9,8 @@ var node = bcoin.fullnode({ passphrase: 'node', prune: process.argv.indexOf('--prune') !== -1, useCheckpoints: process.argv.indexOf('--checkpoints') !== -1, + listen: process.argv.indexOf('--listen') !== -1, + selfish: process.argv.indexOf('--selfish') !== -1, mine: process.argv.indexOf('--mine') !== -1 }); @@ -20,7 +22,6 @@ node.open(function(err) { if (err) throw err; - if (node.options.mine) node.miner.start(); else diff --git a/lib/bcoin/abstractblock.js b/lib/bcoin/abstractblock.js index bb76818a..fd13e690 100644 --- a/lib/bcoin/abstractblock.js +++ b/lib/bcoin/abstractblock.js @@ -7,7 +7,6 @@ var bcoin = require('../bcoin'); var utils = require('./utils'); var network = bcoin.protocol.network; -var BufferWriter = require('./writer'); /** * AbstractBlock @@ -24,7 +23,7 @@ function AbstractBlock(data) { this.ts = data.ts; this.bits = data.bits; this.nonce = data.nonce; - this.totalTX = data.totalTX; + this.totalTX = data.totalTX || 0; this.height = data.height != null ? data.height : -1; this._raw = data._raw || null; @@ -42,21 +41,10 @@ AbstractBlock.prototype.hash = function hash(enc) { }; AbstractBlock.prototype.abbr = function abbr() { - var p; - if (this._raw) return this._raw.slice(0, 80); - p = new BufferWriter(); - - p.write32(this.version); - p.writeHash(this.prevBlock); - p.writeHash(this.merkleRoot); - p.writeU32(this.ts); - p.writeU32(this.bits); - p.writeU32(this.nonce); - - return p.render(); + return bcoin.protocol.framer.blockHeaders(this); }; AbstractBlock.prototype.getSize = function getSize() { diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 1eb42fc3..ca23aea2 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -99,6 +99,47 @@ Block.prototype.hasWitness = function hasWitness() { return false; }; +Block.prototype.addTX = function addTX(tx) { + var hash = tx.hash('hex'); + var index; + + if (this.indexOf(hash) !== -1) + return; + + index = this.txs.push(tx) - 1; + + tx.setBlock(this, index); +}; + +Block.prototype.removeTX = function removeTX(hash) { + var index = this.indexOf(hash); + var tx; + + if (index === -1) + return; + + tx = this.txs.splice(index, 1)[0]; + tx.unsetBlock(); +}; + +Block.prototype.hasTX = function hasTX(hash) { + return this.indexOf(hash) !== -1; +}; + +Block.prototype.indexOf = function indexOf(hash) { + var i; + + if (hash instanceof bcoin.tx) + hash = hash.hash('hex'); + + for (i = 0; i < this.txs.length; i++) { + if (this.txs[i].hash('hex') === hash) + return i; + } + + return -1; +}; + Block.prototype.getSigops = function getSigops(scriptHash, accurate) { var total = 0; var i; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 8ae0c6fe..c3f22fe7 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -12,7 +12,6 @@ var constants = bcoin.protocol.constants; var network = bcoin.protocol.network; var utils = require('./utils'); var assert = utils.assert; -var BufferReader = require('./reader'); var VerifyError = utils.VerifyError; /** @@ -251,18 +250,9 @@ Chain.prototype._preload = function _preload(callback) { utils.debug('Loading %s', url); function parseHeader(buf) { - var p = new BufferReader(buf); - var hash = utils.dsha256(buf.slice(0, 80)); - - return { - hash: utils.toHex(hash), - version: p.readU32(), // Technically signed - prevBlock: p.readHash('hex'), - merkleRoot: p.readHash('hex'), - ts: p.readU32(), - bits: p.readU32(), - nonce: p.readU32() - }; + var headers = bcoin.protocol.parser.parseBlockHeaders(buf); + headers.hash = utils.toHex(utils.dsha256(buf.slice(0, 80))); + return headers; } function save(entry) { @@ -749,7 +739,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac }); }; -Chain.prototype.getHeight = function getHeight(hash) { +Chain.prototype._getCachedHeight = function _getCachedHeight(hash) { if (Buffer.isBuffer(hash)) hash = utils.toHex(hash); else if (hash.hash) @@ -1654,61 +1644,23 @@ Chain.prototype.getCurrentTarget = function getCurrentTarget(callback) { return this.getTargetAsync(this.tip, null, callback); }; -Chain.prototype.getTargetAsync = function getTarget(last, block, callback) { +Chain.prototype.getTargetAsync = function getTargetAsync(last, block, callback) { var self = this; - var powLimit = utils.toCompact(network.powLimit); - var i = 0; - var ts; - callback = utils.asyncify(callback); - - // Genesis - if (!last) - return callback(null, powLimit); - - // Do not retarget if ((last.height + 1) % network.powDiffInterval !== 0) { - if (network.powAllowMinDifficultyBlocks) { - // Special behavior for testnet: - ts = block ? (block.ts || block) : utils.now(); - if (ts > last.ts + network.powTargetSpacing * 2) - return callback(null, powLimit); - - (function next(err, last) { - if (err) - return callback(err); - - assert(last); - - if (last.height > 0 - && last.height % network.powDiffInterval !== 0 - && last.bits === powLimit) { - return last.getPrevious(next); - } - - return callback(null, last.bits); - })(null, last); - - return; - } - return callback(null, last.bits); + if (!network.powAllowMinDifficultyBlocks) + return utils.asyncify(callback)(null, this.getTarget(last, block)); } - (function next(err, first) { + return last.getAncestors(network.powDiffInterval, function(err, ancestors) { if (err) return callback(err); - i++; - assert(first); - - if (i >= network.powDiffInterval) - return callback(null, self.retarget(last, first)); - - first.getPrevious(next); - })(null, last); + return callback(null, self.getTarget(last, block, ancestors)); + }); }; -Chain.prototype.getTarget = function getTarget(last, block) { +Chain.prototype.getTarget = function getTarget(last, block, ancestors) { var powLimit = utils.toCompact(network.powLimit); var ts, first, i, prev; @@ -1716,6 +1668,9 @@ Chain.prototype.getTarget = function getTarget(last, block) { if (!last) return powLimit; + if (!ancestors) + ancestors = last.ancestors; + // Do not retarget if ((last.height + 1) % network.powDiffInterval !== 0) { if (network.powAllowMinDifficultyBlocks) { @@ -1725,7 +1680,7 @@ Chain.prototype.getTarget = function getTarget(last, block) { return powLimit; i = 1; - prev = last.ancestors; + prev = ancestors; while (prev[i] && last.height % network.powDiffInterval !== 0 && last.bits === powLimit) { @@ -1738,7 +1693,7 @@ Chain.prototype.getTarget = function getTarget(last, block) { } // Back 2 weeks - first = last.ancestors[network.powDiffInterval - 1]; + first = ancestors[network.powDiffInterval - 1]; assert(first); @@ -1770,6 +1725,25 @@ Chain.prototype.retarget = function retarget(last, first) { return utils.toCompact(target); }; +Chain.prototype.findLocator = function findLocator(locator, callback) { + var self = this; + + if (!locator) + return utils.nextTick(callback); + + utils.forEachSerial(locator, function(hash, next) { + self.db.has(hash, function(err, result) { + if (err) + return next(err); + + if (result) + return callback(null, hash); + + next(); + }); + }, callback); +}; + // https://github.com/bitcoin/bitcoin/pull/7648/files Chain.prototype.getState = function getState(prev, id, callback) { var self = this; diff --git a/lib/bcoin/fullnode.js b/lib/bcoin/fullnode.js index 6836ad39..0e940e8c 100644 --- a/lib/bcoin/fullnode.js +++ b/lib/bcoin/fullnode.js @@ -55,6 +55,8 @@ Fullnode.prototype._init = function _init() { // Pool needs access to the chain. this.pool = new bcoin.pool(this, { witness: this.network.witness, + listen: this.options.listen, + selfish: this.options.selfish, spv: false }); diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index df609ae7..33a9e332 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -171,7 +171,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { return self.removeOrphan(hash, next); copy = tx.clone(); - copy.ts = existing.ps; + copy.ts = existing.ts; copy.block = existing.block; copy.height = existing.height; copy.ps = existing.ps; diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index 47a418bd..6edc9b36 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -62,6 +62,9 @@ MerkleBlock.prototype.getRaw = function getRaw() { }; MerkleBlock.prototype.hasTX = function hasTX(hash) { + if (hash instanceof bcoin.tx) + hash = hash.hash('hex'); + return this.txMap[hash] === true; }; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index eb186b67..67e60e77 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -41,8 +41,6 @@ function Miner(node, options) { this.running = false; this.timeout = null; - this.fee = new bn(0); - this.last = this.node.chain.tip; this.block = null; this.iterations = 0; this._begin = utils.now(); @@ -81,7 +79,6 @@ Miner.prototype._init = function _init() { this.chain.on('tip', function(tip) { if (!self.running) return; - self.last = tip; self.start(); }); @@ -110,10 +107,8 @@ Miner.prototype.start = function start() { var self = this; // Wait for `tip`. - this.last = this.last || this.chain.tip; - if (!this.last) { + if (!this.chain.tip) { this.chain.on('tip', function(tip) { - self.last = tip; self.start(); }); return; @@ -180,18 +175,14 @@ Miner.prototype.addTX = function addTX(tx) { if (size > constants.blocks.maxSize) return false; - if (this.block._txMap[tx.hash('hex')]) + if (this.block.hasTX(tx)) return false; if (!this.block.witness && tx.hasWitness()) return false; // Add the tx to our block - this.block.txs.push(tx); - this.block._txMap[tx.hash('hex')] = true; - - // Calculate our new reward fee - this.fee.iadd(tx.getFee()); + this.block.addTX(tx); // Update coinbase value this.updateCoinbase(); @@ -204,25 +195,18 @@ Miner.prototype.addTX = function addTX(tx) { Miner.prototype.createBlock = function createBlock(callback) { var self = this; - var ts, target, coinbase, headers, block; + var ts = Math.max(utils.now(), this.chain.tip.ts + 1); + var coinbase, headers, block; // Find target - this.last.ensureAncestors(function(err) { - if (err) { - self.last.free(); + this.chain.getTargetAsync(this.chain.tip, ts, function(err, target) { + if (err) return callback(err); - } // Calculate version with versionbits - self.chain.computeBlockVersion(self.last, function(err, version) { - if (err) { - self.last.free(); + self.chain.computeBlockVersion(self.chain.tip, function(err, version) { + if (err) return callback(err); - } - - ts = Math.max(utils.now(), self.last.ts + 1); - - target = self.chain.getTarget(self.last, ts); // Create a coinbase coinbase = bcoin.mtx(); @@ -235,7 +219,7 @@ Miner.prototype.createBlock = function createBlock(callback) { coin: null, script: new bcoin.script([ // Height (required in v2+ blocks) - bcoin.script.array(self.last.height + 1), + bcoin.script.array(self.chain.height + 1), // extraNonce - incremented when // the nonce overflows. bcoin.script.array(0), @@ -261,20 +245,20 @@ Miner.prototype.createBlock = function createBlock(callback) { // Create our block headers = { version: version, - prevBlock: self.last.hash, + prevBlock: self.chain.tip.hash, merkleRoot: utils.toHex(constants.zeroHash), ts: ts, bits: target, nonce: 0 }; - block = bcoin.block(headers, 'block'); - block._txMap = {}; + block = bcoin.block(headers); - block.txs.push(coinbase); + block.addTX(coinbase); - block.target = utils.fromCompact(target); - block.extraNonce = new bn(0, 'le'); + block.height = self.chain.height + 1; + block.target = utils.fromCompact(target).toBuffer('le', 32); + block.extraNonce = new bn(0); if (self.chain.segwitActive) { // Set up the witness nonce and @@ -294,8 +278,6 @@ Miner.prototype.createBlock = function createBlock(callback) { // Create our merkle root. self.updateMerkle(block); - self.last.free(); - return callback(null, block); }); }); @@ -310,29 +292,20 @@ Miner.prototype.updateCommitment = function updateCommitment(block) { if (!block) block = this.block; - delete block._txMap[coinbase.hash('hex')]; - hash = block.getCommitmentHash(); coinbase.outputs[1].script = bcoin.script.createCommitment(hash); - - block._txMap[coinbase.hash('hex')] = true; }; Miner.prototype.updateCoinbase = function updateCoinbase(block) { var coinbase = block.txs[0]; - var reward = bcoin.block.reward(this.last.height + 1); assert(coinbase); if (!block) block = this.block; - delete block._txMap[coinbase.hash('hex')]; - coinbase.inputs[0].script[1] = block.extraNonce.toBuffer(); - coinbase.outputs[0].value = reward.add(this.fee); - - block._txMap[coinbase.hash('hex')] = true; + coinbase.outputs[0].value = block.getReward(); }; Miner.prototype.updateMerkle = function updateMerkle(block) { @@ -344,7 +317,7 @@ Miner.prototype.updateMerkle = function updateMerkle(block) { if (block.witness) this.updateCommitment(block); - block.ts = utils.now(); + block.ts = Math.max(utils.now(), this.chain.tip.ts + 1); block.merkleRoot = block.getMerkleRoot('hex'); }; @@ -360,7 +333,7 @@ Miner.prototype.iterate = function iterate() { self.chain.add(self.block, function(err) { if (err) { if (err.type === 'VerifyError') - utils.debug('Miner: %s could not be added to chain.', self.block.rhash); + utils.debug('%s could not be added to chain.', self.block.rhash); self.emit('error', err); return self.start(); } @@ -391,25 +364,24 @@ Miner.prototype.sendStatus = function sendStatus() { target: this.block.bits, hashes: this.hashes.toString(10), hashrate: this.rate, - height: this.last.height + 1, - best: utils.revHex(this.last.hash) + height: this.chain.height + 1, + best: utils.revHex(this.chain.tip.hash) }); }; Miner.prototype.findNonce = function findNonce() { var data = this.block.abbr(); - var target = this.block.target.toBuffer('le', 32); var now; // Track how long we've been at it. this._begin = utils.now(); - // assert(this.block.ts > this.last.ts); + assert(this.block.ts > this.chain.tip.ts); // The heart and soul of the miner: match the target. while (this.block.nonce <= 0xffffffff) { // Hash and test against the next target - if (utils.rcmp(this.dsha256(data), target) < 0) + if (rcmp(this.dsha256(data), this.block.target) < 0) return true; // Increment the nonce to get a different hash @@ -436,7 +408,7 @@ Miner.prototype.findNonce = function findNonce() { // performance because we do not have to // recalculate the merkle root. now = utils.now(); - if (now > this.block.ts && now > this.last.ts) { + if (now > this.block.ts && now > this.chain.tip.ts) { this.block.ts = now; // Overflow the nonce this.block.nonce = 0; @@ -456,6 +428,21 @@ Miner.prototype.findNonce = function findNonce() { return false; }; +function rcmp(a, b) { + var i; + + assert(a.length === b.length); + + for (i = a.length - 1; i >= 0; i--) { + if (a[i] < b[i]) + return -1; + if (a[i] > b[i]) + return 1; + } + + return 0; +} + /** * Expose */ diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 6cc26e84..90b4fecf 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -36,6 +36,7 @@ function Peer(pool, options) { this.parser = new bcoin.protocol.parser(); this.framer = new bcoin.protocol.framer(); this.chain = this.pool.chain; + this.mempool = this.pool.mempool; this.bloom = this.pool.bloom; this.version = null; this.destroyed = false; @@ -44,6 +45,7 @@ function Peer(pool, options) { this.ts = this.options.ts || 0; this.sendHeaders = false; this.haveWitness = false; + this.hashContinue = null; this.challenge = null; this.lastPong = 0; @@ -140,7 +142,7 @@ Peer.prototype._init = function init() { this.parser.on('error', function(err) { utils.debug(err.stack + ''); - peer.sendReject(null, 'malformed', 'error parsing message', 100); + self.sendReject(null, 'malformed', 'error parsing message', 100); self._error(err); // Something is wrong here. // Ignore this peer. @@ -150,11 +152,6 @@ Peer.prototype._init = function init() { this.challenge = utils.nonce(); this._ping.timer = setInterval(function() { - if (self.options.witness && !self.haveWitness) { - self._error('Peer does not support segregated witness.'); - self.setMisbehavior(100); - return; - } self._write(self.framer.ping({ nonce: self.challenge })); @@ -171,25 +168,25 @@ Peer.prototype._init = function init() { self.emit('ack'); self.ts = utils.now(); - self._write(self.framer.packet('getaddr')); + self._write(self.framer.getAddr()); if (self.options.headers) { if (self.version && self.version.version > 70012) - self._write(self.framer.packet('sendheaders')); + self._write(self.framer.sendHeaders()); } if (self.options.witness) { if (self.version && self.version.version >= 70012) - self._write(self.framer.packet('havewitness')); + self._write(self.framer.haveWitness()); } - if (self.pool.chain.isFull()) + if (self.chain.isFull()) self.getMempool(); }); // Send hello this._write(this.framer.version({ - height: this.pool.chain.height, + height: this.chain.height, relay: this.options.relay })); }; @@ -286,7 +283,8 @@ Peer.prototype.broadcast = function broadcast(items) { packetType: packetType, type: type, hash: item.hash(), - msg: item + value: item.renderNormal(), + witnessValue: item.renderWitness() }; this._broadcast.map[key] = entry; @@ -358,11 +356,12 @@ Peer.prototype._error = function error(err) { Peer.prototype._req = function _req(cmd, cb) { var self = this; + var entry; if (this.destroyed) - return cb(new Error('Destroyed, sorry')); + return utils.asyncify(cb)(new Error('Destroyed, sorry')); - var entry = { + entry = { cmd: cmd, cb: cb, ontimeout: function() { @@ -420,78 +419,284 @@ Peer.prototype._onPacket = function onPacket(packet) { var payload = packet.payload; if (this.lastBlock && cmd !== 'tx') - this._emitMerkle(this.lastBlock); + this._emitMerkle(); - if (cmd === 'version') - return this._handleVersion(payload); - - if (cmd === 'inv') - return this._handleInv(payload); - - if (cmd === 'headers') - return this._handleHeaders(payload); - - if (cmd === 'getdata') - return this._handleGetData(payload); - - if (cmd === 'addr') - return this._handleAddr(payload); - - if (cmd === 'ping') - return this._handlePing(payload); - - if (cmd === 'pong') - return this._handlePong(payload); - - if (cmd === 'getaddr') - return this._handleGetAddr(); - - if (cmd === 'reject') - return this._handleReject(payload); - - if (cmd === 'alert') - return this._handleAlert(payload); - - if (cmd === 'block') { - payload = bcoin.compactblock(payload); - } else if (cmd === 'merkleblock') { - payload = bcoin.merkleblock(payload); - this.lastBlock = payload; - return; - } else if (cmd === 'tx') { - payload = bcoin.tx(payload, this.lastBlock); - if (this.lastBlock) { - if (payload.block) { - this.lastBlock.txs.push(payload); - return; - } else { - this._emitMerkle(this.lastBlock); + switch (cmd) { + case 'version': + return this._handleVersion(payload); + case 'inv': + return this._handleInv(payload); + case 'headers': + return this._handleHeaders(payload); + case 'getdata': + return this._handleGetData(payload); + case 'addr': + return this._handleAddr(payload); + case 'ping': + return this._handlePing(payload); + case 'pong': + return this._handlePong(payload); + case 'getaddr': + return this._handleGetAddr(payload); + case 'reject': + return this._handleReject(payload); + case 'alert': + return this._handleAlert(payload); + case 'getutxos': + return this._handleGetUTXOs(payload); + case 'utxos': + return this._handleUTXOs(payload); + case 'getblocks': + return this._handleGetBlocks(payload); + case 'getheaders': + return this._handleGetHeaders(payload); + case 'mempool': + return this._handleMempool(payload); + case 'block': + payload = bcoin.compactblock(payload); + this._emit(cmd, payload); + break; + case 'merkleblock': + payload = bcoin.merkleblock(payload); + this.lastBlock = payload; + break; + case 'tx': + payload = bcoin.tx(payload, this.lastBlock); + if (this.lastBlock) { + if (payload.block) { + this.lastBlock.txs.push(payload); + break; + } + this._emitMerkle(); } - } - } - - if (cmd === 'sendheaders') { - this.sendHeaders = true; - return; - } - - if (cmd === 'havewitness') { - this.haveWitness = true; - return; + this._emit(cmd, payload); + break; + case 'sendheaders': + this.sendHeaders = true; + this._res(cmd, payload); + break; + case 'havewitness': + this.haveWitness = true; + this._res(cmd, payload); + break; + case 'verack': + this._emit(cmd, payload); + break; + default: + utils.debug('Unknown packet: %s', cmd); + this._emit(cmd, payload); + break; } +}; +Peer.prototype._emit = function _emit(cmd, payload) { if (this._res(cmd, payload)) return; this.emit(cmd, payload); }; -Peer.prototype._emitMerkle = function _emitMerkle(payload) { - if (!this._res('merkleblock', payload)) - this.emit('merkleblock', payload); +Peer.prototype._emitMerkle = function _emitMerkle() { + if (this.lastBlock) + this._emit('merkleblock', this.lastBlock); this.lastBlock = null; }; +Peer.prototype._handleUTXOs = function _handleUTXOs(payload) { + payload.coins = payload.coins(function(coin) { + return new bcoin.coin(coin); + }); + utils.debug('Received %d utxos from %s.', payload.coins.length, this.host); + this.emit('utxos', payload); +}; + +Peer.prototype._handleGetUTXOs = function _handleGetUTXOs(payload) { + var self = this; + var coins = []; + var notfound = []; + + function checkMempool(hash, index, callback) { + if (!self.mempool) + return callback(); + + if (!payload.mempool) + return callback(); + + self.mempool.getCoin(hash, index, callback); + } + + function isSpent(hash, index, callback) { + if (!self.mempool) + return callback(null, false); + + if (!payload.mempool) + return callback(null, false); + + self.mempool.isSpent(hash, index, callback); + } + + utils.forEachSerial(payload.prevout, function(prevout, next, i) { + var hash = prevout.hash; + var index = prevout.index; + + checkMempool(hash, index, function(err, coin) { + if (err) + return next(err); + + if (coin) { + coins.push(coin); + return next(); + } + + isSpent(hash, index, function(err, result) { + if (err) + return next(err); + + if (result) { + notfound.push(i); + return next(); + } + + self.chain.db.getCoin(hash, index, function(err, coin) { + if (err) + return next(err); + + if (!coin) { + notfound.push(i); + return next(); + } + + coins.push(coin); + + next(); + }); + }); + }); + }, function(err) { + if (err) + self.emit('error', err); + + self._write(self.framer.UTXOs({ + height: self.chain.height, + tip: self.chain.tip.hash, + notfound: notfound, + coins: coins + })); + }); +}; + +Peer.prototype._handleGetHeaders = function _handleGetHeaders(payload) { + var self = this; + var headers = []; + + if (this.pool.options.selfish) + return; + + if (this.chain.db.options.spv) + return; + + function collect(err, hash) { + if (err) + return done(err); + + if (!hash) + return done(); + + self.chain.db.get(hash, function(err, entry) { + if (err) + return done(err); + + if (!entry) + return done(); + + (function next(err, entry) { + if (err) + return done(err); + + if (!entry) + return done(); + + headers.push(new bcoin.headers(entry)); + + if (headers.length === 2000) + return done(); + + if (entry.hash === payload.stop) + return done(); + + entry.getNext(next); + })(null, entry); + }); + } + + function done(err) { + if (err) + return self.emit('error', err); + + self._write(self.framer.headers(headers)); + } + + if (!payload.locator) + return collect(null, payload.stop); + + this.chain.findLocator(payload.locator, function(err, hash) { + if (err) + return collect(err); + + if (!hash) + return collect(); + + self.chain.db.getNextHash(hash, collect); + }); +}; + +Peer.prototype._handleGetBlocks = function _handleGetBlocks(payload) { + var self = this; + var blocks = []; + + if (this.pool.options.selfish) + return; + + if (this.chain.db.options.spv) + return; + + function done(err) { + if (err) + return self.emit('error', err); + self._write(self.framer.inv(blocks)); + } + + this.chain.findLocator(payload.locator, function(err, tip) { + if (err) + return done(err); + + if (!tip) + return done(); + + (function next(hash) { + self.chain.db.getNextHash(hash, function(err, hash) { + if (err) + return done(err); + + if (!hash) + return done(); + + blocks.push({ type: constants.inv.block, hash: hash }); + + if (hash === payload.stop) + return done(); + + if (blocks.length === 500) { + self.hashContinue = hash; + return done(); + } + + next(hash); + }); + })(tip); + }); +}; + Peer.prototype._handleVersion = function handleVersion(payload) { if (payload.version < constants.minVersion) { this._error('Peer doesn\'t support required protocol version.'); @@ -523,13 +728,17 @@ Peer.prototype._handleVersion = function handleVersion(payload) { } } - // if (this.options.witness) { - // if (!payload.witness) { - // this._error('Peer does not support segregated witness service.'); - // this.setMisbehavior(100); - // return; - // } - // } + if (this.options.witness) { + if (!payload.witness) { + this._req('havewitness', function(err, payload) { + if (err) { + self._error('Peer does not support segregated witness.'); + self.setMisbehavior(100); + return; + } + }); + } + } if (payload.witness) this.haveWitness = true; @@ -540,32 +749,57 @@ Peer.prototype._handleVersion = function handleVersion(payload) { this.emit('version', payload); }; -Peer.prototype._handleGetData = function handleGetData(items) { - items.forEach(function(item) { - // Filter out not broadcasted things - var hash = utils.toHex(item.hash); - var entry = this._broadcast.map[hash]; - var isWitness = item.type & constants.invWitnessMask; - var value; +Peer.prototype._handleMempool = function _handleMempool() { + var self = this; + var items = []; + var i; - if (!entry) - return; + if (!this.mempool) + return; + + if (this.pool.options.selfish) + return; + + this.mempool.getSnapshot(function(err, hashes) { + if (err) + return self.emit('error', err); + + for (i = 0; i < hashes.length; i++) + items.push({ type: constants.inv.tx, hash: hashes[i] }); + + utils.debug('Sending mempool snapshot to %s.', self.host); + + self._write(self.framer.inv(items)); + }); +}; + +Peer.prototype._handleGetData = function handleGetData(items) { + var self = this; + var check = []; + var notfound = []; + var hash, entry, isWitness, value, i, item; + + if (items.length > 50000) + return this._error('message getdata size() = %d', items.length); + + for (i = 0; i < items.length; i++) { + item = items[i]; + + hash = utils.toHex(item.hash); + entry = this._broadcast.map[hash]; + isWitness = item.type & constants.invWitnessMask; + value = null; + + if (!entry) { + check.push(item); + continue; + } if ((item.type & ~constants.invWitnessMask) !== entry.type) { utils.debug( 'Peer %s requested an existing item with the wrong type.', this.host); - return; - } - - if (isWitness) { - if (!entry.witnessValue) - entry.witnessValue = entry.msg.renderWitness(); - value = entry.witnessValue; - } else { - if (!entry.value) - entry.value = entry.msg.renderNormal(); - value = entry.value; + continue; } utils.debug( @@ -575,21 +809,107 @@ Peer.prototype._handleGetData = function handleGetData(items) { utils.revHex(utils.toHex(entry.hash)), isWitness ? 'witness' : 'normal'); - if (entry.value && entry.witnessValue) - delete entry.msg; - - this._write(this.framer.packet(entry.packetType, value)); + if (isWitness) + this._write(this.framer.packet(entry.packetType, entry.witnessValue)); + else + this._write(this.framer.packet(entry.packetType, entry.value)); entry.e.emit('request'); - }, this); + } + + if (this.pool.options.selfish) + return; + + utils.forEachSerial(check, function(item, next) { + var isWitness = item.type & constants.invWitnessMask; + var type = item.type & ~constants.invWitnessMask; + var hash = utils.toHex(item.hash); + var data; + + if (type === constants.inv.tx) { + if (!self.mempool) { + notfound.push({ type: type, hash: hash }); + return next(); + } + return self.mempool.getTX(hash, function(err, tx) { + if (err) + return next(err); + + if (!tx) { + notfound.push({ type: type, hash: hash }); + return next(); + } + + if (isWitness) + data = tx.renderWitness(); + else + data = tx.renderNormal(); + + self._write(self.framer.packet('tx', data)); + + next(); + }); + } + + if (type === constants.inv.block) { + if (self.chain.db.options.spv) { + notfound.push({ type: type, hash: hash }); + return next(); + } + return self.chain.db.getBlock(hash, function(err, block) { + if (err) + return next(err); + + if (!block) { + notfound.push({ type: type, hash: hash }); + return next(); + } + + if (isWitness) + data = block.renderWitness(); + else + data = block.renderNormal(); + + self._write(self.framer.packet('block', data)); + + if (hash === self.hashContinue) { + self._write(self.framer.inv([{ + type: type, + hash: self.chain.tip.hash + }])); + self.hashContinue = null; + } + + next(); + }); + } + + notfound.push({ type: type, hash: hash }); + return next(); + }, function(err) { + if (err) + self.emit('error', err); + + utils.debug( + 'Served %d items to %s with getdata (notfound=%d).', + items.length - notfound.length, + notfound.length, + self.host); + + if (notfound.length > 0) + self._write(self.framer.notFound(notfound)); + }); }; Peer.prototype._handleAddr = function handleAddr(addrs) { var now = utils.now(); + var i, addr, ts, host; - addrs.forEach(function(addr) { - var ts = addr.ts; - var host = addr.ipv4 !== '0.0.0.0' + for (i = 0; i < addrs.length; i++) { + addr = addrs[i]; + + ts = addr.ts; + host = addr.ipv4 !== '0.0.0.0' ? addr.ipv4 : addr.ipv6; @@ -608,7 +928,7 @@ Peer.prototype._handleAddr = function handleAddr(addrs) { headers: addr.version >= 31800, spv: addr.bloom && addr.version >= 70011 }); - }, this); + } utils.debug( 'Recieved %d peers (seeds=%d, peers=%d).', @@ -635,33 +955,38 @@ Peer.prototype._handlePong = function handlePong(data) { Peer.prototype._handleGetAddr = function handleGetAddr() { var hosts = {}; - var peers; + var peers = this.pool.peers.all; + var items = []; + var i, peer, ip, version; - peers = this.pool.peers.all.map(function(peer) { - var ip, version; + if (this.pool.options.selfish) + return; + + for (i = 0; i < peers.length; i++) { + peer = peers[i]; if (!peer.socket || !peer.socket.remoteAddress) - return; + continue; ip = peer.socket.remoteAddress; version = utils.isIP(ip); if (!version) - return; + continue; if (hosts[ip]) - return; + continue; hosts[ip] = true; - return { + items.push({ ts: peer.ts, services: peer.version ? peer.version.services : null, ipv4: version === 4 ? ip : null, ipv6: version === 6 ? ip : null, port: peer.socket.remotePort || network.port - }; - }).filter(Boolean); + }); + } return this._write(this.framer.addr(peers)); }; @@ -734,7 +1059,7 @@ Peer.prototype.getHeaders = function getHeaders(hashes, stop) { this.host); utils.debug('Height: %s, Hash: %s, Stop: %s', - this.pool.chain.getHeight(hashes[0]), + this.chain._getCachedHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); @@ -747,7 +1072,7 @@ Peer.prototype.getBlocks = function getBlocks(hashes, stop) { this.host); utils.debug('Height: %s, Hash: %s, Stop: %s', - this.pool.chain.getHeight(hashes[0]), + this.chain._getCachedHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 60718b62..435237aa 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -11,6 +11,7 @@ var utils = require('../utils'); var assert = utils.assert; var BufferWriter = require('../writer'); var DUMMY = new Buffer([]); +var bn = require('bn.js'); /** * Framer @@ -56,8 +57,7 @@ Framer.prototype.header = function header(cmd, payload) { Framer.prototype.packet = function packet(cmd, payload) { var header; - if (!payload) - payload = DUMMY; + assert(payload, 'No payload.'); header = this.header(cmd, payload); @@ -77,6 +77,50 @@ Framer.prototype.verack = function verack() { return this.packet('verack', Framer.verack()); }; +Framer.prototype.mempool = function mempool() { + return this.packet('mempool', Framer.mempool()); +}; + +Framer.prototype.getUTXOs = function getUTXOs(data) { + return this.packet('getutxos', Framer.getUTXOs(data)); +}; + +Framer.prototype.UTXOs = function UTXOs(data) { + return this.packet('utxos', Framer.UTXOs(data)); +}; + +Framer.prototype.getAddr = function getAddr() { + return this.packet('getaddr', Framer.getAddr()); +}; + +Framer.prototype.submitOrder = function submitOrder() { + return this.packet('submitorder', Framer.submitOrder()); +}; + +Framer.prototype.checkOrder = function checkOrder() { + return this.packet('checkorder', Framer.checkOrder()); +}; + +Framer.prototype.reply = function reply() { + return this.packet('reply', Framer.reply()); +}; + +Framer.prototype.sendHeaders = function sendHeaders() { + return this.packet('sendheaders', Framer.sendHeaders()); +}; + +Framer.prototype.haveWitness = function haveWitness() { + return this.packet('havewitness', Framer.haveWitness()); +}; + +Framer.prototype.filterAdd = function filterAdd(data) { + return this.packet('filteradd', Framer.filterAdd(data)); +}; + +Framer.prototype.filterClear = function filterClear() { + return this.packet('filterclear', Framer.filterClear()); +}; + Framer.prototype.inv = function inv(items) { return this.packet('inv', Framer.inv(items)); }; @@ -101,10 +145,6 @@ Framer.prototype.filterLoad = function filterLoad(bloom, update) { return this.packet('filterload', Framer.filterLoad(bloom, update)); }; -Framer.prototype.filterClear = function filterClear() { - return this.packet('filterclear', Framer.filterClear()); -}; - Framer.prototype.getHeaders = function getHeaders(hashes, stop) { return this.packet('getheaders', Framer.getHeaders(hashes, stop)); }; @@ -118,11 +158,11 @@ Framer.prototype.utxo = function _coin(coin) { }; Framer.prototype.tx = function tx(tx) { - return this.packet('tx', Framer.tx(tx)); + return this.packet('tx', Framer.renderTX(tx, false)); }; Framer.prototype.witnessTX = function witnessTX(tx) { - return this.packet('tx', Framer.witnessTX(tx)); + return this.packet('tx', Framer.renderTX(tx, true)); }; Framer.prototype.block = function _block(block) { @@ -149,10 +189,6 @@ Framer.prototype.addr = function addr(peers) { return this.packet('addr', Framer.addr(peers)); }; -Framer.prototype.mempool = function mempool() { - return this.packet('mempool', Framer.mempool()); -}; - Framer.address = function address(data, full, writer) { var p = new BufferWriter(writer); @@ -230,7 +266,7 @@ Framer.version = function version(options, writer) { }; Framer.verack = function verack() { - return new Buffer([]); + return DUMMY; }; Framer._inv = function _inv(items, writer) { @@ -295,33 +331,42 @@ Framer.filterLoad = function filterLoad(bloom, update, writer) { return p; }; -Framer.filterClear = function filterClear() { - return new Buffer([]); +Framer.getHeaders = function getHeaders(locator, stop, writer) { + // NOTE: getheaders can have an empty locator. + return Framer._getBlocks(locator || [], stop, writer); }; -Framer.getHeaders = function getHeaders(hashes, stop, writer) { - // NOTE: getheaders can have a null hash - if (!hashes) - hashes = []; - - return Framer._getBlocks(hashes, stop, writer); +Framer.getBlocks = function getBlocks(locator, stop, writer) { + return Framer._getBlocks(locator, stop, writer); }; -Framer.getBlocks = function getBlocks(hashes, stop, writer) { - return Framer._getBlocks(hashes, stop, writer); -}; +Framer._getBlocks = function _getBlocks(locator, stop, writer) { + var p, i, version; -Framer._getBlocks = function _getBlocks(hashes, stop, writer) { - var p = new BufferWriter(writer); - var i; + if (locator.locator) { + writer = stop; + stop = locator.stop; + version = locator.version; + locator = locator.locator; + } - p.writeU32(constants.version); - p.writeVarint(hashes.length); + if (!version) + version = constants.version; - for (i = 0; i < hashes.length; i++) - p.writeHash(hashes[i]); + if (!stop) + stop = constants.zeroHash; - p.writeHash(stop || constants.zeroHash); + assert(locator, 'getblocks requires a locator'); + + p = new BufferWriter(writer); + + p.writeU32(version); + p.writeVarint(locator.length); + + for (i = 0; i < locator.length; i++) + p.writeHash(locator[i]); + + p.writeHash(stop); if (!writer) p = p.render(); @@ -430,7 +475,7 @@ Framer.output = function _output(output, writer) { Framer.witnessTX = function _witnessTX(tx, writer) { var p = new BufferWriter(writer); var witnessSize = 0; - var i; + var i, start; p.write32(tx.version); p.writeU8(0); @@ -447,7 +492,7 @@ Framer.witnessTX = function _witnessTX(tx, writer) { Framer.output(tx.outputs[i], p); for (i = 0; i < tx.inputs.length; i++) { - var start = p.written; + start = p.written; Framer.witness(tx.inputs[i].witness, p); witnessSize += p.written - start; } @@ -588,7 +633,30 @@ Framer.merkleBlock = function _merkleBlock(block, writer) { return p; }; -Framer.headers = function _headers(block, writer) { +Framer.headers = function _headers(headers, writer) { + var p = new BufferWriter(writer); + var i, header; + + p.writeVarint(headers.length); + + for (i = 0; i < headers.length; i++) { + header = headers[i]; + p.write32(header.version); + p.writeHash(header.prevBlock); + p.writeHash(header.merkleRoot); + p.writeU32(header.ts); + p.writeU32(header.bits); + p.writeU32(header.nonce); + p.writeVarint(header.totalTX); + } + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.blockHeaders = function blockHeaders(block, writer) { var p = new BufferWriter(writer); p.write32(block.version); @@ -597,7 +665,6 @@ Framer.headers = function _headers(block, writer) { p.writeU32(block.ts); p.writeU32(block.bits); p.writeU32(block.nonce); - p.writeVarint(block.totalTX); if (!writer) p = p.render(); @@ -647,10 +714,6 @@ Framer.addr = function addr(peers, writer) { return p; }; -Framer.mempool = function mempool() { - return new Buffer([]); -}; - Framer.alert = function alert(data, writer) { var p, i, payload; @@ -694,6 +757,134 @@ Framer.alert = function alert(data, writer) { return p; }; +Framer.mempool = function mempool() { + return DUMMY; +}; + +Framer.getAddr = function getAddr() { + return DUMMY; +}; + +Framer.getUTXOs = function getUTXOs(data, writer) { + var p = new BufferWriter(writer); + var i, prevout; + + p.writeU8(data.mempool ? 1 : 0); + p.writeVarint(data.prevout.length); + + for (i = 0; i < data.prevout.length; i++) { + prevout = data.prevout[i]; + p.writeHash(prevout.hash); + p.writeU32(prevout.index); + } + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.UTXOs = function UTXOs(data, writer) { + var p = new BufferWriter(writer); + var i, j, coin, height, index, map; + + if (!data.map) { + assert(data.notfound); + map = new bn(0); + j = -1; + for (i = 0; i < data.notfound.length; i++) { + index = data.notfound[i]; + while (++j < index) { + map.iushln(1); + map.iuorn(1); + } + map.iushln(1); + map.iuorn(0); + } + map = map.toBuffer('be'); + } else { + map = data.map; + } + + p.writeU32(data.height); + p.writeHash(data.tip); + p.writeVarBytes(map); + p.writeVarInt(data.coins.length); + + for (i = 0; i < data.coins.length; i++) { + coin = data.coins[i]; + height = coin.height; + + if (height === -1) + height = 0x7fffffff; + + p.writeU32(coin.version); + p.writeU32(height); + Framer.output(coin, p); + } + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.submitOrder = function submitOrder(order, writer) { + var p = new BufferWriter(writer); + + p.writeHash(order.hash); + Framer.renderTX(order.tx, true, p); + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.checkOrder = function checkOrder(order, writer) { + return Framer.submitOrder(order, writer); +}; + +Framer.reply = function reply(data, writer) { + var p = new BufferWriter(writer); + + p.writeHash(data.hash); + p.writeU32(data.code || 0); + + if (data.publicKey) + p.writeVarBytes(data.publicKey); + else + p.writeVarInt(0); + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.sendHeaders = function sendHeaders() { + return DUMMY; +}; + +Framer.haveWitness = function haveWitness() { + return DUMMY; +}; + +Framer.filterAdd = function filterAdd(data, writer) { + var p = new BufferWriter(writer); + + p.writeVarBytes(data.data || data); + + if (!writer) + p = p.render(); + + return p; +}; + +Framer.filterClear = function filterClear() { + return DUMMY; +}; + // Total size and size of witness Framer.block._sizes = function blockSizes(block) { var writer = new BufferWriter(); diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index a72735b1..95229d09 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -119,44 +119,198 @@ Parser.prototype.parseHeader = function parseHeader(h) { }; }; +Parser.parseMempool = function parseMempool(p) { + return {}; +}; + +Parser.parseSubmitOrder = function parseSubmitOrder(p) { + var hash, tx; + p = new BufferReader(p); + p.start(); + return { + hash: p.readHash(), + tx: Parser.parseTX(p), + _size: p.end() + }; +}; + +Parser.parseCheckOrder = function parseCheckOrder(p) { + return Parser.parseSubmitOrder(p); +}; + +Parser.parseReply = function parseReply(p) { + var hash, code, publicKey; + p = new BufferReader(p); + p.start(); + return { + hash: p.readHash(), + code: p.readU32(), + publicKey: p.readVarBytes(), + _size: p.end() + }; +}; + +Parser.parseSendHeaders = function parseSendHeaders(p) { + return {}; +}; + +Parser.parseHaveWitness = function parseHaveWitness(p) { + return {}; +}; + +Parser.parseGetAddr = function parseGetAddr(p) { + return {}; +}; + +Parser.parseFilterLoad = function parseFilterLoad(p) { + return {}; +}; + +Parser.parseFilterAdd = function parseFilterAdd(p) { + p = new BufferReader(p); + p.start(); + return { + data: p.readVarBytes(), + _size: p.end() + }; +}; + +Parser.parseFilterClear = function parseFilterClear(p) { + return {}; +}; + Parser.prototype.parsePayload = function parsePayload(cmd, p) { - if (cmd === 'version') - return Parser.parseVersion(p); + switch (cmd) { + case 'version': + return Parser.parseVersion(p); + case 'verack': + return Parser.parseVerack(p); + case 'mempool': + return Parser.parseMempool(p); + case 'getaddr': + return Parser.parseGetAddr(p); + case 'submitorder': + return Parser.parseSubmitOrder(p); + case 'checkorder': + return Parser.parseCheckOrder(p); + case 'reply': + return Parser.parseReply(p); + case 'sendheaders': + return Parser.parseSendHeaders(p); + case 'havewitness': + return Parser.parseHaveWitness(p); + case 'filterload': + return Parser.parseFilterLoad(p); + case 'filteradd': + return Parser.parseFilterAdd(p); + case 'filterclear': + return Parser.parseFilterClear(p); + case 'inv': + return Parser.parseInv(p); + case 'getdata': + return Parser.parseGetData(p); + case 'notfound': + return Parser.parseNotFound(p); + case 'getheaders': + return Parser.parseGetHeaders(p); + case 'getblocks': + return Parser.parseGetBlocks(p); + case 'merkleblock': + return Parser.parseMerkleBlock(p); + case 'headers': + return Parser.parseHeaders(p); + case 'block': + return Parser.parseBlockCompact(p); + case 'tx': + return Parser.parseTX(p); + case 'reject': + return Parser.parseReject(p); + case 'addr': + return Parser.parseAddr(p); + case 'ping': + return Parser.parsePing(p); + case 'pong': + return Parser.parsePong(p); + case 'alert': + return Parser.parseAlert(p); + case 'getutxos': + return Parser.parseGetUTXOs(p); + case 'utxos': + return Parser.parseUTXOs(p); + default: + utils.debug('Unknown packet: %s', cmd); + return p; + } +}; - if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound') - return Parser.parseInvList(p); +Parser.parseGetUTXOs = function parseGetUTXOs(p) { + var mempool, prevout, count, i; - if (cmd === 'merkleblock') - return Parser.parseMerkleBlock(p); + p = new BufferReader(p); + p.start(); - if (cmd === 'headers') - return Parser.parseHeaders(p); + mempool = p.readU8() === 1; + prevout = []; + count = p.readVarint(); - if (cmd === 'block') - return Parser.parseBlockCompact(p); + for (i = 0; i < count; i++) { + prevout.push({ + hash: p.readHash('hex'), + index: p.readU32() + }); + } - if (cmd === 'tx') - return Parser.parseTX(p); + return { + mempool: mempool, + prevout: prevout, + _size: p.end() + }; +}; - if (cmd === 'reject') - return Parser.parseReject(p); +Parser.parseUTXOs = function parseUTXOs(p) { + var chainHeight, tip, map, count, coins; + var coin, version, height, i, notfound, ch, j; - if (cmd === 'addr') - return Parser.parseAddr(p); + p = new BufferReader(p); + p.start(); - if (cmd === 'ping') - return Parser.parsePing(p); + chainHeight = p.readU32(); + tip = p.readHash('hex'); + map = p.readVarBytes(); + count = p.readVarint(); + coins = []; + notfound = []; - if (cmd === 'pong') - return Parser.parsePong(p); + for (i = 0; i < map.length; i++) { + ch = map[i]; + for (j = 0; j < 8; j++) { + if ((ch & 1) === 0) + notfound.push(i + j); + ch >>>= 1; + } + } - if (cmd === 'alert') - return Parser.parseAlert(p); + for (i = 0; i < count; i++) { + version = p.readU32(); + height = p.readU32(); - if (cmd === 'utxo') - return Parser.parseUTXO(p); + if (height === 0x7fffffff) + height = -1; - return p; + coin = Parser.parseOutput(p); + coin.version = version; + coin.height = height; + coins.push(coin); + } + + return { + height: chainHeight, + tip: tip, + map: map, + coins: coins, + notfound: notfound, + _size: p.end() + }; }; Parser.parsePing = function parsePing(p) { @@ -223,7 +377,58 @@ Parser.parseVersion = function parseVersion(p) { }; }; -Parser.parseInvList = function parseInvList(p) { +Parser.parseVerack = function parseVerack(p) { + return {}; +}; + +Parser.parseNotFound = function parseNotFound(p) { + return Parser.parseInv(p); +}; + +Parser.parseGetData = function parseGetData(p) { + return Parser.parseInv(p); +}; + +Parser._parseGetBlocks = function _parseGetBlocks(p) { + var version, count, locator, i, stop; + + p = new BufferReader(p); + p.start(); + + version = p.readU32(); + count = p.readVarint(); + locator = []; + + for (i = 0; i < count; i++) + locator.push(p.readHash('hex')); + + stop = p.readHash('hex'); + + if (stop === constants.nullHash) + stop = null; + + return { + version: version, + locator: locator, + stop: stop, + _size: p.end() + }; +}; + +Parser.parseGetBlocks = function parseGetBlocks(p) { + var data = Parser._parseGetBlocks(p); + assert(data.locator.length > 0, 'getblocks requires a locator.'); + return data; +}; + +Parser.parseGetHeaders = function parseGetHeaders(p) { + var data = Parser._parseGetBlocks(p); + if (data.locator.length === 0) + data.locator = null; + return data; +}; + +Parser.parseInv = function parseInv(p) { var items = []; var i, count; @@ -308,6 +513,22 @@ Parser.parseHeaders = function parseHeaders(p) { return headers; }; +Parser.parseBlockHeaders = function parseBlockHeaders(p) { + p = new BufferReader(p); + p.start(); + + return { + version: p.readU32(), // Technically signed + prevBlock: p.readHash('hex'), + merkleRoot: p.readHash('hex'), + ts: p.readU32(), + bits: p.readU32(), + nonce: p.readU32(), + totalTX: p.readVarint(), + _size: p.end() + } +}; + Parser.parseBlock = function parseBlock(p) { var txs = []; var witnessSize = 0; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index dfe8bd1c..d7f0c526 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -587,6 +587,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version continue; } + if (op === opcodes.OP_1NEGATE) { + stack.push(STACK_NEGATE); + continue; + } + switch (op) { case opcodes.OP_NOP: case opcodes.OP_NOP1: @@ -601,10 +606,6 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version throw new ScriptError('Upgradable NOP used.', op, ip); break; } - case opcodes.OP_1NEGATE: { - stack.push(STACK_NEGATE); - break; - } case opcodes.OP_IF: case opcodes.OP_NOTIF: { if (stack.length < 1) @@ -1047,13 +1048,13 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (stack.length === 0) throw new ScriptError('Stack too small.', op, ip); - // NOTE: Bitcoind accepts 5 byte locktimes. - // 4 byte locktimes become useless in 2106. - locktime = Script.num(stack.top(-1), flags, 5).toNumber(); + locktime = Script.num(stack.top(-1), flags, 5); - if (locktime < 0) + if (locktime.cmpn(0) < 0) throw new ScriptError('Negative locktime.', op, ip); + locktime = locktime.uand(utils.U32).toNumber(); + if (!Script.checkLocktime(locktime, tx, index)) throw new ScriptError('Locktime verification failed.', op, ip); @@ -1073,14 +1074,13 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (stack.length === 0) throw new ScriptError('Stack too small.', op, ip); - // NOTE: Bitcoind accepts 5 byte locktimes. - // 4 byte locktimes become useless in 2106 - // (will people still be using bcoin then?). - locktime = Script.num(stack.top(-1), flags, 4).toNumber(); + locktime = Script.num(stack.top(-1), flags, 5); - if (locktime < 0) + if (locktime.cmpn(0) < 0) throw new ScriptError('Negative sequence.', op, ip); + locktime = locktime.uand(utils.U32).toNumber(); + if ((locktime & constants.sequenceLocktimeDisableFlag) !== 0) break; @@ -1153,14 +1153,6 @@ Script.checkSequence = function checkSequence(sequence, tx, i) { Script.bool = function bool(value) { var i; - // Should never happen: - // if (typeof value === 'boolean') - // return value; - - // Should never happen: - // if (utils.isFinite(value)) - // return value !== 0; - if (bn.isBN(value)) return value.cmpn(0) !== 0; @@ -1210,10 +1202,10 @@ Script.num = function num(value, flags, size) { // negative flag. if (value[value.length - 1] & 0x80) { if (utils.isNegZero(value, 'le')) { - value = new bn(0, 'le'); + value = new bn(0); } else { value = new bn(value, 'le'); - value = value.notn(value.bitLength()).addn(1).neg(); + value = value.notn(size * 8).addn(1).neg(); } } else { value = new bn(value, 'le'); @@ -1237,7 +1229,7 @@ Script.array = function(value) { if (value.cmpn(0) === 0) value = new bn(0); else - value = value.neg().notn(value.bitLength()).subn(1); + value = value.neg().notn(value.byteLength() * 8).subn(1); } if (value.cmpn(0) === 0) @@ -1349,6 +1341,7 @@ Script.createMultisig = function createMultisig(keys, m, n) { }; Script.createScripthash = function createScripthash(hash) { + assert(hash.length === 20); return new Script([ opcodes.OP_HASH160, hash, @@ -1357,6 +1350,8 @@ Script.createScripthash = function createScripthash(hash) { }; Script.createNulldata = function createNulldata(flags) { + assert(Buffer.isBuffer(flags)); + assert(flags.length <= constants.script.maxOpReturn, 'Nulldata too large.'); return new Script([ opcodes.OP_RETURN, flags @@ -1366,11 +1361,12 @@ Script.createNulldata = function createNulldata(flags) { Script.createWitnessProgram = function createWitnessProgram(version, data) { assert(typeof version === 'number' && version >= 0 && version <= 16); assert(Buffer.isBuffer(data)); - assert(data.length === 20 || data.length === 32); + assert(data.length >= 2 && data.length <= 32); return new Script([version === 0 ? 0 : version + 0x50, data]); }; Script.createCommitment = function createCommitment(hash) { + assert(hash.length === 32); return new Script([ opcodes.OP_RETURN, Buffer.concat([new Buffer([0xaa, 0x21, 0xa9, 0xed]), hash]) @@ -1859,8 +1855,6 @@ Script.createOutputScript = function(options) { flags = options.flags; if (typeof flags === 'string') flags = new Buffer(flags, 'utf8'); - assert(Buffer.isBuffer(flags)); - assert(flags.length <= constants.script.maxOpReturn, 'Nulldata too large.'); return Script.createNulldata(flags); } @@ -1875,9 +1869,6 @@ Script.createOutputScript = function(options) { } else if (options.keys) { m = options.m; n = options.n || options.keys.length; - assert(m >= 1 && m <= n, 'm must be between 1 and n'); - assert(n >= 1 && n <= (options.scriptHash ? 15 : 3), - 'n must be between 1 and 15'); script = Script.createMultisig(options.keys, m, n); } else if (Buffer.isBuffer(options.scriptHash)) { if (options.version != null) { diff --git a/lib/bcoin/spvnode.js b/lib/bcoin/spvnode.js index bf06631a..a0510175 100644 --- a/lib/bcoin/spvnode.js +++ b/lib/bcoin/spvnode.js @@ -45,6 +45,8 @@ SPVNode.prototype._init = function _init() { // Pool needs access to the chain. this.pool = new bcoin.pool(this, { witness: this.network.witness, + selfish: true, + listen: false, spv: true }); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 0e936c2e..876720af 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -59,7 +59,7 @@ function TX(data, block, index) { if (block && this.ts === 0) { if (block.type === 'merkleblock') { - if (block.hasTX(this.hash('hex'))) + if (block.hasTX(this)) this.setBlock(block, index); } else { this.setBlock(block, index); @@ -118,6 +118,15 @@ TX.prototype.setBlock = function setBlock(block, index) { this.block = block.hash('hex'); this.height = block.height; this.index = index; + this.ps = 0; +}; + +TX.prototype.unsetBlock = function unsetBlock() { + this.ts = 0; + this.block = null; + this.height = -1; + this.index = -1; + this.ps = utils.now(); }; TX.prototype.hash = function hash(enc) { @@ -702,7 +711,8 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) { if (prev && prev.isWitnessScripthash()) { prev = input.witness.getRedeem(); - cost += prev.getSigops(true); + if (prev) + cost += prev.getSigops(true); } else { cost += 0; } diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 223cabc3..526362ee 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1444,21 +1444,6 @@ utils.cmp = function cmp(a, b) { return 0; }; -utils.rcmp = function rcmp(a, b) { - var i; - - assert(a.length === b.length); - - for (i = a.length - 1; i >= 0; i--) { - if (a[i] < b[i]) - return -1; - if (a[i] > b[i]) - return 1; - } - - return 0; -}; - // memcmp in constant time (can only return true or false) // https://cryptocoding.net/index.php/Coding_rules // $ man 3 memcmp (see NetBSD's consttime_memequal)