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) {
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;
size = this.getSize(force);
witnessSize = this.getWitnessSize(force);
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;
};
/**
* 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.
* @param {Boolean?} force - If true, always recalculate.
@ -202,24 +225,6 @@ Block.prototype.indexOf = function indexOf(hash) {
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.
* @param {String?} enc - Encoding, can be `'hex'` or null.
@ -327,6 +332,7 @@ Block.prototype.__defineGetter__('commitmentHash', function() {
Block.prototype._verify = function _verify(ret) {
var sigops = 0;
var scale = constants.WITNESS_SCALE_FACTOR;
var i, tx, merkle;
if (!ret)
@ -337,7 +343,7 @@ Block.prototype._verify = function _verify(ret) {
// Size can't be bigger than MAX_BLOCK_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.score = 100;
return false;
@ -366,13 +372,13 @@ Block.prototype._verify = function _verify(ret) {
return false;
// Count legacy sigops (do not count scripthash or witness)
// sigops += tx._getSigops();
// if (sigops > constants.block.MAX_SIGOPS) {
// return callback(new VerifyError(block,
// 'invalid',
// 'bad-blk-sigops',
// 100));
// }
sigops += tx.getLegacySigops();
if (sigops * scale > constants.block.MAX_SIGOPS_COST) {
return callback(new VerifyError(block,
'invalid',
'bad-blk-sigops',
100));
}
}
// 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().
ts = (state.lockFlags & constants.flags.MEDIAN_TIME_PAST) !== 0
? medianTime
@ -819,21 +828,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
tx = block.txs[i];
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
if (tx.isCoinbase())
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)) {
return callback(new VerifyError(block,
'invalid',

View File

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

View File

@ -460,15 +460,14 @@ MinerBlock.prototype.updateMerkle = function updateMerkle() {
*/
MinerBlock.prototype.addTX = function addTX(tx) {
var size;
var cost;
if (tx.mutable)
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 (size > constants.block.MAX_SIZE)
if (cost > constants.block.MAX_COST)
return false;
if (this.block.hasTX(tx))

View File

@ -369,6 +369,14 @@ 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.
* @enum {Number}
@ -377,7 +385,9 @@ exports.hashTypeByVal = utils.revMap(exports.hashType);
exports.block = {
MAX_SIZE: 1000000,
MAX_COST: 4000000,
MAX_SIGOPS: 1000000 / 50,
MAX_SIGOPS_COST: 80000,
MEDIAN_TIMESPAN: 11,
BIP16_TIME: 1333238400,
SIGHASH_LIMIT: 1300000000
@ -404,10 +414,12 @@ exports.bip30 = {
exports.tx = {
MAX_VERSION: 2,
MAX_SIZE: 100000,
MAX_COST: 400000,
MIN_FEE: 10000,
BARE_MULTISIG: true,
FREE_THRESHOLD: exports.COIN.muln(144).divn(250),
MAX_SIGOPS: exports.block.MAX_SIGOPS / 5,
MAX_SIGOPS_COST: exports.block.MAX_SIGOPS_COST / 5,
COINBASE_MATURITY: 100
};

View File

@ -1423,9 +1423,8 @@ Framer.tx.size = function txSize(tx) {
*/
Framer.block.virtualSize = function blockVirtualSize(block) {
var sizes = Framer.block.sizes(block);
var base = sizes.size - sizes.witnessSize;
return (base * 4 + sizes.witnessSize + 3) / 4 | 0;
var scale = constants.WITNESS_SCALE_FACTOR;
return (Framer.block.cost(block) + scale - 1) / scale | 0;
};
/**
@ -1435,9 +1434,34 @@ Framer.block.virtualSize = function blockVirtualSize(block) {
*/
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 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;

View File

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

View File

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

View File

@ -7,6 +7,23 @@
var url = require('url');
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) {
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) {
try {
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) {
var query = {};
var data = address;
@ -57,5 +88,10 @@ exports.stringify = function stringify(address, amount) {
if (data.request)
query.r = data.request;
return uri + querystring.stringify(query);
query = querystring.stringify(query);
if (query.length === 0)
return uri;
return uri + '?' + query;
};