From 482fd53f3d32f95a81d9d34f9ab51518476401dc Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Mon, 20 Jul 2015 11:48:13 -0600 Subject: [PATCH] use getChainWork from bitcoind --- lib/block.js | 13 ---- lib/chain.js | 152 ++---------------------------------------- lib/db.js | 7 +- test/block.unit.js | 20 ------ test/chain.unit.js | 162 ++++++--------------------------------------- 5 files changed, 31 insertions(+), 323 deletions(-) diff --git a/lib/block.js b/lib/block.js index 1e5660da..6e5e9c13 100644 --- a/lib/block.js +++ b/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); diff --git a/lib/chain.js b/lib/chain.js index 2d1a61eb..b8cb942d 100644 --- a/lib/chain.js +++ b/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')); }); }; diff --git a/lib/db.js b/lib/db.js index 1d5d852d..ef9a4f31 100644 --- a/lib/db.js +++ b/lib/db.js @@ -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() { diff --git a/test/block.unit.js b/test/block.unit.js index 039fd994..c8869cec 100644 --- a/test/block.unit.js +++ b/test/block.unit.js @@ -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'); diff --git a/test/chain.unit.js b/test/chain.unit.js index d6dad174..78ea3097 100644 --- a/test/chain.unit.js +++ b/test/chain.unit.js @@ -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(); }); });