From 2ec7989b6a4864141b8e435537eb19f0e22b989d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 6 Feb 2016 21:51:38 -0800 Subject: [PATCH] no more dynamic heights. --- lib/bcoin/block.js | 29 ++++++++--- lib/bcoin/chain.js | 17 +++++-- lib/bcoin/coin.js | 18 ++++--- lib/bcoin/input.js | 2 +- lib/bcoin/peer.js | 26 +++++++++- lib/bcoin/pool.js | 31 +++++++---- lib/bcoin/script.js | 26 ++++------ lib/bcoin/tx-pool.js | 18 +++++-- lib/bcoin/tx.js | 119 +++++++++++++++++++------------------------ 9 files changed, 168 insertions(+), 118 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 2dc99b0c..d6c75aa6 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -31,6 +31,7 @@ function Block(data, subtype) { this.bits = data.bits; this.nonce = data.nonce; this.totalTX = data.totalTX; + this.height = data.height != null ? data.height : -1; this.hashes = (data.hashes || []).map(function(hash) { return utils.toHex(hash); }); @@ -81,6 +82,7 @@ function Block(data, subtype) { tx.block = null; if (tx.ts === self.ts) tx.ts = 0; + tx.height = -1; return tx; }); } @@ -200,6 +202,8 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() { var merkleTree = []; var i, j, size, i2, hash; + assert(this.subtype === 'block'); + for (i = 0; i < this.txs.length; i++) { merkleTree.push(this.txs[i].hash('hex')); } @@ -485,8 +489,12 @@ Block.prototype.isGenesis = function isGenesis() { }; Block.prototype.getHeight = function getHeight() { + if (this.height !== -1) + return this.height; + if (!this.chain) return -1; + return this.chain.getHeight(this.hash('hex')); }; @@ -526,9 +534,11 @@ Block.prototype._getReward = function _getReward() { if (this._reward) return this._reward; - base = Block.reward(this.getHeight()); + base = Block.reward(this.height); - if (this.txs.length === 0 || !this.txs[0].isCoinbase()) { + if (this.subtype !== 'block' + || this.txs.length === 0 + || !this.txs[0].isCoinbase()) { return this._reward = { fee: new bn(0), reward: base, @@ -583,9 +593,15 @@ Block.prototype.isOrphan = function isOrphan() { }; Block.prototype.getCoinbase = function getCoinbase() { - var tx = this.txs[0]; + var tx; + + if (this.subtype !== 'block') + return; + + tx = this.txs[0]; if (!tx || !tx.isCoinbase()) return; + return tx; }; @@ -597,10 +613,6 @@ Block.prototype.__defineGetter__('rhash', function() { return utils.revHex(this.hash('hex')); }); -Block.prototype.__defineGetter__('height', function() { - return this.getHeight(); -}); - Block.prototype.__defineGetter__('nextBlock', function() { return this.getNextBlock(); }); @@ -632,7 +644,6 @@ Block.prototype.inspect = function inspect() { delete copy._chain; copy.hash = this.hash('hex'); copy.rhash = this.rhash; - copy.height = this.getHeight(); copy.nextBlock = this.getNextBlock(); copy.reward = utils.btc(this.getReward()); copy.fee = utils.btc(this.getFee()); @@ -648,6 +659,7 @@ Block.prototype.toJSON = function toJSON() { hash: this.hash('hex'), prevBlock: this.prevBlock, ts: this.ts, + height: this.height, network: this.network, relayedBy: this.relayedBy, block: utils.toHex(this.render()) @@ -669,6 +681,7 @@ Block.fromJSON = function fromJSON(json) { else if (json.subtype === 'block' || json.subtype === 'header') data = parser.parseBlock(raw); + data.height = json.height; data.network = json.network; data.relayedBy = json.relayedBy; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index a3303bf1..1c90793e 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -367,6 +367,12 @@ Chain.prototype.add = function add(block, peer) { if (code !== Chain.codes.okay) break; + // Update the block height + block.height = entry.height; + block.txs.forEach(function(tx) { + tx.height = entry.height; + }, this); + // Keep track of the number of blocks we // added and the number of orphans resolved. total++; @@ -376,11 +382,11 @@ Chain.prototype.add = function add(block, peer) { this.emit('block', block, peer); this.emit('entry', entry); if (block !== initial) - this.emit('resolved', entry); - } + this.emit('resolved', entry.hash); - // Fullfill request - this.request.fullfill(hash, block); + // Fullfill request + this.request.fullfill(hash, block); + } if (!this.orphan.map[hash]) break; @@ -396,6 +402,9 @@ Chain.prototype.add = function add(block, peer) { // Failsafe for large orphan chains. Do not // allow more than 20mb stored in memory. if (this.orphan.size > 20971520) { + Object.keys(this.orphan.bmap).forEach(function(hash) { + this.emit('unresolved', hash); + }, this); this.orphan.map = {}; this.orphan.bmap = {}; this.orphan.count = 0; diff --git a/lib/bcoin/coin.js b/lib/bcoin/coin.js index f2c8000d..6385ecca 100644 --- a/lib/bcoin/coin.js +++ b/lib/bcoin/coin.js @@ -34,7 +34,7 @@ function Coin(tx, index) { this.index = index; this.value = tx.outputs[index].value; this.script = tx.outputs[index].script; - this.height = tx.getHeight(); + this.height = tx.height; } else { options = tx; this.hash = options.hash; @@ -64,13 +64,17 @@ Coin.prototype.__defineGetter__('chain', function() { return this._chain || bcoin.chain.global; }); -Coin.prototype.getConfirmations = function getConfirmations() { +Coin.prototype.getConfirmations = function getConfirmations(height) { var top; - if (!this.chain) - return 0; + if (height == null) { + if (!this.chain) + return 0; - top = this.chain.height(); + top = this.chain.height(); + } else { + top = height; + } if (this.height === -1) return 0; @@ -85,8 +89,8 @@ Coin.prototype.__defineGetter__('confirmations', function() { return this.getConfirmations(); }); -Coin.prototype.getAge = function getAge() { - var age = this.getConfirmations(); +Coin.prototype.getAge = function getAge(height) { + var age = this.getConfirmations(height); if (age === -1) age = 0; diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 7f270f50..283847b6 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -35,7 +35,7 @@ function Input(options) { this.prevout.hash = utils.toHex(this.prevout.hash); if (!this.prevout.rhash) - this.prevout.rhash = utils.toHex(this.prevout.hash); + this.prevout.rhash = utils.revHex(this.prevout.hash); this.script = options.script ? options.script.slice() : []; this.sequence = options.sequence == null ? 0xffffffff : options.sequence; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 67d96236..88fb8c1e 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -341,6 +341,9 @@ Peer.prototype._onPacket = function onPacket(packet) { var cmd = packet.cmd; var payload = packet.payload; + if (this.lastBlock && cmd !== 'tx') + this._emitMerkle(this.lastBlock); + if (cmd === 'version') return this._handleVersion(payload); @@ -368,15 +371,28 @@ Peer.prototype._onPacket = function onPacket(packet) { if (cmd === 'reject') return this._handleReject(payload); - if (cmd === 'merkleblock' || cmd === 'block') { + if (cmd === 'block') { payload.network = true; payload.relayedBy = this.host || '0.0.0.0'; - payload = bcoin.block(payload, cmd); + payload = bcoin.block(payload, 'block'); + } else if (cmd === 'merkleblock') { + payload.network = true; + payload.relayedBy = this.host || '0.0.0.0'; + payload = bcoin.block(payload, 'merkleblock'); this.lastBlock = payload; + return; } else if (cmd === 'tx') { payload.network = true; payload.relayedBy = this.host || '0.0.0.0'; payload = bcoin.tx(payload, this.lastBlock); + if (this.lastBlock) { + if (payload.block) { + this.lastBlock.txs.push(payload); + return; + } else { + this._emitMerkle(this.lastBlock); + } + } } if (this._res(cmd, payload)) @@ -385,6 +401,12 @@ Peer.prototype._onPacket = function onPacket(packet) { this.emit(cmd, payload); }; +Peer.prototype._emitMerkle = function _emitMerkle(payload) { + if (!this._res('merkleblock', payload)) + this.emit('merkleblock', payload); + this.lastBlock = null; +}; + Peer.prototype._handleVersion = function handleVersion(payload) { if (payload.v < constants.minVersion) return this._error('peer doesn\'t support required protocol version'); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 1262f0b5..e2c5ff7a 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -175,6 +175,12 @@ Pool.prototype._init = function _init() { this.chain.on('block', function(block, peer) { self.emit('block', block, peer); + // Emit merkle txs after the fact + if (block.subtype === 'merkleblock') { + block.txs.forEach(function(tx) { + self._handleTX(tx, peer); + }); + } }); this.chain.on('fork', function(data, peer) { @@ -748,14 +754,7 @@ Pool.prototype._createPeer = function _createPeer(priority) { }); peer.on('tx', function(tx) { - var requested = self._response(tx); - var added = self._addTX(tx, 1); - - if (added || tx.block) - self.emit('tx', tx, peer); - - if (!self.options.fullNode && tx.block) - self.emit('watched', tx, peer); + self._handleTX(tx); }); peer.on('addr', function(data) { @@ -791,6 +790,17 @@ Pool.prototype._createPeer = function _createPeer(priority) { return peer; }; +Pool.prototype._handleTX = function _handleTX(tx, peer) { + var requested = this._response(tx); + var added = this._addTX(tx, 1); + + if (added || tx.block) + this.emit('tx', tx, peer); + + if (!this.options.fullNode && tx.block) + this.emit('watched', tx, peer); +}; + Pool.prototype._addPeer = function _addPeer() { var self = this; var peer; @@ -1171,11 +1181,12 @@ Pool.prototype.searchWallet = function(w, h) { return; if (w == null) { - height = this.wallets.reduce(function(ts, w) { + height = this.wallets.reduce(function(height, w) { if (w.lastHeight < height) return w.lastHeight; - return ts; + return height; }, Infinity); + assert(height !== Infinity); ts = this.wallets.reduce(function(ts, w) { if (w.lastTs < ts) return w.lastTs; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index fa713a10..7d1ddf52 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -282,7 +282,7 @@ script.concat = function concat(scripts) { var s = []; var i; - s.push(scripts[0]); + s = s.concat(scripts[0]); for (i = 1; i < scripts.length; i++) { s.push('codeseparator'); @@ -2129,20 +2129,16 @@ script.format = function format(s) { scripts.push(s.script); } - scripts = scripts.map(function(script) { - return script.map(function(chunk) { - if (Array.isArray(chunk)) { - if (chunk.length === 0) - return 0 + ''; - return '[' + utils.toHex(chunk) + ']'; - } - if (typeof chunk === 'number') - return chunk + ''; - return chunk; - }).join(' '); - }); - - return script.concat(scripts); + return script.concat(scripts).map(function(chunk) { + if (Array.isArray(chunk)) { + if (chunk.length === 0) + return 0 + ''; + return '[' + utils.toHex(chunk) + ']'; + } + if (typeof chunk === 'number') + return chunk + ''; + return chunk; + }).join(' '); }; script.isPushOnly = function isPushOnly(s) { diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index de6dbb58..75f85ce1 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -84,7 +84,7 @@ TXPool.prototype._init = function init() { TXPool.prototype.add = function add(tx, noWrite, strict) { var hash = tx.hash('hex'); var updated = false; - var i, input, output, unspent, index, orphan; + var i, input, output, coin, unspent, index, orphan; var key, orphans, some; this._wallet.fillPrevout(tx); @@ -105,9 +105,15 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { this._all[hash].ps = 0; this._all[hash].ts = tx.ts; this._all[hash].block = tx.block; + this._all[hash].height = tx.height; + this._all[hash].outputs.forEach(function(output, i) { + var key = hash + '/' + i; + if (this._unspent[key]) + this._unspent[key].height = tx.height; + }); this._storeTX(hash, tx, noWrite); this._lastTs = Math.max(tx.ts, this._lastTs); - this._lastHeight = Math.max(tx.getHeight(), this._lastHeight); + this._lastHeight = Math.max(tx.height, this._lastHeight); this.emit('update', this._lastTs, this._lastHeight, tx); this.emit('confirmed', tx); } @@ -159,7 +165,7 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { assert(index !== -1); assert(orphan.tx.inputs[index].prevout.hash === tx.hash('hex')); assert(orphan.tx.inputs[index].prevout.index === i); - orphan.tx.inputs[index].output = bcoin.coin(tx, i); + orphan.tx.inputs[index].output = coin; // Verify that input script is correct, if not - add output to unspent // and remove orphan from storage @@ -180,6 +186,8 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { if (!this._wallet.ownOutput(tx, i)) continue; + coin = bcoin.coin(tx, i); + this._addOutput(tx, i); key = hash + '/' + i; @@ -194,13 +202,13 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { delete this._orphans[key]; if (!orphans) { - this._unspent[key] = bcoin.coin(tx, i); + this._unspent[key] = coin; updated = true; } } this._lastTs = Math.max(tx.ts, this._lastTs); - this._lastHeight = Math.max(tx.getHeight(), this._lastHeight); + this._lastHeight = Math.max(tx.height, this._lastHeight); if (updated) this.emit('update', this._lastTs, this._lastHeight, tx); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 54b672b6..909d5a1f 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -38,6 +38,7 @@ function TX(data, block) { this._raw = data._raw || null; this._size = data._size || 0; + this.height = data.height != null ? data.height : -1; this.network = data.network || false; this.relayedBy = data.relayedBy || '0.0.0.0'; @@ -64,10 +65,12 @@ function TX(data, block) { if (block.hasTX(this.hash('hex'))) { this.ts = block.ts; this.block = block.hash('hex'); + this.height = block.height; } } else { this.ts = block.ts; this.block = block.hash('hex'); + this.height = block.height; } } @@ -1392,11 +1395,18 @@ TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { return outputs; }; -TX.prototype.avoidFeeSnipping = function avoidFeeSnipping() { - if (!this.chain) - return; +TX.prototype.avoidFeeSnipping = function avoidFeeSnipping(height) { + if (height == null) { + if (!this.chain) + return; - this.setLocktime(this.chain.height()); + height = this.chain.height(); + } + + if (height === -1) + height = 0; + + this.setLocktime(height); if ((Math.random() * 10 | 0) === 0) this.setLocktime(Math.max(0, this.locktime - (Math.random() * 100 | 0))); @@ -1477,50 +1487,10 @@ TX.prototype.fillPrevout = function fillPrevout(txs, unspent) { return inputs.length === this.inputs.length; }; -// Used for verifyContext/ContextualBlockCheck and miner isFinalTx call. -// BIP113 will require that time-locked transactions have nLockTime set to -// less than the median time of the previous block they're contained in. -TX.prototype.isFinalBlock = function isFinalBlock(block, prev, useMedian) { - var height = prev.height + 1; - var ts = useMedian ? prev.getMedianTime() : block.ts; - return this.isFinal(height, ts); -}; - -// Used in AcceptToMemoryPool -TX.prototype.isFinalMempool = function isFinalMempool(useMedian) { - var height, ts; - - if (!this.chain) - return true; - - height = this.chain.height() + 1; - ts = useMedian - ? this.chain.getTip().getMedianTime() - : utils.now(); - - return this.isFinal(height, ts); -}; - -// Used in the original bitcoind code for AcceptBlock -TX.prototype.isFinalLegacy = function isFinalLegacy(block) { - var ts, height; - - if (!this.chain) - return true; - - ts = block ? block.ts : utils.now(); - height = this.chain.height(); - - return this.isFinal(height, ts); -}; - TX.prototype.isFinal = function isFinal(height, ts) { var threshold = constants.locktimeThreshold; var i; - if (!this.chain) - return true; - if (this.locktime === 0) return true; @@ -1649,18 +1619,26 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { }; TX.prototype.getPriority = function getPriority() { - var size, value, i, input, age; + var size, sum, i, input, age, height; + + height = this.height; + + if (height === -1) + height = null; + + if (!this.hasPrevout()) + return new bn(0); size = this.maxSize(); - value = new bn(0); + sum = new bn(0); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (!input.output) - return constants.tx.freeThreshold.clone(); + return new bn(0); - age = input.output.getConfirmations(); + age = input.output.getConfirmations(height); if (age === -1) age = 0; @@ -1668,15 +1646,19 @@ TX.prototype.getPriority = function getPriority() { if (age !== 0) age += 1; - value.iadd(input.output.value.muln(age)); + sum.iadd(input.output.value.muln(age)); } - return value.divn(size); + return sum.divn(size); }; TX.prototype.isFree = function isFree() { - var size = this.maxSize(); - var priority; + var size, priority; + + if (!this.hasPrevout()) + return false; + + size = this.maxSize(); if (size >= constants.tx.maxFreeSize) return false; @@ -1687,19 +1669,28 @@ TX.prototype.isFree = function isFree() { }; TX.prototype.getHeight = function getHeight() { + if (this.height !== -1) + return this.height; + if (!this.chain) return -1; + return this.block ? this.chain.getHeight(this.block) : -1; }; -TX.prototype.getConfirmations = function getConfirmations() { +TX.prototype.getConfirmations = function getConfirmations(height) { var top, height; - if (!this.chain) - return 0; + if (height == null) { + if (!this.chain) + return 0; - top = this.chain.height(); - height = this.getHeight(); + top = this.chain.height(); + } else { + top = height; + } + + height = this.height; if (height === -1) return 0; @@ -1748,10 +1739,6 @@ TX.prototype.__defineGetter__('value', function() { return this.getValue(); }); -TX.prototype.__defineGetter__('height', function() { - return this.getHeight(); -}); - TX.prototype.__defineGetter__('confirmations', function() { return this.getConfirmations(); }); @@ -1773,10 +1760,8 @@ TX.prototype.inspect = function inspect() { copy.rblock = this.rblock; copy.value = utils.btc(this.getValue()); copy.fee = utils.btc(this.getFee()); - copy.height = this.getHeight(); copy.confirmations = this.getConfirmations(); - if (this.hasPrevout()) - copy.priority = this.getPriority().toString(10); + copy.priority = this.getPriority().toString(10); copy.date = new Date((copy.ts || 0) * 1000).toISOString(); if (copy.hardFee) copy.hardFee = utils.btc(copy.hardFee); @@ -1791,12 +1776,13 @@ TX.prototype.toJSON = function toJSON() { ts: this.ts, ps: this.ps, block: this.block, + height: this.height, network: this.network, relayedBy: this.relayedBy, changeAddress: this.changeAddress, changeIndex: this.changeIndex, hardFee: this.hardFee ? utils.btc(this.hardFee) : null, - outputs: this.inputs.map(function(input) { + coins: this.inputs.map(function(input) { return input.output ? input.output.toJSON() : null; }), tx: utils.toHex(this.render()) @@ -1825,11 +1811,12 @@ TX.fromJSON = function fromJSON(json) { data._size = raw.length; tx = new TX(data); + tx.height = json.height; tx.ts = json.ts; tx.block = json.block || null; tx.ps = json.ps; - json.outputs.forEach(function(output, i) { + json.coins.forEach(function(output, i) { if (!output) return;