Merge remote-tracking branch 'matiu/feature/Block-test'

Conflicts:
	util/util.js
This commit is contained in:
Ryan X. Charles 2014-03-21 14:57:45 -07:00
commit fa47ee9984
10 changed files with 199 additions and 301 deletions

297
Block.js
View File

@ -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 "<Block " + util.formatHashAlt(this.hash) + " height="+this.height+">";
};
/**
* 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);

View File

@ -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: {

View File

@ -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() {

View File

@ -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');

View File

@ -52,6 +52,7 @@ var modules = [
'util/util',
'util/EncodedData',
'util/VersionedData',
'util/BinaryParser',
];
var createBitcore = function(opts) {

Binary file not shown.

View File

@ -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);
});
});

View File

@ -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;

View File

@ -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';

View File

@ -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;