stop using vsize. switch to cost.

This commit is contained in:
Christopher Jeffrey 2016-04-27 16:56:57 -07:00
parent c312550429
commit e258755671
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 166 additions and 71 deletions

View File

@ -125,13 +125,24 @@ Block.prototype._getSize = function _getSize() {
*/ */
Block.prototype.getVirtualSize = function getVirtualSize(force) { Block.prototype.getVirtualSize = function getVirtualSize(force) {
var scale = constants.WITNESS_SCALE_FACTOR;
return (this.getCost() + scale - 1) / scale | 0;
};
/**
* Calculate block cost.
* @param {Boolean?} force - If true, always recalculate.
* @returns {Number} cost
*/
Block.prototype.getCost = function getCost(force) {
var size, witnessSize, base; var size, witnessSize, base;
size = this.getSize(force); size = this.getSize(force);
witnessSize = this.getWitnessSize(force); witnessSize = this.getWitnessSize(force);
base = size - witnessSize; base = size - witnessSize;
return (base * 4 + witnessSize + 3) / 4 | 0; return base * (constants.WITNESS_SCALE_FACTOR - 1) + size;
}; };
/** /**
@ -146,6 +157,18 @@ Block.prototype.getSize = function getSize(force) {
return this._size; return this._size;
}; };
/**
* Get base block size (without witness).
* @param {Boolean?} force - If true, always recalculate.
* @returns {Number} size
*/
Block.prototype.getBaseSize = function getBaseSize(force) {
if (force || this._size === 0)
this._getSize();
return this._size - this._witnessSize;
};
/** /**
* Get the total size of the witnesses. * Get the total size of the witnesses.
* @param {Boolean?} force - If true, always recalculate. * @param {Boolean?} force - If true, always recalculate.
@ -202,24 +225,6 @@ Block.prototype.indexOf = function indexOf(hash) {
return -1; return -1;
}; };
/**
* Count sigops taking into account the cost vs. witness ops.
* @param {Boolean?} scriptHash - Whether to count redeem script ops.
* @param {Boolean?} accurate - Whether to use accurate counting
* for CHECKMULTISIG(VERIFY).
* @returns {Number} sigops
*/
Block.prototype.getSigops = function getSigops(scriptHash, accurate) {
var total = 0;
var i;
for (i = 0; i < this.txs.length; i++)
total += this.txs[i].getSigops(scriptHash, accurate);
return total;
};
/** /**
* Calculate merkle root. * Calculate merkle root.
* @param {String?} enc - Encoding, can be `'hex'` or null. * @param {String?} enc - Encoding, can be `'hex'` or null.
@ -327,6 +332,7 @@ Block.prototype.__defineGetter__('commitmentHash', function() {
Block.prototype._verify = function _verify(ret) { Block.prototype._verify = function _verify(ret) {
var sigops = 0; var sigops = 0;
var scale = constants.WITNESS_SCALE_FACTOR;
var i, tx, merkle; var i, tx, merkle;
if (!ret) if (!ret)
@ -337,7 +343,7 @@ Block.prototype._verify = function _verify(ret) {
// Size can't be bigger than MAX_BLOCK_SIZE // Size can't be bigger than MAX_BLOCK_SIZE
if (this.txs.length > constants.block.MAX_SIZE if (this.txs.length > constants.block.MAX_SIZE
|| this.getVirtualSize() > constants.block.MAX_SIZE) { || this.getBaseSize() > constants.block.MAX_SIZE) {
ret.reason = 'bad-blk-length'; ret.reason = 'bad-blk-length';
ret.score = 100; ret.score = 100;
return false; return false;
@ -366,13 +372,13 @@ Block.prototype._verify = function _verify(ret) {
return false; return false;
// Count legacy sigops (do not count scripthash or witness) // Count legacy sigops (do not count scripthash or witness)
// sigops += tx._getSigops(); sigops += tx.getLegacySigops();
// if (sigops > constants.block.MAX_SIGOPS) { if (sigops * scale > constants.block.MAX_SIGOPS_COST) {
// return callback(new VerifyError(block, return callback(new VerifyError(block,
// 'invalid', 'invalid',
// 'bad-blk-sigops', 'bad-blk-sigops',
// 100)); 100));
// } }
} }
// Check merkle root // Check merkle root

View File

@ -555,6 +555,15 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
} }
} }
// Check block cost (different from block size
// check in non-contextual verification).
if (block.getCost() > constants.block.MAX_COST) {
return done(new VerifyError(block,
'invalid',
'bad-blk-cost',
100));
}
// Get timestamp for tx.isFinal(). // Get timestamp for tx.isFinal().
ts = (state.lockFlags & constants.flags.MEDIAN_TIME_PAST) !== 0 ts = (state.lockFlags & constants.flags.MEDIAN_TIME_PAST) !== 0
? medianTime ? medianTime
@ -819,21 +828,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
tx = block.txs[i]; tx = block.txs[i];
hash = tx.hash('hex'); hash = tx.hash('hex');
// Check for block sigops limits
// Start counting P2SH sigops once block
// timestamps reach March 31st, 2012.
if (flags & constants.flags.VERIFY_P2SH)
sigops += tx.getSigops(true);
else
sigops += tx.getSigops();
if (sigops > constants.block.MAX_SIGOPS) {
return callback(new VerifyError(block,
'invalid',
'bad-blk-sigops',
100));
}
// Coinbases do not have prevouts // Coinbases do not have prevouts
if (tx.isCoinbase()) if (tx.isCoinbase())
continue; continue;
@ -876,6 +870,16 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
} }
} }
// Count sigops (legacy + scripthash? + witness?)
sigops += tx.getSigopsCost(flags);
if (sigops > constants.block.MAX_SIGOPS_COST) {
return callback(new VerifyError(block,
'invalid',
'bad-blk-sigops',
100));
}
if (!tx.checkInputs(height, ret)) { if (!tx.checkInputs(height, ret)) {
return callback(new VerifyError(block, return callback(new VerifyError(block,
'invalid', 'invalid',

View File

@ -550,6 +550,8 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
return callback(err); return callback(err);
if (!tx.hasCoins()) { if (!tx.hasCoins()) {
// if (tx.getSize() > 5000)
// return callback();
if (self.totalSize > constants.mempool.MAX_MEMPOOL_SIZE) { if (self.totalSize > constants.mempool.MAX_MEMPOOL_SIZE) {
return callback(new VerifyError(tx, return callback(new VerifyError(tx,
'insufficientfee', 'insufficientfee',
@ -700,7 +702,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
0)); 0));
} }
if (tx.getSigops(true) > constants.tx.MAX_SIGOPS) { if (tx.getSigopsCost(flags) > constants.tx.MAX_SIGOPS_COST) {
return callback(new VerifyError(tx, return callback(new VerifyError(tx,
'nonstandard', 'nonstandard',
'bad-txns-too-many-sigops', 'bad-txns-too-many-sigops',

View File

@ -460,15 +460,14 @@ MinerBlock.prototype.updateMerkle = function updateMerkle() {
*/ */
MinerBlock.prototype.addTX = function addTX(tx) { MinerBlock.prototype.addTX = function addTX(tx) {
var size; var cost;
if (tx.mutable) if (tx.mutable)
tx = tx.toTX(); tx = tx.toTX();
size = this.block.getVirtualSize(true) + tx.getVirtualSize(); cost = this.block.getCost(true) + tx.getCost();
// Deliver me from the block size debate, please if (cost > constants.block.MAX_COST)
if (size > constants.block.MAX_SIZE)
return false; return false;
if (this.block.hasTX(tx)) if (this.block.hasTX(tx))

View File

@ -369,6 +369,14 @@ exports.hashType = {
exports.hashTypeByVal = utils.revMap(exports.hashType); exports.hashTypeByVal = utils.revMap(exports.hashType);
/**
* Amount to multiply base/non-witness sizes by.
* @const {Number}
* @default
*/
exports.WITNESS_SCALE_FACTOR = 4;
/** /**
* Block-related constants. * Block-related constants.
* @enum {Number} * @enum {Number}
@ -377,7 +385,9 @@ exports.hashTypeByVal = utils.revMap(exports.hashType);
exports.block = { exports.block = {
MAX_SIZE: 1000000, MAX_SIZE: 1000000,
MAX_COST: 4000000,
MAX_SIGOPS: 1000000 / 50, MAX_SIGOPS: 1000000 / 50,
MAX_SIGOPS_COST: 80000,
MEDIAN_TIMESPAN: 11, MEDIAN_TIMESPAN: 11,
BIP16_TIME: 1333238400, BIP16_TIME: 1333238400,
SIGHASH_LIMIT: 1300000000 SIGHASH_LIMIT: 1300000000
@ -404,10 +414,12 @@ exports.bip30 = {
exports.tx = { exports.tx = {
MAX_VERSION: 2, MAX_VERSION: 2,
MAX_SIZE: 100000, MAX_SIZE: 100000,
MAX_COST: 400000,
MIN_FEE: 10000, MIN_FEE: 10000,
BARE_MULTISIG: true, BARE_MULTISIG: true,
FREE_THRESHOLD: exports.COIN.muln(144).divn(250), FREE_THRESHOLD: exports.COIN.muln(144).divn(250),
MAX_SIGOPS: exports.block.MAX_SIGOPS / 5, MAX_SIGOPS: exports.block.MAX_SIGOPS / 5,
MAX_SIGOPS_COST: exports.block.MAX_SIGOPS_COST / 5,
COINBASE_MATURITY: 100 COINBASE_MATURITY: 100
}; };

View File

@ -1423,9 +1423,8 @@ Framer.tx.size = function txSize(tx) {
*/ */
Framer.block.virtualSize = function blockVirtualSize(block) { Framer.block.virtualSize = function blockVirtualSize(block) {
var sizes = Framer.block.sizes(block); var scale = constants.WITNESS_SCALE_FACTOR;
var base = sizes.size - sizes.witnessSize; return (Framer.block.cost(block) + scale - 1) / scale | 0;
return (base * 4 + sizes.witnessSize + 3) / 4 | 0;
}; };
/** /**
@ -1435,9 +1434,34 @@ Framer.block.virtualSize = function blockVirtualSize(block) {
*/ */
Framer.tx.virtualSize = function txVirtualSize(tx) { Framer.tx.virtualSize = function txVirtualSize(tx) {
var scale = constants.WITNESS_SCALE_FACTOR;
return (Framer.tx.cost(tx) + scale - 1) / scale | 0;
};
/**
* Calculate block cost.
* @param {NakedBlock|Block} block
* @returns {Number} cost
*/
Framer.block.cost = function blockCost(block) {
var sizes = Framer.block.sizes(block);
var base = sizes.size - sizes.witnessSize;
var scale = constants.WITNESS_SCALE_FACTOR;
return base * (scale - 1) + sizes.size;
};
/**
* Calculate transaction cost.
* @param {NakedTX|TX} tx
* @returns {Number} cost
*/
Framer.tx.cost = function txCost(tx) {
var sizes = Framer.tx.sizes(tx); var sizes = Framer.tx.sizes(tx);
var base = sizes.size - sizes.witnessSize; var base = sizes.size - sizes.witnessSize;
return (base * 4 + sizes.witnessSize + 3) / 4 | 0; var scale = constants.WITNESS_SCALE_FACTOR;
return base * (scale - 1) + sizes.size;
}; };
return Framer; return Framer;

View File

@ -3437,9 +3437,6 @@ Script.prototype.getSigops = function getSigops(accurate) {
var lastOp = -1; var lastOp = -1;
var i, op; var i, op;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
for (i = 0; i < this.code.length; i++) { for (i = 0; i < this.code.length; i++) {
op = this.code[i]; op = this.code[i];

View File

@ -290,6 +290,17 @@ TX.prototype.getRaw = function getRaw() {
*/ */
TX.prototype.getVirtualSize = function getVirtualSize() { TX.prototype.getVirtualSize = function getVirtualSize() {
var scale = constants.WITNESS_SCALE_FACTOR;
return (this.getCost() + scale - 1) / scale | 0;
};
/**
* Calculate the cost of the transaction.
* Note that this is cached.
* @returns {Number} cost
*/
TX.prototype.getCost = function getCost() {
var size, witnessSize, base; var size, witnessSize, base;
this.getRaw(); this.getRaw();
@ -298,7 +309,7 @@ TX.prototype.getVirtualSize = function getVirtualSize() {
witnessSize = this._witnessSize; witnessSize = this._witnessSize;
base = size - witnessSize; base = size - witnessSize;
return (base * 4 + witnessSize + 3) / 4 | 0; return base * (constants.WITNESS_SCALE_FACTOR - 1) + size;
}; };
/** /**
@ -886,11 +897,11 @@ TX.prototype.getLegacySigops = function getLegacySigops() {
var total = 0; var total = 0;
var i; var i;
for (i = 0; i < tx.inputs.length; i++) for (i = 0; i < this.inputs.length; i++)
total += tx.inputs[i].script.getSigops(false); total += this.inputs[i].script.getSigops(false);
for (i = 0; i < tx.outputs.length; i++) for (i = 0; i < this.outputs.length; i++)
total += tx.outputs[i].script.getSigops(false); total += this.outputs[i].script.getSigops(false);
return total; return total;
}; };
@ -907,8 +918,12 @@ TX.prototype.getScripthashSigops = function getScripthashSigops() {
if (this.isCoinbase()) if (this.isCoinbase())
return 0; return 0;
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
input = tx.inputs[i]; input = this.inputs[i];
if (!input.coin)
continue;
if (input.coin.script.isScripthash()) if (input.coin.script.isScripthash())
total += input.coin.script.getSigops(input.script); total += input.coin.script.getSigops(input.script);
} }
@ -923,7 +938,7 @@ TX.prototype.getScripthashSigops = function getScripthashSigops() {
*/ */
TX.prototype.getSigopsCost = function getSigopsCost(flags) { TX.prototype.getSigopsCost = function getSigopsCost(flags) {
var cost = this.getLegacySigops() * 4; var cost = this.getLegacySigops() * constants.WITNESS_SCALE_FACTOR;
var input, i; var input, i;
if (flags == null) if (flags == null)
@ -933,10 +948,14 @@ TX.prototype.getSigopsCost = function getSigopsCost(flags) {
return cost; return cost;
if (flags & constants.flags.VERIFY_P2SH) if (flags & constants.flags.VERIFY_P2SH)
cost += this.getScripthashSigops() * 4; cost += this.getScripthashSigops() * constants.WITNESS_SCALE_FACTOR;
for (i = 0; i < this.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i]; input = this.inputs[i];
if (!input.coin)
continue;
cost += Script.getWitnessSigops( cost += Script.getWitnessSigops(
input.script, input.script,
input.coin.script, input.coin.script,
@ -989,7 +1008,7 @@ TX.prototype.isSane = function isSane(ret) {
return false; return false;
} }
if (this.getVirtualSize() > constants.block.MAX_SIZE) { if (this.getSize() > constants.block.MAX_SIZE) {
ret.reason = 'bad-txns-oversize'; ret.reason = 'bad-txns-oversize';
ret.score = 100; ret.score = 100;
return false; return false;
@ -1079,7 +1098,7 @@ TX.prototype.isStandard = function isStandard(flags, ret) {
return false; return false;
} }
if (this.getVirtualSize() > constants.tx.MAX_SIZE) { if (this.getCost() > constants.tx.MAX_COST) {
ret.reason = 'tx-size'; ret.reason = 'tx-size';
return false; return false;
} }
@ -1162,11 +1181,7 @@ TX.prototype.hasStandardInputs = function hasStandardInputs(flags) {
if ((flags & constants.flags.VERIFY_P2SH) if ((flags & constants.flags.VERIFY_P2SH)
&& input.coin.script.isScripthash()) { && input.coin.script.isScripthash()) {
// Not accurate: assert(input.script.isPushOnly());
// Failsafe to avoid getting dos'd in case we ever
// call hasStandardInputs before isStandard.
if (!input.script.isPushOnly())
return false;
stack = new Stack(); stack = new Stack();

View File

@ -7,6 +7,23 @@
var url = require('url'); var url = require('url');
var querystring = require('querystring'); var querystring = require('querystring');
var utils = require('./utils');
/**
* @typedef {Object} ParsedURI
* @property {Base58Address} address
* @property {BN?} amount? - Amount in satoshis.
* @property {String?} label
* @property {String?} message
* @property {String?} request - Payment request URL.
*/
/**
* Parse a bitcoin URI.
* @param {String} uri - Bitcoin URI.
* @returns {ParsedURI}
* @throws on non-bitcoin uri
*/
exports.parse = function parse(uri) { exports.parse = function parse(uri) {
var data = url.parse(uri); var data = url.parse(uri);
@ -24,6 +41,12 @@ exports.parse = function parse(uri) {
}; };
}; };
/**
* Test whether an object is a bitcoin URI.
* @param {String} uri
* @returns {Boolean}
*/
exports.validate = function validate(uri) { exports.validate = function validate(uri) {
try { try {
exports.parse(uri); exports.parse(uri);
@ -33,6 +56,14 @@ exports.validate = function validate(uri) {
} }
}; };
/**
* Encode data as a bitcoin URI.
* @param {ParsedURI|Base58Address} data/address
* @param {BN?} amount
* @returns {String} URI
* @throws when no address provided
*/
exports.stringify = function stringify(address, amount) { exports.stringify = function stringify(address, amount) {
var query = {}; var query = {};
var data = address; var data = address;
@ -57,5 +88,10 @@ exports.stringify = function stringify(address, amount) {
if (data.request) if (data.request)
query.r = data.request; query.r = data.request;
return uri + querystring.stringify(query); query = querystring.stringify(query);
if (query.length === 0)
return uri;
return uri + '?' + query;
}; };