diff --git a/Block.js b/Block.js index aeda5f989..84be543ce 100644 --- a/Block.js +++ b/Block.js @@ -93,15 +93,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; }; @@ -199,7 +195,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), new Buffer(this.merkle_root)) !== 0) { throw new VerificationError('Merkle root incorrect'); } @@ -236,214 +232,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) @@ -461,85 +249,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/Gruntfile.js b/Gruntfile.js index 76f758c7c..f6c0b2fe7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,8 @@ module.exports = function(grunt) { stdout: true, stderr: true }, - command: 'node ./browser/build.js -a', + command: grunt.option('target') === 'dev' ? + 'node ./browser/build.js -a -d ' : 'node ./browser/build.js -a' } }, watch: { diff --git a/Transaction.js b/Transaction.js index f6a5ca340..8c81bbf15 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,7 +45,10 @@ TransactionIn.prototype.getScript = function getScript() { }; TransactionIn.prototype.isCoinBase = function isCoinBase() { - return buffertools.compare(this.o, COINBASE_OP) === 0; + if (!this.o) return false; + + //The new Buffer is for Firefox compatibility + return buffertools.compare(new Buffer(this.o), COINBASE_OP) === 0; }; TransactionIn.prototype.serialize = function serialize() { diff --git a/bitcore.js b/bitcore.js index 33364066b..79e8ffbf5 100644 --- a/bitcore.js +++ b/bitcore.js @@ -21,6 +21,7 @@ requireWhenAccessed('networks', './networks'); requireWhenAccessed('util', './util/util'); requireWhenAccessed('EncodedData', './util/EncodedData'); requireWhenAccessed('VersionedData', './util/VersionedData'); +requireWhenAccessed('BinaryParser', './util/BinaryParser'); requireWhenAccessed('Address', './Address'); requireWhenAccessed('Opcode', './Opcode'); requireWhenAccessed('Script', './Script'); diff --git a/browser/build.js b/browser/build.js index 14f14ed43..736ae3d0e 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/data/blk86756-testnet.dat b/test/data/blk86756-testnet.dat new file mode 100644 index 000000000..207abdac0 Binary files /dev/null and b/test/data/blk86756-testnet.dat differ diff --git a/test/test.Block.js b/test/test.Block.js index f159f430c..b29ffff4e 100644 --- a/test/test.Block.js +++ b/test/test.Block.js @@ -4,9 +4,33 @@ var chai = chai || require('chai'); var bitcore = bitcore || require('../bitcore'); var should = chai.should(); +var testdata = testdata || require('./testdata'); var BlockModule = bitcore.Block; +var BinaryParser = bitcore.BinaryParser; var Block; + +var getBlock = function (onlyHeader) { + + var testnetMagic = bitcore.networks.testnet.magic.toString('hex'); + + var b = new Block(); + // this is block 86756 from testnet3 + var p = new BinaryParser(testdata.dataRawBlock); + + + var magic = p.buffer(4).toString('hex'); + + + if (magic !== testnetMagic ) + throw new Error('CRITICAL ERROR: Magic number mismatch: ' + + magic + ' : ' + testnetMagic); + + p.word32le(); + b.parse(p, onlyHeader); + return b; +}; + describe('Block', function() { it('should initialze the main object', function() { should.exist(BlockModule); @@ -16,8 +40,149 @@ describe('Block', function() { should.exist(Block); }); it('should be able to create instance', function() { - var p = new Block(); - should.exist(p); + var b = new Block(); + should.exist(b); + }); + + it('should be able to parse a block from hex', function() { + var b = getBlock(); + should.exist(b); + should.exist(b.getHash()); + }); + + it('should be able to check block contents', function() { + var b = getBlock(); + should.exist(b.getHash()); + b.checkHash().should.equal(true); + b.checkProofOfWork().should.equal(true); + b.getWork().toString().should.equal('17180131332'); + b.checkTimestamp().should.equal(true); + + }); + it('#checkBlock should be able to check block contents', function() { + var b = getBlock(); + should.exist(b.getHash()); + b.checkBlock().should.equal(true); + }); + + + it('should be able to check Transactions', function() { + var b = getBlock(); + + 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(new Buffer(b.merkle_root))); + + b.checkMerkleRoot(b.txs); + + delete b['merkle_root']; + b.checkMerkleRoot.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(); + + b.hash = bitcore.buffertools.reverse(new Buffer('000000000b99b16390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11', 'hex')); + b.checkHash().should.equal(true); + b.checkProofOfWork().should.equal(true); + + // wrong hash hash, ok proof of work + b.hash = bitcore.buffertools.reverse(new Buffer('000000000000016390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11', 'hex')); + b.checkProofOfWork().should.equal(true); + b.checkHash().should.equal(false); + + + // wrong hash hash, wrong proof of work + b.hash = bitcore.buffertools.reverse(new Buffer('0000000bbb99b16390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11', 'hex')); + b.checkHash().should.equal(false); + 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); + + bitcore.util.formatHashFull(b.getHash()).should.equal('000000000b99b16390660d79fcc138d2ad0c89a0d044c4201a02bdf1f61ffa11'); + + bitcore.util.formatHashFull(b.getHeader()).should.equal('d6383bd51c3fffc051be10ce58e6d52d1eb00470ae1ab4d5a3375c0f51382c6f249fff84e9888286974cfc97000000003c35b5e70b13d5b938fef4e998a977c17bea978390273b7c50a9aa4b00000002'); + + bitcore.util.formatHashFull(b.merkle_root).should.equal('58e6d52d1eb00470ae1ab4d5a3375c0f51382c6f249fff84e9888286974cfc97'); + + }); + + + 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 3c7f3fda5..72e0d4e91 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -30,3 +30,6 @@ module.exports.dataSigNonCanonical = dataSigNonCanonical; module.exports.dataBase58KeysValid = dataBase58KeysValid; module.exports.dataBase58KeysInvalid = dataBase58KeysInvalid; +var buffer = new Buffer(fs.readFileSync('test/data/blk86756-testnet.dat')); +module.exports.dataRawBlock = buffer; + diff --git a/util/error.js b/util/error.js index 428fc894e..4e5097f92 100644 --- a/util/error.js +++ b/util/error.js @@ -9,7 +9,8 @@ function MissingSourceError(msg, missingTxHash) { // TODO: Since this happens in normal operation, perhaps we should // avoid generating a whole stack trace. Error.call(this); - Error.captureStackTrace(this, arguments.callee); +// This is not compatible with firefox. +// Error.captureStackTrace(this, arguments.callee); this.message = msg; this.missingTxHash = missingTxHash; this.name = 'MissingSourceError'; @@ -19,6 +20,7 @@ MissingSourceError.prototype.__proto__ = Error.prototype; exports.MissingSourceError = MissingSourceError; + /** * Used in several places to indicate invalid data. * @@ -29,7 +31,9 @@ function VerificationError(msg, missingTxHash) { // TODO: Since this happens in normal operation, perhaps we should // avoid generating a whole stack trace. Error.call(this); - Error.captureStackTrace(this, arguments.callee); + +// This is not compatible with firefox. +// Error.captureStackTrace(this, arguments.callee); this.message = msg; this.missingTxHash = missingTxHash; this.name = 'VerificationError'; diff --git a/util/util.js b/util/util.js index e4fa531d8..921cda5dc 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;