use getChainWork from bitcoind

This commit is contained in:
Patrick Nagurny 2015-07-20 11:48:13 -06:00
parent 94ceaa1857
commit 482fd53f3d
5 changed files with 31 additions and 323 deletions

View File

@ -26,19 +26,6 @@ Block.prototype.validate = function(chain, callback) {
setImmediate(callback);
};
/**
* @returns {Boolean} - If the proof-of-work hash satisfies the target difficulty
*/
Block.prototype.validProofOfWork = function validProofOfWork(chain) {
var pow = new BN(this.hash, 'hex');
var target = chain.getTargetFromBits(this.bits);
if (pow.cmp(target) > 0) {
return false;
}
return true;
};
Block.fromBuffer = function(buffer) {
var br = new BufferReader(buffer);
return Block.fromBufferReader(br);

View File

@ -83,155 +83,15 @@ Chain.prototype.buildGenesisBlock = function buildGenesisBlock(options) {
return genesis;
};
/**
* Calculates the number of blocks for a retargeting interval
* @returns {BN}
*/
Chain.prototype.getDifficultyInterval = function getDifficultyInterval() {
return this.targetTimespan / this.targetSpacing;
};
/**
* Will recalculate the bits based on the hash rate of the previous interval
* @see https://en.bitcoin.it/wiki/Difficulty#How_is_difficulty_stored_in_blocks.3F
* @param {Number} bits - The bits of the previous interval block
* @param {Number} timespan - The number of milliseconds that elapsed in the last interval
* @returns {Number} The compacted target in bits
*/
Chain.prototype.getRetargetedBits = function getRetargetedBits(bits, timespan) {
// Based off Bitcoin's code:
// https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp#L53
if(timespan < this.targetTimespan / 4) {
timespan = Math.floor(this.targetTimespan / 4);
} else if(timespan > this.targetTimespan * 4) {
timespan = this.targetTimespan * 4;
}
var oldTarget = this.getTargetFromBits(bits);
var newTarget = oldTarget.mul(new BN(timespan, 10)).div(new BN(this.targetTimespan, 10));
var newBits = this.getBitsFromTarget(newTarget);
if(newBits > this.maxBits) {
newBits = this.maxBits;
}
return newBits;
};
/**
* Calculates the number of blocks for a retargeting interval
* @param {Block} - block - An instance of a block
* @param {Function} - callback - A callback function that accepts arguments: Error and Number
*/
Chain.prototype.getNextWorkRequired = function getNextWorkRequired(block, callback) {
Chain.prototype.getWeight = function getWeight(blockHash, callback) {
var self = this;
var interval = this.getDifficultyInterval();
self.getHeightForBlock(block.hash, function(err, height) {
if (err) {
return callback(err);
setImmediate(function() {
var weight = self.db.bitcoind.getChainWork(blockHash);
if(weight === undefined) {
return callback(new Error('Weight not found for ' + blockHash));
}
if (height === 0) {
return callback(null, self.maxBits);
}
// not on interval, return the same amount of difficulty
if ((height + 1) % interval !== 0) {
return callback(null, block.bits);
}
// otherwise compute the new difficulty
self.getBlockAtHeight(block, height + 1 - interval, function(err, lastIntervalBlock){
if (err) {
callback(err);
}
var timespan = (Math.floor(block.timestamp.getTime() / 1000) * 1000) - (Math.floor(lastIntervalBlock.timestamp.getTime() / 1000) * 1000);
var bits = self.getRetargetedBits(lastIntervalBlock.bits, timespan);
return callback(null, bits);
});
});
};
/**
* Calculates the actual target from the compact form
* @param {Number} - bits
* @returns {BN}
*/
Chain.prototype.getTargetFromBits = function getTargetFromBits(bits) {
if(bits <= this.minBits) {
throw new Error('bits is too small (' + bits + ')');
}
if(bits > this.maxBits) {
throw new Error('bits is too big (' + bits + ')');
}
var a = bits & 0xffffff;
var b = bits >>> 24;
var exp = (8 * (b - 3));
// Exponents via bit shift (works for powers of 2)
var z = (new BN(2, 10)).shln(exp - 1);
var target = (new BN(a, 10)).mul(z);
return target;
};
/**
* Calculates the compact target "bits" from the target
* @param {BN|Number} - target
* @returns {Number}
*/
Chain.prototype.getBitsFromTarget = function getBitsFromTarget(target) {
target = new BN(target, 'hex');
var tmp = target;
var b = 0;
while(tmp.cmp(new BN(0, 10)) > 0) {
b++;
tmp = tmp.shrn(8);
}
var a = target.shrn((b - 3) * 8);
var bits = Number('0x' + b.toString(16) + a.toString(16, 6));
return bits;
};
Chain.prototype.getDifficultyFromBits = function getDifficultyFromBits(bits) {
var currentTarget = this.getTargetFromBits(bits);
var genesisTarget = this.getTargetFromBits(this.genesis.bits);
return genesisTarget.div(currentTarget);
};
Chain.prototype.getBlockWeight = function getBlockWeight(blockHash, callback) {
var self = this;
self.db.getBlock(blockHash, function(err, block) {
if(err) {
return callback(err);
} else if(!block) {
return callback(new Error('Block not found (' + blockHash + ')'));
}
var target = self.getTargetFromBits(block.bits);
var a = self.maxHashes.sub(target).sub(new BN(1, 10));
var b = target.add(new BN(1, 10));
var c = a.div(b);
var d = c.add(new BN(1, 10));
return callback(null, d);
callback(null, new BN(weight, 'hex'));
});
};

View File

@ -64,7 +64,12 @@ DB.prototype.getTransaction = function(txid, queryMempool, callback) {
DB.prototype.validateBlockData = function(block, callback) {
// bitcoind does the validation
return callback();
setImmediate(callback);
};
DB.prototype._updateWeight = function(hash, weight, callback) {
// bitcoind has all work for each block
setImmediate(callback);
};
DB.prototype.buildGenesisData = function() {

View File

@ -23,26 +23,6 @@ describe('Bitcoin Block', function() {
});
});
describe('#validProofOfWork', function() {
it('returns false if block hash is greater than target from bits', function() {
var block = new Block(chainData[1]);
var target = new BN('00000000c4769616456f587f6744a779f19a1f9056431ad03d0949458ec85ac0', 'hex');
var valid = block.validProofOfWork({
getTargetFromBits: sinon.stub().returns(target)
});
valid.should.equal(false);
});
it('returns true if block hash is less than target from bits', function() {
var block = new Block(chainData[1]);
var target = new BN('f2345678c4769616456f587f6744a779f19a1f9056431ad03d0949458ec85ac0', 'hex');
var valid = block.validProofOfWork({
getTargetFromBits: sinon.stub().returns(target)
});
valid.should.equal(true);
});
});
describe('#fromBuffer', function() {
var buffer = new Buffer('010000004404c1ff5f300e5ed830b45ec9f68fbe9a0c51c4b4eaa4ce09a03ac4ddde01750000000000000000000000000000000000000000000000000000000000000000b134de547fcc071f4a020000abcdef', 'hex');

View File

@ -50,24 +50,6 @@ describe('Bitcoin Chain', function() {
});
});
describe('#getInterval', function() {
it('get default interval', function() {
var chain = new Chain();
chain.targetTimespan.toString(10).should.equal('1209600000');
chain.targetSpacing.toString(10).should.equal('600000');
chain.getDifficultyInterval().toString(10).should.equal('2016');
});
it('get custom interval', function() {
var chain = new Chain({
targetTimespan: 30 * 60 * 1000
});
chain.getDifficultyInterval().toString(10).should.equal('3');
});
});
describe('#buildGenesisBlock', function() {
it('can handle no options', function() {
var db = {
@ -106,133 +88,27 @@ describe('Bitcoin Chain', function() {
});
describe('#getRetargetedBits', function() {
it('should get the correct bits', function() {
var chain = new Chain();
var bits = chain.getRetargetedBits(486604799, 12 * 24 * 60 * 60 * 1000);
bits.should.equal(484142299);
});
it('should get the correct bits if actual timespan was really small', function() {
var chain = new Chain();
var bits1 = chain.getRetargetedBits(486604799, 2 * 24 * 60 * 60 * 1000);
bits1.should.equal(473956288);
var bits2 = chain.getRetargetedBits(486604799, 1 * 24 * 60 * 60 * 1000);
bits2.should.equal(473956288);
});
it('should get the correct bits if actual timespan was really large', function() {
var chain = new Chain();
var bits1 = chain.getRetargetedBits(436567560, 60 * 24 * 60 * 60 * 1000);
bits1.should.equal(437647392);
var bits2 = chain.getRetargetedBits(436567560, 70 * 24 * 60 * 60 * 1000);
bits2.should.equal(437647392);
});
it('should not give higher than max bits', function() {
var chain = new Chain();
var bits = chain.getRetargetedBits(486604799, 16 * 24 * 60 * 60 * 1000);
bits.should.equal(486604799);
});
});
describe('#getWeight', function() {
var work = '000000000000000000000000000000000000000000005a7b3c42ea8b844374e9';
var chain = new Chain();
chain.db = {
bitcoind: {
getChainWork: sinon.stub().returns(work)
}
};
describe('#getTargetFromBits/#getBitsFromTarget', function() {
var target1;
var target2;
var target3;
it('should calculate the target correctly', function() {
var chain = new Chain();
var target1 = chain.getTargetFromBits(0x1b0404cb);
var expected = '00000000000404cb000000000000000000000000000000000000000000000000';
target1.toString('hex', 32).should.equal(expected);
});
it('should error if bits is too small', function() {
var chain = new Chain();
(function(){
var target1 = chain.getTargetFromBits(Chain.DEFAULTS.MIN_BITS - 1);
}).should.throw('bits is too small');
});
it('should error if bits is too large', function() {
var chain = new Chain();
(function(){
var target1 = chain.getTargetFromBits(Chain.DEFAULTS.MAX_BITS + 1);
}).should.throw('bits is too big');
});
it('should get the bits', function() {
var chain = new Chain();
var expected = '00000000000404cb000000000000000000000000000000000000000000000000';
var bits = chain.getBitsFromTarget(expected);
bits.should.equal(0x1b0404cb);
});
});
describe('#getDifficultyFromBits', function() {
it('should return the correct difficulty', function() {
var genesis = {bits: 0x1d00ffff};
var chain = new Chain({genesis: genesis});
var difficulty = chain.getDifficultyFromBits(0x1818bb87);
difficulty.toString(10).should.equal('44455415962');
});
});
describe('#getBlockWeight', function() {
it('should return the correct block weight for normal targets', function(done) {
var block = {bits: 0x1d00ffff};
var db = {
getBlock: sinon.stub().callsArgWith(1, null, block)
};
var chain = new Chain({db: db});
chain.getBlockWeight(block, function(err, weight) {
weight.toString(16).should.equal('100010001');
done();
});
});
it('should correctly report an error if it happens', function(done) {
var block = {bits: 0x1d00ffff};
var db = {
getBlock: sinon.stub().callsArgWith(1, new Error('fake error'))
};
var chain = new Chain({db: db});
chain.getBlockWeight(block, function(err, weight) {
should.exist(err);
err.message.should.equal('fake error');
done();
});
});
it('should correctly report an error for a null block', function(done) {
var block = {bits: 0x1d00ffff};
var db = {
getBlock: sinon.stub().callsArgWith(1, null, null)
};
var chain = new Chain({db: db});
chain.getBlockWeight(block, function(err, weight) {
should.exist(err);
err.message.should.match(/Block not found/);
done();
});
});
});
describe('Bitcoin POW', function() {
it('should calculate correct difficulty for block 201600', function(done) {
var chain = new Chain();
var beginBlock = {
timestamp: new Date(1348092851000),
bits: 436591499
};
var lastBlock = {
timestamp: new Date(1349227021000),
bits: 436591499
};
chain.getHeightForBlock = sinon.stub().callsArgWith(1, null, 201599);
chain.getBlockAtHeight = sinon.stub().callsArgWith(2, null, beginBlock);
chain.getNextWorkRequired(lastBlock, function(err, bits) {
it('should give the weight as a BN', function(done) {
chain.getWeight('hash', function(err, weight) {
should.not.exist(err);
bits.should.equal(436567560);
weight.toString(16).should.equal('5a7b3c42ea8b844374e9');
done();
});
});
it('should give an error if the weight is undefined', function(done) {
chain.db.bitcoind.getChainWork = sinon.stub().returns(undefined);
chain.getWeight('hash2', function(err, weight) {
should.exist(err);
done();
});
});