standardness. chain. pool.
This commit is contained in:
parent
83c5a6f906
commit
050d801849
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -204,6 +204,7 @@ exports.block = {
|
||||
};
|
||||
|
||||
exports.tx = {
|
||||
version: 1,
|
||||
maxSize: 100000,
|
||||
minFee: 10000,
|
||||
bareMultisig: true,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user