diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index bb6e1163..d6f474c2 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -78,6 +78,7 @@ function Chain(options) { this.lastUpdate = utils.now(); this.tip = null; this.height = -1; + this.synced = false; this.segwitActive = null; this.csvActive = null; @@ -217,8 +218,10 @@ Chain.prototype._init = function _init() { self.tip = tip; self.height = tip.height; - if (self.bestHeight === -1) + if (tip.height > self.bestHeight) { + self.bestHeight = tip.height; network.height = tip.height; + } self._getInitialState(function(err) { if (err) @@ -234,8 +237,10 @@ Chain.prototype._init = function _init() { self.emit('open'); self.emit('tip', tip); - if (self.isFull()) + if (!self.synced && self.isFull()) { + self.synced = true; self.emit('full'); + } }); }); }); @@ -621,7 +626,7 @@ Chain.prototype._checkDeployments = function _checkDeployments(block, prev, call if (block.version < 4 && prev.isOutdated(4)) return callback(new VerifyError(block, 'obsolete', 'bad-version', 0)); - // Only allow version 5 blocks (segwit) + // Only allow version 5 blocks (bip141 - segnet3) // once the majority of blocks are using it. if (network.segwitHeight !== -1 && height >= network.segwitHeight) { if (block.version < 5 && prev.isOutdated(5)) @@ -629,10 +634,8 @@ Chain.prototype._checkDeployments = function _checkDeployments(block, prev, call } // Make sure the height contained in the coinbase is correct. - if (network.block.bip34height !== -1 && height >= network.block.bip34height) { - if (block.version >= 2 && prev.isUpgraded(2)) - state.coinbaseHeight = true; - } + if (block.version >= 2 && prev.isUpgraded(2)) + state.coinbaseHeight = true; // Signature validation is now enforced (bip66) if (block.version >= 3 && prev.isUpgraded(3)) @@ -691,9 +694,9 @@ Chain.prototype._checkDeployments = function _checkDeployments(block, prev, call }; /** - * Check block for duplicate txids in blockchain - * history (BIP30). Note that txids are only considered - * duplicate if they are not yet completely spent. + * Determine whether to check block for duplicate txids in blockchain + * history (BIP30). If we're on a chain that has bip34 activated, we + * can skip this. * @private * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki * @param {Block|MerkleBlock} block @@ -711,6 +714,38 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (block.isGenesis()) return callback(); + if (network.block.bip34height === -1 || height <= network.block.bip34height) + return this._findDuplicates(block, prev, callback); + + self.db.get(network.block.bip34height, function(err, entry) { + if (err) + return callback(err); + + // It was no longer possible to create duplicate + // TXs once bip34 went into effect. We can check + // for this to avoid a DB lookup. + if (entry && entry.hash === network.block.bip34hash) + return callback(); + + return self._findDuplicates(block, prev, callback); + }); +}; + +/** + * Check block for duplicate txids in blockchain + * history (BIP30). Note that txids are only considered + * duplicate if they are not yet completely spent. + * @private + * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki + * @param {Block|MerkleBlock} block + * @param {ChainBlock} prev + * @param {Function} callback - Returns [{@link VerifyError}]. + */ + +Chain.prototype._findDuplicates = function _findDuplicates(block, prev, callback) { + var self = this; + var height = prev.height + 1; + // Check all transactions utils.forEachSerial(block.txs, function(tx, next) { var hash = tx.hash('hex'); @@ -724,8 +759,8 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba // Blocks 91842 and 91880 created duplicate // txids by using the same exact output script // and extraNonce. - if (network.type === 'main') { - if (height === 91842 || height === 91880) + if (constants.bip30[height]) { + if (block.hash('hex') === constants.bip30[height]) return next(); } return next(new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100)); @@ -1051,8 +1086,8 @@ Chain.prototype.disconnect = function disconnect(entry, callback) { self.tip = entry; self.height = entry.height; - if (self.bestHeight === -1) - network.height = entry.height; + self.bestHeight = entry.height; + network.height = entry.height; self.emit('tip', entry); @@ -1106,8 +1141,8 @@ Chain.prototype.connect = function connect(entry, callback) { self.tip = entry; self.height = entry.height; - if (self.bestHeight === -1) - network.height = entry.height; + self.bestHeight = entry.height; + network.height = entry.height; self.emit('tip', entry); @@ -1164,9 +1199,6 @@ Chain.prototype._setBestChain = function _setBestChain(entry, prev, block, callb self.tip = entry; self.height = entry.height; - if (self.bestHeight === -1) - network.height = entry.height; - self.emit('tip', entry); return callback(); @@ -1409,6 +1441,11 @@ Chain.prototype.add = function add(block, callback, force) { network.height = self.bestHeight; } + if (height > self.bestHeight) { + self.bestHeight = height; + network.height = height; + } + // If previous block wasn't ever seen, // add it current to orphans and break. if (!prev) { @@ -1554,8 +1591,10 @@ Chain.prototype.add = function add(block, callback, force) { } utils.nextTick(function() { - if (self.isFull()) + if (!self.synced && self.isFull()) { + self.synced = true; self.emit('full'); + } if (err) callback(err); @@ -1793,7 +1832,7 @@ Chain.prototype.getOrphan = function getOrphan(hash) { * @returns {Boolean} */ -Chain.prototype.isFull = function isFull() { +Chain.prototype.isFullLegacy = function isFull() { var delta; if (!this.tip) @@ -1804,6 +1843,15 @@ Chain.prototype.isFull = function isFull() { return delta < 4 * 60 * 60; }; +/** + * Test the chain to see if it is synced. + * @returns {Boolean} + */ + +Chain.prototype.isFull = function isFull() { + return !this.isInitial(); +}; + /** * Test the chain to see if it is still in the initial * syncing phase. Mimic's bitcoind's `IsInitialBlockDownload()` @@ -1812,7 +1860,7 @@ Chain.prototype.isFull = function isFull() { * @returns {Boolean} */ -Chain.prototype.isInitial = function isInitial() { +Chain.prototype.isInitialLegacy = function isInitial() { var now, delta; if (!this.tip) @@ -1825,6 +1873,28 @@ Chain.prototype.isInitial = function isInitial() { return delta < 10 && this.tip.ts < now - 24 * 60 * 60; }; +/** + * Test the chain to see if it is still in the initial + * syncing phase. Mimic's bitcoind's `IsInitialBlockDownload()` + * function. + * @see IsInitalBlockDownload() + * @returns {Boolean} + */ + +Chain.prototype.isInitial = function isInitial() { + if (!this.tip) + return true; + + if (this.synced) + return false; + + if (this.height < network.checkpoints.lastHeight) + return true; + + return this.height < this.bestHeight - 24 * 6 + || this.tip.ts < utils.now() - network.block.maxTipAge; +}; + /** * Get the fill percentage. * @returns {Number} percent - Ranges from 0.0 to 1.0. diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 1d484961..d6987804 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -1545,8 +1545,7 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) { if (!this.prune) return callback(); - // Keep the genesis block - if (block.isGenesis()) + if (block.height <= network.block.pruneAfterHeight) return callback(); futureHeight = pad32(block.height + this.keepBlocks); diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index a9dc50d5..4b7c8edc 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -330,6 +330,16 @@ exports.block = { SIGHASH_LIMIT: 1300000000 }; +/** + * Map of historical blocks which create duplicate transactions hashes. + * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki + */ + +exports.bip30 = { + 91842: 'eccae000e3c8e4e093936360431f3b7603c563c1ff6181390a4d0a0000000000', + 91880: '21d77ccb4c08386a04ac0196ae10f6a1d2c2a377558ca190f143070000000000' +}; + /** * TX-related constants. * @enum {Number} diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 4814b798..be83cade 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -229,7 +229,10 @@ main.block = { majorityEnforceUpgrade: 750, majorityRejectOutdated: 950, majorityWindow: 1000, - bip34height: 227931 + bip34height: 227931, + bip34hash: 'b808089c756add1591b1d17bab44bba3fed9e02f942ab4894b02000000000000', + pruneAfterHeight: 100000, + maxTipAge: 24 * 60 * 60 }; /** @@ -271,10 +274,20 @@ main.minerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing */ main.deployments = { + testdummy: { + bit: 28, + startTime: 1199145601, // January 1, 2008 + timeout: 1230767999 // December 31, 2008 + }, csv: { bit: 0, - startTime: 1459468800, // April 1st, 2016 - timeout: 1491004800 // April 1st, 2017 + startTime: 1462060800, // May 1st, 2016 + timeout: 1493596800 // May 1st, 2017 + }, + witness: { + bit: 1, + startTime: 2000000000, // Far in the future + timeout: 2100000000 } // bip109: { // bit: 4, @@ -418,7 +431,10 @@ testnet.block = { majorityEnforceUpgrade: 51, majorityRejectOutdated: 75, majorityWindow: 100, - bip34height: 21111 + bip34height: 21111, + bip34hash: 'f88ecd9912d00d3f5c2a8e0f50417d3e415c75b3abe584346da9b32300000000', + pruneAfterHeight: 1000, + maxTipAge: 0x7fffffff }; testnet.witness = false; @@ -428,10 +444,20 @@ testnet.segwitHeight = 2000000000; testnet.ruleChangeActivationThreshold = 1512; // 75% for testchains testnet.minerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing testnet.deployments = { + testdummy: { + bit: 28, + startTime: 1199145601, // January 1, 2008 + timeout: 1230767999 // December 31, 2008 + }, csv: { bit: 0, - startTime: 1459468800, - timeout: 1491004800 + startTime: 1456790400, // March 1st, 2016 + timeout: 1493596800 // May 1st, 2017 + }, + witness: { + bit: 1, + startTime: 2000000000, // Far in the future + timeout: 2100000000 } }; @@ -529,7 +555,10 @@ regtest.block = { majorityEnforceUpgrade: 750, majorityRejectOutdated: 950, majorityWindow: 1000, - bip34height: -1 + bip34height: -1, + bip34hash: null, + pruneAfterHeight: 1000, + maxTipAge: 24 * 60 * 60 }; regtest.witness = false; @@ -539,10 +568,20 @@ regtest.segwitHeight = -1; regtest.ruleChangeActivationThreshold = 108; // 75% for testchains regtest.minerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016) regtest.deployments = { + testdummy: { + bit: 28, + startTime: 0, + timeout: 999999999999 + }, csv: { bit: 0, startTime: 0, timeout: 999999999999 + }, + witness: { + bit: 1, + startTime: 0, + timeout: 999999999999 } }; @@ -591,12 +630,8 @@ segnet3.magic = 0xcaea962e; segnet3.port = 28333; -segnet3.alertKey = new Buffer('' - + '04302390343f91cc401d56d68b123028bf52e5f' - + 'ca1939df127f63c6467cdf9c8e2c14b61104cf8' - + '17d0b780da337893ecc4aaff1309e536162dabb' - + 'db45200ca2b0a', - 'hex'); +segnet3.alertKey = new Buffer( + '0300000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63', 'hex'); segnet3.checkpoints = {}; segnet3.checkpoints.tsLastCheckpoint = 0; @@ -643,7 +678,10 @@ segnet3.block = { majorityEnforceUpgrade: 7, majorityRejectOutdated: 9, majorityWindow: 10, - bip34height: -1 + bip34height: -1, + bip34hash: null, + pruneAfterHeight: 1000, + maxTipAge: 0x7fffffff }; segnet3.witness = true; @@ -696,12 +734,8 @@ segnet4.magic = 0xc4a1abdc; segnet4.port = 28901; -segnet4.alertKey = new Buffer('' - + '04302390343f91cc401d56d68b123028bf52e5f' - + 'ca1939df127f63c6467cdf9c8e2c14b61104cf8' - + '17d0b780da337893ecc4aaff1309e536162dabb' - + 'db45200ca2b0a', - 'hex'); +segnet4.alertKey = new Buffer( + '0300000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63', 'hex'); segnet4.checkpoints = {}; segnet4.checkpoints.tsLastCheckpoint = 0; @@ -749,7 +783,10 @@ segnet4.block = { majorityEnforceUpgrade: 7, majorityRejectOutdated: 9, majorityWindow: 10, - bip34height: -1 + bip34height: -1, + bip34hash: null, + pruneAfterHeight: 1000, + maxTipAge: 0x7fffffff }; segnet4.witness = true; @@ -759,10 +796,15 @@ segnet4.segwitHeight = -1; segnet4.ruleChangeActivationThreshold = 108; segnet4.minerConfirmationWindow = 144; segnet4.deployments = { + testdummy: { + bit: 28, + startTime: 1199145601, // January 1, 2008 + timeout: 1230767999 // December 31, 2008 + }, csv: { bit: 0, - startTime: 1459468800, // April 1st, 2016 - timeout: 1491004800 // April 1st, 2017 + startTime: 1456790400, // March 1st, 2016 + timeout: 1493596800 // May 1st, 2017 }, witness: { bit: 1,