chaindb: state work.

This commit is contained in:
Christopher Jeffrey 2016-08-17 19:51:23 -07:00
parent 4502e942d4
commit bf20425e11
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -246,6 +246,8 @@ function ChainDB(chain, options) {
this.keepBlocks = options.keepBlocks || 288;
this.prune = !!options.prune;
this.state = new ChainState();
this.pending = null;
this.current = null;
this.loaded = false;
@ -328,6 +330,85 @@ ChainDB.prototype._close = function close(callback) {
this.db.close(callback);
};
/**
* Start a batch.
* @returns {Batch}
*/
ChainDB.prototype.start = function start() {
assert(!this.current);
assert(!this.pending);
this.current = this.db.batch();
this.pending = this.state.clone();
return this.current;
};
/**
* Put key and value to current batch.
* @param {String} key
* @param {Buffer} value
*/
ChainDB.prototype.put = function put(key, value) {
assert(this.current);
this.current.put(key, value);
};
/**
* Delete key from current batch.
* @param {String} key
*/
ChainDB.prototype.del = function del(key) {
assert(this.current);
this.current.del(key);
};
/**
* Get current batch.
* @returns {Batch}
*/
ChainDB.prototype.batch = function batch() {
assert(this.current);
return this.current;
};
/**
* Drop current batch.
* @returns {Batch}
*/
ChainDB.prototype.drop = function drop() {
assert(this.current);
assert(this.pending);
this.current.clear();
this.current = null;
this.pending = null;
};
/**
* Commit current batch.
* @param {Function} callback
*/
ChainDB.prototype.commit = function commit(callback) {
var self = this;
assert(this.current);
assert(this.pending);
this.current.write(function(err) {
if (err) {
self.current = null;
self.pending = null;
return callback(err);
}
self.current = null;
self.state = self.pending;
self.pending = null;
callback();
});
};
/**
* Add an entry to the LRU cache.
* @param {ChainEntry} entry
@ -575,35 +656,41 @@ ChainDB.prototype.get = function get(hash, callback) {
*/
ChainDB.prototype.save = function save(entry, block, view, connect, callback) {
var batch = this.db.batch();
var self = this;
var hash = block.hash();
var height = new Buffer(4);
this.start();
height.writeUInt32LE(entry.height, 0, true);
batch.put(layout.h(hash), height);
batch.put(layout.e(hash), entry.toRaw());
this.put(layout.h(hash), height);
this.put(layout.e(hash), entry.toRaw());
this.cacheHash.set(entry.hash, entry);
if (!connect) {
return this.saveBlock(block, view, batch, false, function(err) {
if (err)
return this.saveBlock(block, view, false, function(err) {
if (err) {
self.drop();
return callback(err);
batch.write(callback);
}
self.commit(callback);
});
}
this.cacheHeight.set(entry.height, entry);
batch.put(layout.n(entry.prevBlock), hash);
batch.put(layout.H(entry.height), hash);
batch.put(layout.R, this.state.commit(hash));
this.put(layout.n(entry.prevBlock), hash);
this.put(layout.H(entry.height), hash);
this.saveBlock(block, view, batch, true, function(err) {
if (err)
this.saveBlock(block, view, true, function(err) {
if (err) {
self.drop();
return callback(err);
batch.write(callback);
}
self.put(layout.R, self.pending.mark(hash));
self.commit(callback);
});
};
@ -644,29 +731,34 @@ ChainDB.prototype.getTip = function getTip(callback) {
*/
ChainDB.prototype.reconnect = function reconnect(entry, block, view, callback) {
var batch = this.db.batch();
var self = this;
var hash = block.hash();
batch.put(layout.n(entry.prevBlock), hash);
batch.put(layout.H(entry.height), hash);
batch.put(layout.R, this.state.commit(hash));
this.start();
this.put(layout.n(entry.prevBlock), hash);
this.put(layout.H(entry.height), hash);
this.cacheHash.set(entry.hash, entry);
this.cacheHeight.set(entry.height, entry);
if (this.options.spv) {
return batch.write(function(err) {
this.put(layout.R, this.pending.mark(hash));
return this.commit(function(err) {
if (err)
return callback(err);
return callback(null, entry, block);
});
}
this.connectBlock(block, view, batch, function(err) {
if (err)
this.connectBlock(block, view, function(err) {
if (err) {
self.drop();
return callback(err);
}
batch.write(function(err) {
self.put(layout.R, self.pending.mark(hash));
self.commit(function(err) {
if (err)
return callback(err);
callback(null, entry, block);
@ -683,16 +775,16 @@ ChainDB.prototype.reconnect = function reconnect(entry, block, view, callback) {
ChainDB.prototype.disconnect = function disconnect(entry, callback) {
var self = this;
var batch = this.db.batch();
batch.del(layout.n(entry.prevBlock));
batch.del(layout.H(entry.height));
batch.put(layout.R, this.state.commit(entry.prevBlock));
this.start();
this.del(layout.n(entry.prevBlock));
this.del(layout.H(entry.height));
this.cacheHeight.remove(entry.height);
if (this.options.spv) {
return batch.write(function(err) {
this.put(layout.R, this.pending.mark(entry.prevBlock));
return this.commit(function(err) {
if (err)
return callback(err);
callback(null, entry, entry.toHeaders());
@ -700,17 +792,24 @@ ChainDB.prototype.disconnect = function disconnect(entry, callback) {
}
this.getBlock(entry.hash, function(err, block) {
if (err)
if (err) {
self.drop();
return callback(err);
}
if (!block)
if (!block) {
self.drop();
return callback(new Error('Block not found.'));
}
self.disconnectBlock(block, batch, function(err) {
if (err)
self.disconnectBlock(block, function(err) {
if (err) {
self.drop();
return callback(err);
}
batch.write(function(err) {
self.put(layout.R, self.pending.mark(entry.prevBlock));
self.commit(function(err) {
if (err)
return callback(err);
callback(null, entry, block);
@ -777,7 +876,6 @@ ChainDB.prototype.isMainChain = function isMainChain(hash, callback) {
ChainDB.prototype.reset = function reset(block, callback) {
var self = this;
var batch;
this.get(block, function(err, entry) {
if (err)
@ -800,23 +898,25 @@ ChainDB.prototype.reset = function reset(block, callback) {
if (!tip)
return callback();
batch = self.db.batch();
self.start();
if (tip.hash === entry.hash) {
batch.put(layout.R, self.state.commit(tip.hash));
return batch.write(callback);
self.put(layout.R, self.pending.mark(tip.hash));
return self.commit(callback);
}
batch.del(layout.H(tip.height));
batch.del(layout.h(tip.hash));
batch.del(layout.e(tip.hash));
batch.del(layout.n(tip.prevBlock));
self.del(layout.H(tip.height));
self.del(layout.h(tip.hash));
self.del(layout.e(tip.hash));
self.del(layout.n(tip.prevBlock));
self.removeBlock(tip.hash, batch, function(err) {
if (err)
self.removeBlock(tip.hash, function(err) {
if (err) {
self.drop();
return callback(err);
}
batch.write(function(err) {
self.commit(function(err) {
if (err)
return next(err);
self.get(tip.prevBlock, next);
@ -850,32 +950,30 @@ ChainDB.prototype.has = function has(height, callback) {
* Save a block (not an entry) to the
* database and potentially connect the inputs.
* @param {Block} block
* @param {Batch} batch
* @param {Boolean} connect - Whether to connect the inputs.
* @param {Function} callback - Returns [Error, {@link Block}].
*/
ChainDB.prototype.saveBlock = function saveBlock(block, view, batch, connect, callback) {
ChainDB.prototype.saveBlock = function saveBlock(block, view, connect, callback) {
if (this.options.spv)
return utils.asyncify(callback)(null, block);
batch.put(layout.b(block.hash()), block.toRaw());
this.put(layout.b(block.hash()), block.toRaw());
if (!connect)
return utils.asyncify(callback)(null, block);
this.connectBlock(block, view, batch, callback);
this.connectBlock(block, view, callback);
};
/**
* Remove a block (not an entry) to the database.
* Disconnect inputs.
* @param {Block|Hash} block - {@link Block} or hash.
* @param {Batch} batch
* @param {Function} callback - Returns [Error, {@link Block}].
*/
ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) {
ChainDB.prototype.removeBlock = function removeBlock(hash, callback) {
var self = this;
this.getBlock(hash, function(err, block) {
@ -885,23 +983,22 @@ ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) {
if (!block)
return callback();
batch.del(layout.b(block.hash()));
self.del(layout.b(block.hash()));
if (self.options.spv)
return callback(null, block);
self.disconnectBlock(block, batch, callback);
self.disconnectBlock(block, callback);
});
};
/**
* Connect block inputs.
* @param {Block} block
* @param {Batch} batch
* @param {Function} callback - Returns [Error, {@link Block}].
*/
ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callback) {
ChainDB.prototype.connectBlock = function connectBlock(block, view, callback) {
var undo = new BufferWriter();
var i, j, tx, input, output, prev, hashes, address, hash, coins, raw;
@ -910,23 +1007,23 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
// Genesis block's coinbase is unspendable.
if (this.chain.isGenesis(block)) {
this.state.connect(block);
this.pending.connect(block);
return utils.asyncify(callback)(null, block);
}
this.state.connect(block);
this.pending.connect(block);
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
hash = tx.hash();
if (this.options.indexTX) {
batch.put(layout.t(hash), tx.toExtended());
this.put(layout.t(hash), tx.toExtended());
if (this.options.indexAddress) {
hashes = tx.getHashes();
for (j = 0; j < hashes.length; j++) {
address = hashes[j];
batch.put(layout.T(address, hash), DUMMY);
this.put(layout.T(address, hash), DUMMY);
}
}
}
@ -943,13 +1040,13 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
address = input.getHash();
if (address) {
prev = input.prevout;
batch.del(layout.C(address, prev.hash, prev.index));
this.del(layout.C(address, prev.hash, prev.index));
}
}
input.coin.toRaw(undo);
this.state.spend(input.coin);
this.pending.spend(input.coin);
}
for (j = 0; j < tx.outputs.length; j++) {
@ -961,10 +1058,10 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
if (this.options.indexAddress) {
address = output.getHash();
if (address)
batch.put(layout.C(address, hash, j), DUMMY);
this.put(layout.C(address, hash, j), DUMMY);
}
this.state.add(output);
this.pending.add(output);
}
}
@ -973,19 +1070,19 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
for (i = 0; i < view.length; i++) {
coins = view[i];
if (coins.size() === 0) {
batch.del(layout.c(coins.hash));
this.del(layout.c(coins.hash));
this.coinCache.remove(coins.hash);
} else {
raw = coins.toRaw();
batch.put(layout.c(coins.hash), raw);
this.put(layout.c(coins.hash), raw);
this.coinCache.set(coins.hash, raw);
}
}
if (undo.written > 0)
batch.put(layout.u(block.hash()), undo.render());
this.put(layout.u(block.hash()), undo.render());
this._pruneBlock(block, batch, function(err) {
this._pruneBlock(block, function(err) {
if (err)
return callback(err);
callback(null, block);
@ -995,11 +1092,10 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
/**
* Disconnect block inputs.
* @param {Block|Hash} block - {@link Block} or hash.
* @param {Batch} batch
* @param {Function} callback - Returns [Error, {@link Block}].
*/
ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callback) {
ChainDB.prototype.disconnectBlock = function disconnectBlock(block, callback) {
var self = this;
var i, j, tx, input, output, prev, hashes, address, hash, coins, raw;
@ -1010,19 +1106,19 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
if (err)
return callback(err);
self.state.disconnect(block);
self.pending.disconnect(block);
for (i = block.txs.length - 1; i >= 0; i--) {
tx = block.txs[i];
hash = tx.hash('hex');
if (self.options.indexTX) {
batch.del(layout.t(hash));
self.del(layout.t(hash));
if (self.options.indexAddress) {
hashes = tx.getHashes();
for (j = 0; j < hashes.length; j++) {
address = hashes[j];
batch.del(layout.T(address, hash));
self.del(layout.T(address, hash));
}
}
}
@ -1039,11 +1135,11 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
address = input.getHash();
if (address) {
prev = input.prevout;
batch.put(layout.C(address, prev.hash, prev.index), DUMMY);
self.put(layout.C(address, prev.hash, prev.index), DUMMY);
}
}
self.state.add(input.coin);
self.pending.add(input.coin);
}
// Add all of the coins we are about to
@ -1060,13 +1156,13 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
if (self.options.indexAddress) {
address = output.getHash();
if (address)
batch.del(layout.C(address, hash, j));
self.del(layout.C(address, hash, j));
}
// Spend added coin.
view.spend(hash, j);
self.state.spend(output);
self.pending.spend(output);
}
}
@ -1075,16 +1171,16 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
for (i = 0; i < view.length; i++) {
coins = view[i];
if (coins.size() === 0) {
batch.del(layout.c(coins.hash));
self.del(layout.c(coins.hash));
self.coinCache.remove(coins.hash);
} else {
raw = coins.toRaw();
batch.put(layout.c(coins.hash), raw);
self.put(layout.c(coins.hash), raw);
self.coinCache.set(coins.hash, raw);
}
}
batch.del(layout.u(block.hash()));
self.del(layout.u(block.hash()));
callback(null, block);
});
@ -1628,11 +1724,10 @@ ChainDB.prototype.hasCoins = function hasCoins(hash, callback) {
* add current block to the prune queue.
* @private
* @param {Block}
* @param {Batch} batch
* @param {Function} callback
*/
ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
ChainDB.prototype._pruneBlock = function _pruneBlock(block, callback) {
var futureHeight, key;
if (this.options.spv)
@ -1646,7 +1741,7 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
futureHeight = block.height + this.keepBlocks;
batch.put(layout.q(futureHeight), block.hash());
this.put(layout.q(futureHeight), block.hash());
key = layout.q(block.height);
@ -1660,9 +1755,9 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
if (!hash)
return callback();
batch.del(key);
batch.del(layout.b(hash));
batch.del(layout.u(hash));
self.del(key);
self.del(layout.b(hash));
self.del(layout.u(hash));
callback();
});
@ -1675,6 +1770,15 @@ function ChainState() {
this.value = 0;
}
ChainState.prototype.clone = function clone() {
var state = new ChainState();
state.hash = this.hash;
state.tx = this.tx;
state.coin = this.coin;
state.value = this.value;
return state;
};
ChainState.prototype.connect = function connect(block) {
this.tx += block.txs.length;
};
@ -1693,7 +1797,7 @@ ChainState.prototype.spend = function spend(coin) {
this.value -= coin.value;
};
ChainState.prototype.commit = function commit(hash) {
ChainState.prototype.mark = function mark(hash) {
this.hash = hash;
if (typeof this.hash !== 'string')
this.hash = this.hash.toString('hex');