From 4502e942d43d3763600fc8a865c88adb88a1c84f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 17 Aug 2016 18:54:54 -0700 Subject: [PATCH] chaindb: chain state. --- lib/bcoin/chain.js | 2 + lib/bcoin/chaindb.js | 119 ++++++++++++++++++++++++++++++++------- lib/bcoin/http/base.js | 18 +++--- lib/bcoin/http/rpc.js | 6 +- lib/bcoin/http/server.js | 45 +++++++-------- lib/bcoin/mempool.js | 11 +++- lib/bcoin/miner.js | 23 ++++++-- lib/bcoin/pool.js | 4 +- 8 files changed, 168 insertions(+), 60 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 708bfd8f..8dc2a098 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -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); diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index e1743d28..c5455c8d 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -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 */ diff --git a/lib/bcoin/http/base.js b/lib/bcoin/http/base.js index e53436e7..c6d0ff77 100644 --- a/lib/bcoin/http/base.js +++ b/lib/bcoin/http/base.js @@ -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); }); }; diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index b730b956..82c9aaa2 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -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) }); }; diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index 701c7f87..392260c9 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -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); }; /** diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 69c32dac..8b2d14df 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -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(); + }); }; /** diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index b47bb118..45b2109b 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -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(); }; /** diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 2657b161..7a63a631 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -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); });