chaindb: state work.
This commit is contained in:
parent
4502e942d4
commit
bf20425e11
@ -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');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user