diff --git a/lib/protocol/errors.js b/lib/protocol/errors.js index 765e4356..2cdef39d 100644 --- a/lib/protocol/errors.js +++ b/lib/protocol/errors.js @@ -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 */ diff --git a/lib/protocol/network.js b/lib/protocol/network.js index 412bc303..057e2fe8 100644 --- a/lib/protocol/network.js +++ b/lib/protocol/network.js @@ -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 ``; + } + + /** + * 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 ``; -}; - -/** - * 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. */ diff --git a/lib/protocol/timedata.js b/lib/protocol/timedata.js index e3572408..cfff6fcb 100644 --- a/lib/protocol/timedata.js +++ b/lib/protocol/timedata.js @@ -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