chaindb: chain state.

This commit is contained in:
Christopher Jeffrey 2016-08-17 18:54:54 -07:00
parent 2831af4300
commit 4502e942d4
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 168 additions and 60 deletions

View File

@ -205,6 +205,8 @@ Chain.prototype._open = function open(callback) {
self.tip = tip;
self.height = tip.height;
self.logger.info('Chain Height: %d', tip.height);
if (tip.height > self.bestHeight) {
self.bestHeight = tip.height;
self.network.updateHeight(tip.height);

View File

@ -245,6 +245,7 @@ function ChainDB(chain, options) {
this.keepBlocks = options.keepBlocks || 288;
this.prune = !!options.prune;
this.state = new ChainState();
this.loaded = false;
@ -286,6 +287,12 @@ ChainDB.prototype._open = function open(callback) {
return callback(err);
self.logger.info('Chain successfully loaded.');
self.logger.info('Chain State: hash=%s tx=%d coin=%d value=%s.',
utils.revHex(self.state.hash),
self.state.tx,
self.state.coin,
utils.btc(self.state.value)
);
self.db.checkVersion('V', 0, callback);
}
@ -299,7 +306,7 @@ ChainDB.prototype._open = function open(callback) {
return done(err);
if (exists)
return done();
return self.initState(done);
block = bcoin.block.fromRaw(self.network.genesisBlock, 'hex');
block.setHeight(0);
@ -591,7 +598,7 @@ ChainDB.prototype.save = function save(entry, block, view, connect, callback) {
batch.put(layout.n(entry.prevBlock), hash);
batch.put(layout.H(entry.height), hash);
batch.put(layout.R, hash);
batch.put(layout.R, this.state.commit(hash));
this.saveBlock(block, view, batch, true, function(err) {
if (err)
@ -600,25 +607,31 @@ ChainDB.prototype.save = function save(entry, block, view, connect, callback) {
});
};
/**
* Retrieve the chain state.
* @param {Function} callback - Returns [Error, {@link ChainState}].
*/
ChainDB.prototype.initState = function initState(callback) {
var self = this;
this.db.fetch(layout.R, function(data) {
return ChainState.fromRaw(data);
}, function(err, state) {
if (err)
return callback(err);
assert(state);
self.state = state;
return callback(null, state);
});
};
/**
* Retrieve the tip entry from the tip record.
* @param {Function} callback - Returns [Error, {@link ChainEntry}].
*/
ChainDB.prototype.getTip = function getTip(callback) {
var self = this;
this.db.fetch(layout.R, function(data) {
assert(data.length === 32, 'Database corruption.');
return data.toString('hex');
}, function(err, hash) {
if (err)
return callback(err);
if (!hash)
return callback();
self.get(hash, callback);
});
this.get(this.state.hash, callback);
};
/**
@ -636,7 +649,7 @@ ChainDB.prototype.reconnect = function reconnect(entry, block, view, callback) {
batch.put(layout.n(entry.prevBlock), hash);
batch.put(layout.H(entry.height), hash);
batch.put(layout.R, hash);
batch.put(layout.R, this.state.commit(hash));
this.cacheHash.set(entry.hash, entry);
this.cacheHeight.set(entry.height, entry);
@ -674,7 +687,7 @@ ChainDB.prototype.disconnect = function disconnect(entry, callback) {
batch.del(layout.n(entry.prevBlock));
batch.del(layout.H(entry.height));
batch.put(layout.R, new Buffer(entry.prevBlock, 'hex'));
batch.put(layout.R, this.state.commit(entry.prevBlock));
this.cacheHeight.remove(entry.height);
@ -790,7 +803,7 @@ ChainDB.prototype.reset = function reset(block, callback) {
batch = self.db.batch();
if (tip.hash === entry.hash) {
batch.put(layout.R, new Buffer(tip.hash, 'hex'));
batch.put(layout.R, self.state.commit(tip.hash));
return batch.write(callback);
}
@ -896,8 +909,12 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
return utils.asyncify(callback)(null, block);
// Genesis block's coinbase is unspendable.
if (this.chain.isGenesis(block))
if (this.chain.isGenesis(block)) {
this.state.connect(block);
return utils.asyncify(callback)(null, block);
}
this.state.connect(block);
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
@ -931,6 +948,8 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
}
input.coin.toRaw(undo);
this.state.spend(input.coin);
}
for (j = 0; j < tx.outputs.length; j++) {
@ -944,6 +963,8 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
if (address)
batch.put(layout.C(address, hash, j), DUMMY);
}
this.state.add(output);
}
}
@ -989,6 +1010,8 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
if (err)
return callback(err);
self.state.disconnect(block);
for (i = block.txs.length - 1; i >= 0; i--) {
tx = block.txs[i];
hash = tx.hash('hex');
@ -1019,6 +1042,8 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
batch.put(layout.C(address, prev.hash, prev.index), DUMMY);
}
}
self.state.add(input.coin);
}
// Add all of the coins we are about to
@ -1040,6 +1065,8 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
// Spend added coin.
view.spend(hash, j);
self.state.spend(output);
}
}
@ -1641,6 +1668,60 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
});
};
function ChainState() {
this.hash = null;
this.tx = 0;
this.coin = 0;
this.value = 0;
}
ChainState.prototype.connect = function connect(block) {
this.tx += block.txs.length;
};
ChainState.prototype.disconnect = function connect(block) {
this.tx -= block.txs.length;
};
ChainState.prototype.add = function add(coin) {
this.coin++
this.value += coin.value;
};
ChainState.prototype.spend = function spend(coin) {
this.coin--;
this.value -= coin.value;
};
ChainState.prototype.commit = function commit(hash) {
this.hash = hash;
if (typeof this.hash !== 'string')
this.hash = this.hash.toString('hex');
return this.toRaw(hash);
};
ChainState.prototype.toRaw = function toRaw(hash) {
var p = new BufferWriter();
p.writeHash(hash || this.hash);
p.writeU64(this.tx);
p.writeU64(this.coin);
p.writeU64(this.value);
return p.render();
};
ChainState.fromRaw = function fromRaw(data) {
var state = new ChainState();
var p = new BufferReader(data);
state.hash = p.readHash('hex');
// XXX Allow just a hash until I write a migration.
if (p.left() > 0) {
state.tx = p.readU53();
state.coin = p.readU53();
state.value = p.readU53();
}
return state;
};
/*
* Helpers
*/

View File

@ -381,17 +381,21 @@ HTTPBase.prototype.address = function address() {
HTTPBase.prototype.listen = function listen(port, host, callback) {
var self = this;
var addr;
this.server.listen(port, host, function(err) {
if (!callback) {
if (err)
throw err;
return;
if (err) {
if (callback)
return callback(err);
throw err;
}
if (err)
return callback(err);
addr = self.address();
return callback(null, self.address());
self.emit('listening', addr);
if (callback)
callback(null, addr);
});
};

View File

@ -1334,11 +1334,11 @@ RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args, callback) {
callback(null, {
height: this.chain.height,
bestblock: this.chain.tip.rhash,
transactions: 0,
txouts: 0,
transactions: this.chain.db.state.tx,
txouts: this.chain.db.state.coin,
bytes_serialized: 0,
hash_serialized: 0,
total_amount: 0
total_amount: +utils.btc(this.chain.db.state.value)
});
};

View File

@ -91,6 +91,11 @@ HTTPServer.prototype._init = function _init() {
req.pathname, req.socket.remoteAddress);
});
this.server.on('listening', function(address) {
self.logger.info('HTTP server listening on %s (port=%d).',
address.address, address.port);
});
this.use(function(req, res, next, send) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
@ -1099,14 +1104,22 @@ HTTPServer.prototype._initIO = function _initIO() {
*/
HTTPServer.prototype.open = function open(callback) {
if (this.apiKey) {
this.logger.info('API key: %s', this.apiKey);
this.apiKey = null;
} else if (!this.apiHash) {
this.logger.warning('WARNING: Your http server is open to the world.');
}
var self = this;
this.server.open(function(err) {
if (err)
return callback(err);
this.server.open(callback);
self.logger.info('HTTP server loaded.');
if (self.apiKey) {
self.logger.info('HTTP API key: %s', self.apiKey);
self.apiKey = null;
} else if (!self.apiHash) {
self.logger.warning('WARNING: Your http server is open to the world.');
}
callback();
});
};
/**
@ -1163,23 +1176,7 @@ HTTPServer.prototype.del = function del(path, callback) {
*/
HTTPServer.prototype.listen = function listen(port, host, callback) {
var self = this;
this.server.listen(port, host, function(err, address) {
if (err) {
if (callback)
return callback(err);
return self.emit('error', err);
}
self.logger.info('HTTP server listening on %s (port=%d).',
address.address, address.port);
self.loaded = true;
self.emit('open');
if (callback)
callback();
});
this.server.listen(port, host, callback);
};
/**

View File

@ -119,7 +119,16 @@ utils.inherits(Mempool, AsyncObject);
*/
Mempool.prototype._open = function open(callback) {
this.chain.open(callback);
var self = this;
var size = (self.maxSize / 1024).toFixed(2);
this.chain.open(function(err) {
if (err)
return callback(err);
self.logger.info('Mempool loaded (maxsize=%dkb).', size);
callback();
});
};
/**

View File

@ -134,10 +134,23 @@ Miner.prototype._init = function _init() {
*/
Miner.prototype._open = function open(callback) {
if (this.mempool)
this.mempool.open(callback);
else
this.chain.open(callback);
var self = this;
function open(callback) {
if (self.mempool)
self.mempool.open(callback);
else
self.chain.open(callback);
}
open(function(err) {
if (err)
return callback(err);
self.logger.info('Miner loaded (flags=%s).', self.coinbaseFlags);
callback();
});
};
/**
@ -147,7 +160,7 @@ Miner.prototype._open = function open(callback) {
*/
Miner.prototype._close = function close(callback) {
utils.nextTick(callback);
callback();
};
/**

View File

@ -322,6 +322,8 @@ Pool.prototype._open = function _open(callback) {
if (err)
return callback(err);
self.logger.info('Pool loaded (maxpeers=%d).', self.maxPeers);
if (!self.options.listen)
return callback();
@ -477,7 +479,7 @@ Pool.prototype.listen = function listen(callback) {
this.server.on('listening', function() {
var data = self.server.address();
self.logger.info(
'Bitcoin server listening on %s (port=%d).',
'Pool server listening on %s (port=%d).',
data.address, data.port);
});