handle forks better.

This commit is contained in:
Christopher Jeffrey 2016-03-30 04:15:33 -07:00
parent d2767e4e34
commit b352bfbe3d
2 changed files with 199 additions and 180 deletions

View File

@ -329,14 +329,10 @@ Chain.prototype._preload = function _preload(callback) {
// Create a chain entry.
entry = new bcoin.chainblock(self, data, lastEntry);
// Filthy hack to avoid writing
// redundant blocks to disk!
if (entry.height <= chainHeight) {
if (entry.height <= chainHeight)
self.db.addCache(entry);
// self.db.bloom(entry.hash, 'hex');
} else {
self.db.save(entry);
}
else
self.db.save(entry, null, true);
if ((height + 1) % 50000 === 0)
utils.debug('Received %d headers from electrum.org.', height + 1);
@ -588,11 +584,11 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, peer,
// and extraNonce.
if (result) {
utils.debug('Block is overwriting txids: %s', block.rhash);
if (!(network.type === 'main' && (height === 91842 || height === 91880))) {
self.emit('verify-error',
block, 'invalid', 'bad-txns-BIP30', 100, peer);
return next(null, false);
}
if (network.type === 'main' && (height === 91842 || height === 91880))
return next(null, true);
self.emit('verify-error',
block, 'invalid', 'bad-txns-BIP30', 100, peer);
return next(null, false);
}
next(null, true);
@ -867,34 +863,37 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
});
};
Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
// Set main chain only if chainwork is higher.
// Add the block but do _not_ connect the inputs.
if (entry.chainwork.cmp(this.tip.chainwork) <= 0) {
return this.db.save(entry, block, false, function(err) {
if (err)
return callback(err);
return callback(null, false);
});
}
// Attempt to add block to the chain index.
return this._setBestChain(entry, block, function(err) {
if (err)
return callback(err);
return callback(null, true);
});
};
Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
var self = this;
callback = utils.asyncify(callback);
this.lastUpdate = utils.now();
// Start fsyncing writes once we're no
// longer dealing with historical data.
if (this.isFull())
this.db.fsync = true;
if (!this.tip) {
if (entry.hash !== network.genesis.hash)
return callback(new Error('Bad genesis block.'));
done();
} else if (entry.prevBlock === this.tip.hash) {
done();
} else {
self._reorganize(entry, block, done);
}
function done(err) {
if (err)
return callback(err);
self.db.save(entry, block, function(err) {
self.db.save(entry, block, true, function(err) {
if (err)
return callback(err);
@ -909,6 +908,17 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
return callback();
});
}
if (!this.tip) {
if (entry.hash !== network.genesis.hash)
return utils.asyncify(callback)(new Error('Bad genesis block.'));
done();
} else if (entry.prevBlock === this.tip.hash) {
done();
} else {
self._reorganize(entry, block, done);
}
};
Chain.prototype.reset = function reset(height, callback, force) {
@ -1203,12 +1213,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
height: height
}, prev);
// Set main chain only if chainwork is higher.
if (entry.chainwork.cmp(self.tip.chainwork) <= 0)
return done();
// Attempt to add block to the chain index.
self._setBestChain(entry, block, function(err) {
self._addEntry(entry, block, function(err, mainChain) {
if (err)
return done(err);
@ -1217,8 +1223,10 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
total++;
// Emit our block (and potentially resolved
// orphan) so the programmer can save it.
self.emit('block', block, entry, peer);
// orphan) only if it is on the main chain.
if (mainChain)
self.emit('block', block, entry, peer);
if (block.hash('hex') !== initial.hash('hex'))
self.emit('resolved', block, entry, peer);

View File

@ -56,59 +56,6 @@ function ChainDB(chain, options) {
this._init();
}
function DumbCache(size) {
this.data = {};
this.count = 0;
this.size = size;
}
DumbCache.prototype.set = function set(key, value) {
key = key + '';
assert(value !== undefined);
if (this.count > this.size)
this.reset();
if (this.data[key] === undefined)
this.count++;
this.data[key] = value;
};
DumbCache.prototype.remove = function remove(key) {
key = key + '';
if (this.data[key] === undefined)
return;
this.count--;
delete this.data[key];
};
DumbCache.prototype.get = function get(key) {
key = key + '';
return this.data[key];
};
DumbCache.prototype.has = function has(key) {
key = key + '';
return this.data[key] !== undefined;
};
DumbCache.prototype.reset = function reset() {
this.data = {};
this.count = 0;
};
function NullCache(size) {}
NullCache.prototype.set = function set(key, value) {};
NullCache.prototype.remove = function remove(key) {};
NullCache.prototype.get = function get(key) {};
NullCache.prototype.has = function has(key) {};
NullCache.prototype.reset = function reset() {};
utils.inherits(ChainDB, EventEmitter);
ChainDB.prototype._init = function _init() {
@ -162,7 +109,7 @@ ChainDB.prototype._init = function _init() {
block = bcoin.block.fromRaw(network.genesisBlock, 'hex');
block.height = 0;
self.save(genesis, block, finish);
self.save(genesis, block, true, finish);
});
});
};
@ -206,6 +153,8 @@ ChainDB.prototype.getCache = function getCache(hash) {
};
ChainDB.prototype.getHeight = function getHeight(hash, callback) {
callback = utils.asyncify(callback);
if (hash == null || hash < 0)
return callback(null, -1);
@ -231,6 +180,8 @@ ChainDB.prototype.getHeight = function getHeight(hash, callback) {
};
ChainDB.prototype.getHash = function getHash(height, callback) {
callback = utils.asyncify(callback);
if (height == null || height < 0)
return callback(null, null);
@ -305,7 +256,7 @@ ChainDB.prototype.getBoth = function getBoth(block, callback) {
var hash, height;
if (block == null || block < 0)
return callback(null, null, -1);
return utils.asyncify(callback)(null, null, -1);
if (typeof block === 'string')
hash = block;
@ -340,7 +291,7 @@ ChainDB.prototype._getEntry = function _getEntry(hash, callback) {
var entry;
if (hash == null || hash < 0)
return callback();
return utils.nextTick(callback);
return this.getBoth(hash, function(err, hash, height) {
if (err && err.type !== 'NotFoundError')
@ -369,8 +320,6 @@ ChainDB.prototype._getEntry = function _getEntry(hash, callback) {
ChainDB.prototype.get = function get(height, callback) {
var self = this;
callback = utils.asyncify(callback);
return this._getEntry(height, function(err, entry) {
if (err)
return callback(err);
@ -378,35 +327,52 @@ ChainDB.prototype.get = function get(height, callback) {
if (!entry)
return callback();
self.addCache(entry);
// There's no efficient way to check whether
// this is in the main chain or not, so
// don't add it to the height cache.
self.cacheHash.set(entry.hash, entry);
return callback(null, entry);
});
};
ChainDB.prototype.save = function save(entry, block, callback) {
ChainDB.prototype.save = function save(entry, block, connect, callback) {
var self = this;
var batch, height;
var batch, hash, height;
callback = utils.asyncify(callback);
callback = utils.ensure(callback);
assert(entry.height >= 0);
this.addCache(entry);
batch = this.db.batch();
hash = new Buffer(entry.hash, 'hex');
height = new Buffer(4);
utils.writeU32(height, entry.height, 0);
batch.put('c/b/' + entry.hash, height);
batch.put('c/c/' + entry.hash, entry.toRaw());
batch.put('c/n/' + entry.prevBlock, new Buffer(entry.hash, 'hex'));
batch.put('c/h/' + pad32(entry.height), new Buffer(entry.hash, 'hex'));
batch.put('c/t', new Buffer(entry.hash, 'hex'));
batch.put('c/n/' + entry.prevBlock, hash);
this.cacheHash.set(entry.hash, entry);
if (!connect) {
return this.saveBlock(block, batch, false, function(err) {
if (err)
return callback(err);
return batch.write(callback);
});
}
this.cacheHeight.set(entry.height, entry);
batch.put('c/h/' + pad32(entry.height), hash);
batch.put('c/t', hash);
this.emit('add entry', entry);
this.saveBlock(block, batch, function(err) {
this.saveBlock(block, batch, true, function(err) {
if (err)
return callback(err);
@ -435,7 +401,7 @@ ChainDB.prototype.connect = function connect(block, callback) {
var self = this;
var batch;
this._get(block, function(err, entry) {
this._ensureEntry(block, function(err, entry) {
if (err)
return callback(err);
@ -468,7 +434,7 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) {
var self = this;
var batch;
this._get(block, function(err, entry) {
this._ensureEntry(block, function(err, entry) {
if (err)
return callback(err);
@ -497,7 +463,7 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) {
});
};
ChainDB.prototype._get = function _get(block, callback) {
ChainDB.prototype._ensureEntry = function _ensureEntry(block, callback) {
if (block instanceof bcoin.chainblock)
return callback(null, block);
return this.get(block, callback);
@ -571,7 +537,7 @@ ChainDB.prototype.reset = function reset(block, callback) {
ChainDB.prototype.has = function has(height, callback) {
if (height == null || height < 0)
return callback(null, false);
return utils.asyncify(callback)(null, false);
return this.getBoth(height, function(err, hash, height) {
if (err)
@ -580,9 +546,9 @@ ChainDB.prototype.has = function has(height, callback) {
});
};
ChainDB.prototype.saveBlock = function saveBlock(block, batch, callback) {
ChainDB.prototype.saveBlock = function saveBlock(block, batch, connect, callback) {
if (this.options.spv)
return callback();
return utils.nextTick(callback);
batch.put('b/b/' + block.hash('hex'), block.toCompact());
@ -590,6 +556,9 @@ ChainDB.prototype.saveBlock = function saveBlock(block, batch, callback) {
batch.put('t/t/' + tx.hash('hex'), tx.toExtended());
});
if (!connect)
return utils.nextTick(callback);
this.connectBlock(block, batch, callback);
};
@ -597,9 +566,9 @@ ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) {
var self = this;
if (this.options.spv)
return callback();
return utils.nextTick(callback);
this._getTXBlock(hash, function(err, block) {
this._ensureHistory(hash, function(err, block) {
if (err)
return callback(err);
@ -621,10 +590,10 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
if (this.options.spv) {
self.emit('add block', block);
return callback();
return utils.nextTick(callback);
}
this._getCoinBlock(block, function(err, block) {
this._ensureBlock(block, function(err, block) {
var height;
if (err)
@ -638,6 +607,7 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
var uniq = {};
tx.inputs.forEach(function(input) {
var key = input.prevout.hash + '/' + input.prevout.index;
var address;
if (tx.isCoinbase())
@ -653,18 +623,15 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
batch.put('t/a/' + address + '/' + hash, DUMMY);
}
if (address) {
batch.del(
'u/a/' + address
+ '/' + input.prevout.hash
+ '/' + input.prevout.index);
}
if (address)
batch.del('u/a/' + address + '/' + key);
}
batch.del('u/t/' + input.prevout.hash + '/' + input.prevout.index);
batch.del('u/t/' + key);
});
tx.outputs.forEach(function(output, i) {
var key = hash + '/' + i;
var address;
if (self.options.indexAddress) {
@ -676,10 +643,10 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
}
if (address)
batch.put('u/a/' + address + '/' + hash + '/' + i, DUMMY);
batch.put('u/a/' + address + '/' + key, DUMMY);
}
batch.put('u/t/' + hash + '/' + i, bcoin.coin(tx, i).toRaw());
batch.put('u/t/' + key, bcoin.coin(tx, i).toRaw());
});
});
@ -693,9 +660,9 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
var self = this;
if (this.options.spv)
return callback();
return utils.nextTick(callback);
this._getTXBlock(hash, function(err, block) {
this._ensureHistory(hash, function(err, block) {
if (err)
return callback(err);
@ -710,6 +677,7 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
var uniq = {};
tx.inputs.forEach(function(input) {
var key = input.prevout.hash + '/' + input.prevout.index;
var address;
if (tx.isCoinbase())
@ -725,21 +693,15 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
batch.del('t/a/' + address + '/' + hash);
}
if (address) {
batch.put('u/a/' + address
+ '/' + input.prevout.hash
+ '/' + input.prevout.index,
DUMMY);
}
if (address)
batch.put('u/a/' + address + '/' + key, DUMMY);
}
batch.put('u/t/'
+ input.prevout.hash
+ '/' + input.prevout.index,
input.coin.toRaw());
batch.put('u/t/' + key, input.coin.toRaw());
});
tx.outputs.forEach(function(output, i) {
var key = hash + '/' + i;
var address;
if (self.options.indexAddress) {
@ -751,10 +713,10 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
}
if (address)
batch.del('u/a/' + address + '/' + hash + '/' + i);
batch.del('u/a/' + address + '/' + key);
}
batch.del('u/t/' + hash + '/' + i);
batch.del('u/t/' + key);
});
});
@ -767,8 +729,6 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
ChainDB.prototype.fillCoins = function fillCoins(tx, callback) {
var self = this;
callback = utils.asyncify(callback);
if (Array.isArray(tx)) {
return utils.forEachSerial(tx, function(tx, next) {
self.fillCoins(tx, next);
@ -780,7 +740,7 @@ ChainDB.prototype.fillCoins = function fillCoins(tx, callback) {
}
if (tx.isCoinbase())
return callback(null, tx);
return utils.asyncify(callback)(null, tx);
utils.forEachSerial(tx.inputs, function(input, next) {
if (input.coin)
@ -805,8 +765,6 @@ ChainDB.prototype.fillCoins = function fillCoins(tx, callback) {
ChainDB.prototype.fillTX = function fillTX(tx, callback) {
var self = this;
callback = utils.asyncify(callback);
if (Array.isArray(tx)) {
return utils.forEachSerial(tx, function(tx, next) {
self.fillTX(tx, next);
@ -818,7 +776,7 @@ ChainDB.prototype.fillTX = function fillTX(tx, callback) {
}
if (tx.isCoinbase())
return callback(null, tx);
return utils.asyncify(callback)(null, tx);
if (this.prune) {
return utils.forEachSerial(tx.inputs, function(input, next) {
@ -935,15 +893,15 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, opti
};
ChainDB.prototype.getCoin = function getCoin(hash, index, callback) {
var id = 'u/t/' + hash + '/' + index;
var key = 'u/t/' + hash + '/' + index;
var coin;
this.db.get(id, function(err, data) {
if (err) {
if (err.type === 'NotFoundError')
return callback();
this.db.get(key, function(err, data) {
if (err && err.type !== 'NotFoundError')
return callback(err);
}
if (!data)
return callback();
try {
coin = bcoin.coin.fromRaw(data);
@ -1036,10 +994,10 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, options, c
ChainDB.prototype.getTX = function getTX(hash, callback) {
var self = this;
var id = 't/t/' + hash;
var key = 't/t/' + hash;
var tx;
this.db.get(id, function(err, data) {
this.db.get(key, function(err, data) {
if (err) {
if (err.type === 'NotFoundError')
return callback();
@ -1097,11 +1055,11 @@ ChainDB.prototype.getFullBlock = function getFullBlock(hash, callback) {
});
};
ChainDB.prototype._getCoinBlock = function _getCoinBlock(hash, callback) {
ChainDB.prototype._ensureBlock = function _ensureBlock(hash, callback) {
var self = this;
if (hash instanceof bcoin.block)
return callback(null, hash);
return utils.asyncify(callback)(null, hash);
return this.getBlock(hash, function(err, block) {
if (err)
@ -1114,11 +1072,11 @@ ChainDB.prototype._getCoinBlock = function _getCoinBlock(hash, callback) {
});
};
ChainDB.prototype._getTXBlock = function _getTXBlock(hash, callback) {
ChainDB.prototype._ensureHistory = function _ensureHistory(hash, callback) {
var self = this;
if (hash instanceof bcoin.block)
return callback(null, hash);
return utils.asyncify(callback)(null, hash);
return this.getBlock(hash, function(err, block) {
if (err)
@ -1133,7 +1091,7 @@ ChainDB.prototype._getTXBlock = function _getTXBlock(hash, callback) {
ChainDB.prototype.fillBlock = function fillBlock(block, callback) {
return this.fillCoins(block.txs, function(err) {
var coins, i, tx, hash, j, input, id;
var coins, i, tx, hash, j, input, key;
if (err)
return callback(err);
@ -1146,10 +1104,10 @@ ChainDB.prototype.fillBlock = function fillBlock(block, callback) {
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
id = input.prevout.hash + '/' + input.prevout.index;
if (!input.coin && coins[id]) {
input.coin = coins[id];
delete coins[id];
key = input.prevout.hash + '/' + input.prevout.index;
if (!input.coin && coins[key]) {
input.coin = coins[key];
delete coins[key];
}
}
@ -1163,7 +1121,7 @@ ChainDB.prototype.fillBlock = function fillBlock(block, callback) {
ChainDB.prototype.fillTXBlock = function fillTXBlock(block, callback) {
return this.fillTX(block.txs, function(err) {
var coins, i, tx, hash, j, input, id;
var coins, i, tx, hash, j, input, key;
if (err)
return callback(err);
@ -1176,10 +1134,10 @@ ChainDB.prototype.fillTXBlock = function fillTXBlock(block, callback) {
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
id = input.prevout.hash + '/' + input.prevout.index;
if (!input.coin && coins[id]) {
input.coin = coins[id];
delete coins[id];
key = input.prevout.hash + '/' + input.prevout.index;
if (!input.coin && coins[key]) {
input.coin = coins[key];
delete coins[key];
}
}
@ -1193,7 +1151,7 @@ ChainDB.prototype.fillTXBlock = function fillTXBlock(block, callback) {
ChainDB.prototype.getBlock = function getBlock(hash, callback) {
var self = this;
var id, block;
var key, block;
return this.getHash(hash, function(err, hash) {
if (err)
@ -1202,14 +1160,14 @@ ChainDB.prototype.getBlock = function getBlock(hash, callback) {
if (!hash)
return callback();
id = 'b/b/' + hash;
key = 'b/b/' + hash;
self.db.get(id, function(err, data) {
if (err) {
if (err.type === 'NotFoundError')
return callback();
self.db.get(key, function(err, data) {
if (err && errr.type !== 'NotFoundError')
return callback(err);
}
if (!data)
return callback();
try {
block = bcoin.block.fromCompact(data);
@ -1344,24 +1302,24 @@ ChainDB.prototype._pruneQueue = function _pruneQueue(block, batch, callback) {
hash = utils.toHex(hash);
self.db.get('b/b/' + hash, function(err, cblock) {
self.db.get('b/b/' + hash, function(err, compact) {
if (err && err.type !== 'NotFoundError')
return callback(err);
batch.del(key);
if (!cblock)
if (!compact)
return callback();
try {
cblock = bcoin.block.fromCompact(cblock);
compact = bcoin.block.fromCompact(compact);
} catch (e) {
return callback(e);
}
batch.del('b/b/' + hash);
cblock.hashes.forEach(function(hash) {
compact.hashes.forEach(function(hash) {
batch.del('t/t/' + hash);
});
@ -1407,10 +1365,10 @@ ChainDB.prototype._pruneCoinQueue = function _pruneQueue(block, batch, callback)
ChainDB.prototype._getPruneCoin = function _getPruneCoin(hash, index, callback) {
var self = this;
var id = 'u/x/' + hash + '/' + index;
var key = 'u/x/' + hash + '/' + index;
var coin;
this.db.get(id, function(err, data) {
this.db.get(key, function(err, data) {
if (err && err.type !== 'NotFoundError')
return callback(err);
@ -1429,6 +1387,59 @@ ChainDB.prototype._getPruneCoin = function _getPruneCoin(hash, index, callback)
});
};
function DumbCache(size) {
this.data = {};
this.count = 0;
this.size = size;
}
DumbCache.prototype.set = function set(key, value) {
key = key + '';
assert(value !== undefined);
if (this.count > this.size)
this.reset();
if (this.data[key] === undefined)
this.count++;
this.data[key] = value;
};
DumbCache.prototype.remove = function remove(key) {
key = key + '';
if (this.data[key] === undefined)
return;
this.count--;
delete this.data[key];
};
DumbCache.prototype.get = function get(key) {
key = key + '';
return this.data[key];
};
DumbCache.prototype.has = function has(key) {
key = key + '';
return this.data[key] !== undefined;
};
DumbCache.prototype.reset = function reset() {
this.data = {};
this.count = 0;
};
function NullCache(size) {}
NullCache.prototype.set = function set(key, value) {};
NullCache.prototype.remove = function remove(key) {};
NullCache.prototype.get = function get(key) {};
NullCache.prototype.has = function has(key) {};
NullCache.prototype.reset = function reset() {};
/**
* Expose
*/