refactoring.

This commit is contained in:
Christopher Jeffrey 2016-03-30 04:13:26 -07:00
parent 359cef93a0
commit d2767e4e34
9 changed files with 286 additions and 177 deletions

View File

@ -7,6 +7,7 @@
var bcoin = require('../bcoin');
var utils = require('./utils');
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var BufferReader = require('./reader');
var BufferWriter = require('./writer');
@ -133,7 +134,7 @@ Input.prototype.isFinal = function isFinal() {
};
Input.prototype.isCoinbase = function isCoinbase() {
return +this.prevout.hash === 0;
return this.prevout.hash === constants.nullHash;
};
Input.prototype.test = function test(addressMap) {

View File

@ -286,6 +286,7 @@ Mempool.prototype.add =
Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
var self = this;
var hash, ts, height, now;
var flags = Mempool.flags;
var ret = {};
var unlock = this._lock(addTX, [tx, peer, callback], force);
@ -300,6 +301,11 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
if (!peer)
peer = DUMMY_PEER;
if (this.chain.segwitActive) {
flags |= constants.flags.VERIFY_WITNESS;
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
}
hash = tx.hash('hex');
assert(tx.ts === 0);
@ -308,24 +314,29 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
callback = utils.asyncify(callback);
if (!this.chain.segwitActive) {
if (tx.hasWitness())
if (tx.hasWitness()) {
peer.sendReject(tx, 'nonstandard', 'no-witness-yet', 0);
return callback(new VerifyError('nonstandard', 'no-witness-yet', 0));
}
}
if (!this.checkTX(tx, peer))
return callback(new VerifyError('invalid', 'CheckTransaction failed', -1));
if (!tx.isSane(ret)) {
peer.sendReject(tx, 'invalid', ret.reason, ret.score);
return callback(new VerifyError('invalid', ret.reason, ret.score));
}
if (tx.isCoinbase()) {
peer.sendReject(tx, 'invalid', 'coinbase', 100);
return callback(new VerifyError('invalid', 'coinbase', 100));
}
ts = utils.now();
height = this.chain.height + 1;
if (this.requireStandard && !tx.isStandard(Mempool.flags, ts, height, ret)) {
peer.sendReject(tx, 'nonstandard', ret.reason, 0);
return callback(new VerifyError(ret.reason, 0));
if (this.requireStandard) {
ts = utils.now();
height = this.chain.height + 1;
if (!tx.isStandard(flags, ts, height, ret)) {
peer.sendReject(tx, 'nonstandard', ret.reason, 0);
return callback(new VerifyError(ret.reason, 0));
}
}
this._hasTX(tx, function(err, exists) {
@ -441,6 +452,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
if (this.chain.segwitActive) {
flags |= constants.flags.VERIFY_WITNESS;
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
mandatory |= constants.flags.VERIFY_WITNESS;
}
@ -455,7 +467,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
0));
}
if (self.requireStandard && !tx.isStandardInputs(flags)) {
if (self.requireStandard && !tx.hasStandardInputs(flags)) {
return callback(new VerifyError(
'nonstandard',
'bad-txns-nonstandard-inputs',
@ -794,68 +806,6 @@ Mempool.prototype.getSnapshot = function getSnapshot(callback) {
return this.tx.getAllHashes(callback);
};
Mempool.prototype.checkTX = function checkTX(tx, peer) {
return Mempool.checkTX(tx, peer);
};
Mempool.checkTX = function checkTX(tx, peer) {
var uniq = {};
var total = new bn(0);
var i, input, output, size;
if (!peer)
peer = DUMMY_PEER;
if (tx.inputs.length === 0)
return peer.sendReject(tx, 'invalid', 'bad-txns-vin-empty', 100);
if (tx.outputs.length === 0)
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-empty', 100);
if (tx.getVirtualSize() > constants.block.maxSize)
return peer.sendReject(tx, 'invalid', 'bad-txns-oversize', 100);
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
if (output.value.cmpn(0) < 0)
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-negative', 100);
if (output.value.cmp(constants.maxMoney) > 0)
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-toolarge', 100);
total.iadd(output.value);
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
return peer.sendReject(tx,
'invalid',
'bad-txns-txouttotal-toolarge',
100);
}
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (uniq[input.prevout.hash])
return peer.sendReject(tx, 'invalid', 'bad-txns-inputs-duplicate', 100);
uniq[input.prevout.hash] = true;
}
if (tx.isCoinbase()) {
size = tx.inputs[0].script.getSize();
if (size < 2 || size > 100)
return peer.sendReject(tx, 'invalid', 'bad-cb-length', 100);
} else {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (+input.prevout.hash === 0)
return peer.sendReject(tx, 'invalid', 'bad-txns-prevout-null', 10);
}
}
return true;
};
Mempool.prototype.getLocks = function getLocks(tx, flags, entry, callback) {
var self = this;
var mask = constants.sequenceLocktimeMask

View File

@ -522,13 +522,13 @@ Peer.prototype._handleVersion = function handleVersion(payload) {
}
}
if (this.options.witness) {
if (!payload.witness) {
// this._error('Peer does not support segregated witness service.');
// this.setMisbehavior(100);
// return;
}
}
// if (this.options.witness) {
// if (!payload.witness) {
// this._error('Peer does not support segregated witness service.');
// this.setMisbehavior(100);
// return;
// }
// }
if (payload.witness)
this.haveWitness = true;
@ -712,8 +712,19 @@ Peer.prototype._handleReject = function handleReject(payload) {
entry.e.emit('reject', payload);
};
Peer.prototype._handleAlert = function handleAlert(payload) {
this.emit('alert', payload);
Peer.prototype._handleAlert = function handleAlert(details) {
var hash = utils.dsha256(details.payload);
var signature = details.signature;
if (!bcoin.ec.verify(hash, signature, network.alertKey)) {
utils.debug('Peer %s sent a phony alert packet.', this.host);
// Let's look at it because why not?
utils.debug(details);
this.setMisbehavior(100);
return;
}
this.emit('alert', details);
};
Peer.prototype.getHeaders = function getHeaders(hashes, stop) {

View File

@ -798,10 +798,13 @@ Pool.prototype._createPeer = function _createPeer(options) {
});
peer.on('reject', function(payload) {
var data = payload.data ? utils.revHex(utils.toHex(payload.data)) : null;
var data = payload.data
? utils.revHex(utils.toHex(payload.data))
: null;
utils.debug(
'Reject: msg=%s ccode=%s reason=%s data=%s',
'Reject (%s): msg=%s ccode=%s reason=%s data=%s',
peer.host,
payload.message,
payload.ccode,
payload.reason,
@ -1663,7 +1666,7 @@ Pool.prototype.getTX = function getTX(hash, range, callback) {
Pool.prototype.sendTX = function sendTX(tx, callback) {
// Failsafe to avoid getting banned by bitcoind nodes.
if (!bcoin.mempool.checkTX(tx))
if (!tx.isSane())
return utils.asyncify(callback)(new Error('CheckTransaction failed.'));
return this.broadcast(tx, callback);

View File

@ -269,6 +269,9 @@ exports.zeroHash = new Buffer(
'hex'
);
exports.nullHash =
'0000000000000000000000000000000000000000000000000000000000000000';
exports.userVersion = require('../../../package.json').version;
exports.userAgent = '/bcoin:' + exports.userVersion + '/';

View File

@ -736,10 +736,6 @@ Parser.parseAlert = function parseAlert(p) {
signature = p.readVarBytes();
size = p.end();
assert(
bcoin.ec.verify(utils.dsha256(payload), signature, network.alertKey),
'Alert does not verify.');
p = new BufferReader(payload);
p.start();
version = p.read32();

View File

@ -175,11 +175,14 @@ Stack.prototype.__defineSetter__('length', function(value) {
return this.items.length = value;
});
Stack.prototype.getRedeem = function getRedeem() {
Stack.prototype.getRedeem = function getRedeem(pop) {
var redeem = Script.getRedeem(this.items);
if (!redeem)
return;
this.pop();
if (pop !== false)
this.pop();
return redeem;
};
@ -1399,6 +1402,10 @@ Script.prototype.getType = function getType() {
|| 'unknown';
};
Script.prototype.isUnknown = function isUnknown() {
return this.getType() === 'unknown';
};
Script.prototype.isStandard = function isStandard() {
var type = this.getType();
var m, n;
@ -1420,6 +1427,41 @@ Script.prototype.isStandard = function isStandard() {
return type !== 'unknown';
};
Script.prototype.isStandardProgram = function isStandardProgram(witness, flags) {
var program, witnessScript, i;
assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
assert(this.isWitnessProgram());
program = this.getWitnessProgram();
if (!program.type)
return false;
if (program.version > 0) {
if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
return false;
return true;
}
if (program.type === 'witnesspubkeyhash') {
if (witness.items.length !== 2)
return false;
} else if (program.type === 'witnessscripthash') {
if (witness.items.length === 0)
return false;
} else {
assert(false);
}
for (i = 0; i < witness.items.length; i++) {
if (witness.items[i].length > constants.script.maxSize)
return false;
}
return true;
};
Script.prototype.getSize = function getSize() {
return this.encode().length;
};
@ -1745,6 +1787,10 @@ Script.getInputType = function getInputType(code, prev, isWitness) {
return type;
};
Script.prototype.isInputUnknown = function isInputUnknown() {
return this.getInputType() === 'unknown';
};
Script.createOutputScript = function(options) {
var script, keys, m, n, hash, flags, address, redeem;

View File

@ -12,6 +12,7 @@ var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var Script = bcoin.script;
var Stack = bcoin.script.stack;
var BufferReader = require('./reader');
var BufferWriter = require('./writer');
@ -243,7 +244,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
type = constants.hashType[type];
assert(index >= 0 && index < this.inputs.length);
assert(prev instanceof bcoin.script);
assert(prev instanceof Script);
// Clone the transaction.
copy = {
@ -333,7 +334,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) {
type = constants.hashType[type];
assert(index >= 0 && index < this.inputs.length);
assert(prev instanceof bcoin.script);
assert(prev instanceof Script);
if (!(type & constants.hashType.anyonecanpay)) {
hashPrevouts = new BufferWriter();
@ -412,7 +413,7 @@ TX.prototype.verify = function verify(index, force, flags) {
return false;
}
return bcoin.script.verify(
return Script.verify(
input.script,
input.witness,
input.coin.script,
@ -437,7 +438,8 @@ TX.prototype.verifyAsync = function verifyAsync(index, force, flags, callback) {
};
TX.prototype.isCoinbase = function isCoinbase() {
return this.inputs.length === 1 && +this.inputs[0].prevout.hash === 0;
return this.inputs.length === 1
&& this.inputs[0].prevout.hash === constants.nullHash;
};
TX.prototype.getFee = function getFee() {
@ -719,6 +721,88 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
return (cost + 3) / 4 | 0;
};
// CheckTransaction
TX.prototype.isSane = function isSane(ret) {
var uniq = {};
var total = new bn(0);
var i, input, output, size;
if (!ret)
ret = {};
if (this.inputs.length === 0) {
ret.reason = 'bad-txns-vin-empty';
ret.score = 100;
return false;
}
if (this.outputs.length === 0) {
ret.reason = 'bad-txns-vout-empty';
ret.score = 100;
return false;
}
if (this.getVirtualSize() > constants.block.maxSize) {
ret.reason = 'bad-txns-oversize';
ret.score = 100;
return false;
}
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
if (output.value.cmpn(0) < 0) {
ret.reason = 'bad-txns-vout-negative';
ret.score = 100;
return false;
}
if (output.value.cmp(constants.maxMoney) > 0) {
ret.reason = 'bad-txns-vout-toolarge';
ret.score = 100;
return false;
}
total.iadd(output.value);
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
ret.reason = 'bad-txns-txouttotal-toolarge';
ret.score = 100;
return false;
}
}
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (uniq[input.prevout.hash]) {
ret.reason = 'bad-txns-inputs-duplicate';
ret.score = 100;
return false;
}
uniq[input.prevout.hash] = true;
}
if (this.isCoinbase()) {
size = this.inputs[0].script.getSize();
if (size < 2 || size > 100) {
ret.reason = 'bad-cb-length';
ret.score = 100;
return false;
}
} else {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (input.prevout.hash === constants.nullHash) {
ret.reason = 'bad-txns-prevout-null';
ret.score = 10;
return false;
}
}
}
return true;
};
// IsStandardTx
TX.prototype.isStandard = function isStandard(flags, ts, height, ret) {
var i, input, output, type;
@ -805,9 +889,9 @@ TX.prototype.isStandard = function isStandard(flags, ts, height, ret) {
};
// AreInputsStandard
TX.prototype.isStandardInputs = function isStandardInputs(flags) {
var i, input, args, stack, res, redeem, targs, hadWitness;
TX.prototype.hasStandardInputs = function hasStandardInputs(flags) {
var maxSigops = constants.script.maxScripthashSigops;
var i, input, args, stack, res, redeem, targs;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
@ -821,67 +905,49 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
if (!input.coin)
return false;
if ((flags & constants.flags.VERIFY_WITNESS)
&& input.coin.script.isWitnessProgram()) {
// Input script must be empty.
if (input.script.code.length !== 0)
return false;
// Verify the program in the output script
if (!input.coin.script.isStandardProgram(input.witness, flags))
return false;
continue;
}
args = input.coin.script.getArgs();
if (args < 0)
return false;
stack = new bcoin.script.stack([]);
// XXX Not accurate:
// Not accurate:
// Failsafe to avoid getting dos'd in case we ever
// call isStandardInputs before isStandard.
// call hasStandardInputs before isStandard.
if (!input.script.isPushOnly())
return false;
stack = new Stack([]);
res = input.script.execute(stack, flags, this, i, 0);
if (!res)
return false;
if ((flags & constants.flags.VERIFY_WITNESS)
&& input.coin.script.isWitnessProgram()) {
hadWitness = true;
// Input script must be empty.
if (input.script.code.length !== 0)
return false;
// Verify the program in the output script
if (!this.isStandardProgram(input.witness, input.coin.script, flags))
return false;
}
if ((flags & constants.flags.VERIFY_P2SH)
&& input.coin.script.isScripthash()) {
if (stack.length === 0)
return false;
redeem = bcoin.script.getRedeem(stack);
redeem = stack.getRedeem(false);
if (!redeem)
return false;
// Not accurate?
if (redeem.getSize() > 520)
return false;
// Also consider scripthash "unknown"?
if (redeem.getType() === 'unknown') {
if (redeem.getSigops(true) > maxSigops)
return false;
continue;
}
targs = redeem.getArgs();
if (targs < 0)
return false;
args += targs;
if ((flags & constants.flags.VERIFY_WITNESS)
&& redeem.isWitnessProgram()) {
hasWitness = true;
// Input script must be exactly one push of the redeem script.
if (!(input.script.code.length === 1
&& utils.isEqual(input.script.code[0], redeem.raw))) {
@ -889,13 +955,25 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
}
// Verify the program in the redeem script
if (!this.isStandardProgram(input.witness, redeem, flags))
if (!redeem.isStandardProgram(input.witness, flags))
return false;
}
}
if (hadWitness)
continue;
continue;
}
if (redeem.isUnknown()) {
if (redeem.getSigops(true) > maxSigops)
return false;
continue;
}
targs = redeem.getArgs();
if (targs < 0)
return false;
args += targs;
}
if (stack.length !== args)
return false;
@ -904,36 +982,49 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
return true;
};
TX.prototype.isStandardProgram = function isStandardProgram(witness, output, flags) {
var program, witnessScript, j;
// AreInputsStandard (segwit branch)
TX.prototype.hasStandardInputsWitness = function hasStandardInputsWitness(flags) {
var maxSigops = constants.script.maxScripthashSigops;
var i, input, stack, res, redeem;
assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
assert(output.isWitnessProgram());
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
program = output.getWitnessProgram();
if (!program.type)
return false;
if (program.version > 0) {
if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
return false;
if (this.isCoinbase())
return true;
}
if (program.type === 'witnesspubkeyhash') {
if (witness.items.length !== 2)
return false;
} else if (program.type === 'witnessscripthash') {
if (witness.items.length === 0)
return false;
} else {
assert(false);
}
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
for (j = 0; j < witness.items.length; j++) {
if (witness.items[j].length > constants.script.maxSize)
if (!input.coin)
return false;
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;
stack = new Stack([]);
res = input.script.execute(stack, flags, this, i, 0);
if (!res)
return false;
if (stack.length === 0)
return false;
redeem = stack.getRedeem(false);
if (!redeem)
return false;
if (redeem.getSigops(true) > maxSigops)
return false;
}
}
return true;
@ -962,18 +1053,7 @@ TX.prototype.getPriority = function getPriority(height, size) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (!input.coin)
return new bn(0);
age = input.coin.getConfirmations(height);
if (age === -1)
age = 0;
if (age !== 0)
age += 1;
age = input.coin.getAge(height);
sum.iadd(input.coin.value.muln(age));
}
@ -1011,7 +1091,7 @@ TX.prototype.getMinFee = function getMinFee(size) {
fee = new bn(constants.tx.minFee).muln(size).divn(1000);
if (fee.cmpn(0) === 0 && constants.tx.minFee.cmpn(0) > 0)
if (fee.cmpn(0) === 0 && constants.tx.minFee > 0)
fee = new bn(constants.tx.minFee);
return fee;
@ -1060,6 +1140,10 @@ TX.prototype.__defineGetter__('rhash', function() {
return utils.revHex(this.hash('hex'));
});
TX.prototype.__defineGetter__('rwhash', function() {
return utils.revHex(this.witnessHash('hex'));
});
TX.prototype.__defineGetter__('fee', function() {
return this.getFee();
});
@ -1233,7 +1317,7 @@ TX._fromExtended = function _fromExtended(buf, saveCoins) {
tx.ps = p.readU32();
// tx.changeIndex = p.readU32();
if (+tx.block === 0)
if (tx.block === constants.nullHash)
tx.block = null;
if (tx.height === 0x7fffffff)

View File

@ -1751,6 +1751,19 @@ utils.mb = function mb(size) {
};
utils.inherits = function inherits(obj, from) {
var f;
obj.super_ = from;
if (Object.setPrototypeOf) {
Object.setPrototypeOf(obj.prototype, from.prototype);
Object.defineProperty(obj.prototype, 'constructor', {
value: obj,
enumerable: false
});
return;
}
if (Object.create) {
obj.prototype = Object.create(from.prototype, {
constructor: {
@ -1760,9 +1773,11 @@ utils.inherits = function inherits(obj, from) {
});
return;
}
var f = function() {};
f = function() {};
f.prototype = from.prototype;
obj.prototype = new f;
obj.prototype.constructor = obj;
};
utils.once = function once(callback) {