use getChainWork from bitcoind
This commit is contained in:
parent
94ceaa1857
commit
482fd53f3d
13
lib/block.js
13
lib/block.js
@ -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);
|
||||
|
||||
152
lib/chain.js
152
lib/chain.js
@ -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'));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user