chaindb: chain state.
This commit is contained in:
parent
2831af4300
commit
4502e942d4
@ -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);
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user