add tx.isFinal. refactor chain usage.

This commit is contained in:
Christopher Jeffrey 2016-01-05 17:09:58 -08:00
parent c6c75d509b
commit 680bf01e98
8 changed files with 293 additions and 90 deletions

View File

@ -42,6 +42,8 @@ function Block(data, subtype) {
this.network = data.network || false;
this.relayedBy = data.relayedBy || '0.0.0.0';
this._chain = data.chain;
this.valid = null;
this._hash = null;
@ -226,7 +228,8 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() {
// This mimics the behavior of CheckBlockHeader()
// and CheckBlock() in bitcoin/src/main.cpp.
Block.prototype._verify = function _verify() {
var i, unique, hash, merkleRoot;
var uniq = {};
var i, tx, hash;
// Check proof of work matches claimed amount
if (!utils.testTarget(this.bits, this.hash()))
@ -251,55 +254,106 @@ Block.prototype._verify = function _verify() {
}
// First TX must be a coinbase
if (!this.txs.length
|| this.txs[0].inputs.length !== 1
|| +this.txs[0].inputs[0].out.hash !== 0)
if (!this.txs.length || !this.txs[0].isCoinbase())
return false;
// The rest of the txs must not be coinbases
for (i = 1; i < this.txs.length; i++) {
if (this.txs[i].inputs.length === 1
&& +this.txs[i].inputs[0].out.hash === 0)
return false;
}
// Check for duplicate tx ids
unique = {};
// Test all txs
for (i = 0; i < this.txs.length; i++) {
hash = this.txs[i].hash('hex');
if (unique[hash])
return false;
unique[hash] = true;
}
tx = this.txs[i];
// Build MerkleTree
merkleRoot = this.getMerkleRoot();
// The rest of the txs must not be coinbases
if (i > 0 && tx.isCoinbase())
return false;
// Check for duplicate txids
hash = tx.hash('hex');
if (uniq[hash])
return false;
uniq[hash] = true;
}
// Check merkle root
if (merkleRoot !== this.merkleRoot)
if (this.getMerkleRoot() !== this.merkleRoot)
return false;
return true;
};
Block.prototype.getHeight = function getHeight(chain) {
chain = chain || bcoin.chain.global;
Block.prototype.postVerify = function postVerify() {
var prev, i, tx, cb;
if (!chain)
return -1;
if (this.subtype !== 'block')
return true;
return chain.getHeight(this.hash('hex'));
if (!this.chain)
return true;
prev = this.chain.getBlock(this.prevBlock);
// Ensure it's not an orphan
if (!prev)
return false;
// Ensure the timestamp is correct
if (this.ts <= prev.getMedianTime())
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))
return false;
}
if (this.bits !== this.chain.target(prev, this))
return false;
if (this.version < 2 && prev.isOutdated(2))
return false;
if (this.version < 3 && prev.isOutdated(3))
return false;
if (this.version < 4 && prev.isOutdated(4))
return false;
// Enforce height in coinbase
if (this.version >= 2 && prev.needsUpgrade(2)) {
cb = bcoin.script.isCoinbase(this.txs[0].inputs[0].script, this);
if (!cb)
return false;
if (cb.height !== prev.height + 1)
return false;
}
// sig validation (bip66)
if (this.version >= 3 && prev.needsUpgrade(3))
this.scriptFlags |= 1; // dersig
// checklocktimeverify (bip65)
if (this.version >= 4 && prev.needsUpgrade(4))
this.scriptFlags |= 2;
return true;
};
Block.prototype.getNextBlock = function getNextBlock(chain) {
Block.prototype.getHeight = function getHeight() {
if (!this.chain)
return -1;
return this.chain.getHeight(this.hash('hex'));
};
Block.prototype.getNextBlock = function getNextBlock() {
var next;
chain = chain || bcoin.chain.global;
if (!chain)
if (!this.chain)
return utils.toHex(constants.zeroHash);
next = chain.getNextBlock(this.hash('hex'));
next = this.chain.getNextBlock(this.hash('hex'));
if (!next)
return utils.toHex(constants.zeroHash);
@ -361,26 +415,32 @@ Block.prototype.getReward = function getReward() {
};
};
Block.prototype.getEntry = function getEntry(chain) {
chain = chain || bcoin.chain.global;
return chain.getBlock(this);
Block.prototype.getEntry = function getEntry() {
if (!this.chain)
return;
return this.chain.getBlock(this);
};
Block.prototype.isOrphan = function isOrphan(chain) {
chain = chain || bcoin.chain.global;
return chain.hasBlock(this.prevBlock);
Block.prototype.isOrphan = function isOrphan() {
if (!this.chain)
return true;
return this.chain.hasBlock(this.prevBlock);
};
Block.prototype.__defineGetter__('chain', function() {
return this._chain || bcoin.chain.global;
});
Block.prototype.__defineGetter__('rhash', function() {
return utils.revHex(this.hash('hex'));
});
Block.prototype.__defineGetter__('height', function() {
return this.getHeight(bcoin.chain.global);
return this.getHeight();
});
Block.prototype.__defineGetter__('nextBlock', function() {
return this.getNextBlock(bcoin.chain.global);
return this.getNextBlock();
});
Block.prototype.__defineGetter__('reward', function() {
@ -399,17 +459,18 @@ Block.prototype.__defineGetter__('coinbase', function() {
});
Block.prototype.__defineGetter__('entry', function() {
return this.getEntry(bcoin.chain.global);
return this.getEntry();
});
Block.prototype.__defineGetter__('orphan', function() {
return this.isOrphan(bcoin.chain.global);
return this.isOrphan();
});
Block.prototype.inspect = function inspect() {
var copy = bcoin.block(this, this.subtype);
copy.__proto__ = null;
delete copy._raw;
delete copy._chain;
copy.hash = this.hash('hex');
copy.rhash = this.rhash;
copy.height = this.height;

View File

@ -149,7 +149,7 @@ Chain.prototype._addIndex = function _addIndex(entry) {
return Chain.codes.unchanged;
}
// Duplcate height
// Duplicate height
if (this.index.hashes[entry.height] === entry.hash)
return Chain.codes.unchanged;
@ -294,8 +294,16 @@ Chain.prototype.add = function add(block, peer) {
// If we have a block at the same height, use chain with higher work
if (this.index.hashes[entry.height]) {
if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
if (!block.postVerify()) {
throw new Error;
//code = Chain.codes.invalid;
//break;
}
this.resetHeight(entry.height - 1);
this._addIndex(entry);
code = this._addIndex(entry);
assert(code !== Chain.codes.unchanged);
if (code !== Chain.codes.okay)
break;
code = Chain.codes.forked;
// Breaking here only works because
// we deleted the orphan map in resetHeight.
@ -308,7 +316,15 @@ Chain.prototype.add = function add(block, peer) {
}
// Validated known block at this point - add it to index
if (!block.postVerify()) {
throw new Error;
//code = Chain.codes.invalid;
//break;
}
code = this._addIndex(entry);
assert(code !== Chain.codes.unchanged);
if (code !== Chain.codes.okay)
break;
this.emit('block', block, peer);
this.emit('entry', entry);
if (block !== initial)
@ -531,26 +547,24 @@ Chain.prototype.height = function height() {
};
Chain.prototype.target = function target(last, block) {
var proofOfWorkLimit = utils.toCompact(network.powLimit);
var adjustmentInterval = network.powTargetTimespan / network.powTargetSpacing;
var newBlockTs, heightFirst, first;
adjustmentInterval |= 0;
var powLimit = utils.toCompact(network.powLimit);
var interval = network.powTargetTimespan / network.powTargetSpacing | 0;
var first, ts;
if (!last)
last = this.getTip();
// Do not retarget
if ((last.height + 1) % adjustmentInterval) {
if ((last.height + 1) % interval) {
if (network.powAllowMinDifficultyBlocks) {
// Special behavior for testnet:
newBlockTs = block ? block.ts : utils.now();
if (newBlockTs > last.ts + network.powTargetSpacing * 2)
return proofOfWorkLimit;
ts = block ? (block.ts || block) : utils.now();
if (ts > last.ts + network.powTargetSpacing * 2)
return powLimit;
while (last.prev
&& last.height % adjustmentInterval !== 0
&& last.bits !== proofOfWorkLimit) {
&& last.height % interval !== 0
&& last.bits !== powLimit) {
last = last.prev;
}
@ -560,23 +574,22 @@ Chain.prototype.target = function target(last, block) {
}
// Back 2 weeks
heightFirst = last.height - (adjustmentInterval - 1);
first = this.byHeight(heightFirst);
first = this.byHeight(last.height - (interval - 1));
if (!first)
return 0;
return this.retarget(last, first.ts);
return this.retarget(last, first);
};
Chain.prototype.retarget = function retarget(last, firstTs) {
Chain.prototype.retarget = function retarget(last, first) {
var powTargetTimespan = new bn(network.powTargetTimespan);
var actualTimespan, powLimit, target;
if (network.powNoRetargeting)
return last.bits;
actualTimespan = new bn(last.ts).subn(firstTs);
actualTimespan = new bn(last.ts).subn(first.ts);
if (actualTimespan.cmp(powTargetTimespan.divn(4)) < 0)
actualTimespan = powTargetTimespan.divn(4);
@ -752,6 +765,45 @@ ChainBlock.prototype.getChainwork = function() {
return (this.prev ? this.prev.chainwork : new bn(0)).add(this.proof);
};
ChainBlock.prototype.getMedianTime = function() {
var entry = this;
var median = [];
var timeSpan = constants.block.medianTimespan;
var i;
for (i = 0; i < timeSpan && entry; i++, entry = entry.prev)
median.push(entry.ts);
median = median.sort();
return median[median.length / 2 | 0];
};
ChainBlock.prototype.isOutdated = function(version) {
return this.isSuperMajority(version,
network.block.majorityRejectBlockOutdated);
};
ChainBlock.prototype.needsUpgrade = function(version) {
return this.isSuperMajority(version,
network.block.majorityEnforceBlockUpgrade);
};
ChainBlock.prototype.isSuperMajority = function(version, required) {
var entry = this;
var found = 0;
var majorityWindow = network.block.majorityWindow;
var i;
for (i = 0; i < majorityWindow && found < required && entry; i++) {
if (entry.version >= version)
found++;
entry = entry.prev;
}
return found >= required;
};
ChainBlock.prototype.toJSON = function() {
// return [
// this.hash,

View File

@ -134,12 +134,17 @@ Miner.prototype.addTX = function addTX(tx) {
return;
}
// Pretty important
// Ignore if it's already in a block
if (tx.height !== -1)
return;
if (!tx.verify())
return;
// Ignore if it's already in a block
if (tx.height !== -1)
if (tx.isCoinbase())
return;
if (!tx.isFinal(this.block, this.last))
return;
// Deliver me from the block size debate, please
@ -161,10 +166,12 @@ Miner.prototype.addTX = function addTX(tx) {
};
Miner.prototype.createBlock = function createBlock(tx) {
var target, coinbase, headers, block;
var ts, target, coinbase, headers, block;
// Update target
target = this.chain.target(this.last);
ts = Math.max(utils.now(), this.last.ts + 1);
// Find target
target = this.chain.target(this.last, ts);
// Create a coinbase
coinbase = bcoin.tx();
@ -194,7 +201,7 @@ Miner.prototype.createBlock = function createBlock(tx) {
? this.last.hash('hex')
: this.last.hash,
merkleRoot: utils.toHex(constants.zeroHash.slice()),
ts: utils.now(),
ts: ts,
bits: utils.toCompact(target),
nonce: 0
};

View File

@ -149,8 +149,9 @@ Peer.prototype._init = function init() {
this._req('verack', function(err, payload) {
if (err) {
self._error(err);
self.destroy();
return self._error(err);
return;
}
self.ack = true;
self.emit('ack');
@ -318,7 +319,7 @@ Peer.prototype._res = function _res(cmd, payload) {
for (i = 0; i < this._request.queue.length; i++) {
entry = this._request.queue[i];
if (!entry || entry.cmd && entry.cmd !== cmd)
if (!entry || (entry.cmd && entry.cmd !== cmd))
return false;
res = entry.cb(null, payload, cmd);

View File

@ -43,7 +43,9 @@ function Pool(options) {
this.size = options.size || 32;
this.parallel = options.parallel || 2000;
this.redundancy = options.redundancy || 2;
this.seeds = network.seeds.slice();
this.seeds = options.seeds
? options.seeds.slice()
: network.seeds.slice();
this._createConnection = options.createConnection;
this._createSocket = options.createSocket;
@ -280,6 +282,10 @@ Pool.prototype._addLoader = function _addLoader() {
relay: this.options.relay
});
peer.once('socket', function() {
self.emit('debug', 'Added loader peer: %s', peer.host);
});
this.peers.load = peer;
this.peers.all.push(peer);
@ -494,8 +500,10 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
return;
// Make sure the block is valid
if (!block.verify())
if (!block.verify()) {
this.emit('debug', 'Block verification failed for %s', block.hash('hex'));
return;
}
// Resolve orphan chain
if (!this.options.headers) {
@ -768,8 +776,15 @@ Pool.prototype._removePeer = function _removePeer(peer) {
if (i !== -1)
this.peers.all.splice(i, 1);
if (this.peers.load === 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);
}
};
Pool.prototype.watch = function watch(id) {
@ -1407,9 +1422,15 @@ Pool.prototype.usableSeed = function usableSeed(addrs, force) {
if (!addrs)
addrs = this.seeds;
for (i = 0; i < addrs.length; i++) {
if (!this.getPeer(addrs[i]))
return addrs[i];
addrs = addrs.slice().sort(function() {
return Math.random() > 0.50 ? 1 : -1;
});
if (this.peers.loader) {
for (i = 0; i < addrs.length; i++) {
if (!this.getPeer(addrs[i]))
return addrs[i];
}
}
if (!force)

View File

@ -155,7 +155,8 @@ exports.hashTypeByVal = Object.keys(exports.hashType).reduce(function(out, type)
exports.block = {
maxSize: 1000000,
maxSigops: 1000000 / 50,
maxOrphanTx: 1000000 / 100
maxOrphanTx: 1000000 / 100,
medianTimeSpan: 11
};
exports.script = {

View File

@ -140,6 +140,12 @@ main.powTargetSpacing = 10 * 60;
main.powAllowMinDifficultyBlocks = false;
main.powNoRetargeting = false;
main.block = {
majorityEnforceBlockUpgrade: 750,
majorityRejectBlockOutdated: 950,
majorityWindow: 1000
};
/**
* Testnet (v3)
* https://en.bitcoin.it/wiki/Testnet
@ -237,3 +243,9 @@ testnet.powTargetTimespan = 14 * 24 * 60 * 60; // two weeks
testnet.powTargetSpacing = 10 * 60;
testnet.powAllowMinDifficultyBlocks = true;
testnet.powNoRetargeting = false;
testnet.block = {
majorityEnforceBlockUpgrade: 51,
majorityRejectBlockOutdated: 75,
majorityWindow: 100
};

View File

@ -37,6 +37,8 @@ function TX(data, block) {
this.network = data.network || false;
this.relayedBy = data.relayedBy || '0.0.0.0';
this._chain = data.chain;
this._lock = this.lock;
if (data.inputs) {
@ -834,25 +836,66 @@ TX.prototype.funds = function funds(side) {
return acc;
};
TX.prototype.getHeight = function getHeight(chain) {
chain = chain || bcoin.chain.global;
if (!chain)
return -1;
return this.block ? chain.getHeight(this.block) : -1;
// 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) {
var height = prev.height + 1;
var ts = this.locktimeMedian ? prev.getMedianTime() : block.ts;
return this._isFinal(height, ts);
};
TX.prototype.getConfirmations = function getConfirmations(chain) {
// Used in AcceptToMemoryPool
TX.prototype.isFinalAccept = function isFinalAccept() {
var height = this.chain.height() + 1;
var ts = this.lockTimeMedian
? this.chain.getTip().getMedianTime()
: utils.now();
return this._isFinalTx(height, ts);
};
// Used in the original bitcoind code for AcceptBlock
TX.prototype._isFinalOriginal = function _isFinalOriginal(block) {
var ts = block ? block.ts : utils.now();
var height = this.chain.height();
return this._isFinalTx(height, ts);
};
TX.prototype._isFinal = function _isFinal(height, ts) {
var threshold = constants.locktimeThreshold;
var i;
if (!this.chain)
return true;
if (this.lock === 0)
return true;
if (this.lock < (this.lock < threshold ? height : ts))
return true;
for (i = 0; i < this.inputs.length; i++) {
if (this.inputs[i].seq !== 0xffffffff)
return false;
}
return true;
};
TX.prototype.getHeight = function getHeight() {
if (!this.chain)
return -1;
return this.block ? this.chain.getHeight(this.block) : -1;
};
TX.prototype.getConfirmations = function getConfirmations() {
var top, height;
chain = chain || bcoin.chain.global;
if (!chain)
if (!this.chain)
return 0;
top = chain.height();
height = this.getHeight(chain);
top = this.chain.height();
height = this.getHeight();
if (height === -1)
return 0;
@ -860,6 +903,10 @@ TX.prototype.getConfirmations = function getConfirmations(chain) {
return top - height + 1;
};
TX.prototype.__defineGetter__('chain', function() {
return this._chain || bcoin.chain.global;
});
TX.prototype.__defineGetter__('rblock', function() {
return this.block
? utils.revHex(this.block)
@ -879,11 +926,11 @@ TX.prototype.__defineGetter__('value', function() {
});
TX.prototype.__defineGetter__('height', function() {
return this.getHeight(bcoin.chain.global);
return this.getHeight();
});
TX.prototype.__defineGetter__('confirmations', function() {
return this.getConfirmations(bcoin.chain.global);
return this.getConfirmations();
});
TX.prototype.inspect = function inspect() {
@ -892,6 +939,7 @@ TX.prototype.inspect = function inspect() {
if (this.block)
copy.block = this.block;
delete copy._raw;
delete copy._chain;
copy.hash = this.hash('hex');
copy.rhash = this.rhash;
copy.rblock = this.rblock;