chain: versionbits refactor.

This commit is contained in:
Christopher Jeffrey 2016-11-23 15:18:38 -08:00
parent 390f7d8ddb
commit 1f22013ce0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 227 additions and 103 deletions

View File

@ -436,6 +436,7 @@ Chain.prototype.verify = co(function* verify(block, prev) {
*/
Chain.prototype.getDeployments = co(function* getDeployments(block, prev) {
var deployments = this.network.deployments;
var height = prev.height + 1;
var state = new DeploymentState();
var active;
@ -496,7 +497,7 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) {
// CHECKSEQUENCEVERIFY and median time
// past locktimes are now usable (bip9 & bip113).
active = yield this.isActive(prev, 'csv');
active = yield this.isActive(prev, deployments.csv);
if (active) {
state.flags |= constants.flags.VERIFY_CHECKSEQUENCEVERIFY;
state.lockFlags |= constants.flags.VERIFY_SEQUENCE;
@ -504,7 +505,7 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) {
}
// Segregrated witness is now usable (bip141 - segnet4)
active = yield this.isActive(prev, 'witness');
active = yield this.isActive(prev, deployments.witness);
if (active) {
if (this.options.witness)
state.flags |= constants.flags.VERIFY_WITNESS;
@ -645,7 +646,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
}
// Verify sequence locks.
valid = yield this.checkLocks(prev, tx, state.lockFlags);
valid = yield this.verifyLocks(prev, tx, state.lockFlags);
if (!valid) {
throw new VerifyError(block,
@ -936,6 +937,13 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
yield this.reorganize(entry, block);
}
// Warn of unknown versionbits.
if (entry.hasUnknown()) {
this.logger.warning(
'Unknown version bits in block %d: %d.',
entry.height, entry.version);
}
// Otherwise, everything is in order.
// Do "contextual" verification on our block
// now that we're certain its previous
@ -995,6 +1003,13 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
throw e;
}
// Warn of unknown versionbits.
if (entry.hasUnknown()) {
this.logger.warning(
'Unknown version bits in block %d: %d.',
entry.height, entry.version);
}
yield this.db.save(entry, block);
});
@ -1912,7 +1927,7 @@ Chain.prototype.findLocator = co(function* findLocator(locator) {
* @returns {Promise} - Returns Number.
*/
Chain.prototype.isActive = co(function* isActive(prev, id) {
Chain.prototype.isActive = co(function* isActive(prev, deployment) {
var state;
if (!this.options.witness) {
@ -1920,7 +1935,7 @@ Chain.prototype.isActive = co(function* isActive(prev, id) {
return false;
}
state = yield this.getState(prev, id);
state = yield this.getState(prev, deployment);
return state === constants.thresholdStates.ACTIVE;
});
@ -1935,20 +1950,14 @@ Chain.prototype.isActive = co(function* isActive(prev, id) {
* @returns {Promise} - Returns Number.
*/
Chain.prototype.getState = co(function* getState(prev, id) {
Chain.prototype.getState = co(function* getState(prev, deployment) {
var period = this.network.minerWindow;
var threshold = this.network.activationThreshold;
var deployment = this.network.deployments[id];
var thresholdStates = constants.thresholdStates;
var bit = deployment.bit;
var timeStart, timeTimeout, compute, height;
var i, entry, count, state, block, medianTime;
assert(deployment);
timeStart = deployment.startTime;
timeTimeout = deployment.timeout;
compute = [];
var compute = [];
var i, entry, count, state;
var block, time, height;
if (!prev)
return thresholdStates.DEFINED;
@ -1972,9 +1981,9 @@ Chain.prototype.getState = co(function* getState(prev, id) {
break;
}
medianTime = yield entry.getMedianTimeAsync();
time = yield entry.getMedianTimeAsync();
if (medianTime < timeStart) {
if (time < deployment.startTime) {
state = thresholdStates.DEFINED;
this.db.stateCache.set(bit, entry, state);
break;
@ -1991,23 +2000,23 @@ Chain.prototype.getState = co(function* getState(prev, id) {
switch (state) {
case thresholdStates.DEFINED:
medianTime = yield entry.getMedianTimeAsync();
time = yield entry.getMedianTimeAsync();
if (medianTime >= timeTimeout) {
if (time >= deployment.timeout) {
state = thresholdStates.FAILED;
break;
}
if (medianTime >= timeStart) {
if (time >= deployment.startTime) {
state = thresholdStates.STARTED;
break;
}
break;
case thresholdStates.STARTED:
medianTime = yield entry.getMedianTimeAsync();
time = yield entry.getMedianTimeAsync();
if (medianTime >= timeTimeout) {
if (time >= deployment.timeout) {
state = thresholdStates.FAILED;
break;
}
@ -2016,7 +2025,7 @@ Chain.prototype.getState = co(function* getState(prev, id) {
count = 0;
for (i = 0; i < period; i++) {
if (block.hasBit(deployment))
if (block.hasBit(bit))
count++;
if (count >= threshold) {
@ -2054,18 +2063,16 @@ Chain.prototype.getState = co(function* getState(prev, id) {
*/
Chain.prototype.computeBlockVersion = co(function* computeBlockVersion(prev) {
var keys = Object.keys(this.network.deployments);
var version = 0;
var i, id, deployment, state;
var i, deployment, state;
for (i = 0; i < keys.length; i++) {
id = keys[i];
deployment = this.network.deployments[id];
state = yield this.getState(prev, id);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
state = yield this.getState(prev, deployment);
if (state === constants.thresholdStates.LOCKED_IN
|| state === constants.thresholdStates.STARTED) {
version |= (1 << deployment.bit);
version |= 1 << deployment.bit;
}
}
@ -2165,31 +2172,6 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
return new LockTimes(minHeight, minTime);
});
/**
* Evaluate sequence locks.
* @param {ChainEntry} prev
* @param {Number} minHeight
* @param {Number} minTime
* @returns {Promise} - Returns Boolean.
*/
Chain.prototype.evalLocks = co(function* evalLocks(prev, minHeight, minTime) {
var medianTime;
if (minHeight >= prev.height + 1)
return false;
if (minTime === -1)
return true;
medianTime = yield prev.getMedianTimeAsync();
if (minTime >= medianTime)
return false;
return true;
});
/**
* Verify sequence locks.
* @param {TX} tx
@ -2198,9 +2180,22 @@ Chain.prototype.evalLocks = co(function* evalLocks(prev, minHeight, minTime) {
* @returns {Promise} - Returns Boolean.
*/
Chain.prototype.checkLocks = co(function* checkLocks(prev, tx, flags) {
var times = yield this.getLocks(prev, tx, flags);
return yield this.evalLocks(prev, times.height, times.time);
Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, flags) {
var locks = yield this.getLocks(prev, tx, flags);
var medianTime;
if (locks.height >= prev.height + 1)
return false;
if (locks.time === -1)
return true;
medianTime = yield prev.getMedianTimeAsync();
if (locks.time >= medianTime)
return false;
return true;
});
/**

View File

@ -144,6 +144,8 @@ ChainDB.prototype._open = co(function* open() {
block = Block.fromRaw(this.network.genesisBlock, 'hex');
block.setHeight(0);
entry = ChainEntry.fromBlock(this.chain, block);
this.logger.info('Writing genesis block to ChainDB.');
yield this.save(entry, block, new CoinView());
}
@ -793,8 +795,8 @@ ChainDB.prototype.getStateCache = co(function* getStateCache() {
var i, items, item;
items = yield this.db.range({
gte: layout.s(0, constants.ZERO_HASH),
lte: layout.s(255, constants.MAX_HASH),
gte: layout.v(0, constants.ZERO_HASH),
lte: layout.v(255, constants.MAX_HASH),
values: true
});
@ -816,8 +818,8 @@ ChainDB.prototype.invalidateCache = co(function* invalidateCache(bit, batch) {
var i, keys, key;
keys = yield this.db.keys({
gte: layout.s(bit, constants.ZERO_HASH),
lte: layout.s(bit, constants.MAX_HASH)
gte: layout.v(bit, constants.ZERO_HASH),
lte: layout.v(bit, constants.MAX_HASH)
});
for (i = 0; i < keys.length; i++) {
@ -833,11 +835,12 @@ ChainDB.prototype.invalidateCache = co(function* invalidateCache(bit, batch) {
ChainDB.prototype.verifyDeployments = co(function* verifyDeployments() {
var expected = this.stateCache.toDeployments();
var current = yield this.db.get(layout.v);
var current = yield this.db.get(layout.V);
var i, invalid, bit, batch;
if (!current) {
yield this.db.put(layout.v, expected);
this.logger.info('Writing deployment params to ChainDB.');
yield this.db.put(layout.V, expected);
return true;
}
@ -855,7 +858,7 @@ ChainDB.prototype.verifyDeployments = co(function* verifyDeployments() {
yield this.invalidateCache(bit, batch);
}
batch.put(layout.v, expected);
batch.put(layout.V, expected);
yield batch.write();
@ -1358,7 +1361,7 @@ ChainDB.prototype.saveUpdates = function saveUpdates() {
for (i = 0; i < updates.length; i++) {
update = updates[i];
this.put(layout.s(update.bit, update.hash), update.toRaw());
this.put(layout.v(update.bit, update.hash), update.toRaw());
}
};
@ -2001,34 +2004,33 @@ ChainState.fromRaw = function fromRaw(data) {
*/
function StateCache(network) {
this.deployments = network.deployments;
this.bits = {};
this.cache = {};
this.network = network;
this.cache = [];
this.updates = [];
this._init();
}
StateCache.prototype._init = function _init() {
var keys = Object.keys(this.deployments);
var i, key, deployment, bit;
var i, deployment;
for (i = 0; i < keys.length; i++) {
key = keys[i];
deployment = this.deployments[key];
bit = deployment.bit;
this.cache[bit] = {};
this.bits[bit] = deployment;
for (i = 0; i < 32; i++)
this.cache.push(null);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
assert(!this.cache[deployment.bit]);
this.cache[deployment.bit] = {};
}
};
StateCache.prototype.toDeployments = function toDeployments() {
var p = new BufferWriter();
var keys = Object.keys(this.deployments);
var i, key, deployment;
var i, deployment;
for (i = 0; i < keys.length; i++) {
key = keys[i];
deployment = this.deployments[key];
p.writeU8(this.network.deploys.length);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
p.writeU8(deployment.bit);
p.writeU32(deployment.startTime);
p.writeU32(deployment.timeout);
@ -2040,13 +2042,16 @@ StateCache.prototype.toDeployments = function toDeployments() {
StateCache.prototype.verifyDeployments = function verifyDeployments(raw) {
var p = new BufferReader(raw);
var invalid = [];
var deployment, bit, start, timeout;
var i, count, deployment;
var bit, start, timeout;
while (p.left()) {
count = p.readU8();
for (i = 0; i < count; i++) {
bit = p.readU8();
start = p.readU32();
timeout = p.readU32();
deployment = this.bits[bit];
deployment = this.network.byBit(bit);
if (deployment
&& start === deployment.startTime
@ -2103,7 +2108,7 @@ StateCache.prototype.drop = function drop() {
};
StateCache.prototype.setRaw = function setRaw(key, value) {
var pair = layout.ss(key);
var pair = layout.vv(key);
var bit = pair[0];
var hash = pair[1];
var state = value[0];

View File

@ -362,16 +362,31 @@ ChainEntry.prototype.isHistorical = function isHistorical() {
return false;
};
/**
* Test whether the entry contains an unknown version bit.
* @returns {Boolean}
*/
ChainEntry.prototype.hasUnknown = function hasUnknown() {
var bits = this.version & constants.versionbits.TOP_MASK;
var topBits = constants.versionbits.TOP_BITS;
if ((bits >>> 0) !== topBits)
return false;
return (this.version & this.network.unknownBits) !== 0;
};
/**
* Test whether the entry contains a version bit.
* @param {Object} deployment
* @returns {Boolean}
*/
ChainEntry.prototype.hasBit = function hasBit(deployment) {
ChainEntry.prototype.hasBit = function hasBit(bit) {
var bits = this.version & constants.versionbits.TOP_MASK;
var topBits = constants.versionbits.TOP_BITS;
var mask = 1 << deployment.bit;
var mask = 1 << bit;
return (bits >>> 0) === topBits && (this.version & mask) !== 0;
};

View File

@ -13,7 +13,7 @@ var pad32 = util.pad32;
var layout = {
R: 'R',
O: 'O',
v: 'v',
V: 'v',
e: function e(hash) {
return 'e' + hex(hash);
},
@ -41,10 +41,10 @@ var layout = {
u: function u(hash) {
return 'u' + hex(hash);
},
s: function s(bit, hash) {
return 's' + pad8(bit) + hex(hash);
v: function v(bit, hash) {
return 'v' + pad8(bit) + hex(hash);
},
ss: function ss(key) {
vv: function vv(key) {
return [+key.slice(1, 4), key.slice(4, 36)];
},
T: function T(address, hash) {

View File

@ -30,7 +30,7 @@
var layout = {
R: new Buffer([0x52]),
O: new Buffer([0x4f]),
v: new Buffer([0x76]),
V: new Buffer([0x76]),
e: function e(hash) {
return pair(0x65, hash);
},
@ -58,14 +58,14 @@ var layout = {
u: function u(hash) {
return pair(0x75, hash);
},
s: function s(bit, hash) {
v: function v(bit, hash) {
var key = new Buffer(1 + 1 + 32);
key[0] = 0x73;
key[0] = 0x76;
key[1] = bit;
write(key, hash, 2);
return key;
},
ss: function ss(key) {
vv: function vv(key) {
return [key[1], key.toString('hex', 2, 34)];
},
T: function T(address, hash) {

View File

@ -848,7 +848,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
var ret = new VerifyResult();
var now, minFee, count, result;
result = yield this.checkLocks(tx, lockFlags);
result = yield this.verifyLocks(tx, lockFlags);
if (!result) {
throw new VerifyError(tx,
@ -1522,8 +1522,8 @@ Mempool.prototype.getSnapshot = function getSnapshot() {
* @returns {Promise} - Returns Boolean.
*/
Mempool.prototype.checkLocks = function checkLocks(tx, flags) {
return this.chain.checkLocks(this.chain.tip, tx, flags);
Mempool.prototype.verifyLocks = function verifyLocks(tx, flags) {
return this.chain.verifyLocks(this.chain.tip, tx, flags);
};
/**

View File

@ -8,7 +8,9 @@
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var networks = require('./networks');
var constants = require('./constants');
/**
* Represents a network.
@ -39,6 +41,8 @@ function Network(options) {
this.activationThreshold = options.activationThreshold;
this.minerWindow = options.minerWindow;
this.deployments = options.deployments;
this.deploys = options.deploys;
this.unknownBits = ~constants.versionbits.TOP_MASK;
this.keyPrefix = options.keyPrefix;
this.addressPrefix = options.addressPrefix;
this.requireStandard = options.requireStandard;
@ -49,6 +53,8 @@ function Network(options) {
this.selfConnect = options.selfConnect;
this.requestMempool = options.requestMempool;
this.batchSize = options.batchSize;
this._init();
}
/**
@ -76,6 +82,39 @@ Network.segnet3 = null;
Network.segnet4 = null;
Network.simnet = null;
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
Network.prototype._init = function _init() {
var bits = 0;
var i, deployment;
for (i = 0; i < this.deploys.length; i++) {
deployment = this.deploys[i];
bits |= 1 << deployment.bit;
}
bits |= constants.versionbits.TOP_MASK;
this.unknownBits = ~bits;
};
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
Network.prototype.byBit = function byBit(bit) {
var index = util.binarySearch(this.deploys, bit, cmpBit);
if (index === -1)
return null;
return this.deploys[index];
};
/**
* Determine how many blocks to request
* based on current height of the chain.
@ -240,6 +279,14 @@ Network.isNetwork = function isNetwork(obj) {
Network.set(process.env.BCOIN_NETWORK || 'main');
/*
* Helpers
*/
function cmpBit(a, b) {
return a.bit - b;
}
/*
* Expose
*/

View File

@ -334,24 +334,28 @@ main.minerWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
main.deployments = {
testdummy: {
name: 'testdummy',
bit: 28,
startTime: 1199145601, // January 1, 2008
timeout: 1230767999, // December 31, 2008
force: true
},
csv: {
name: 'csv',
bit: 0,
startTime: 1462060800, // May 1st, 2016
timeout: 1493596800, // May 1st, 2017
force: true
},
witness: {
name: 'witness',
bit: 1,
startTime: 1479168000, // November 15th, 2016.
timeout: 1510704000, // November 15th, 2017.
force: false
},
mast: {
name: 'mast',
bit: 2,
startTime: 0xffffffff, // Far in the future
timeout: 0xffffffff,
@ -359,6 +363,19 @@ main.deployments = {
}
};
/**
* Deployments for versionbits (array form, sorted).
* @const {Array}
* @default
*/
main.deploys = [
main.deployments.csv,
main.deployments.witness,
main.deployments.mast,
main.deployments.testdummy
];
/**
* Key prefixes.
* @enum {Number}
@ -562,24 +579,28 @@ testnet.minerWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
testnet.deployments = {
testdummy: {
name: 'testdummy',
bit: 28,
startTime: 1199145601, // January 1, 2008
timeout: 1230767999, // December 31, 2008
force: true
},
csv: {
name: 'csv',
bit: 0,
startTime: 1456790400, // March 1st, 2016
timeout: 1493596800, // May 1st, 2017
force: true
},
witness: {
name: 'witness',
bit: 1,
startTime: 1462060800, // May 1st 2016
timeout: 1493596800, // May 1st 2017
force: false
},
mast: {
name: 'mast',
bit: 2,
startTime: 0xffffffff, // Far in the future
timeout: 0xffffffff,
@ -587,6 +608,13 @@ testnet.deployments = {
}
};
testnet.deploys = [
testnet.deployments.csv,
testnet.deployments.witness,
testnet.deployments.mast,
testnet.deployments.testdummy
];
testnet.keyPrefix = {
privkey: 0xef,
xpubkey: 0x043587cf,
@ -709,24 +737,28 @@ regtest.minerWindow = 144; // Faster than normal for regtest (144 instead of 201
regtest.deployments = {
testdummy: {
name: 'testdummy',
bit: 28,
startTime: 0,
timeout: 0xffffffff,
force: true
},
csv: {
name: 'csv',
bit: 0,
startTime: 0,
timeout: 0xffffffff,
force: true
},
witness: {
name: 'witness',
bit: 1,
startTime: 0,
timeout: 0xffffffff,
force: false
},
mast: {
name: 'mast',
bit: 2,
startTime: 0xffffffff, // Far in the future
timeout: 0xffffffff,
@ -734,6 +766,13 @@ regtest.deployments = {
}
};
regtest.deploys = [
regtest.deployments.csv,
regtest.deployments.witness,
regtest.deployments.mast,
regtest.deployments.testdummy
];
regtest.keyPrefix = {
privkey: 0xef,
xpubkey: 0x043587cf,
@ -857,6 +896,8 @@ segnet3.minerWindow = 144;
segnet3.deployments = {};
segnet3.deploys = [];
segnet3.keyPrefix = {
privkey: 0x9e,
xpubkey: 0x053587cf,
@ -978,18 +1019,21 @@ segnet4.minerWindow = 144;
segnet4.deployments = {
testdummy: {
name: 'testdummy',
bit: 28,
startTime: 1199145601, // January 1, 2008
timeout: 1230767999, // December 31, 2008
force: true
},
csv: {
name: 'csv',
bit: 0,
startTime: 1456790400, // March 1st, 2016
timeout: 1493596800, // May 1st, 2017
force: true
},
witness: {
name: 'witness',
bit: 1,
startTime: 0,
timeout: 999999999999,
@ -997,6 +1041,12 @@ segnet4.deployments = {
}
};
segnet4.deploys = [
segnet4.deployments.csv,
segnet4.deployments.witness,
segnet4.deployments.testdummy
];
segnet4.keyPrefix = {
privkey: 0x9e,
xpubkey: 0x053587cf,
@ -1120,24 +1170,28 @@ simnet.minerWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
simnet.deployments = {
testdummy: {
name: 'testdummy',
bit: 28,
startTime: 1199145601, // January 1, 2008
timeout: 1230767999, // December 31, 2008
force: true
},
csv: {
name: 'csv',
bit: 0,
startTime: 1456790400, // March 1st, 2016
timeout: 1493596800, // May 1st, 2017
force: true
},
witness: {
name: 'witness',
bit: 1,
startTime: 1462060800, // May 1st 2016
timeout: 1493596800, // May 1st 2017
force: false
},
mast: {
name: 'mast',
bit: 2,
startTime: 0xffffffff, // Far in the future
timeout: 0xffffffff,
@ -1145,6 +1199,13 @@ simnet.deployments = {
}
};
simnet.deploys = [
simnet.deployments.csv,
simnet.deployments.witness,
simnet.deployments.mast,
simnet.deployments.testdummy
];
simnet.keyPrefix = {
privkey: 0x64,
xpubkey: 0x0420bd3a,

View File

@ -263,10 +263,11 @@ describe('Chain', function() {
}));
it('should activate csv', cob(function* () {
var deployments = chain.network.deployments;
var i, block, prev, state, cache;
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, 'csv');
state = yield chain.getState(prev, deployments.csv);
assert(state === 0);
for (i = 0; i < 417; i++) {
@ -275,17 +276,17 @@ describe('Chain', function() {
switch (chain.height) {
case 144:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, 'csv');
state = yield chain.getState(prev, deployments.csv);
assert(state === 1);
break;
case 288:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, 'csv');
state = yield chain.getState(prev, deployments.csv);
assert(state === 2);
break;
case 432:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, 'csv');
state = yield chain.getState(prev, deployments.csv);
assert(state === 3);
break;
}