standardness. chain. pool.

This commit is contained in:
Christopher Jeffrey 2016-02-18 15:03:03 -08:00
parent 83c5a6f906
commit 050d801849
8 changed files with 236 additions and 120 deletions

View File

@ -48,6 +48,7 @@ function Block(data, subtype) {
this.valid = null;
this._hash = null;
this._cbHeight = null;
// https://gist.github.com/sipa/bf69659f43e763540550
// http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html
@ -317,6 +318,35 @@ Block.prototype.getHeight = function getHeight() {
return this.chain.getHeight(this.hash('hex'));
};
Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
var coinbase, s, height;
if (this.subtype !== 'block')
return -1;
if (this.version < 2)
return -1;
if (this._cbHeight != null)
return this._cbHeight;
coinbase = this.txs[0];
if (!coinbase || coinbase.inputs.length === 0)
return -1;
s = coinbase.inputs[0].script;
if (Array.isArray(s[0]))
height = bcoin.script.num(s[0], true);
else
height = -1;
this._cbHeight = height;
return height;
};
Block.prototype.getNextBlock = function getNextBlock() {
var next;

View File

@ -50,6 +50,7 @@ function Chain(options) {
this.orphanLimit = options.orphanLimit || 20 * 1024 * 1024;
this.pendingLimit = options.pendingLimit || 20 * 1024 * 1024;
this.invalid = {};
this.bestHeight = -1;
this.orphan = {
map: {},
@ -153,11 +154,6 @@ Chain.prototype._init = function _init() {
utils.debug('Starting chain load at height: %s', i);
function doneForReal() {
self.loading = false;
self.emit('load');
}
function done(height) {
if (height != null) {
utils.debug(
@ -168,31 +164,11 @@ Chain.prototype._init = function _init() {
utils.debug('Chain successfully loaded.');
}
if (!self.blockdb)
return doneForReal();
self.blockdb.getHeight(function(err, height) {
self.syncHeight(function(err) {
if (err)
throw err;
assert(height !== -1);
if (height === self.tip.height)
return doneForReal();
utils.debug('ChainDB and BlockDB are out of sync. Syncing...');
if (height < self.tip.height)
return self.resetHeight(height);
if (height > self.tip.height) {
return self.blockdb.resetHeight(self.tip.height, function(err) {
if (err)
throw err;
return doneForReal();
});
}
self.loading = false;
self.emit('load');
});
}
@ -534,16 +510,7 @@ Chain.prototype._verify = function _verify(block, prev) {
// Make sure the height contained in the coinbase is correct.
if (coinbaseHeight) {
cb = bcoin.script.getCoinbaseData(block.txs[0].inputs[0].script);
// Make sure the coinbase is parseable.
if (!cb) {
utils.debug('Block has malformed coinbase: %s', block.rhash);
return false;
}
// Make sure coinbase height is equal to the actual height.
if (cb.height !== height) {
if (block.getCoinbaseHeight() !== height) {
utils.debug('Block has bad coinbase height: %s', block.rhash);
return false;
}
@ -791,15 +758,11 @@ Chain.prototype.resetHeight = function resetHeight(height) {
var count = this.db.count();
var i, existing;
assert(height < count);
assert(height <= count - 1);
assert(this.tip);
// Reset the orphan map completely. There may
// have been some orphans on a forked chain we
// no longer need.
this.orphan.map = {};
this.orphan.bmap = {};
this.orphan.count = 0;
this.orphan.size = 0;
if (height === count - 1)
return;
for (i = height + 1; i < count; i++) {
existing = this.db.get(i);
@ -808,12 +771,130 @@ Chain.prototype.resetHeight = function resetHeight(height) {
this.db.remove(i);
}
// Reset the orphan map completely. There may
// have been some orphans on a forked chain we
// no longer need.
this.emit('purge', this.orphan.count, this.orphan.size);
this.orphan.map = {};
this.orphan.bmap = {};
this.orphan.count = 0;
this.orphan.size = 0;
this.tip = this.db.get(height);
assert(this.tip);
this.height = this.tip.height;
this.emit('tip', this.tip);
};
Chain.prototype.revertHeight = function revertHeight(height, callback) {
var self = this;
var chainHeight;
var lock = this.locked;
assert(!this.locked);
callback = utils.asyncify(callback);
this.locked = true;
function done(err, result) {
self.locked = lock;
callback(err, result);
}
chainHeight = this.db.count() - 1;
if (chainHeight < 0)
return done(new Error('Bad chain height.'));
if (!this.blockdb) {
if (height > chainHeight)
return done(new Error('Cannot reset height.'));
this.resetHeight(height);
return done();
}
this.blockdb.getHeight(function(err, blockHeight) {
if (err)
return done(err);
if (blockHeight < 0)
return done(new Error('Bad block height.'));
if (chainHeight !== blockHeight)
return done(new Error('ChainDB and BlockDB are out of sync.'));
if (height === chainHeight)
return done();
if (height > chainHeight)
return done(new Error('Cannot reset height.'));
self.blockdb.resetHeight(height, function(err) {
if (err)
return done(err);
self.resetHeight(height);
return done();
});
});
};
Chain.prototype.syncHeight = function syncHeight(callback) {
var self = this;
var chainHeight;
var lock = this.locked;
callback = utils.asyncify(callback);
assert(!this.locked);
this.locked = true;
function done(err, result) {
self.locked = lock;
callback(err, result);
}
chainHeight = this.db.count() - 1;
if (chainHeight < 0)
return done(new Error('Bad chain height.'));
if (!this.blockdb)
return done();
this.blockdb.getHeight(function(err, blockHeight) {
if (err)
return done(err);
if (blockHeight < 0)
return done(new Error('Bad block height.'));
if (blockHeight === chainHeight)
return done();
utils.debug('ChainDB and BlockDB are out of sync.');
if (blockHeight < chainHeight) {
utils.debug('BlockDB is higher than ChainDB. Syncing...');
self.resetHeight(blockHeight);
return done();
}
if (blockHeight > chainHeight) {
utils.debug('ChainDB is higher than BlockDB. Syncing...');
self.blockdb.resetHeight(chainHeight, function(err) {
if (err)
return done(err);
return done();
});
}
});
};
Chain.prototype.resetTime = function resetTime(ts) {
var entry = this.byTime(ts);
if (!entry)
@ -826,6 +907,8 @@ Chain.prototype.add = function add(initial, peer, callback) {
var host = peer ? peer.host : 'unknown';
var total = 0;
assert(!this.loading);
if (this.locked) {
this.pending.push([initial, peer, callback]);
this.pendingBlocks[initial.hash('hex')] = true;
@ -905,6 +988,13 @@ Chain.prototype.add = function add(initial, peer, callback) {
return done();
}
// Update the best height based on the coinbase.
// We do this even for orphans (peers will send
// us their highest block during the initial
// getblocks sync, making it an orphan).
if (block.getCoinbaseHeight() > self.bestHeight)
self.bestHeight = block.getCoinbaseHeight();
// If previous block wasn't ever seen,
// add it current to orphans and break.
if (prevHeight == null) {

View File

@ -229,7 +229,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
return callback(new Error('Previous outputs not found.'));
}
if (!tx.isStandard()) {
if (!tx.isStandard(flags)) {
return callback(new Error('TX is not standard.'));
peer.reject({
data: tx.hash(),
@ -239,7 +239,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
return callback(new Error('TX is not standard.'));
}
if (!tx.isStandardInputs()) {
if (!tx.isStandardInputs(flags)) {
return callback(new Error('TX inputs are not standard.'));
peer.reject({
data: tx.hash(),

View File

@ -50,8 +50,6 @@ function Peer(pool, options) {
this.lastPong = 0;
this.banScore = 0;
this.orphans = 0;
this.orphanTime = 0;
if (options.socket) {
this.socket = options.socket;

View File

@ -118,7 +118,7 @@ function Pool(options) {
};
this.block = {
bestHeight: 0,
versionHeight: 0,
bestHash: null,
type: !options.spv ? 'block' : 'filtered'
};
@ -222,23 +222,15 @@ Pool.prototype._init = function _init() {
if (!peer)
return;
// Resolve orphan chain
if (!self.options.headers) {
// Make sure the peer doesn't send us
// more than 200 orphans every 3 minutes.
if (self.isOrphaning(peer)) {
utils.debug('Peer is orphaning (%s)', host);
self.setMisbehavior(peer, 100);
return;
}
// Resolve orphan chain.
self.loadOrphan(self.peers.load, null, data.hash);
} else {
// Increase banscore by 10 if we're using getheaders.
// Increase banscore by 10 if we're using getheaders.
if (self.options.headers) {
if (!self.options.multiplePeers)
self.setMisbehavior(peer, 10);
return;
}
// Resolve orphan chain
self.loadOrphan(self.peers.load, null, data.hash);
});
this.options.wallets.forEach(function(w) {
@ -582,30 +574,34 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
// Resolve orphan chain.
if (self.chain.hasOrphan(hash)) {
utils.debug('Peer sent a hash that is already a known orphan.');
// Make sure the peer doesn't send us
// more than 200 orphans every 3 minutes.
if (self.isOrphaning(peer)) {
utils.debug('Peer is orphaning (%s)', peer.host);
self.setMisbehavior(peer, 100);
return;
}
// Resolve orphan chain.
self.loadOrphan(peer, null, hash);
continue;
}
// Request block if we don't have it or if
// this is the last hash: this is done as
// a failsafe because we _need_ to request
// the hashContinue no matter what.
if (!self.chain.has(hash) || i === hashes.length - 1) {
// Request a block if we don't have it.
if (!self.chain.has(hash)) {
self._request(peer, self.block.type, hash);
continue;
}
// Normally we request the hashContinue.
// In the odd case where we already have
// it, we can do one of two things: either
// force re-downloading of the block to
// continue the sync, or do a getblocks
// from the last hash.
if (i === hashes.length - 1) {
// Request more hashes:
self.loadBlocks(peer, hash, null);
// Re-download the block (traditional method):
// self._request(peer, self.block.type, hash);
continue;
}
}
});
@ -682,9 +678,10 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
if (self.chain.height % 20 === 0) {
utils.debug(
'Got: %s from %s chain len %d blocks %d orp %d act %d queue %d target %s peers %d pending %d',
'Status: %s @ %s height=%d blocks=%d orphan=%d active=%d'
+ ' queue=%d target=%s peers=%d pending=%d highest=%d',
block.rhash,
new Date(block.ts * 1000).toString(),
new Date(block.ts * 1000).toISOString(),
self.chain.height,
self.chain.total,
self.chain.orphan.count,
@ -692,7 +689,8 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
peer._blockQueue.length,
self.chain.currentTarget(),
self.peers.all.length,
self.chain.pending.length);
self.chain.pending.length,
self.chain.bestHeight);
}
return callback(null, true);
@ -793,8 +791,8 @@ Pool.prototype._createPeer = function _createPeer(options) {
});
peer.on('version', function(version) {
if (version.height > self.block.bestHeight)
self.block.bestHeight = version.height;
if (version.height > self.block.versionHeight)
self.block.versionHeight = version.height;
self.emit('version', version, peer);
utils.debug(
'Received version from %s: version=%d height=%d agent=%s',
@ -1821,23 +1819,6 @@ Pool.prototype.removeSeed = function removeSeed(seed) {
return true;
};
Pool.prototype.isOrphaning = function isOrphaning(peer) {
if (!this.options.orphanDOS)
return false;
if (utils.now() > peer.orphanTime + 3 * 60) {
peer.orphans = 0;
peer.orphanTime = utils.now();
}
peer.orphans += 1;
if (peer.orphans > 200)
return true;
return false;
};
Pool.prototype.setMisbehavior = function setMisbehavior(peer, dos) {
peer.banScore += dos;

View File

@ -204,6 +204,7 @@ exports.block = {
};
exports.tx = {
version: 1,
maxSize: 100000,
minFee: 10000,
bareMultisig: true,

View File

@ -1303,9 +1303,10 @@ script.getOutputType = function getOutputType(s) {
};
script.isStandard = function isStandard(s) {
var type = script.getType(s);
var m, n;
if (script.isMultisig(s)) {
if (type === 'multisig') {
m = s[0];
n = s[s.length - 2];
@ -1314,12 +1315,12 @@ script.isStandard = function isStandard(s) {
if (m < 1 || m > n)
return false;
} else if (script.isNulldata(s)) {
} else if (type === 'nulldata') {
if (script.getSize(s) > constants.script.maxOpReturnBytes)
return false;
}
return type != null;
return type !== 'unknown';
};
script.getSize = function getSize(s) {

View File

@ -1536,10 +1536,13 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
return n;
};
TX.prototype.isStandard = function isStandard() {
TX.prototype.isStandard = function isStandard(flags) {
var i, input, output, type;
var nulldata = 0;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (this.version > constants.tx.version || this.version < 1)
return false;
@ -1556,8 +1559,10 @@ TX.prototype.isStandard = function isStandard() {
if (this.isCoinbase())
continue;
if (!bcoin.script.isPushOnly(input.script))
return false;
if (flags & constants.flags.VERIFY_SIGPUSHONLY) {
if (!bcoin.script.isPushOnly(input.script))
return false;
}
}
for (i = 0; i < this.outputs.length; i++) {
@ -1567,7 +1572,7 @@ TX.prototype.isStandard = function isStandard() {
if (!bcoin.script.isStandard(output.script))
return false;
if (!type)
if (type === 'unknown')
return false;
if (type === 'nulldata') {
@ -1589,7 +1594,11 @@ TX.prototype.isStandard = function isStandard() {
};
TX.prototype.isStandardInputs = function isStandardInputs(flags) {
var i, input, args, stack, res, s, targs;
var i, input, args, stack, res, redeem, targs;
var maxSigops = constants.script.maxScripthashSigops;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (this.isCoinbase())
return true;
@ -1612,25 +1621,31 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
if (!res)
return false;
if (bcoin.script.isScripthash(input.output.script)) {
if ((flags & constants.flags.VERIFY_P2SH)
&& bcoin.script.isScripthash(input.output.script)) {
if (stack.length === 0)
return false;
s = stack[stack.length - 1];
redeem = bcoin.script.getRedeem(stack);
if (!Array.isArray(s))
if (!redeem)
return false;
s = bcoin.script.decode(s);
// Not accurate?
if (bcoin.script.getSize(redeem) > 520)
return false;
if (bcoin.script.getType(s) !== 'unknown') {
targs = bcoin.script.getArgs(s);
if (targs < 0)
// Also consider scripthash "unknown"?
if (bcoin.script.getType(redeem) === 'unknown') {
if (bcoin.script.getSigops(redeem, true) > maxSigops)
return false;
args += targs;
} else {
return script.getSigops(s, true) <= constants.script.maxScripthashSigops;
continue;
}
targs = bcoin.script.getArgs(redeem);
if (targs < 0)
return false;
args += targs;
}
if (stack.length !== args)