protocol: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 19:46:19 -08:00
parent 93fe6669bf
commit 9240d2f827
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 438 additions and 426 deletions

View File

@ -14,11 +14,11 @@
const assert = require('assert');
/**
* Verify Error
* An error thrown during verification. Can be either
* a mempool transaction validation error or a blockchain
* block verification error. Ultimately used to send
* `reject` packets to peers.
* @constructor
* @extends Error
* @param {Block|TX} msg
* @param {String} code - Reject packet code.
@ -26,38 +26,43 @@ const assert = require('assert');
* @param {Number} score - Ban score increase
* (can be -1 for no reject packet).
* @param {Boolean} malleated
* @property {String} code
* @property {Buffer} hash
* @property {Number} height (will be the coinbase height if not present).
* @property {Number} score
* @property {String} message
* @property {Boolean} malleated
*/
function VerifyError(msg, code, reason, score, malleated) {
Error.call(this);
class VerifyError extends Error {
/**
* Create a verify error.
* @constructor
* @param {Block|TX} msg
* @param {String} code - Reject packet code.
* @param {String} reason - Reject packet reason.
* @param {Number} score - Ban score increase
* (can be -1 for no reject packet).
* @param {Boolean} malleated
*/
assert(typeof code === 'string');
assert(typeof reason === 'string');
assert(score >= 0);
constructor(msg, code, reason, score, malleated) {
super();
this.type = 'VerifyError';
this.message = '';
this.code = code;
this.reason = reason;
this.score = score;
this.hash = msg.hash('hex');
this.malleated = malleated || false;
assert(typeof code === 'string');
assert(typeof reason === 'string');
assert(score >= 0);
this.message = `Verification failure: ${reason}`
+ ` (code=${code} score=${score} hash=${msg.rhash()})`;
this.type = 'VerifyError';
this.message = '';
this.code = code;
this.reason = reason;
this.score = score;
this.hash = msg.hash('hex');
this.malleated = malleated || false;
if (Error.captureStackTrace)
Error.captureStackTrace(this, VerifyError);
this.message = `Verification failure: ${reason}`
+ ` (code=${code} score=${score} hash=${msg.rhash()})`;
if (Error.captureStackTrace)
Error.captureStackTrace(this, VerifyError);
}
}
Object.setPrototypeOf(VerifyError.prototype, Error.prototype);
/*
* Expose
*/

View File

@ -14,49 +14,341 @@ const consensus = require('./consensus');
const TimeData = require('./timedata');
/**
* Network
* Represents a network.
* @alias module:protocol.Network
* @constructor
* @param {Object|NetworkType} options - See {@link module:network}.
*/
function Network(options) {
if (!(this instanceof Network))
return new Network(options);
class Network {
/**
* Create a network.
* @constructor
* @param {Object} options
*/
assert(!Network[options.type], 'Cannot create two networks.');
constructor(options) {
assert(!Network[options.type], 'Cannot create two networks.');
this.type = options.type;
this.seeds = options.seeds;
this.magic = options.magic;
this.port = options.port;
this.checkpointMap = options.checkpointMap;
this.lastCheckpoint = options.lastCheckpoint;
this.checkpoints = [];
this.halvingInterval = options.halvingInterval;
this.genesis = options.genesis;
this.genesisBlock = options.genesisBlock;
this.pow = options.pow;
this.block = options.block;
this.bip30 = options.bip30;
this.activationThreshold = options.activationThreshold;
this.minerWindow = options.minerWindow;
this.deployments = options.deployments;
this.deploys = options.deploys;
this.unknownBits = ~consensus.VERSION_TOP_MASK;
this.keyPrefix = options.keyPrefix;
this.addressPrefix = options.addressPrefix;
this.requireStandard = options.requireStandard;
this.rpcPort = options.rpcPort;
this.walletPort = options.walletPort;
this.minRelay = options.minRelay;
this.feeRate = options.feeRate;
this.maxFeeRate = options.maxFeeRate;
this.selfConnect = options.selfConnect;
this.requestMempool = options.requestMempool;
this.time = new TimeData();
this.type = options.type;
this.seeds = options.seeds;
this.magic = options.magic;
this.port = options.port;
this.checkpointMap = options.checkpointMap;
this.lastCheckpoint = options.lastCheckpoint;
this.checkpoints = [];
this.halvingInterval = options.halvingInterval;
this.genesis = options.genesis;
this.genesisBlock = options.genesisBlock;
this.pow = options.pow;
this.block = options.block;
this.bip30 = options.bip30;
this.activationThreshold = options.activationThreshold;
this.minerWindow = options.minerWindow;
this.deployments = options.deployments;
this.deploys = options.deploys;
this.unknownBits = ~consensus.VERSION_TOP_MASK;
this.keyPrefix = options.keyPrefix;
this.addressPrefix = options.addressPrefix;
this.requireStandard = options.requireStandard;
this.rpcPort = options.rpcPort;
this.walletPort = options.walletPort;
this.minRelay = options.minRelay;
this.feeRate = options.feeRate;
this.maxFeeRate = options.maxFeeRate;
this.selfConnect = options.selfConnect;
this.requestMempool = options.requestMempool;
this.time = new TimeData();
this.init();
this.init();
}
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
init() {
let bits = 0;
for (const deployment of this.deploys)
bits |= 1 << deployment.bit;
bits |= consensus.VERSION_TOP_MASK;
this.unknownBits = ~bits >>> 0;
for (const key of Object.keys(this.checkpointMap)) {
const hash = this.checkpointMap[key];
const height = Number(key);
this.checkpoints.push({ hash, height });
}
this.checkpoints.sort(cmpNode);
}
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
byBit(bit) {
const index = binary.search(this.deploys, bit, cmpBit);
if (index === -1)
return null;
return this.deploys[index];
}
/**
* Get network adjusted time.
* @returns {Number}
*/
now() {
return this.time.now();
}
/**
* Get network adjusted time in milliseconds.
* @returns {Number}
*/
ms() {
return this.time.ms();
}
/**
* Create a network. Get existing network if possible.
* @param {NetworkType|Object} options
* @returns {Network}
*/
static create(options) {
if (typeof options === 'string')
options = networks[options];
assert(options, 'Unknown network.');
if (Network[options.type])
return Network[options.type];
const network = new Network(options);
Network[network.type] = network;
if (!Network.primary)
Network.primary = network;
return network;
}
/**
* Set the default network. This network will be used
* if nothing is passed as the `network` option for
* certain objects.
* @param {NetworkType} type - Network type.
* @returns {Network}
*/
static set(type) {
assert(typeof type === 'string', 'Bad network.');
Network.primary = Network.get(type);
Network.type = type;
return Network.primary;
}
/**
* Get a network with a string or a Network object.
* @param {NetworkType|Network} type - Network type.
* @returns {Network}
*/
static get(type) {
if (!type) {
assert(Network.primary, 'No default network.');
return Network.primary;
}
if (type instanceof Network)
return type;
if (typeof type === 'string')
return Network.create(type);
throw new Error('Unknown network.');
}
/**
* Get a network with a string or a Network object.
* @param {NetworkType|Network} type - Network type.
* @returns {Network}
*/
static ensure(type) {
if (!type) {
assert(Network.primary, 'No default network.');
return Network.primary;
}
if (type instanceof Network)
return type;
if (typeof type === 'string') {
if (networks[type])
return Network.create(type);
}
assert(Network.primary, 'No default network.');
return Network.primary;
}
/**
* Get a network by an associated comparator.
* @private
* @param {Object} value
* @param {Function} compare
* @param {Network|null} network
* @param {String} name
* @returns {Network}
*/
static by(value, compare, network, name) {
if (network) {
network = Network.get(network);
if (compare(network, value))
return network;
throw new Error(`Network mismatch for ${name}.`);
}
for (const type of networks.types) {
network = networks[type];
if (compare(network, value))
return Network.get(type);
}
throw new Error(`Network not found for ${name}.`);
}
/**
* Get a network by its magic number.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
static fromMagic(value, network) {
return Network.by(value, cmpMagic, network, 'magic number');
}
/**
* Get a network by its WIF prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
static fromWIF(prefix, network) {
return Network.by(prefix, cmpWIF, network, 'WIF');
}
/**
* Get a network by its xpubkey prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
static fromPublic(prefix, network) {
return Network.by(prefix, cmpPub, network, 'xpubkey');
}
/**
* Get a network by its xprivkey prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
static fromPrivate(prefix, network) {
return Network.by(prefix, cmpPriv, network, 'xprivkey');
}
/**
* Get a network by its xpubkey base58 prefix.
* @param {String} prefix
* @param {Network?} network
* @returns {Network}
*/
static fromPublic58(prefix, network) {
return Network.by(prefix, cmpPub58, network, 'xpubkey');
}
/**
* Get a network by its xprivkey base58 prefix.
* @param {String} prefix
* @param {Network?} network
* @returns {Network}
*/
static fromPrivate58(prefix, network) {
return Network.by(prefix, cmpPriv58, network, 'xprivkey');
}
/**
* Get a network by its base58 address prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
static fromAddress(prefix, network) {
return Network.by(prefix, cmpAddress, network, 'base58 address');
}
/**
* Get a network by its bech32 address prefix.
* @param {String} hrp
* @param {Network?} network
* @returns {Network}
*/
static fromBech32(hrp, network) {
return Network.by(hrp, cmpBech32, network, 'bech32 address');
}
/**
* Convert the network to a string.
* @returns {String}
*/
toString() {
return this.type;
}
/**
* Inspect the network.
* @returns {String}
*/
inspect() {
return `<Network: ${this.type}>`;
}
/**
* Test an object to see if it is a Network.
* @param {Object} obj
* @returns {Boolean}
*/
static isNetwork(obj) {
return obj instanceof Network;
}
}
/**
@ -83,294 +375,6 @@ Network.regtest = null;
Network.segnet4 = null;
Network.simnet = null;
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
Network.prototype.init = function init() {
let bits = 0;
for (const deployment of this.deploys)
bits |= 1 << deployment.bit;
bits |= consensus.VERSION_TOP_MASK;
this.unknownBits = ~bits >>> 0;
for (const key of Object.keys(this.checkpointMap)) {
const hash = this.checkpointMap[key];
const height = Number(key);
this.checkpoints.push({ hash, height });
}
this.checkpoints.sort(cmpNode);
};
/**
* Get a deployment by bit index.
* @param {Number} bit
* @returns {Object}
*/
Network.prototype.byBit = function byBit(bit) {
const index = binary.search(this.deploys, bit, cmpBit);
if (index === -1)
return null;
return this.deploys[index];
};
/**
* Get network adjusted time.
* @returns {Number}
*/
Network.prototype.now = function now() {
return this.time.now();
};
/**
* Get network adjusted time in milliseconds.
* @returns {Number}
*/
Network.prototype.ms = function ms() {
return this.time.ms();
};
/**
* Create a network. Get existing network if possible.
* @param {NetworkType|Object} options
* @returns {Network}
*/
Network.create = function create(options) {
if (typeof options === 'string')
options = networks[options];
assert(options, 'Unknown network.');
if (Network[options.type])
return Network[options.type];
const network = new Network(options);
Network[network.type] = network;
if (!Network.primary)
Network.primary = network;
return network;
};
/**
* Set the default network. This network will be used
* if nothing is passed as the `network` option for
* certain objects.
* @param {NetworkType} type - Network type.
* @returns {Network}
*/
Network.set = function set(type) {
assert(typeof type === 'string', 'Bad network.');
Network.primary = Network.get(type);
Network.type = type;
return Network.primary;
};
/**
* Get a network with a string or a Network object.
* @param {NetworkType|Network} type - Network type.
* @returns {Network}
*/
Network.get = function get(type) {
if (!type) {
assert(Network.primary, 'No default network.');
return Network.primary;
}
if (type instanceof Network)
return type;
if (typeof type === 'string')
return Network.create(type);
throw new Error('Unknown network.');
};
/**
* Get a network with a string or a Network object.
* @param {NetworkType|Network} type - Network type.
* @returns {Network}
*/
Network.ensure = function ensure(type) {
if (!type) {
assert(Network.primary, 'No default network.');
return Network.primary;
}
if (type instanceof Network)
return type;
if (typeof type === 'string') {
if (networks[type])
return Network.create(type);
}
assert(Network.primary, 'No default network.');
return Network.primary;
};
/**
* Get a network by an associated comparator.
* @private
* @param {Object} value
* @param {Function} compare
* @param {Network|null} network
* @param {String} name
* @returns {Network}
*/
Network.by = function by(value, compare, network, name) {
if (network) {
network = Network.get(network);
if (compare(network, value))
return network;
throw new Error(`Network mismatch for ${name}.`);
}
for (const type of networks.types) {
network = networks[type];
if (compare(network, value))
return Network.get(type);
}
throw new Error(`Network not found for ${name}.`);
};
/**
* Get a network by its magic number.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
Network.fromMagic = function fromMagic(value, network) {
return Network.by(value, cmpMagic, network, 'magic number');
};
/**
* Get a network by its WIF prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
Network.fromWIF = function fromWIF(prefix, network) {
return Network.by(prefix, cmpWIF, network, 'WIF');
};
/**
* Get a network by its xpubkey prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
Network.fromPublic = function fromPublic(prefix, network) {
return Network.by(prefix, cmpPub, network, 'xpubkey');
};
/**
* Get a network by its xprivkey prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
Network.fromPrivate = function fromPrivate(prefix, network) {
return Network.by(prefix, cmpPriv, network, 'xprivkey');
};
/**
* Get a network by its xpubkey base58 prefix.
* @param {String} prefix
* @param {Network?} network
* @returns {Network}
*/
Network.fromPublic58 = function fromPublic58(prefix, network) {
return Network.by(prefix, cmpPub58, network, 'xpubkey');
};
/**
* Get a network by its xprivkey base58 prefix.
* @param {String} prefix
* @param {Network?} network
* @returns {Network}
*/
Network.fromPrivate58 = function fromPrivate58(prefix, network) {
return Network.by(prefix, cmpPriv58, network, 'xprivkey');
};
/**
* Get a network by its base58 address prefix.
* @param {Number} value
* @param {Network?} network
* @returns {Network}
*/
Network.fromAddress = function fromAddress(prefix, network) {
return Network.by(prefix, cmpAddress, network, 'base58 address');
};
/**
* Get a network by its bech32 address prefix.
* @param {String} hrp
* @param {Network?} network
* @returns {Network}
*/
Network.fromBech32 = function fromBech32(hrp, network) {
return Network.by(hrp, cmpBech32, network, 'bech32 address');
};
/**
* Convert the network to a string.
* @returns {String}
*/
Network.prototype.toString = function toString() {
return this.type;
};
/**
* Inspect the network.
* @returns {String}
*/
Network.prototype.inspect = function inspect() {
return `<Network: ${this.type}>`;
};
/**
* Test an object to see if it is a Network.
* @param {Object} obj
* @returns {Boolean}
*/
Network.isNetwork = function isNetwork(obj) {
return obj instanceof Network;
};
/*
* Set initial network.
*/

View File

@ -12,123 +12,126 @@ const util = require('../utils/util');
const binary = require('../utils/binary');
/**
* Time Data
* An object which handles "adjusted time". This may not
* look it, but this is actually a semi-consensus-critical
* piece of code. It handles version packets from peers
* and calculates what to offset our system clock's time by.
* @alias module:protocol.TimeData
* @constructor
* @param {Number} [limit=200]
* @extends EventEmitter
* @property {Array} samples
* @property {Object} known
* @property {Number} limit
* @property {Number} offset
*/
function TimeData(limit) {
if (!(this instanceof TimeData))
return new TimeData(limit);
class TimeData extends EventEmitter {
/**
* Create time data.
* @constructor
* @param {Number} [limit=200]
*/
EventEmitter.call(this);
constructor(limit) {
super();
if (limit == null)
limit = 200;
if (limit == null)
limit = 200;
this.samples = [];
this.known = new Map();
this.limit = limit;
this.offset = 0;
this.checked = false;
}
this.samples = [];
this.known = new Map();
this.limit = limit;
this.offset = 0;
this.checked = false;
}
Object.setPrototypeOf(TimeData.prototype, EventEmitter.prototype);
/**
* Add time data.
* @param {String} id
* @param {Number} time
*/
/**
* Add time data.
* @param {String} id
* @param {Number} time
*/
add(id, time) {
if (this.samples.length >= this.limit)
return;
TimeData.prototype.add = function add(id, time) {
if (this.samples.length >= this.limit)
return;
if (this.known.has(id))
return;
if (this.known.has(id))
return;
const sample = time - util.now();
const sample = time - util.now();
this.known.set(id, sample);
this.known.set(id, sample);
binary.insert(this.samples, sample, compare);
binary.insert(this.samples, sample, compare);
this.emit('sample', sample, this.samples.length);
this.emit('sample', sample, this.samples.length);
if (this.samples.length >= 5 && this.samples.length % 2 === 1) {
let median = this.samples[this.samples.length >>> 1];
if (this.samples.length >= 5 && this.samples.length % 2 === 1) {
let median = this.samples[this.samples.length >>> 1];
if (Math.abs(median) >= 70 * 60) {
if (!this.checked) {
let match = false;
if (Math.abs(median) >= 70 * 60) {
if (!this.checked) {
let match = false;
for (const offset of this.samples) {
if (offset !== 0 && Math.abs(offset) < 5 * 60) {
match = true;
break;
}
}
for (const offset of this.samples) {
if (offset !== 0 && Math.abs(offset) < 5 * 60) {
match = true;
break;
if (!match) {
this.checked = true;
this.emit('mismatch');
}
}
if (!match) {
this.checked = true;
this.emit('mismatch');
}
median = 0;
}
median = 0;
this.offset = median;
this.emit('offset', this.offset);
}
this.offset = median;
this.emit('offset', this.offset);
}
};
/**
* Get the current adjusted time.
* @returns {Number} Adjusted Time.
*/
/**
* Get the current adjusted time.
* @returns {Number} Adjusted Time.
*/
TimeData.prototype.now = function now() {
return util.now() + this.offset;
};
now() {
return util.now() + this.offset;
}
/**
* Adjust a timestamp.
* @param {Number} time
* @returns {Number} Adjusted Time.
*/
/**
* Adjust a timestamp.
* @param {Number} time
* @returns {Number} Adjusted Time.
*/
TimeData.prototype.adjust = function adjust(time) {
return time + this.offset;
};
adjust(time) {
return time + this.offset;
}
/**
* Unadjust a timestamp.
* @param {Number} time
* @returns {Number} Local Time.
*/
/**
* Unadjust a timestamp.
* @param {Number} time
* @returns {Number} Local Time.
*/
TimeData.prototype.local = function local(time) {
return time - this.offset;
};
local(time) {
return time - this.offset;
}
/**
* Get the current adjusted time in milliseconds.
* @returns {Number} Adjusted Time.
*/
/**
* Get the current adjusted time in milliseconds.
* @returns {Number} Adjusted Time.
*/
TimeData.prototype.ms = function ms() {
return Date.now() + this.offset * 1000;
};
ms() {
return Date.now() + this.offset * 1000;
}
}
/*
* Helpers