DB Service: Include a version number for upgrading purposes
This commit is contained in:
parent
b0a0f629e2
commit
98bd8ee560
|
@ -38,6 +38,10 @@ function DB(options) {
|
||||||
|
|
||||||
Service.call(this, options);
|
Service.call(this, options);
|
||||||
|
|
||||||
|
// Used to keep track of the version of the indexes
|
||||||
|
// to determine during an upgrade if a reindex is required
|
||||||
|
this.version = 2;
|
||||||
|
|
||||||
this.tip = null;
|
this.tip = null;
|
||||||
this.genesis = null;
|
this.genesis = null;
|
||||||
|
|
||||||
|
@ -66,6 +70,7 @@ util.inherits(DB, Service);
|
||||||
DB.dependencies = ['bitcoind'];
|
DB.dependencies = ['bitcoind'];
|
||||||
|
|
||||||
DB.PREFIXES = {
|
DB.PREFIXES = {
|
||||||
|
VERSION: new Buffer('ff', 'hex'),
|
||||||
BLOCKS: new Buffer('01', 'hex'),
|
BLOCKS: new Buffer('01', 'hex'),
|
||||||
TIP: new Buffer('04', 'hex')
|
TIP: new Buffer('04', 'hex')
|
||||||
};
|
};
|
||||||
|
@ -90,6 +95,48 @@ DB.prototype._setDataPath = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DB.prototype._checkVersion = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
var options = {
|
||||||
|
keyEncoding: 'binary',
|
||||||
|
valueEncoding: 'binary'
|
||||||
|
};
|
||||||
|
self.store.get(DB.PREFIXES.TIP, options, function(err) {
|
||||||
|
if (err instanceof levelup.errors.NotFoundError) {
|
||||||
|
// The database is brand new and doesn't have a tip stored
|
||||||
|
// we can skip version checking
|
||||||
|
return callback();
|
||||||
|
} else if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.store.get(DB.PREFIXES.VERSION, options, function(err, buffer) {
|
||||||
|
var version;
|
||||||
|
if (err instanceof levelup.errors.NotFoundError) {
|
||||||
|
// The initial version (1) of the database didn't store the version number
|
||||||
|
version = 1;
|
||||||
|
} else if (err) {
|
||||||
|
return callback(err);
|
||||||
|
} else {
|
||||||
|
version = buffer.readUInt32BE();
|
||||||
|
}
|
||||||
|
if (self.version !== version) {
|
||||||
|
return callback(new Error(
|
||||||
|
'The version of the database "' + version + '" does not match the expected version "' +
|
||||||
|
self.version + '". A reindex (can take several hours) is required or to switch ' +
|
||||||
|
'versions of software to match.'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._setVersion = function(callback) {
|
||||||
|
var versionBuffer = new Buffer(new Array(4));
|
||||||
|
versionBuffer.writeUInt32BE(this.version);
|
||||||
|
this.store.put(DB.PREFIXES.VERSION, versionBuffer, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by Node to start the service.
|
* Called by Node to start the service.
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
|
@ -116,14 +163,26 @@ DB.prototype.start = function(callback) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
self.loadTip(function(err) {
|
async.series([
|
||||||
if(err) {
|
function(next) {
|
||||||
|
self._checkVersion(next);
|
||||||
|
},
|
||||||
|
function(next) {
|
||||||
|
self._setVersion(next);
|
||||||
|
}
|
||||||
|
], function(err) {
|
||||||
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
self.loadTip(function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
self.sync();
|
self.sync();
|
||||||
self.emit('ready');
|
self.emit('ready');
|
||||||
setImmediate(callback);
|
setImmediate(callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,135 @@ describe('DB Service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#_checkVersion', function() {
|
||||||
|
var config = {
|
||||||
|
node: {
|
||||||
|
network: Networks.get('testnet'),
|
||||||
|
datadir: 'testdir'
|
||||||
|
},
|
||||||
|
store: memdown
|
||||||
|
};
|
||||||
|
it('will handle an error while retrieving the tip', function() {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = sinon.stub().callsArgWith(2, new Error('test'));
|
||||||
|
db._checkVersion(function(err) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.equal('test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('will handle an error while retrieving the version', function() {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = function() {};
|
||||||
|
var callCount = 0;
|
||||||
|
sinon.stub(db.store, 'get', function(key, options, callback) {
|
||||||
|
if (callCount === 1) {
|
||||||
|
return callback(new Error('test'));
|
||||||
|
}
|
||||||
|
callCount++;
|
||||||
|
setImmediate(callback);
|
||||||
|
});
|
||||||
|
db._checkVersion(function(err) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.equal('test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('will NOT check the version if a tip is not found', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError());
|
||||||
|
db._checkVersion(done);
|
||||||
|
});
|
||||||
|
it('will NOT give an error if the versions match', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = function() {};
|
||||||
|
var callCount = 0;
|
||||||
|
sinon.stub(db.store, 'get', function(key, options, callback) {
|
||||||
|
if (callCount === 1) {
|
||||||
|
var versionBuffer = new Buffer(new Array(4));
|
||||||
|
versionBuffer.writeUInt32BE(2);
|
||||||
|
return callback(null, versionBuffer);
|
||||||
|
}
|
||||||
|
callCount++;
|
||||||
|
setImmediate(callback);
|
||||||
|
});
|
||||||
|
db.version = 2;
|
||||||
|
db._checkVersion(done);
|
||||||
|
});
|
||||||
|
it('will give an error if the versions do NOT match', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = function() {};
|
||||||
|
var callCount = 0;
|
||||||
|
sinon.stub(db.store, 'get', function(key, options, callback) {
|
||||||
|
if (callCount === 1) {
|
||||||
|
var versionBuffer = new Buffer(new Array(4));
|
||||||
|
versionBuffer.writeUInt32BE(2);
|
||||||
|
return callback(null, versionBuffer);
|
||||||
|
}
|
||||||
|
callCount++;
|
||||||
|
setImmediate(callback);
|
||||||
|
});
|
||||||
|
db.version = 3;
|
||||||
|
db._checkVersion(function(err) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.match(/^The version of the database/);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('will default to version 1 if the version is NOT found', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.get = function() {};
|
||||||
|
var callCount = 0;
|
||||||
|
sinon.stub(db.store, 'get', function(key, options, callback) {
|
||||||
|
if (callCount === 1) {
|
||||||
|
return callback(new levelup.errors.NotFoundError());
|
||||||
|
}
|
||||||
|
callCount++;
|
||||||
|
setImmediate(callback);
|
||||||
|
});
|
||||||
|
db.version = 1;
|
||||||
|
db._checkVersion(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_setVersion', function() {
|
||||||
|
var config = {
|
||||||
|
node: {
|
||||||
|
network: Networks.get('testnet'),
|
||||||
|
datadir: 'testdir'
|
||||||
|
},
|
||||||
|
store: memdown
|
||||||
|
};
|
||||||
|
it('will give an error from the store', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.put = sinon.stub().callsArgWith(2, new Error('test'));
|
||||||
|
db._setVersion(function(err) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.equal('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('will set the version', function(done) {
|
||||||
|
var db = new DB(config);
|
||||||
|
db.store = {};
|
||||||
|
db.store.put = sinon.stub().callsArgWith(2, null);
|
||||||
|
db.version = 5;
|
||||||
|
db._setVersion(function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
db.store.put.args[0][0].should.deep.equal(new Buffer('ff', 'hex'));
|
||||||
|
db.store.put.args[0][1].should.deep.equal(new Buffer('00000005', 'hex'));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#start', function() {
|
describe('#start', function() {
|
||||||
var TestDB;
|
var TestDB;
|
||||||
|
|
||||||
|
@ -126,6 +255,8 @@ describe('DB Service', function() {
|
||||||
};
|
};
|
||||||
db.loadTip = sinon.stub().callsArg(0);
|
db.loadTip = sinon.stub().callsArg(0);
|
||||||
db.connectBlock = sinon.stub().callsArg(1);
|
db.connectBlock = sinon.stub().callsArg(1);
|
||||||
|
db._checkVersion = sinon.stub().callsArg(0);
|
||||||
|
db._setVersion = sinon.stub().callsArg(0);
|
||||||
db.sync = sinon.stub();
|
db.sync = sinon.stub();
|
||||||
var readyFired = false;
|
var readyFired = false;
|
||||||
db.on('ready', function() {
|
db.on('ready', function() {
|
||||||
|
@ -144,6 +275,8 @@ describe('DB Service', function() {
|
||||||
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
||||||
db.loadTip = sinon.stub().callsArg(0);
|
db.loadTip = sinon.stub().callsArg(0);
|
||||||
db.connectBlock = sinon.stub().callsArg(1);
|
db.connectBlock = sinon.stub().callsArg(1);
|
||||||
|
db._checkVersion = sinon.stub().callsArg(0);
|
||||||
|
db._setVersion = sinon.stub().callsArg(0);
|
||||||
db.sync = sinon.stub();
|
db.sync = sinon.stub();
|
||||||
db.start(function() {
|
db.start(function() {
|
||||||
db.sync = function() {
|
db.sync = function() {
|
||||||
|
@ -161,6 +294,8 @@ describe('DB Service', function() {
|
||||||
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
||||||
db.loadTip = sinon.stub().callsArg(0);
|
db.loadTip = sinon.stub().callsArg(0);
|
||||||
db.connectBlock = sinon.stub().callsArg(1);
|
db.connectBlock = sinon.stub().callsArg(1);
|
||||||
|
db._checkVersion = sinon.stub().callsArg(0);
|
||||||
|
db._setVersion = sinon.stub().callsArg(0);
|
||||||
db.node.stopping = true;
|
db.node.stopping = true;
|
||||||
db.sync = sinon.stub();
|
db.sync = sinon.stub();
|
||||||
db.start(function() {
|
db.start(function() {
|
||||||
|
|
Loading…
Reference in New Issue