refactor. work.

This commit is contained in:
Christopher Jeffrey 2016-01-07 13:39:08 -08:00
parent 42fbef82ed
commit df420a4a8b
11 changed files with 253 additions and 134 deletions

View File

@ -216,63 +216,79 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() {
return merkleTree[merkleTree.length - 1];
};
// This mimics the behavior of CheckBlockHeader()
// and CheckBlock() in bitcoin/src/main.cpp.
Block.prototype._verify = function _verify() {
var uniq = {};
var i, tx, hash;
// Check proof of work matches claimed amount
if (!utils.testTarget(this.bits, this.hash()))
// Check proof of work
if (!utils.testTarget(this.bits, this.hash())) {
this.chain.emit('debug', 'Block failed POW test: %s', this.rhash);
return false;
// Check timestamp
if (this.ts > utils.now() + 2 * 60 * 60)
return false;
if (this.subtype === 'merkleblock') {
if (!this._verifyPartial())
return false;
}
// Check timestamp against now + 2 hours
if (this.ts > utils.now() + 2 * 60 * 60) {
this.chain.emit('debug', 'Block timestamp is too high: %s', this.rhash);
return false;
}
// Verify the partial merkle tree if we are a merkleblock.
if (this.subtype === 'merkleblock') {
if (!this._verifyPartial()) {
this.chain.emit('debug', 'Block failed merkle test: %s', this.rhash);
return false;
}
}
// Merkleblock and headers cannot do anymore tests.
if (this.subtype !== 'block')
return true;
// Size can't be bigger than MAX_BLOCK_SIZE
if (this.txs.length > constants.block.maxSize
|| this.size() > constants.block.maxSize) {
this.chain.emit('debug', 'Block is too large: %s', this.rhash);
return false;
}
// First TX must be a coinbase
if (!this.txs.length || !this.txs[0].isCoinbase())
if (!this.txs.length || !this.txs[0].isCoinbase()) {
this.chain.emit('debug', 'Block has no coinbase: %s', this.rhash);
return false;
}
// Test all txs
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
// The rest of the txs must not be coinbases
if (i > 0 && tx.isCoinbase())
if (i > 0 && tx.isCoinbase()) {
this.chain.emit('debug', 'Block more than one coinbase: %s', this.rhash);
return false;
}
// Check for duplicate txids
hash = tx.hash('hex');
if (uniq[hash])
if (uniq[hash]) {
this.chain.emit('debug', 'Block has duplicate txids: %s', this.rhash);
return false;
}
uniq[hash] = true;
}
// Check merkle root
if (this.getMerkleRoot() !== this.merkleRoot)
if (this.getMerkleRoot() !== this.merkleRoot) {
this.chain.emit('debug', 'Block failed merkleroot test: %s', this.rhash);
return false;
}
return true;
};
Block.prototype.postVerify = function postVerify() {
var flags = {};
var prev, height, i, j, tx, cb, sigops, input;
var sigops = 0;
var prev, height, ts, i, j, tx, cb, input;
if (this.subtype !== 'block')
return true;
@ -284,96 +300,121 @@ Block.prototype.postVerify = function postVerify() {
return true;
prev = this.chain.getBlock(this.prevBlock);
height = prev.height + 1;
// Ensure it's not an orphan
if (!prev) {
console.log('NO PREV: %s', height);
this.chain.emit('debug', 'Block has no previous entry: %s', this.rhash);
return false;
}
height = prev.height + 1;
// Ensure the timestamp is correct
if (this.ts <= prev.getMedianTime()) {
console.log('BAD TIME: %s', height);
this.chain.emit('debug', 'Block time is lower than median: %s', this.rhash);
return false;
}
// Test all txs
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
// TXs must be finalized with regards to seq and locktime
if (!tx.isFinal(this, prev)) {
console.log('IS NOT FINAL: %s', height);
return false;
}
}
// Ensure the miner's target is equal to what we expect
if (this.bits !== this.chain.target(prev, this)) {
console.log('BAD TARGET: %s', height);
this.chain.emit('debug', 'Block is using wrong target: %s', this.rhash);
return false;
}
// Only allow version 2 blocks (coinbase height)
// once the majority of blocks are using it.
if (this.version < 2 && prev.isOutdated(2)) {
console.log('OUTDATED 2: %s', height);
this.chain.emit('debug', 'Block is outdated (v2): %s', this.rhash);
return false;
}
// Only allow version 3 blocks (sig validation)
// once the majority of blocks are using it.
if (this.version < 3 && prev.isOutdated(3)) {
console.log('OUTDATED 3: %s', height);
this.chain.emit('debug', 'Block is outdated (v3): %s', this.rhash);
return false;
}
// Only allow version 4 blocks (checklocktimeverify)
// once the majority of blocks are using it.
if (this.version < 4 && prev.isOutdated(4)) {
console.log('OUTDATED 4: %s', height);
this.chain.emit('debug', 'Block is outdated (v4): %s', this.rhash);
return false;
}
// Make sure the height contained in the
// coinbase is correct.
// Only allow version 8 blocks (locktime median past)
// once the majority of blocks are using it.
// if (this.version < 8 && prev.isOutdated(8)) {
// this.chain.emit('debug', 'Block is outdated (v8): %s', this.rhash);
// return false;
// }
// Make sure the height contained in the coinbase is correct.
if (this.version >= 2 && prev.isUpgraded(2)) {
cb = bcoin.script.isCoinbase(this.txs[0].inputs[0].script, this);
// Make sure the coinbase is parseable.
if (!cb) {
console.log('BAD COINBASE: %s', height);
this.chain.emit('debug', 'Block has malformed coinbase: %s', this.rhash);
return false;
}
// Make sure coinbase height is equal to the actual height.
if (cb.height !== height) {
console.log('BAD COINBASE HEIGHT: %s', height);
this.chain.emit('debug', 'Block has bad coinbase height: %s', this.rhash);
return false;
}
}
// Signature validation is now enforced (bip66)
if (this.version >= 3 && prev.isUpgraded(3))
flags.strictder = true;
if (!(this.version >= 3 && prev.isUpgraded(3)))
flags.strictder = false;
// CHECKLOCKTIMEVERIFY is now usable (bip65)
if (this.version >= 4 && prev.isUpgraded(4))
flags.cltv = true;
if (!(this.version >= 4 && prev.isUpgraded(4)))
flags.cltv = false;
// Check for sigops limits
sigops = 0;
// Use nLockTime median past (bip113)
// https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki
// Support version bits:
// https://gist.github.com/sipa/bf69659f43e763540550
// http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html
// if (this.version >= 8 && prev.isUpgraded(8))
// flags.locktimeMedian = true;
// If we are an ancestor of a checkpoint, we can
// skip the input verification.
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
flags.scriptChecks = false;
// Get timestamp for tx.isFinal().
ts = flags.locktimeMedian
? prev.getMedianTime()
: this.ts;
// Check all transactions
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
// Transactions must be finalized with
// regards to nSequence and nLockTime.
if (!tx.isFinal(height, ts)) {
this.chain.emit('debug', 'TX is not final: %s (%s)', this.rhash, i);
return false;
}
// Check for tx sigops limits
// Bitcoind does not check for this when accepting
// a block even though it probably should.
// if (tx.sigops(true) > constants.script.maxTxSigops) {
// // Block 71036 abused checksig to
// // include a huge number of sigops.
// this.chain.emit('debug', 'Block TX has too many sigops: %s', this.rhash);
// if (!(network.type === 'main' && height === 71036))
// return false;
// }
// Check for block sigops limits
// Start counting P2SH sigops once block
// timestamps reach March 31st, 2012.
if (this.ts >= constants.block.bip16time)
@ -382,50 +423,46 @@ Block.prototype.postVerify = function postVerify() {
sigops += tx.sigops();
if (sigops > constants.script.maxBlockSigops) {
console.log('BAD SIGOPS: %s', height);
this.chain.emit('debug', 'Block has too many sigops: %s', this.rhash);
return false;
}
}
// BIP30 - Ensure there are no duplicate txids
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
// BIP30 - Ensure there are no duplicate txids
if (this.chain.index[tx.hash('hex')]) {
// Blocks 91842 and 91880 created duplicate
// txids by carefully crafting the coinbases.
if (!(network.type === 'main' && height === 91842 && height === 91880))
this.chain.emit('debug', 'Block is overwriting txids: %s', this.rhash);
if (!(network.type === 'main' && (height === 91842 || height === 91880)))
return false;
}
}
// If we are an ancestor of a checkpoint,
// we can skip the input verification.
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
flags.noScriptChecks = true;
// Verify the inputs of every tx (CheckInputs)
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
if (tx.isCoinbase())
continue;
if (flags.noScriptChecks)
continue;
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
if (!input.out.tx)
// Verify the inputs of every tx (CheckInputs)
if (flags.scriptChecks !== false) {
if (tx.isCoinbase())
continue;
assert(input.out.tx);
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
if (!tx.verify(j, true, flags))
return false;
// We need the previous output in order
// to verify the script.
if (!input.out.tx)
continue;
// if (this.chain.isSpent(input.out.hash, input.out.index))
// return false;
assert(input.out.tx);
// Verify the script
if (!tx.verify(j, true, flags)) {
this.chain.emit('debug', 'Block has invalid inputs: %s', this.rhash);
return false;
}
// Ensure tx is not double spending an output
// if (this.chain.isSpent(input.out.hash, input.out.index)) {
// this.chain.emit('debug', 'Block is using spent inputs: %s', this.rhash);
// return false;
// }
}
}
}

View File

@ -50,6 +50,12 @@ function Chain(options) {
this.request = new utils.RequestCache();
this.fromJSON(require('./protocol/preload-full'));
this.storage = null;
//this.resetHeight(133000);
this.resetHeight(145000);
if (0)
this.fromJSON({
v: 2,
type: 'chain',
@ -608,7 +614,10 @@ Chain.prototype.height = function height() {
return this.getTip().height;
};
// /home/chjj/bitcoin/src/pow.cpp
Chain.prototype.currentTarget = function currentTarget() {
return this.target(this.getTip());
};
Chain.prototype.target = function target(last, block) {
var powLimit = utils.toCompact(network.powLimit);
var ts, first, i;

View File

@ -139,7 +139,9 @@ Miner.prototype.addBlock = function addBlock(block) {
};
Miner.prototype.addTX = function addTX(tx) {
var full = this.index.inputs.every(function(input) {
var full, ts;
full = this.index.inputs.every(function(input) {
return !!input.out.tx;
});
@ -160,7 +162,12 @@ Miner.prototype.addTX = function addTX(tx) {
if (tx.isCoinbase())
return;
if (!tx.isFinal(this.block, this.last))
// Get timestamp for tx.isFinal() - bip113
ts = this.block.version === 8
? this.last.getMedianTime()
: this.block.ts;
if (!tx.isFinal(this.last.height + 1, ts))
return;
// Deliver me from the block size debate, please

View File

@ -548,6 +548,10 @@ Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
this.emit('debug',
'Requesting headers packet from %s with getheaders',
this.host);
this.pool.emit('debug', 'Height: %s, Hash: %s, Stop: %s',
this.pool.chain.getHeight(hashes[0]),
hashes ? utils.revHex(hashes[0]) : 0,
stop ? utils.revHex(stop) : 0);
this._write(this.framer.getHeaders(hashes, stop));
};
@ -555,6 +559,10 @@ Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) {
this.emit('debug',
'Requesting inv packet from %s with getblocks',
this.host);
this.pool.emit('debug', 'Height: %s, Hash: %s, Stop: %s',
this.pool.chain.getHeight(hashes[0]),
hashes ? utils.revHex(hashes[0]) : 0,
stop ? utils.revHex(stop) : 0);
this._write(this.framer.getBlocks(hashes, stop));
};
@ -571,6 +579,13 @@ Peer.prototype.loadMempool = function loadMempool() {
this._write(this.framer.mempool());
};
Peer.prototype.reject = function reject(details) {
this.emit('debug',
'Sending reject packet to %s',
this.host);
this._write(this.framer.reject(details));
};
/**
* Expose
*/

View File

@ -42,7 +42,7 @@ function Pool(options) {
this.destroyed = false;
this.size = options.size || 32;
this.parallel = options.parallel || 2000;
this.redundancy = options.redundancy || 2;
this.redundancy = options.redundancy || 1;
this.seeds = options.seeds
? options.seeds.slice()
: network.seeds.slice();
@ -320,10 +320,14 @@ Pool.prototype._addLoader = function _addLoader() {
});
peer.on('merkleblock', function(block) {
self._startInterval();
self._startTimer();
self._handleBlock(block, peer);
});
peer.on('block', function(block) {
self._startInterval();
self._startTimer();
self._handleBlock(block, peer);
});
@ -346,7 +350,6 @@ Pool.prototype._addLoader = function _addLoader() {
};
Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
var reqs = 0;
var i, header, last, block;
assert(this.options.headers);
@ -375,10 +378,8 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
if (!block.verify())
break;
if (!this.chain.has(block)) {
if (!this.chain.has(block))
this._request(this.block.type, block.hash('hex'));
reqs++;
}
last = block;
}
@ -401,15 +402,9 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
// Reset timeout to avoid killing the loader
this._startTimer();
this.emit('debug',
'Requesting %s block packets from %s with getdata',
reqs, peer.host
);
};
Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
var reqs = 0;
var i, hash;
assert(!this.options.headers);
@ -435,7 +430,6 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
// Request block if we don't have it
if (!this.chain.has(hash)) {
this._request(this.block.type, hash);
reqs++;
continue;
}
@ -457,11 +451,6 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
// Reset timeout to avoid killing the loader
this._startTimer();
this.emit('debug',
'Requesting %s block packets from %s with getdata',
reqs, peer.host
);
};
Pool.prototype._handleInv = function _handleInv(hashes, peer) {
@ -490,10 +479,6 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
var requested = this._response(block);
// Someone is sending us blocks without us requesting them.
if (!requested)
return;
// Emulate bip37 - emit all the "watched" txs
if (this.options.fullNode && this.listeners('watched').length > 0) {
block.txs.forEach(function(tx) {
@ -503,15 +488,21 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
}
// Ignore if we already have
if (this.chain.has(block))
if (this.chain.has(block)) {
this.emit('debug', 'Already have block %s (%s)', block.height, peer.host);
return;
}
// Make sure the block is valid
if (!block.verify()) {
this.emit('debug', 'Block verification failed for %s', block.hash('hex'));
this.emit('debug', 'Block verification failed for %s (%s)', block.rhash, peer.host);
return;
}
// Someone is sending us blocks without us requesting them.
if (!requested)
this.emit('debug', 'Recieved unrequested block: %s (%s)', block.rhash, peer.host);
// Resolve orphan chain
if (!this.options.headers) {
if (!this.chain.hasBlock(block.prevBlock)) {
@ -521,10 +512,11 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
// the height until later.
if (this._addIndex(block, peer))
this.emit('pool block', block, peer);
peer.loadBlocks(
this.peers.load.loadBlocks(
this.chain.locatorHashes(),
this.chain.getOrphanRoot(block)
);
this.emit('debug', 'Handled orphan %s (%s)', block.rhash, peer.host);
return;
}
}
@ -786,11 +778,6 @@ Pool.prototype._removePeer = function _removePeer(peer) {
if (this.peers.load === peer) {
this.emit('debug', 'Removed loader peer (%s).', peer.host);
this.peers.load = null;
// i = this.seeds.indexOf(peer.host);
// if (i === -1)
// i = this.seeds.indexOf(peer.host + ':' + peer.port);
// if (i !== -1)
// this.seeds.splice(i, 1);
}
};
@ -1236,6 +1223,9 @@ Pool.prototype._doRequests = function _doRequests() {
if (above && below && this.load.hiReached)
this._load();
if (items.length === 0)
return;
if (this.options.multiplePeers) {
mapReq = function(item) {
return item.start(this.peers.block[i]);
@ -1258,6 +1248,12 @@ Pool.prototype._doRequests = function _doRequests() {
return item.start(this.peers.load);
}, this);
this.emit('debug',
'Requesting %s/%s items from %s with getdata',
req.length,
this.request.queue.length,
this.peers.load.host);
this.peers.load.getData(req);
};
@ -1506,16 +1502,17 @@ LoadRequest.prototype.start = function start(peer) {
var reqType;
assert(!this.active);
assert(!this.timer);
assert(!this.peer);
this.active = true;
this.pool.request.active++;
assert(!this.timer);
this.timer = setTimeout(function() {
self.timer = null;
self.retry();
}, this.pool.requestTimeout);
// this.timer = setTimeout(function() {
// self.timer = null;
// self.retry();
// }, this.pool.requestTimeout);
assert(!this.peer);
this.peer = peer;
this.peer.once('close', this.onclose);

View File

@ -5,6 +5,7 @@
*/
var bcoin = require('../../bcoin');
var bn = require('bn.js');
var utils = bcoin.utils;
var i;
@ -219,3 +220,7 @@ exports.zeroHash = utils.toArray(
exports.userVersion = require('../../../package.json').version;
exports.userAgent = '/bcoin:' + exports.userVersion + '/';
exports.coin = new bn(10000000).muln(10);
exports.cent = new bn(1000000);
exports.maxMoney = new bn(21000000).mul(exports.coin);

View File

@ -357,6 +357,32 @@ Framer.prototype.merkleBlock = function merkleBlock(block) {
return this.packet('merkleblock', Framer.block(block, 'merkleblock'));
};
Framer.prototype.reject = function reject(details) {
var p = [];
var off = 0;
var message = details.message || '';
var ccode = constants.reject[details.ccode] || constants.reject.malformed;
var reason = details.reason || '';
var data = details.data || [];
off += utils.writeIntv(p, message.length, off);
utils.writeAscii(p, message, off);
off += message.length;
p[off] = ccode;
off++;
off += utils.writeIntv(p, reason.length, off);
utils.writeAscii(p, reason, off);
off += reason.length;
utils.copy(data, p, off, true);
off += data.length;
return this.packet('reject', p);
};
Framer.prototype.addr = function addr(peers) {
var p = [];
var off = 0;

View File

@ -295,12 +295,12 @@ Parser.prototype.parseBlock = function parseBlock(p) {
off = result.off;
totalTX = result.r;
if (p.length >= off + 10) {
for (i = 0; i < totalTX; i++) {
tx = this.parseTX(p.slice(off));
off += tx._off;
txs.push(tx);
}
for (i = 0; i < totalTX; i++) {
tx = this.parseTX(p.slice(off));
if (!tx)
return this._error('Invalid tx count for block');
off += tx._off;
txs.push(tx);
}
return {

View File

@ -686,8 +686,13 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
if (!constants.hashTypeByVal[type & 0x1f])
return false;
if (!script.isValidSig(sig))
return false;
if (flags.strictder !== false) {
if (!script.isValidSig(sig))
return false;
} else {
if (!script.isSig(sig))
return false;
}
subscript = script.subscript(s, lastSep);
hash = tx.subscriptHash(index, subscript, type);
@ -743,8 +748,13 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
if (!constants.hashTypeByVal[type & 0x1f])
return false;
if (!script.isValidSig(sig))
return false;
if (flags.strictder !== false) {
if (!script.isValidSig(sig))
return false;
} else {
if (!script.isSig(sig))
return false;
}
hash = tx.subscriptHash(index, subscript, type);
@ -775,6 +785,9 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
}
case 'checklocktimeverify': {
// OP_CHECKLOCKTIMEVERIFY = OP_NOP2
if (flags.cltv === false)
break;
if (!tx || stack.length === 0)
return false;
@ -1298,6 +1311,16 @@ script.isCoinbase = function isCoinbase(s, block) {
return coinbase;
};
script.isSig = function isSig(sig, allowZero) {
if (!Array.isArray(sig))
return false;
if (allowZero && sig.length === 0)
return true;
return 9 <= sig.length && sig.length <= 73;
};
// https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
/**
* A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>

View File

@ -838,29 +838,29 @@ TX.prototype.funds = function funds(side) {
// Used for postVerify/ContextualBlockCheck and miner isFinalTx call.
// BIP113 will require that time-locked transactions have nLockTime set to
// less than the median time of the previous block they're contained in.
TX.prototype.isFinal = function isFinal(block, prev) {
TX.prototype.isFinalBlock = function isFinalBlock(block, prev, useMedian) {
var height = prev.height + 1;
var ts = this.locktimeMedian ? prev.getMedianTime() : block.ts;
return this._isFinal(height, ts);
var ts = useMedian ? prev.getMedianTime() : block.ts;
return this.isFinal(height, ts);
};
// Used in AcceptToMemoryPool
TX.prototype.isFinalAccept = function isFinalAccept() {
TX.prototype.isFinalMempool = function isFinalMempool(useMedian) {
var height = this.chain.height() + 1;
var ts = this.lockTimeMedian
var ts = useMedian
? this.chain.getTip().getMedianTime()
: utils.now();
return this._isFinalTx(height, ts);
return this.isFinal(height, ts);
};
// Used in the original bitcoind code for AcceptBlock
TX.prototype._isFinalOriginal = function _isFinalOriginal(block) {
TX.prototype.isFinalLegacy = function isFinalLegacy(block) {
var ts = block ? block.ts : utils.now();
var height = this.chain.height();
return this._isFinalTx(height, ts);
return this.isFinal(height, ts);
};
TX.prototype._isFinal = function _isFinal(height, ts) {
TX.prototype.isFinal = function isFinal(height, ts) {
var threshold = constants.locktimeThreshold;
var i;

View File

@ -81,7 +81,7 @@ function Wallet(options, passphrase) {
this.m = 1;
this.n = 1;
this.prefix = 'bt/' + this.getOwnAddress() + '/';
this.prefix = 'bt/wallet/' + this.getOwnAddress() + '/';
this.multisig(options.multisig || {});