From 684be77268b342ac07bd3b47a6fb0feae4839d2f Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 21 Mar 2014 16:39:38 -0300 Subject: [PATCH] small fixes in block, adapt to browser bignum. remove legacy code --- Block.js | 297 +-------------------------------------------- Transaction.js | 3 + browser/build.js | 1 + test/test.Block.js | 79 +++++++++++- test/testdata.js | 4 +- util/util.js | 11 +- 6 files changed, 92 insertions(+), 303 deletions(-) diff --git a/Block.js b/Block.js index 64ae287..05f9b69 100644 --- a/Block.js +++ b/Block.js @@ -94,15 +94,11 @@ Block.prototype.checkProofOfWork = function checkProofOfWork() { // TODO: Create a compare method in node-buffertools that uses the correct // endian so we don't have to reverse both buffers before comparing. - buffertools.reverse(this.hash); - - if (buffertools.compare(this.hash, target) > 0) { + var reverseHash = buffertools.reverse(this.hash); + if (buffertools.compare(reverseHash, target) > 0) { throw new VerificationError('Difficulty target not met'); } - // Return the hash to its normal order - buffertools.reverse(this.hash); - return true; }; @@ -200,7 +196,7 @@ Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) { throw new VerificationError('No merkle root'); } - if (buffertools.compare(this.calcMerkleRoot(), this.merkle_root) == 0) { + if (buffertools.compare(this.calcMerkleRoot(txs), this.merkle_root) !== 0) { throw new VerificationError('Merkle root incorrect'); } @@ -237,214 +233,6 @@ Block.prototype.toString = function toString() { return ""; }; -/** - * Initializes some properties based on information from the parent block. - */ -Block.prototype.attachTo = function attachTo(parent) { - this.height = parent.height + 1; - this.setChainWork(parent.getChainWork().add(this.getWork())); -}; - -Block.prototype.setChainWork = function setChainWork(chainWork) { - if (Buffer.isBuffer(chainWork)) { - // Nothing to do - } else if ("function" === typeof chainWork.toBuffer) { // duck-typing bignum - chainWork = chainWork.toBuffer(); - } else { - throw new Error("Block.setChainWork(): Invalid datatype"); - } - - this.chainWork = chainWork; -}; - -Block.prototype.getChainWork = function getChainWork() { - return Bignum.fromBuffer(this.chainWork); -}; - -/** - * Compares the chainWork of two blocks. - */ -Block.prototype.moreWorkThan = function moreWorkThan(otherBlock) { - return this.getChainWork().cmp(otherBlock.getChainWork()) > 0; -}; - -/** - * Returns the difficulty target for the next block after this one. - */ -Block.prototype.getNextWork = -function getNextWork(blockChain, nextBlock, callback) { - var self = this; - - var powLimit = blockChain.getMinDiff(); - var powLimitTarget = util.decodeDiffBits(powLimit, true); - - var targetTimespan = blockChain.getTargetTimespan(); - var targetSpacing = blockChain.getTargetSpacing(); - var interval = targetTimespan / targetSpacing; - - if (this.height == 0) { - callback(null, this.bits); - } - - if ((this.height+1) % interval !== 0) { - if (blockChain.isTestnet()) { - // Special testnet difficulty rules - var lastBlock = blockChain.getTopBlock(); - - // If the new block's timestamp is more than 2 * 10 minutes - // then allow mining of a min-difficulty block. - if (nextBlock.timestamp > this.timestamp + targetSpacing*2) { - callback(null, powLimit); - } else { - // Return last non-"special-min-difficulty" block - if (this.bits != powLimit) { - // Current block is non-min-diff - callback(null, this.bits); - } else { - // Recurse backwards until a non min-diff block is found. - function lookForLastNonMinDiff(block, callback) { - try { - if (block.height > 0 && - block.height % interval !== 0 && - block.bits == powLimit) { - blockChain.getBlockByHeight( - block.height - 1, - function (err, lastBlock) { - try { - if (err) throw err; - lookForLastNonMinDiff(lastBlock, callback); - } catch (err) { - callback(err); - } - } - ); - } else { - callback(null, block.bits); - } - } catch (err) { - callback(err); - } - }; - lookForLastNonMinDiff(this, callback); - } - } - } else { - // Not adjustment interval, next block has same difficulty - callback(null, this.bits); - } - } else { - // Get the first block from the old difficulty period - blockChain.getBlockByHeight( - this.height - interval + 1, - function (err, lastBlock) { - try { - if (err) throw err; - - // Determine how long the difficulty period really took - var actualTimespan = self.timestamp - lastBlock.timestamp; - - // There are some limits to how much we will adjust the difficulty in - // one step - if (actualTimespan < targetTimespan/4) { - actualTimespan = targetTimespan/4; - } - if (actualTimespan > targetTimespan*4) { - actualTimespan = targetTimespan*4; - } - - var oldTarget = util.decodeDiffBits(self.bits, true); - var newTarget = oldTarget.mul(actualTimespan).div(targetTimespan); - - if (newTarget.cmp(powLimitTarget) > 0) { - newTarget = powLimitTarget; - } - - Debug1('Difficulty retarget (target='+targetTimespan + - ', actual='+actualTimespan+')'); - Debug1('Before: '+oldTarget.toBuffer().toString('hex')); - Debug1('After: '+newTarget.toBuffer().toString('hex')); - - callback(null, util.encodeDiffBits(newTarget)); - } catch (err) { - callback(err); - } - } - ); - } -}; - -var medianTimeSpan = 11; - -Block.prototype.getMedianTimePast = -function getMedianTimePast(blockChain, callback) -{ - var self = this; - - Step( - function getBlocks() { - var heights = []; - for (var i = 0, m = medianTimeSpan; i < m && (self.height - i) >= 0; i++) { - heights.push(self.height - i); - } - blockChain.getBlocksByHeights(heights, this); - }, - function calcMedian(err, blocks) { - if (err) throw err; - - var timestamps = blocks.map(function (block) { - if (!block) { - throw new Error("Prior block missing, cannot calculate median time"); - } - - return +block.timestamp; - }); - - // Sort timestamps - timestamps = timestamps.sort(); - - // Return median timestamp - this(null, timestamps[Math.floor(timestamps.length/2)]); - }, - callback - ); -}; - -Block.prototype.verifyChild = -function verifyChild(blockChain, child, callback) -{ - var self = this; - - Step( - function getExpectedDifficulty() { - self.getNextWork(blockChain, child, this); - }, - function verifyExpectedDifficulty(err, nextWork) { - if (err) throw err; - - if (+child.bits !== +nextWork) { - throw new VerificationError("Incorrect proof of work '"+child.bits+"',"+ - " should be '"+nextWork+"'."); - } - - this(); - }, - function getMinimumTimestamp(err) { - if (err) throw err; - - self.getMedianTimePast(blockChain, this); - }, - function verifyTimestamp(err, medianTimePast) { - if (err) throw err; - - if (child.timestamp <= medianTimePast) { - throw new VerificationError("Block's timestamp is too early"); - } - - this(); - }, - callback - ); -}; Block.prototype.createCoinbaseTx = function createCoinbaseTx(beneficiary) @@ -462,85 +250,6 @@ function createCoinbaseTx(beneficiary) return tx; }; -Block.prototype.prepareNextBlock = -function prepareNextBlock(blockChain, beneficiary, time, callback) -{ - var self = this; - - var newBlock = new Block(); - Step( - function getMedianTimePastStep() { - self.getMedianTimePast(blockChain, this); - }, - - function getNextWorkStep(err, medianTimePast) { - if (err) throw err; - - if (!time) { - // TODO: Use getAdjustedTime for the second timestamp - time = Math.max(medianTimePast+1, - Math.floor(new Date().getTime() / 1000)); - } - - self.getNextWork(blockChain, newBlock, this); - }, - - function applyNextWorkStep(err, nextWork) { - if (err) throw err; - newBlock.bits = nextWork; - this(null); - }, - - function miscStep(err) { - if (err) throw err; - - newBlock.version = 1; - newBlock.timestamp = time; - newBlock.prev_hash = self.getHash().slice(0); - newBlock.height = self.height+1; - - // Create coinbase transaction - var txs = []; - - var tx = newBlock.createCoinbaseTx(beneficiary); - txs.push(tx); - - newBlock.merkle_root = newBlock.calcMerkleRoot(txs); - - // Return reference to (unfinished) block - this(null, {block: newBlock, txs: txs}); - }, - callback - ); -}; - -Block.prototype.mineNextBlock = -function mineNextBlock(blockChain, beneficiary, time, miner, callback) -{ - this.prepareNextBlock(blockChain, beneficiary, time, function (err, data) { - try { - if (err) throw err; - - var newBlock = data.block; - var txs = data.txs; - - newBlock.solve(miner, function (err, nonce) { - newBlock.nonce = nonce; - - // Make sure hash is cached - newBlock.getHash(); - - callback(err, newBlock, txs); - }); - - // Return reference to (unfinished) block - return newBlock; - } catch (e) { - callback(e); - } - }); -}; - Block.prototype.solve = function solve(miner, callback) { var header = this.getHeader(); var target = util.decodeDiffBits(this.bits); diff --git a/Transaction.js b/Transaction.js index 6d28741..cb4e9c5 100644 --- a/Transaction.js +++ b/Transaction.js @@ -18,6 +18,8 @@ var PrivateKey = imports.PrivateKey || require('./PrivateKey'); var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]); var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); +Transaction.COINBASE_OP = COINBASE_OP; + function TransactionIn(data) { if ("object" !== typeof data) { data = {}; @@ -43,6 +45,7 @@ TransactionIn.prototype.getScript = function getScript() { }; TransactionIn.prototype.isCoinBase = function isCoinBase() { + if (!this.o) return false; return buffertools.compare(this.o, COINBASE_OP) === 0; }; diff --git a/browser/build.js b/browser/build.js index 14f14ed..736ae3d 100644 --- a/browser/build.js +++ b/browser/build.js @@ -52,6 +52,7 @@ var modules = [ 'util/util', 'util/EncodedData', 'util/VersionedData', + 'util/BinaryParser', ]; var createBitcore = function(opts) { diff --git a/test/test.Block.js b/test/test.Block.js index 7d366b1..771c1e6 100644 --- a/test/test.Block.js +++ b/test/test.Block.js @@ -55,7 +55,6 @@ describe('Block', function() { should.exist(b.getHash()); b.checkHash().should.equal(true); b.checkProofOfWork().should.equal(true); - b.checkProofOfWork().should.equal(true); b.getWork().toString().should.equal('17180131332'); b.checkTimestamp().should.equal(true); @@ -73,17 +72,31 @@ describe('Block', function() { b.checkTransactions(b.txs).should.equal(true); b.checkTransactions.bind([]).should.throw(); + var coinbase = b.txs.shift; + b.checkTransactions.bind(b.txs).should.throw(); + b.txs.push(coinbase); + b.checkTransactions.bind(b.txs).should.throw(); + + + }); + + it('should be able to checkMerkleRoot', function() { + + var b = getBlock(); b.getMerkleTree(b.txs).length.should.equal(45); bitcore.buffertools.toHex(b.calcMerkleRoot(b.txs)).should.equal(bitcore.buffertools.toHex(b.merkle_root)); - var coinbase = b.txs.shift; - b.checkTransactions.bind(b.txs).should.throw(); + b.checkMerkleRoot(b.txs); - b.txs.push(coinbase); + delete b['merkle_root']; + b.checkMerkleRoot.bind(b.txs).should.throw(); - b.checkTransactions.bind(b.txs).should.throw(); + b.merkle_root=new Buffer('wrong'); + b.checkMerkleRoot.bind(b.txs).should.throw(); }); + + it('should be able to checkProofOfWork', function() { var b = getBlock(); @@ -104,6 +117,14 @@ describe('Block', function() { b.checkProofOfWork.bind().should.throw(); }); + + it('should be able to check via checkBlock', function() { + var b = getBlock(); + b.checkBlock.bind(b.txs).should.throw(); + b.getHash(); + b.checkBlock(b.txs).should.equal(true); + }); + it('should be able to get components from blocks', function() { var b = getBlock(true); @@ -116,6 +137,54 @@ describe('Block', function() { }); + it('#getBlockValue should return the correct block value', function() { + var c = bitcore.util.COIN; + bitcore.Block.getBlockValue(0).div(c).toNumber().should.equal(50); + bitcore.Block.getBlockValue(1).div(c).toNumber().should.equal(50); + bitcore.Block.getBlockValue(209999).div(c).toNumber().should.equal(50); + bitcore.Block.getBlockValue(210000).div(c).toNumber().should.equal(25); + bitcore.Block.getBlockValue(2100000).toNumber().should.equal(4882812); + }); + + + it('#getStandardizedObject should return object', function() { + var b = getBlock(); + var o = b.getStandardizedObject(b.txs); + + o.hash.should.equal('000000000b99b16390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11'); + o.n_tx.should.equal(22); + o.size.should.equal(8003); + var o2 = b.getStandardizedObject(); + o2.hash.should.equal('000000000b99b16390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11'); + o2.size.should.equal(0); + }); + + + it('#miner should call the callback', function(done) { + var b = getBlock(); + var Miner = function() {}; + Miner.prototype.solve = function (header,target,cb) { + this.called=1; + should.exist(header); + should.exist(target); + return cb(); + }; + var miner = new Miner(); + b.solve(miner, function () { + miner.called.should.equal(1); + done(); + }); + + }); + + + it('#createCoinbaseTx should create a tx', function() { + var b = new Block(); + var pubkey = new Buffer('02d20b3fba521dcf88dfaf0eee8c15a8ba692d7eb0cb957d5bcf9f4cc052fb9cc6'); + var tx = b.createCoinbaseTx(pubkey); + should.exist(tx); + tx.isCoinBase().should.equal(true); + }); }); diff --git a/test/testdata.js b/test/testdata.js index a92fe41..72e0d4e 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -30,8 +30,6 @@ module.exports.dataSigNonCanonical = dataSigNonCanonical; module.exports.dataBase58KeysValid = dataBase58KeysValid; module.exports.dataBase58KeysInvalid = dataBase58KeysInvalid; -var fd = fs.openSync('test/data/blk86756-testnet.dat', 'r'); -var buffer = new Buffer(9000); -fs.readSync(fd, buffer, 0, 9000, 0); +var buffer = new Buffer(fs.readFileSync('test/data/blk86756-testnet.dat')); module.exports.dataRawBlock = buffer; diff --git a/util/util.js b/util/util.js index 7a33869..2f6085b 100644 --- a/util/util.js +++ b/util/util.js @@ -359,8 +359,17 @@ var generateNonce = exports.generateNonce = function () { */ var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) { diffBits = +diffBits; + var target = bignum(diffBits & 0xffffff); - target = target.shiftLeft(8*((diffBits >>> 24) - 3)); + /* + * shiftLeft is not implemented on the bignum browser + * + * target = target.shiftLeft(8*((diffBits >>> 24) - 3)); + */ + + var mov = 8*((diffBits >>> 24) - 3); + while (mov-- > 0) + target = target.mul(2); if (asBigInt) { return target;