From 7c6f71b7859d4a1ecfe1d4217e56434b21a75326 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Mar 2016 01:14:03 -0800 Subject: [PATCH] chainblock methods. version bits deployment checking. --- lib/bcoin/chain.js | 175 ++++++++++++++++++++++++++++++-- lib/bcoin/chainblock.js | 101 +++++++++++++----- lib/bcoin/profiler.js | 14 +-- lib/bcoin/protocol/constants.js | 17 ++++ lib/bcoin/protocol/network.js | 34 +++++++ 5 files changed, 303 insertions(+), 38 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index b3025358..6a14e270 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -476,7 +476,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { return done(null, false); } - prev.alloc(function(err) { + prev.ensureAncestors(function(err) { if (err) return callback(err); @@ -955,6 +955,14 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { this.lastUpdate = utils.now(); + // Start fsyncing writes once we're no + // longer dealing with historical data. + if (this.isFull()) { + this.db.fsync = true; + if (this.blockdb) + this.blockdb.fsync = true; + } + if (!this.tip) { if (entry.hash !== network.genesis.hash) return callback(new Error('Bad genesis block.')); @@ -981,14 +989,6 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { self.tip = entry; self.height = entry.height; - // Start fsyncing writes once we're no - // longer dealing with historical data. - if (self.isFull()) { - self.db.fsync = true; - if (self.blockdb) - self.blockdb.fsync = true; - } - return callback(); }); }); @@ -1895,6 +1895,163 @@ Chain.prototype.retarget = function retarget(last, first) { return utils.toCompact(target); }; +// https://github.com/bitcoin/bitcoin/pull/7648/files +Chain.prototype.getState = function getState(prev, block, id, callback) { + var self = this; + var period = network.minerConfirmationWindow; + var threshold = network.ruleChangeActivationThreshold; + var deployment = network.deployments[id]; + var timeStart, timeTimeout, compute, height; + + if (!deployment) + return callback(null, constants.thresholdStates.FAILED); + + timeStart = deployment.startTime; + timeTimeout = deployment.timeout; + compute = []; + + if (block && block.isGenesis()) + return callback(null, constants.thresholdStates.DEFINED); + + if (((prev.height + 1) % period) !== 0) { + height = prev.height - ((prev.height + 1) % period); + return prev.getAncestorByHeight(height, function(err, ancestor) { + if (err) + return callback(err); + if (!ancestor) + return callback(null, constants.thresholdStates.DEFINED); + return self.getState(ancestor, block, callback); + }); + } + + function condition(entry) { + var bits = entry.version & constants.versionbits.TOP_MASK; + var topBits = constants.versionbits.TOP_BITS; + var mask = 1 << deployment.bit; + return (bits === topBits) && (entry.version & mask) !== 0; + } + + (function walk(err, entry) { + if (err) + return callback(err); + + if (!entry) + return walkForward(constants.thresholdStates.DEFINED); + + return entry.getMedianTimeAsync(function(err, medianTime) { + if (err) + return walk(err); + + if (medianTime < timeStart) + return walkForward(constants.thresholdStates.DEFINED); + + compute.push(entry); + + height = entry.height - period; + + return entry.getAncestorByHeight(height, walk); + }); + })(null, prev); + + function walkForward(state) { + var entry; + + if (compute.length === 0) + return callback(null, state); + + entry = compute.pop(); + + switch (state) { + case constants.thresholdStates.DEFINED: + return entry.getMedianTimeAsync(function(err, medianTime) { + if (err) + return callback(err); + + if (medianTime >= timeTimeout) + return walkForward(constants.thresholdStates.FAILED); + + if (medianTime >= timeStart) + return walkForward(constants.thresholdStates.STARTED); + + return walkForward(state); + }); + case constants.thresholdStates.STARTED: + return entry.getMedianTimeAsync(function(err, medianTime) { + if (err) + return callback(err); + + if (medianTime >= timeTimeout) + return walkForward(constants.thresholdStates.FAILED); + + var count = 0; + var i = 0; + + (function next(err, entry) { + if (err) + return callback(err); + + if (!entry) + return doneCounting(); + + if (i++ >= period) + return doneCounting(); + + if (condition(entry)) + count++; + + return self.db.get(entry.prevBlock, next); + })(null, entry); + + function doneCounting(err) { + if (err) + return callback(err); + + if (count >= threshold) + return walkForward(constants.thresholdStates.LOCKED_IN); + + return walkForward(state); + } + }); + case constants.thresholdStates.LOCKED_IN: + return walkForward(constants.thresholdStates.ACTIVE); + case constants.thresholdStates.FAILED: + case constants.thresholdStates.ACTIVE: + return walkForward(state); + } + + assert(false, 'Bad state.'); + } +}; + +Chain.prototype.computeBlockVersion = function computeBlockVersion(prev, callback) { + var version = 0; + + utils.forEachSerial(Object.keys(network.deployments), function(id, next) { + var deployment = network.deployments[id]; + self.getState(prev, null, id, function(err, state) { + if (err) + return next(err); + + if (state === constants.thresholdStates.LOCKED_IN + || state === constants.thresholdStates.STARTED) { + version |= (1 << deployment.bit); + } + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + if (version === 0) + return callback(null, constants.versionbits.LAST_OLD_BLOCK_VERSION); + + version |= constants.versionbits.TOP_BITS; + + return callback(null, version); + }); +}; + /** * Expose */ diff --git a/lib/bcoin/chainblock.js b/lib/bcoin/chainblock.js index 4259f9cb..1ff5b527 100644 --- a/lib/bcoin/chainblock.js +++ b/lib/bcoin/chainblock.js @@ -153,7 +153,7 @@ ChainBlock.prototype.isSuperMajorityAsync = function isSuperMajority(version, re })(null, this); }; -ChainBlock.prototype.alloc = function alloc(callback) { +ChainBlock.prototype.ensureAncestors = function ensureAncestors(callback) { var majorityWindow = network.block.majorityWindow; var medianTimespan = constants.block.medianTimespan; var powDiffInterval = network.powDiffInterval; @@ -161,25 +161,52 @@ ChainBlock.prototype.alloc = function alloc(callback) { var max = Math.max(majorityWindow, medianTimespan); if ((this.height + 1) % powDiffInterval === 0 || allowMinDiff) max = Math.max(max, powDiffInterval); - return this._alloc(max, callback); + assert(this.previous.length === 0); + return this.alloc(max, callback); }; -ChainBlock.prototype._alloc = function _alloc(max, callback) { +ChainBlock.prototype.alloc = function alloc(max, callback) { + var self = this; + var i; + + return this.getAncestors(max, function(err, previous) { + if (err) + return callback(err); + + assert(previous); + + self.previous.length = 0; + + for (i = 0; i < previous.length; i++) + self.previous.push(previous[i]); + + return callback(); + }); +}; + +ChainBlock.prototype.getAncestors = function getAncestors(max, callback) { var self = this; var entry = this; + var previous = this.previous.slice(); - assert(this.previous.length === 0); + if (max === 0) + return callback(null, []); + + if (previous.length) + entry = previous.pop(); + + assert(utils.isFinite(max)); // Try to do this iteratively and synchronously // so we don't have to wait on nextTicks. for (;;) { - this.previous.push(entry); + previous.push(entry); - if (this.previous.length >= max) - return callback(); + if (previous.length >= max) + return callback(null, previous); if (!this.chain.db.hasCache(entry.prevBlock)) { - this.previous.pop(); + previous.pop(); break; } @@ -187,18 +214,16 @@ ChainBlock.prototype._alloc = function _alloc(max, callback) { } (function next(err, entry) { - if (err) { - self.free(); + if (err) return callback(err); - } if (!entry) - return callback(); + return callback(null, previous); - self.previous.push(entry); + previous.push(entry); - if (self.previous.length >= max) - return callback(); + if (previous.length >= max) + return callback(null, previous); self.chain.db.get(entry.prevBlock, next); })(null, entry); @@ -208,13 +233,29 @@ ChainBlock.prototype.free = function free() { this.previous.length = 0; }; -ChainBlock.prototype.getMedianTime = function getMedianTime() { +ChainBlock.prototype.getAncestorByHeight = function getAncestorByHeight(height, callback) { + assert(height <= this.height); + return this.getAncestor(this.height - height, callback); +}; + +ChainBlock.prototype.getAncestor = function getAncestor(index, callback) { + return this.getAncestors(index + 1, function(err, previous) { + if (err) + return callback(err); + return callback(null, previous[previous.length - 1]); + }); +}; + +ChainBlock.prototype.getMedianTime = function getMedianTime(previous) { var entry = this; var median = []; var timeSpan = constants.block.medianTimespan; var i; - for (i = 0; i < timeSpan && entry; i++, entry = this.previous[i]) + if (!previous) + previous = this.previous; + + for (i = 0; i < timeSpan && entry; i++, entry = previous[i]) median.push(entry.ts); median = median.sort(); @@ -222,29 +263,43 @@ ChainBlock.prototype.getMedianTime = function getMedianTime() { return median[median.length / 2 | 0]; }; -ChainBlock.prototype.isOutdated = function isOutdated(version) { - return this.isSuperMajority(version, network.block.majorityRejectOutdated); +ChainBlock.prototype.isOutdated = function isOutdated(version, previous) { + return this.isSuperMajority(version, network.block.majorityRejectOutdated, previous); }; -ChainBlock.prototype.isUpgraded = function isUpgraded(version) { - return this.isSuperMajority(version, network.block.majorityEnforceUpgrade); +ChainBlock.prototype.isUpgraded = function isUpgraded(version, previous) { + return this.isSuperMajority(version, network.block.majorityEnforceUpgrade, previous); }; -ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required) { +ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required, previous) { var entry = this; var found = 0; var majorityWindow = network.block.majorityWindow; var i; + if (!previous) + previous = this.previous; + for (i = 0; i < majorityWindow && found < required && entry; i++) { if (entry.version >= version) found++; - entry = this.previous[i + 1]; + entry = previous[i + 1]; } return found >= required; }; +ChainBlock.prototype.getMedianTimeAsync = function getMedianTimeAsync(callback) { + var self = this; + + return this.getAncestors(constants.block.medianTimespan, function(err, previous) { + if (err) + return callback(err); + + return callback(null, self.getMedianTime(previous)); + }); +}; + /** * Expose */ diff --git a/lib/bcoin/profiler.js b/lib/bcoin/profiler.js index b748e6c3..b9fc9ec8 100644 --- a/lib/bcoin/profiler.js +++ b/lib/bcoin/profiler.js @@ -26,9 +26,9 @@ if (profiler) { function Profile(name) { if (profiler) { name = 'profile-' + (name ? name + '-' : '') + Profile.uid++; + utils.debug('Starting CPU profile: %s', name); this.profile = profiler.startProfiling(name, true); this.name = name; - utils.debug('Starting CPU profile: %s', this.name); } } @@ -86,9 +86,9 @@ Profile.prototype.save = function save(callback) { function Snapshot(name) { if (profiler) { name = 'snapshot-' + (name ? name + '-' : '') + Snapshot.uid++; + utils.debug('Taking heap snapshot: %s', name); this.snapshot = profiler.takeSnapshot(name); this.name = name; - utils.debug('Taking heap snapshot: %s', this.name); } } @@ -161,16 +161,18 @@ exports.takeSnapshot = function takeSnapshot(name) { }; exports.snapshot = function snapshot(name, callback) { - var mem = process.memoryUsage(); - var snapshot; + var snapshot, mem; if (typeof name === 'function') { callback = name; name = null; } - utils.debug('Memory: rss=%dmb, heap=%dmb', - utils.mb(mem.rss), utils.mb(mem.heapUsed)); + if (bcoin.debug) { + mem = process.memoryUsage(); + utils.debug('Memory: rss=%dmb, heap=%dmb', + utils.mb(mem.rss), utils.mb(mem.heapUsed)); + } if (!profiler) return callback ? utils.nextTick(callback) : null; diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 73bae982..7b5b8bcc 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -325,3 +325,20 @@ exports.flags.STANDARD_VERIFY_FLAGS = // | exports.flags.VERIFY_WITNESS // | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM // | exports.flags.VERIFY_CHECKSEQUENCEVERIFY; + +exports.versionbits = { + // What block version to use for new blocks (pre versionbits) + LAST_OLD_BLOCK_VERSION: 4, + // What bits to set in version for versionbits blocks + TOP_BITS: 0x20000000, + // What bitmask determines whether versionbits is in use + TOP_MASK: 0xe0000000 +}; + +exports.thresholdStates = { + DEFINED: 0, + STARTED: 1, + LOCKED_IN: 2, + ACTIVE: 3, + FAILED: 4 +}; diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index a681e4d2..78802e60 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -149,6 +149,16 @@ main.segwitHeight = 2000000000; main.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; +main.ruleChangeActivationThreshold = 1916; // 95% of 2016 +main.minerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing +main.deployments = { + csv: { + bit: 0, + startTime: 1459468800, // April 1st, 2016 + timeout: 1491004800 // April 1st, 2017 + } +}; + /** * Testnet (v3) * https://en.bitcoin.it/wiki/Testnet @@ -262,6 +272,16 @@ testnet.segwitHeight = 2000000000; testnet.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; +testnet.ruleChangeActivationThreshold = 1512; // 75% for testchains +testnet.minerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing +testnet.deployments = { + csv: { + bit: 0, + startTime: 1459468800, + timeout: 1491004800 + } +}; + /** * Regtest */ @@ -357,6 +377,16 @@ regtest.segwitHeight = 0; regtest.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; +regtest.ruleChangeActivationThreshold = 108; // 75% for testchains +regtest.minerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016) +regtest.deployments = { + csv: { + bit: 0, + startTime: 0, + timeout: 999999999999 + } +}; + /** * Segnet */ @@ -465,6 +495,10 @@ segnet.segwitHeight = 0; segnet.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a7d719856ffff001d000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; +segnet.ruleChangeActivationThreshold = 108; +segnet.minerConfirmationWindow = 144; +segnet.deployments = {}; + network.xprivkeys = { '76066276': 'main', '70615956': 'testnet',