2014-02-03 18:30:46 -08:00
|
|
|
'use strict';
|
2014-03-10 08:46:48 -07:00
|
|
|
var imports = require('soop').imports();
|
2014-05-20 14:07:25 -07:00
|
|
|
var TIMESTAMP_PREFIX = 'bts-'; // bts-<ts> => <hash>
|
|
|
|
var PREV_PREFIX = 'bpr-'; // bpr-<hash> => <prev_hash>
|
|
|
|
var NEXT_PREFIX = 'bne-'; // bne-<hash> => <next_hash>
|
|
|
|
var MAIN_PREFIX = 'bma-'; // bma-<hash> => <height> (0 is unconnected)
|
|
|
|
var TIP = 'bti-'; // bti = <hash>:<height> last block on the chain
|
2014-03-05 18:03:56 -08:00
|
|
|
var LAST_FILE_INDEX = 'file-'; // last processed file index
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
// txid - blockhash mapping (only for confirmed txs, ONLY FOR BEST BRANCH CHAIN)
|
|
|
|
var IN_BLK_PREFIX = 'btx-'; //btx-<txid> = <block>
|
|
|
|
|
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
var MAX_OPEN_FILES = 500;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Module dependencies.
|
|
|
|
*/
|
|
|
|
var levelup = require('levelup'),
|
|
|
|
config = require('../config/config');
|
|
|
|
var db = imports.db || levelup(config.leveldb + '/blocks',{maxOpenFiles: MAX_OPEN_FILES} );
|
|
|
|
var Rpc = imports.rpc || require('./Rpc');
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
|
|
|
|
var logger = require('./logger').logger;
|
|
|
|
var d = logger.log;
|
|
|
|
var info = logger.info;
|
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
var BlockDb = function() {
|
2014-05-23 17:23:44 -07:00
|
|
|
this.txDb = require('./TransactionDb').default();
|
2014-03-05 18:03:56 -08:00
|
|
|
BlockDb.super(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.close = function(cb) {
|
|
|
|
db.close(cb);
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.drop = function(cb) {
|
|
|
|
var path = config.leveldb + '/blocks';
|
|
|
|
db.close(function() {
|
|
|
|
require('leveldown').destroy(path, function () {
|
|
|
|
db = levelup(path,{maxOpenFiles: MAX_OPEN_FILES} );
|
|
|
|
return cb();
|
2014-02-13 11:14:22 -08:00
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
|
|
|
|
BlockDb.prototype._addBlockScript = function(b, height) {
|
2014-03-05 18:03:56 -08:00
|
|
|
var time_key = TIMESTAMP_PREFIX +
|
|
|
|
( b.time || Math.round(new Date().getTime() / 1000) );
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
return [
|
|
|
|
{
|
|
|
|
type: 'put',
|
|
|
|
key: time_key,
|
|
|
|
value: b.hash,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'put',
|
|
|
|
key: MAIN_PREFIX + b.hash,
|
|
|
|
value: height,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'put',
|
|
|
|
key:PREV_PREFIX + b.hash,
|
|
|
|
value: b.previousblockhash,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype._delTxsScript = function(txs) {
|
|
|
|
var dbScript =[];
|
|
|
|
|
|
|
|
for(var ii in txs){
|
|
|
|
dbScript.push({
|
|
|
|
type: 'del',
|
|
|
|
key: IN_BLK_PREFIX + txs[ii],
|
2014-02-08 05:57:37 -08:00
|
|
|
});
|
2014-05-23 17:23:44 -07:00
|
|
|
}
|
|
|
|
return dbScript;
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype._addTxsScript = function(txs, hash, height) {
|
|
|
|
var dbScript =[];
|
|
|
|
|
|
|
|
for(var ii in txs){
|
|
|
|
dbScript.push({
|
|
|
|
type: 'put',
|
|
|
|
key: IN_BLK_PREFIX + txs[ii],
|
|
|
|
value: hash+':'+height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return dbScript;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Returns blockHash and height for a given txId (If the tx is on the MAIN chain).
|
|
|
|
BlockDb.prototype.getBlockForTx = function(txId, cb) {
|
|
|
|
db.get(IN_BLK_PREFIX + txId,function (err, val) {
|
|
|
|
if (err && err.notFound) return cb();
|
|
|
|
if (err) return cb(err);
|
|
|
|
|
|
|
|
var v = val.split(':');
|
|
|
|
return cb(err,v[0],parseInt(v[1]));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype._changeBlockHeight = function(hash, height, cb) {
|
|
|
|
var self = this;
|
|
|
|
var dbScript1 = this._setHeightScript(hash,height);
|
|
|
|
|
|
|
|
d('Getting TXS FROM %s to set it Main', hash);
|
|
|
|
this.fromHashWithInfo(hash, function(err, bi) {
|
|
|
|
if (!bi || !bi.info || !bi.info.tx)
|
|
|
|
throw new Error('unable to get info for block:'+ hash);
|
|
|
|
|
|
|
|
var dbScript2;
|
|
|
|
if (height>=0) {
|
|
|
|
dbScript2 = self._addTxsScript(bi.info.tx, hash, height);
|
|
|
|
info('\t%s %d Txs', 'Confirming', bi.info.tx.length);
|
|
|
|
} else {
|
|
|
|
dbScript2 = self._delTxsScript(bi.info.tx);
|
|
|
|
info('\t%s %d Txs', 'Unconfirming', bi.info.tx.length);
|
|
|
|
}
|
|
|
|
db.batch(dbScript2.concat(dbScript1),cb);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.setBlockMain = function(hash, height, cb) {
|
|
|
|
this._changeBlockHeight(hash,height,cb);
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.setBlockNotMain = function(hash, cb) {
|
|
|
|
this._changeBlockHeight(hash,-1,cb);
|
|
|
|
};
|
|
|
|
|
|
|
|
// adds a block (and its txs). Does not update Next pointer in
|
|
|
|
// the block prev to the new block, nor TIP pointer
|
|
|
|
//
|
|
|
|
BlockDb.prototype.add = function(b, height, cb) {
|
|
|
|
d('adding block %s #d', b,height);
|
|
|
|
var dbScript = this._addBlockScript(b,height);
|
|
|
|
dbScript = dbScript.concat(this._addTxsScript(b.tx,b.hash, height));
|
|
|
|
this.txDb.addMany(b.tx, function(err) {
|
|
|
|
if (err) return cb(err);
|
|
|
|
db.batch(dbScript,cb);
|
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.getTip = function(cb) {
|
2014-05-23 17:23:44 -07:00
|
|
|
|
|
|
|
if (this.cachedTip){
|
|
|
|
var v = this.cachedTip.split(':');
|
|
|
|
return cb(null,v[0], parseInt(v[1]));
|
|
|
|
}
|
|
|
|
|
|
|
|
var self = this;
|
2014-03-05 18:03:56 -08:00
|
|
|
db.get(TIP, function(err, val) {
|
2014-05-20 14:07:25 -07:00
|
|
|
if (!val) return cb();
|
2014-05-23 17:23:44 -07:00
|
|
|
self.cachedTip = val;
|
2014-05-20 14:07:25 -07:00
|
|
|
var v = val.split(':');
|
|
|
|
return cb(err,v[0], parseInt(v[1]));
|
2014-03-05 18:03:56 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-20 14:07:25 -07:00
|
|
|
BlockDb.prototype.setTip = function(hash, height, cb) {
|
2014-05-23 17:23:44 -07:00
|
|
|
this.cachedTip = hash + ':' + height;
|
|
|
|
db.put(TIP, this.cachedTip, function(err) {
|
2014-03-05 18:03:56 -08:00
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
BlockDb.prototype.getDepth = function(hash, cb) {
|
|
|
|
var v = this.cachedTip.split(':');
|
|
|
|
if (!v) throw new Error('getDepth called with not cachedTip');
|
|
|
|
this.getHeight(hash, function(err,h){
|
|
|
|
return cb(err,parseInt(v[1]) - h);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
//mainly for testing
|
|
|
|
BlockDb.prototype.setPrev = function(hash, prevHash, cb) {
|
|
|
|
db.put(PREV_PREFIX + hash, prevHash, function(err) {
|
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.getPrev = function(hash, cb) {
|
|
|
|
db.get(PREV_PREFIX + hash, function(err,val) {
|
|
|
|
if (err && err.notFound) { err = null; val = null;}
|
|
|
|
return cb(err,val);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
BlockDb.prototype.setLastFileIndex = function(idx, cb) {
|
|
|
|
var self = this;
|
|
|
|
if (this.lastFileIndexSaved === idx) return cb();
|
|
|
|
|
|
|
|
db.put(LAST_FILE_INDEX, idx, function(err) {
|
|
|
|
self.lastFileIndexSaved = idx;
|
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.getLastFileIndex = function(cb) {
|
|
|
|
db.get(LAST_FILE_INDEX, function(err,val) {
|
|
|
|
if (err && err.notFound) { err = null; val = null;}
|
|
|
|
return cb(err,val);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.getNext = function(hash, cb) {
|
|
|
|
db.get(NEXT_PREFIX + hash, function(err,val) {
|
|
|
|
if (err && err.notFound) { err = null; val = null;}
|
|
|
|
return cb(err,val);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-20 14:07:25 -07:00
|
|
|
BlockDb.prototype.getHeight = function(hash, cb) {
|
2014-03-05 18:03:56 -08:00
|
|
|
db.get(MAIN_PREFIX + hash, function(err, val) {
|
|
|
|
if (err && err.notFound) { err = null; val = 0;}
|
|
|
|
return cb(err,parseInt(val));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-23 17:23:44 -07:00
|
|
|
BlockDb.prototype._setHeightScript = function(hash, height) {
|
|
|
|
d('setHeight: %s #%d', hash,height);
|
|
|
|
return ([{
|
|
|
|
type: 'put',
|
|
|
|
key: MAIN_PREFIX + hash,
|
|
|
|
value: height,
|
|
|
|
}]);
|
2014-03-05 18:03:56 -08:00
|
|
|
};
|
2014-05-20 14:07:25 -07:00
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
BlockDb.prototype.setNext = function(hash, nextHash, cb) {
|
|
|
|
db.put(NEXT_PREFIX + hash, nextHash, function(err) {
|
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.countConnected = function(cb) {
|
|
|
|
var c = 0;
|
|
|
|
console.log('Counting connected blocks. This could take some minutes');
|
|
|
|
db.createReadStream({start: MAIN_PREFIX, end: MAIN_PREFIX + '~' })
|
|
|
|
.on('data', function (data) {
|
|
|
|
if (data.value !== 0) c++;
|
|
|
|
})
|
|
|
|
.on('error', function (err) {
|
2014-02-08 05:57:37 -08:00
|
|
|
return cb(err);
|
2014-03-05 18:03:56 -08:00
|
|
|
})
|
|
|
|
.on('end', function () {
|
|
|
|
return cb(null, c);
|
2014-02-08 05:57:37 -08:00
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// .has() return true orphans also
|
|
|
|
BlockDb.prototype.has = function(hash, cb) {
|
|
|
|
var k = PREV_PREFIX + hash;
|
|
|
|
db.get(k, function (err) {
|
|
|
|
var ret = true;
|
|
|
|
if (err && err.notFound) {
|
|
|
|
err = null;
|
|
|
|
ret = false;
|
|
|
|
}
|
|
|
|
return cb(err, ret);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.fromHashWithInfo = function(hash, cb) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
Rpc.getBlock(hash, function(err, info) {
|
|
|
|
if (err || !info) return cb(err);
|
|
|
|
|
2014-05-20 14:07:25 -07:00
|
|
|
//TODO can we get this from RPC .height?
|
|
|
|
self.getHeight(hash, function(err, height) {
|
2014-03-05 18:03:56 -08:00
|
|
|
if (err) return cb(err);
|
|
|
|
|
2014-05-20 14:07:25 -07:00
|
|
|
info.isMainChain = height ? true : false;
|
2014-03-05 18:03:56 -08:00
|
|
|
|
|
|
|
return cb(null, {
|
|
|
|
hash: hash,
|
|
|
|
info: info,
|
2014-02-03 18:30:46 -08:00
|
|
|
});
|
2014-02-18 09:35:54 -08:00
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
BlockDb.prototype.getBlocksByDate = function(start_ts, end_ts, cb) {
|
|
|
|
var list = [];
|
|
|
|
db.createReadStream({
|
|
|
|
start: TIMESTAMP_PREFIX + start_ts,
|
|
|
|
end: TIMESTAMP_PREFIX + end_ts,
|
|
|
|
fillCache: true
|
|
|
|
})
|
|
|
|
.on('data', function (data) {
|
|
|
|
var k = data.key.split('-');
|
|
|
|
list.push({
|
|
|
|
ts: k[1],
|
|
|
|
hash: data.value,
|
2014-02-03 18:30:46 -08:00
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
})
|
|
|
|
.on('error', function (err) {
|
|
|
|
return cb(err);
|
|
|
|
})
|
|
|
|
.on('end', function () {
|
|
|
|
return cb(null, list.reverse());
|
2014-02-03 18:30:46 -08:00
|
|
|
});
|
2014-03-05 18:03:56 -08:00
|
|
|
};
|
2014-02-03 18:30:46 -08:00
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
BlockDb.prototype.blockIndex = function(height, cb) {
|
|
|
|
return Rpc.blockIndex(height,cb);
|
|
|
|
};
|
2014-02-03 18:30:46 -08:00
|
|
|
|
2014-03-05 18:03:56 -08:00
|
|
|
module.exports = require('soop')(BlockDb);
|