miner. network. serve blocks and mempool txs.

This commit is contained in:
Christopher Jeffrey 2016-04-03 21:43:50 -07:00
parent 8124b4de44
commit c5bcd44606
15 changed files with 1083 additions and 362 deletions

View File

@ -9,6 +9,8 @@ var node = bcoin.fullnode({
passphrase: 'node',
prune: process.argv.indexOf('--prune') !== -1,
useCheckpoints: process.argv.indexOf('--checkpoints') !== -1,
listen: process.argv.indexOf('--listen') !== -1,
selfish: process.argv.indexOf('--selfish') !== -1,
mine: process.argv.indexOf('--mine') !== -1
});
@ -20,7 +22,6 @@ node.open(function(err) {
if (err)
throw err;
if (node.options.mine)
node.miner.start();
else

View File

@ -7,7 +7,6 @@
var bcoin = require('../bcoin');
var utils = require('./utils');
var network = bcoin.protocol.network;
var BufferWriter = require('./writer');
/**
* AbstractBlock
@ -24,7 +23,7 @@ function AbstractBlock(data) {
this.ts = data.ts;
this.bits = data.bits;
this.nonce = data.nonce;
this.totalTX = data.totalTX;
this.totalTX = data.totalTX || 0;
this.height = data.height != null ? data.height : -1;
this._raw = data._raw || null;
@ -42,21 +41,10 @@ AbstractBlock.prototype.hash = function hash(enc) {
};
AbstractBlock.prototype.abbr = function abbr() {
var p;
if (this._raw)
return this._raw.slice(0, 80);
p = new BufferWriter();
p.write32(this.version);
p.writeHash(this.prevBlock);
p.writeHash(this.merkleRoot);
p.writeU32(this.ts);
p.writeU32(this.bits);
p.writeU32(this.nonce);
return p.render();
return bcoin.protocol.framer.blockHeaders(this);
};
AbstractBlock.prototype.getSize = function getSize() {

View File

@ -99,6 +99,47 @@ Block.prototype.hasWitness = function hasWitness() {
return false;
};
Block.prototype.addTX = function addTX(tx) {
var hash = tx.hash('hex');
var index;
if (this.indexOf(hash) !== -1)
return;
index = this.txs.push(tx) - 1;
tx.setBlock(this, index);
};
Block.prototype.removeTX = function removeTX(hash) {
var index = this.indexOf(hash);
var tx;
if (index === -1)
return;
tx = this.txs.splice(index, 1)[0];
tx.unsetBlock();
};
Block.prototype.hasTX = function hasTX(hash) {
return this.indexOf(hash) !== -1;
};
Block.prototype.indexOf = function indexOf(hash) {
var i;
if (hash instanceof bcoin.tx)
hash = hash.hash('hex');
for (i = 0; i < this.txs.length; i++) {
if (this.txs[i].hash('hex') === hash)
return i;
}
return -1;
};
Block.prototype.getSigops = function getSigops(scriptHash, accurate) {
var total = 0;
var i;

View File

@ -12,7 +12,6 @@ var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = require('./utils');
var assert = utils.assert;
var BufferReader = require('./reader');
var VerifyError = utils.VerifyError;
/**
@ -251,18 +250,9 @@ Chain.prototype._preload = function _preload(callback) {
utils.debug('Loading %s', url);
function parseHeader(buf) {
var p = new BufferReader(buf);
var hash = utils.dsha256(buf.slice(0, 80));
return {
hash: utils.toHex(hash),
version: p.readU32(), // Technically signed
prevBlock: p.readHash('hex'),
merkleRoot: p.readHash('hex'),
ts: p.readU32(),
bits: p.readU32(),
nonce: p.readU32()
};
var headers = bcoin.protocol.parser.parseBlockHeaders(buf);
headers.hash = utils.toHex(utils.dsha256(buf.slice(0, 80)));
return headers;
}
function save(entry) {
@ -749,7 +739,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
});
};
Chain.prototype.getHeight = function getHeight(hash) {
Chain.prototype._getCachedHeight = function _getCachedHeight(hash) {
if (Buffer.isBuffer(hash))
hash = utils.toHex(hash);
else if (hash.hash)
@ -1654,61 +1644,23 @@ Chain.prototype.getCurrentTarget = function getCurrentTarget(callback) {
return this.getTargetAsync(this.tip, null, callback);
};
Chain.prototype.getTargetAsync = function getTarget(last, block, callback) {
Chain.prototype.getTargetAsync = function getTargetAsync(last, block, callback) {
var self = this;
var powLimit = utils.toCompact(network.powLimit);
var i = 0;
var ts;
callback = utils.asyncify(callback);
// Genesis
if (!last)
return callback(null, powLimit);
// Do not retarget
if ((last.height + 1) % network.powDiffInterval !== 0) {
if (network.powAllowMinDifficultyBlocks) {
// Special behavior for testnet:
ts = block ? (block.ts || block) : utils.now();
if (ts > last.ts + network.powTargetSpacing * 2)
return callback(null, powLimit);
(function next(err, last) {
if (err)
return callback(err);
assert(last);
if (last.height > 0
&& last.height % network.powDiffInterval !== 0
&& last.bits === powLimit) {
return last.getPrevious(next);
}
return callback(null, last.bits);
})(null, last);
return;
}
return callback(null, last.bits);
if (!network.powAllowMinDifficultyBlocks)
return utils.asyncify(callback)(null, this.getTarget(last, block));
}
(function next(err, first) {
return last.getAncestors(network.powDiffInterval, function(err, ancestors) {
if (err)
return callback(err);
i++;
assert(first);
if (i >= network.powDiffInterval)
return callback(null, self.retarget(last, first));
first.getPrevious(next);
})(null, last);
return callback(null, self.getTarget(last, block, ancestors));
});
};
Chain.prototype.getTarget = function getTarget(last, block) {
Chain.prototype.getTarget = function getTarget(last, block, ancestors) {
var powLimit = utils.toCompact(network.powLimit);
var ts, first, i, prev;
@ -1716,6 +1668,9 @@ Chain.prototype.getTarget = function getTarget(last, block) {
if (!last)
return powLimit;
if (!ancestors)
ancestors = last.ancestors;
// Do not retarget
if ((last.height + 1) % network.powDiffInterval !== 0) {
if (network.powAllowMinDifficultyBlocks) {
@ -1725,7 +1680,7 @@ Chain.prototype.getTarget = function getTarget(last, block) {
return powLimit;
i = 1;
prev = last.ancestors;
prev = ancestors;
while (prev[i]
&& last.height % network.powDiffInterval !== 0
&& last.bits === powLimit) {
@ -1738,7 +1693,7 @@ Chain.prototype.getTarget = function getTarget(last, block) {
}
// Back 2 weeks
first = last.ancestors[network.powDiffInterval - 1];
first = ancestors[network.powDiffInterval - 1];
assert(first);
@ -1770,6 +1725,25 @@ Chain.prototype.retarget = function retarget(last, first) {
return utils.toCompact(target);
};
Chain.prototype.findLocator = function findLocator(locator, callback) {
var self = this;
if (!locator)
return utils.nextTick(callback);
utils.forEachSerial(locator, function(hash, next) {
self.db.has(hash, function(err, result) {
if (err)
return next(err);
if (result)
return callback(null, hash);
next();
});
}, callback);
};
// https://github.com/bitcoin/bitcoin/pull/7648/files
Chain.prototype.getState = function getState(prev, id, callback) {
var self = this;

View File

@ -55,6 +55,8 @@ Fullnode.prototype._init = function _init() {
// Pool needs access to the chain.
this.pool = new bcoin.pool(this, {
witness: this.network.witness,
listen: this.options.listen,
selfish: this.options.selfish,
spv: false
});

View File

@ -171,7 +171,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
return self.removeOrphan(hash, next);
copy = tx.clone();
copy.ts = existing.ps;
copy.ts = existing.ts;
copy.block = existing.block;
copy.height = existing.height;
copy.ps = existing.ps;

View File

@ -62,6 +62,9 @@ MerkleBlock.prototype.getRaw = function getRaw() {
};
MerkleBlock.prototype.hasTX = function hasTX(hash) {
if (hash instanceof bcoin.tx)
hash = hash.hash('hex');
return this.txMap[hash] === true;
};

View File

@ -41,8 +41,6 @@ function Miner(node, options) {
this.running = false;
this.timeout = null;
this.fee = new bn(0);
this.last = this.node.chain.tip;
this.block = null;
this.iterations = 0;
this._begin = utils.now();
@ -81,7 +79,6 @@ Miner.prototype._init = function _init() {
this.chain.on('tip', function(tip) {
if (!self.running)
return;
self.last = tip;
self.start();
});
@ -110,10 +107,8 @@ Miner.prototype.start = function start() {
var self = this;
// Wait for `tip`.
this.last = this.last || this.chain.tip;
if (!this.last) {
if (!this.chain.tip) {
this.chain.on('tip', function(tip) {
self.last = tip;
self.start();
});
return;
@ -180,18 +175,14 @@ Miner.prototype.addTX = function addTX(tx) {
if (size > constants.blocks.maxSize)
return false;
if (this.block._txMap[tx.hash('hex')])
if (this.block.hasTX(tx))
return false;
if (!this.block.witness && tx.hasWitness())
return false;
// Add the tx to our block
this.block.txs.push(tx);
this.block._txMap[tx.hash('hex')] = true;
// Calculate our new reward fee
this.fee.iadd(tx.getFee());
this.block.addTX(tx);
// Update coinbase value
this.updateCoinbase();
@ -204,25 +195,18 @@ Miner.prototype.addTX = function addTX(tx) {
Miner.prototype.createBlock = function createBlock(callback) {
var self = this;
var ts, target, coinbase, headers, block;
var ts = Math.max(utils.now(), this.chain.tip.ts + 1);
var coinbase, headers, block;
// Find target
this.last.ensureAncestors(function(err) {
if (err) {
self.last.free();
this.chain.getTargetAsync(this.chain.tip, ts, function(err, target) {
if (err)
return callback(err);
}
// Calculate version with versionbits
self.chain.computeBlockVersion(self.last, function(err, version) {
if (err) {
self.last.free();
self.chain.computeBlockVersion(self.chain.tip, function(err, version) {
if (err)
return callback(err);
}
ts = Math.max(utils.now(), self.last.ts + 1);
target = self.chain.getTarget(self.last, ts);
// Create a coinbase
coinbase = bcoin.mtx();
@ -235,7 +219,7 @@ Miner.prototype.createBlock = function createBlock(callback) {
coin: null,
script: new bcoin.script([
// Height (required in v2+ blocks)
bcoin.script.array(self.last.height + 1),
bcoin.script.array(self.chain.height + 1),
// extraNonce - incremented when
// the nonce overflows.
bcoin.script.array(0),
@ -261,20 +245,20 @@ Miner.prototype.createBlock = function createBlock(callback) {
// Create our block
headers = {
version: version,
prevBlock: self.last.hash,
prevBlock: self.chain.tip.hash,
merkleRoot: utils.toHex(constants.zeroHash),
ts: ts,
bits: target,
nonce: 0
};
block = bcoin.block(headers, 'block');
block._txMap = {};
block = bcoin.block(headers);
block.txs.push(coinbase);
block.addTX(coinbase);
block.target = utils.fromCompact(target);
block.extraNonce = new bn(0, 'le');
block.height = self.chain.height + 1;
block.target = utils.fromCompact(target).toBuffer('le', 32);
block.extraNonce = new bn(0);
if (self.chain.segwitActive) {
// Set up the witness nonce and
@ -294,8 +278,6 @@ Miner.prototype.createBlock = function createBlock(callback) {
// Create our merkle root.
self.updateMerkle(block);
self.last.free();
return callback(null, block);
});
});
@ -310,29 +292,20 @@ Miner.prototype.updateCommitment = function updateCommitment(block) {
if (!block)
block = this.block;
delete block._txMap[coinbase.hash('hex')];
hash = block.getCommitmentHash();
coinbase.outputs[1].script = bcoin.script.createCommitment(hash);
block._txMap[coinbase.hash('hex')] = true;
};
Miner.prototype.updateCoinbase = function updateCoinbase(block) {
var coinbase = block.txs[0];
var reward = bcoin.block.reward(this.last.height + 1);
assert(coinbase);
if (!block)
block = this.block;
delete block._txMap[coinbase.hash('hex')];
coinbase.inputs[0].script[1] = block.extraNonce.toBuffer();
coinbase.outputs[0].value = reward.add(this.fee);
block._txMap[coinbase.hash('hex')] = true;
coinbase.outputs[0].value = block.getReward();
};
Miner.prototype.updateMerkle = function updateMerkle(block) {
@ -344,7 +317,7 @@ Miner.prototype.updateMerkle = function updateMerkle(block) {
if (block.witness)
this.updateCommitment(block);
block.ts = utils.now();
block.ts = Math.max(utils.now(), this.chain.tip.ts + 1);
block.merkleRoot = block.getMerkleRoot('hex');
};
@ -360,7 +333,7 @@ Miner.prototype.iterate = function iterate() {
self.chain.add(self.block, function(err) {
if (err) {
if (err.type === 'VerifyError')
utils.debug('Miner: %s could not be added to chain.', self.block.rhash);
utils.debug('%s could not be added to chain.', self.block.rhash);
self.emit('error', err);
return self.start();
}
@ -391,25 +364,24 @@ Miner.prototype.sendStatus = function sendStatus() {
target: this.block.bits,
hashes: this.hashes.toString(10),
hashrate: this.rate,
height: this.last.height + 1,
best: utils.revHex(this.last.hash)
height: this.chain.height + 1,
best: utils.revHex(this.chain.tip.hash)
});
};
Miner.prototype.findNonce = function findNonce() {
var data = this.block.abbr();
var target = this.block.target.toBuffer('le', 32);
var now;
// Track how long we've been at it.
this._begin = utils.now();
// assert(this.block.ts > this.last.ts);
assert(this.block.ts > this.chain.tip.ts);
// The heart and soul of the miner: match the target.
while (this.block.nonce <= 0xffffffff) {
// Hash and test against the next target
if (utils.rcmp(this.dsha256(data), target) < 0)
if (rcmp(this.dsha256(data), this.block.target) < 0)
return true;
// Increment the nonce to get a different hash
@ -436,7 +408,7 @@ Miner.prototype.findNonce = function findNonce() {
// performance because we do not have to
// recalculate the merkle root.
now = utils.now();
if (now > this.block.ts && now > this.last.ts) {
if (now > this.block.ts && now > this.chain.tip.ts) {
this.block.ts = now;
// Overflow the nonce
this.block.nonce = 0;
@ -456,6 +428,21 @@ Miner.prototype.findNonce = function findNonce() {
return false;
};
function rcmp(a, b) {
var i;
assert(a.length === b.length);
for (i = a.length - 1; i >= 0; i--) {
if (a[i] < b[i])
return -1;
if (a[i] > b[i])
return 1;
}
return 0;
}
/**
* Expose
*/

View File

@ -36,6 +36,7 @@ function Peer(pool, options) {
this.parser = new bcoin.protocol.parser();
this.framer = new bcoin.protocol.framer();
this.chain = this.pool.chain;
this.mempool = this.pool.mempool;
this.bloom = this.pool.bloom;
this.version = null;
this.destroyed = false;
@ -44,6 +45,7 @@ function Peer(pool, options) {
this.ts = this.options.ts || 0;
this.sendHeaders = false;
this.haveWitness = false;
this.hashContinue = null;
this.challenge = null;
this.lastPong = 0;
@ -140,7 +142,7 @@ Peer.prototype._init = function init() {
this.parser.on('error', function(err) {
utils.debug(err.stack + '');
peer.sendReject(null, 'malformed', 'error parsing message', 100);
self.sendReject(null, 'malformed', 'error parsing message', 100);
self._error(err);
// Something is wrong here.
// Ignore this peer.
@ -150,11 +152,6 @@ Peer.prototype._init = function init() {
this.challenge = utils.nonce();
this._ping.timer = setInterval(function() {
if (self.options.witness && !self.haveWitness) {
self._error('Peer does not support segregated witness.');
self.setMisbehavior(100);
return;
}
self._write(self.framer.ping({
nonce: self.challenge
}));
@ -171,25 +168,25 @@ Peer.prototype._init = function init() {
self.emit('ack');
self.ts = utils.now();
self._write(self.framer.packet('getaddr'));
self._write(self.framer.getAddr());
if (self.options.headers) {
if (self.version && self.version.version > 70012)
self._write(self.framer.packet('sendheaders'));
self._write(self.framer.sendHeaders());
}
if (self.options.witness) {
if (self.version && self.version.version >= 70012)
self._write(self.framer.packet('havewitness'));
self._write(self.framer.haveWitness());
}
if (self.pool.chain.isFull())
if (self.chain.isFull())
self.getMempool();
});
// Send hello
this._write(this.framer.version({
height: this.pool.chain.height,
height: this.chain.height,
relay: this.options.relay
}));
};
@ -286,7 +283,8 @@ Peer.prototype.broadcast = function broadcast(items) {
packetType: packetType,
type: type,
hash: item.hash(),
msg: item
value: item.renderNormal(),
witnessValue: item.renderWitness()
};
this._broadcast.map[key] = entry;
@ -358,11 +356,12 @@ Peer.prototype._error = function error(err) {
Peer.prototype._req = function _req(cmd, cb) {
var self = this;
var entry;
if (this.destroyed)
return cb(new Error('Destroyed, sorry'));
return utils.asyncify(cb)(new Error('Destroyed, sorry'));
var entry = {
entry = {
cmd: cmd,
cb: cb,
ontimeout: function() {
@ -420,78 +419,284 @@ Peer.prototype._onPacket = function onPacket(packet) {
var payload = packet.payload;
if (this.lastBlock && cmd !== 'tx')
this._emitMerkle(this.lastBlock);
this._emitMerkle();
if (cmd === 'version')
return this._handleVersion(payload);
if (cmd === 'inv')
return this._handleInv(payload);
if (cmd === 'headers')
return this._handleHeaders(payload);
if (cmd === 'getdata')
return this._handleGetData(payload);
if (cmd === 'addr')
return this._handleAddr(payload);
if (cmd === 'ping')
return this._handlePing(payload);
if (cmd === 'pong')
return this._handlePong(payload);
if (cmd === 'getaddr')
return this._handleGetAddr();
if (cmd === 'reject')
return this._handleReject(payload);
if (cmd === 'alert')
return this._handleAlert(payload);
if (cmd === 'block') {
payload = bcoin.compactblock(payload);
} else if (cmd === 'merkleblock') {
payload = bcoin.merkleblock(payload);
this.lastBlock = payload;
return;
} else if (cmd === 'tx') {
payload = bcoin.tx(payload, this.lastBlock);
if (this.lastBlock) {
if (payload.block) {
this.lastBlock.txs.push(payload);
return;
} else {
this._emitMerkle(this.lastBlock);
switch (cmd) {
case 'version':
return this._handleVersion(payload);
case 'inv':
return this._handleInv(payload);
case 'headers':
return this._handleHeaders(payload);
case 'getdata':
return this._handleGetData(payload);
case 'addr':
return this._handleAddr(payload);
case 'ping':
return this._handlePing(payload);
case 'pong':
return this._handlePong(payload);
case 'getaddr':
return this._handleGetAddr(payload);
case 'reject':
return this._handleReject(payload);
case 'alert':
return this._handleAlert(payload);
case 'getutxos':
return this._handleGetUTXOs(payload);
case 'utxos':
return this._handleUTXOs(payload);
case 'getblocks':
return this._handleGetBlocks(payload);
case 'getheaders':
return this._handleGetHeaders(payload);
case 'mempool':
return this._handleMempool(payload);
case 'block':
payload = bcoin.compactblock(payload);
this._emit(cmd, payload);
break;
case 'merkleblock':
payload = bcoin.merkleblock(payload);
this.lastBlock = payload;
break;
case 'tx':
payload = bcoin.tx(payload, this.lastBlock);
if (this.lastBlock) {
if (payload.block) {
this.lastBlock.txs.push(payload);
break;
}
this._emitMerkle();
}
}
}
if (cmd === 'sendheaders') {
this.sendHeaders = true;
return;
}
if (cmd === 'havewitness') {
this.haveWitness = true;
return;
this._emit(cmd, payload);
break;
case 'sendheaders':
this.sendHeaders = true;
this._res(cmd, payload);
break;
case 'havewitness':
this.haveWitness = true;
this._res(cmd, payload);
break;
case 'verack':
this._emit(cmd, payload);
break;
default:
utils.debug('Unknown packet: %s', cmd);
this._emit(cmd, payload);
break;
}
};
Peer.prototype._emit = function _emit(cmd, payload) {
if (this._res(cmd, payload))
return;
this.emit(cmd, payload);
};
Peer.prototype._emitMerkle = function _emitMerkle(payload) {
if (!this._res('merkleblock', payload))
this.emit('merkleblock', payload);
Peer.prototype._emitMerkle = function _emitMerkle() {
if (this.lastBlock)
this._emit('merkleblock', this.lastBlock);
this.lastBlock = null;
};
Peer.prototype._handleUTXOs = function _handleUTXOs(payload) {
payload.coins = payload.coins(function(coin) {
return new bcoin.coin(coin);
});
utils.debug('Received %d utxos from %s.', payload.coins.length, this.host);
this.emit('utxos', payload);
};
Peer.prototype._handleGetUTXOs = function _handleGetUTXOs(payload) {
var self = this;
var coins = [];
var notfound = [];
function checkMempool(hash, index, callback) {
if (!self.mempool)
return callback();
if (!payload.mempool)
return callback();
self.mempool.getCoin(hash, index, callback);
}
function isSpent(hash, index, callback) {
if (!self.mempool)
return callback(null, false);
if (!payload.mempool)
return callback(null, false);
self.mempool.isSpent(hash, index, callback);
}
utils.forEachSerial(payload.prevout, function(prevout, next, i) {
var hash = prevout.hash;
var index = prevout.index;
checkMempool(hash, index, function(err, coin) {
if (err)
return next(err);
if (coin) {
coins.push(coin);
return next();
}
isSpent(hash, index, function(err, result) {
if (err)
return next(err);
if (result) {
notfound.push(i);
return next();
}
self.chain.db.getCoin(hash, index, function(err, coin) {
if (err)
return next(err);
if (!coin) {
notfound.push(i);
return next();
}
coins.push(coin);
next();
});
});
});
}, function(err) {
if (err)
self.emit('error', err);
self._write(self.framer.UTXOs({
height: self.chain.height,
tip: self.chain.tip.hash,
notfound: notfound,
coins: coins
}));
});
};
Peer.prototype._handleGetHeaders = function _handleGetHeaders(payload) {
var self = this;
var headers = [];
if (this.pool.options.selfish)
return;
if (this.chain.db.options.spv)
return;
function collect(err, hash) {
if (err)
return done(err);
if (!hash)
return done();
self.chain.db.get(hash, function(err, entry) {
if (err)
return done(err);
if (!entry)
return done();
(function next(err, entry) {
if (err)
return done(err);
if (!entry)
return done();
headers.push(new bcoin.headers(entry));
if (headers.length === 2000)
return done();
if (entry.hash === payload.stop)
return done();
entry.getNext(next);
})(null, entry);
});
}
function done(err) {
if (err)
return self.emit('error', err);
self._write(self.framer.headers(headers));
}
if (!payload.locator)
return collect(null, payload.stop);
this.chain.findLocator(payload.locator, function(err, hash) {
if (err)
return collect(err);
if (!hash)
return collect();
self.chain.db.getNextHash(hash, collect);
});
};
Peer.prototype._handleGetBlocks = function _handleGetBlocks(payload) {
var self = this;
var blocks = [];
if (this.pool.options.selfish)
return;
if (this.chain.db.options.spv)
return;
function done(err) {
if (err)
return self.emit('error', err);
self._write(self.framer.inv(blocks));
}
this.chain.findLocator(payload.locator, function(err, tip) {
if (err)
return done(err);
if (!tip)
return done();
(function next(hash) {
self.chain.db.getNextHash(hash, function(err, hash) {
if (err)
return done(err);
if (!hash)
return done();
blocks.push({ type: constants.inv.block, hash: hash });
if (hash === payload.stop)
return done();
if (blocks.length === 500) {
self.hashContinue = hash;
return done();
}
next(hash);
});
})(tip);
});
};
Peer.prototype._handleVersion = function handleVersion(payload) {
if (payload.version < constants.minVersion) {
this._error('Peer doesn\'t support required protocol version.');
@ -523,13 +728,17 @@ Peer.prototype._handleVersion = function handleVersion(payload) {
}
}
// if (this.options.witness) {
// if (!payload.witness) {
// this._error('Peer does not support segregated witness service.');
// this.setMisbehavior(100);
// return;
// }
// }
if (this.options.witness) {
if (!payload.witness) {
this._req('havewitness', function(err, payload) {
if (err) {
self._error('Peer does not support segregated witness.');
self.setMisbehavior(100);
return;
}
});
}
}
if (payload.witness)
this.haveWitness = true;
@ -540,32 +749,57 @@ Peer.prototype._handleVersion = function handleVersion(payload) {
this.emit('version', payload);
};
Peer.prototype._handleGetData = function handleGetData(items) {
items.forEach(function(item) {
// Filter out not broadcasted things
var hash = utils.toHex(item.hash);
var entry = this._broadcast.map[hash];
var isWitness = item.type & constants.invWitnessMask;
var value;
Peer.prototype._handleMempool = function _handleMempool() {
var self = this;
var items = [];
var i;
if (!entry)
return;
if (!this.mempool)
return;
if (this.pool.options.selfish)
return;
this.mempool.getSnapshot(function(err, hashes) {
if (err)
return self.emit('error', err);
for (i = 0; i < hashes.length; i++)
items.push({ type: constants.inv.tx, hash: hashes[i] });
utils.debug('Sending mempool snapshot to %s.', self.host);
self._write(self.framer.inv(items));
});
};
Peer.prototype._handleGetData = function handleGetData(items) {
var self = this;
var check = [];
var notfound = [];
var hash, entry, isWitness, value, i, item;
if (items.length > 50000)
return this._error('message getdata size() = %d', items.length);
for (i = 0; i < items.length; i++) {
item = items[i];
hash = utils.toHex(item.hash);
entry = this._broadcast.map[hash];
isWitness = item.type & constants.invWitnessMask;
value = null;
if (!entry) {
check.push(item);
continue;
}
if ((item.type & ~constants.invWitnessMask) !== entry.type) {
utils.debug(
'Peer %s requested an existing item with the wrong type.',
this.host);
return;
}
if (isWitness) {
if (!entry.witnessValue)
entry.witnessValue = entry.msg.renderWitness();
value = entry.witnessValue;
} else {
if (!entry.value)
entry.value = entry.msg.renderNormal();
value = entry.value;
continue;
}
utils.debug(
@ -575,21 +809,107 @@ Peer.prototype._handleGetData = function handleGetData(items) {
utils.revHex(utils.toHex(entry.hash)),
isWitness ? 'witness' : 'normal');
if (entry.value && entry.witnessValue)
delete entry.msg;
this._write(this.framer.packet(entry.packetType, value));
if (isWitness)
this._write(this.framer.packet(entry.packetType, entry.witnessValue));
else
this._write(this.framer.packet(entry.packetType, entry.value));
entry.e.emit('request');
}, this);
}
if (this.pool.options.selfish)
return;
utils.forEachSerial(check, function(item, next) {
var isWitness = item.type & constants.invWitnessMask;
var type = item.type & ~constants.invWitnessMask;
var hash = utils.toHex(item.hash);
var data;
if (type === constants.inv.tx) {
if (!self.mempool) {
notfound.push({ type: type, hash: hash });
return next();
}
return self.mempool.getTX(hash, function(err, tx) {
if (err)
return next(err);
if (!tx) {
notfound.push({ type: type, hash: hash });
return next();
}
if (isWitness)
data = tx.renderWitness();
else
data = tx.renderNormal();
self._write(self.framer.packet('tx', data));
next();
});
}
if (type === constants.inv.block) {
if (self.chain.db.options.spv) {
notfound.push({ type: type, hash: hash });
return next();
}
return self.chain.db.getBlock(hash, function(err, block) {
if (err)
return next(err);
if (!block) {
notfound.push({ type: type, hash: hash });
return next();
}
if (isWitness)
data = block.renderWitness();
else
data = block.renderNormal();
self._write(self.framer.packet('block', data));
if (hash === self.hashContinue) {
self._write(self.framer.inv([{
type: type,
hash: self.chain.tip.hash
}]));
self.hashContinue = null;
}
next();
});
}
notfound.push({ type: type, hash: hash });
return next();
}, function(err) {
if (err)
self.emit('error', err);
utils.debug(
'Served %d items to %s with getdata (notfound=%d).',
items.length - notfound.length,
notfound.length,
self.host);
if (notfound.length > 0)
self._write(self.framer.notFound(notfound));
});
};
Peer.prototype._handleAddr = function handleAddr(addrs) {
var now = utils.now();
var i, addr, ts, host;
addrs.forEach(function(addr) {
var ts = addr.ts;
var host = addr.ipv4 !== '0.0.0.0'
for (i = 0; i < addrs.length; i++) {
addr = addrs[i];
ts = addr.ts;
host = addr.ipv4 !== '0.0.0.0'
? addr.ipv4
: addr.ipv6;
@ -608,7 +928,7 @@ Peer.prototype._handleAddr = function handleAddr(addrs) {
headers: addr.version >= 31800,
spv: addr.bloom && addr.version >= 70011
});
}, this);
}
utils.debug(
'Recieved %d peers (seeds=%d, peers=%d).',
@ -635,33 +955,38 @@ Peer.prototype._handlePong = function handlePong(data) {
Peer.prototype._handleGetAddr = function handleGetAddr() {
var hosts = {};
var peers;
var peers = this.pool.peers.all;
var items = [];
var i, peer, ip, version;
peers = this.pool.peers.all.map(function(peer) {
var ip, version;
if (this.pool.options.selfish)
return;
for (i = 0; i < peers.length; i++) {
peer = peers[i];
if (!peer.socket || !peer.socket.remoteAddress)
return;
continue;
ip = peer.socket.remoteAddress;
version = utils.isIP(ip);
if (!version)
return;
continue;
if (hosts[ip])
return;
continue;
hosts[ip] = true;
return {
items.push({
ts: peer.ts,
services: peer.version ? peer.version.services : null,
ipv4: version === 4 ? ip : null,
ipv6: version === 6 ? ip : null,
port: peer.socket.remotePort || network.port
};
}).filter(Boolean);
});
}
return this._write(this.framer.addr(peers));
};
@ -734,7 +1059,7 @@ Peer.prototype.getHeaders = function getHeaders(hashes, stop) {
this.host);
utils.debug('Height: %s, Hash: %s, Stop: %s',
this.pool.chain.getHeight(hashes[0]),
this.chain._getCachedHeight(hashes[0]),
hashes ? utils.revHex(hashes[0]) : 0,
stop ? utils.revHex(stop) : 0);
@ -747,7 +1072,7 @@ Peer.prototype.getBlocks = function getBlocks(hashes, stop) {
this.host);
utils.debug('Height: %s, Hash: %s, Stop: %s',
this.pool.chain.getHeight(hashes[0]),
this.chain._getCachedHeight(hashes[0]),
hashes ? utils.revHex(hashes[0]) : 0,
stop ? utils.revHex(stop) : 0);

View File

@ -11,6 +11,7 @@ var utils = require('../utils');
var assert = utils.assert;
var BufferWriter = require('../writer');
var DUMMY = new Buffer([]);
var bn = require('bn.js');
/**
* Framer
@ -56,8 +57,7 @@ Framer.prototype.header = function header(cmd, payload) {
Framer.prototype.packet = function packet(cmd, payload) {
var header;
if (!payload)
payload = DUMMY;
assert(payload, 'No payload.');
header = this.header(cmd, payload);
@ -77,6 +77,50 @@ Framer.prototype.verack = function verack() {
return this.packet('verack', Framer.verack());
};
Framer.prototype.mempool = function mempool() {
return this.packet('mempool', Framer.mempool());
};
Framer.prototype.getUTXOs = function getUTXOs(data) {
return this.packet('getutxos', Framer.getUTXOs(data));
};
Framer.prototype.UTXOs = function UTXOs(data) {
return this.packet('utxos', Framer.UTXOs(data));
};
Framer.prototype.getAddr = function getAddr() {
return this.packet('getaddr', Framer.getAddr());
};
Framer.prototype.submitOrder = function submitOrder() {
return this.packet('submitorder', Framer.submitOrder());
};
Framer.prototype.checkOrder = function checkOrder() {
return this.packet('checkorder', Framer.checkOrder());
};
Framer.prototype.reply = function reply() {
return this.packet('reply', Framer.reply());
};
Framer.prototype.sendHeaders = function sendHeaders() {
return this.packet('sendheaders', Framer.sendHeaders());
};
Framer.prototype.haveWitness = function haveWitness() {
return this.packet('havewitness', Framer.haveWitness());
};
Framer.prototype.filterAdd = function filterAdd(data) {
return this.packet('filteradd', Framer.filterAdd(data));
};
Framer.prototype.filterClear = function filterClear() {
return this.packet('filterclear', Framer.filterClear());
};
Framer.prototype.inv = function inv(items) {
return this.packet('inv', Framer.inv(items));
};
@ -101,10 +145,6 @@ Framer.prototype.filterLoad = function filterLoad(bloom, update) {
return this.packet('filterload', Framer.filterLoad(bloom, update));
};
Framer.prototype.filterClear = function filterClear() {
return this.packet('filterclear', Framer.filterClear());
};
Framer.prototype.getHeaders = function getHeaders(hashes, stop) {
return this.packet('getheaders', Framer.getHeaders(hashes, stop));
};
@ -118,11 +158,11 @@ Framer.prototype.utxo = function _coin(coin) {
};
Framer.prototype.tx = function tx(tx) {
return this.packet('tx', Framer.tx(tx));
return this.packet('tx', Framer.renderTX(tx, false));
};
Framer.prototype.witnessTX = function witnessTX(tx) {
return this.packet('tx', Framer.witnessTX(tx));
return this.packet('tx', Framer.renderTX(tx, true));
};
Framer.prototype.block = function _block(block) {
@ -149,10 +189,6 @@ Framer.prototype.addr = function addr(peers) {
return this.packet('addr', Framer.addr(peers));
};
Framer.prototype.mempool = function mempool() {
return this.packet('mempool', Framer.mempool());
};
Framer.address = function address(data, full, writer) {
var p = new BufferWriter(writer);
@ -230,7 +266,7 @@ Framer.version = function version(options, writer) {
};
Framer.verack = function verack() {
return new Buffer([]);
return DUMMY;
};
Framer._inv = function _inv(items, writer) {
@ -295,33 +331,42 @@ Framer.filterLoad = function filterLoad(bloom, update, writer) {
return p;
};
Framer.filterClear = function filterClear() {
return new Buffer([]);
Framer.getHeaders = function getHeaders(locator, stop, writer) {
// NOTE: getheaders can have an empty locator.
return Framer._getBlocks(locator || [], stop, writer);
};
Framer.getHeaders = function getHeaders(hashes, stop, writer) {
// NOTE: getheaders can have a null hash
if (!hashes)
hashes = [];
return Framer._getBlocks(hashes, stop, writer);
Framer.getBlocks = function getBlocks(locator, stop, writer) {
return Framer._getBlocks(locator, stop, writer);
};
Framer.getBlocks = function getBlocks(hashes, stop, writer) {
return Framer._getBlocks(hashes, stop, writer);
};
Framer._getBlocks = function _getBlocks(locator, stop, writer) {
var p, i, version;
Framer._getBlocks = function _getBlocks(hashes, stop, writer) {
var p = new BufferWriter(writer);
var i;
if (locator.locator) {
writer = stop;
stop = locator.stop;
version = locator.version;
locator = locator.locator;
}
p.writeU32(constants.version);
p.writeVarint(hashes.length);
if (!version)
version = constants.version;
for (i = 0; i < hashes.length; i++)
p.writeHash(hashes[i]);
if (!stop)
stop = constants.zeroHash;
p.writeHash(stop || constants.zeroHash);
assert(locator, 'getblocks requires a locator');
p = new BufferWriter(writer);
p.writeU32(version);
p.writeVarint(locator.length);
for (i = 0; i < locator.length; i++)
p.writeHash(locator[i]);
p.writeHash(stop);
if (!writer)
p = p.render();
@ -430,7 +475,7 @@ Framer.output = function _output(output, writer) {
Framer.witnessTX = function _witnessTX(tx, writer) {
var p = new BufferWriter(writer);
var witnessSize = 0;
var i;
var i, start;
p.write32(tx.version);
p.writeU8(0);
@ -447,7 +492,7 @@ Framer.witnessTX = function _witnessTX(tx, writer) {
Framer.output(tx.outputs[i], p);
for (i = 0; i < tx.inputs.length; i++) {
var start = p.written;
start = p.written;
Framer.witness(tx.inputs[i].witness, p);
witnessSize += p.written - start;
}
@ -588,7 +633,30 @@ Framer.merkleBlock = function _merkleBlock(block, writer) {
return p;
};
Framer.headers = function _headers(block, writer) {
Framer.headers = function _headers(headers, writer) {
var p = new BufferWriter(writer);
var i, header;
p.writeVarint(headers.length);
for (i = 0; i < headers.length; i++) {
header = headers[i];
p.write32(header.version);
p.writeHash(header.prevBlock);
p.writeHash(header.merkleRoot);
p.writeU32(header.ts);
p.writeU32(header.bits);
p.writeU32(header.nonce);
p.writeVarint(header.totalTX);
}
if (!writer)
p = p.render();
return p;
};
Framer.blockHeaders = function blockHeaders(block, writer) {
var p = new BufferWriter(writer);
p.write32(block.version);
@ -597,7 +665,6 @@ Framer.headers = function _headers(block, writer) {
p.writeU32(block.ts);
p.writeU32(block.bits);
p.writeU32(block.nonce);
p.writeVarint(block.totalTX);
if (!writer)
p = p.render();
@ -647,10 +714,6 @@ Framer.addr = function addr(peers, writer) {
return p;
};
Framer.mempool = function mempool() {
return new Buffer([]);
};
Framer.alert = function alert(data, writer) {
var p, i, payload;
@ -694,6 +757,134 @@ Framer.alert = function alert(data, writer) {
return p;
};
Framer.mempool = function mempool() {
return DUMMY;
};
Framer.getAddr = function getAddr() {
return DUMMY;
};
Framer.getUTXOs = function getUTXOs(data, writer) {
var p = new BufferWriter(writer);
var i, prevout;
p.writeU8(data.mempool ? 1 : 0);
p.writeVarint(data.prevout.length);
for (i = 0; i < data.prevout.length; i++) {
prevout = data.prevout[i];
p.writeHash(prevout.hash);
p.writeU32(prevout.index);
}
if (!writer)
p = p.render();
return p;
};
Framer.UTXOs = function UTXOs(data, writer) {
var p = new BufferWriter(writer);
var i, j, coin, height, index, map;
if (!data.map) {
assert(data.notfound);
map = new bn(0);
j = -1;
for (i = 0; i < data.notfound.length; i++) {
index = data.notfound[i];
while (++j < index) {
map.iushln(1);
map.iuorn(1);
}
map.iushln(1);
map.iuorn(0);
}
map = map.toBuffer('be');
} else {
map = data.map;
}
p.writeU32(data.height);
p.writeHash(data.tip);
p.writeVarBytes(map);
p.writeVarInt(data.coins.length);
for (i = 0; i < data.coins.length; i++) {
coin = data.coins[i];
height = coin.height;
if (height === -1)
height = 0x7fffffff;
p.writeU32(coin.version);
p.writeU32(height);
Framer.output(coin, p);
}
if (!writer)
p = p.render();
return p;
};
Framer.submitOrder = function submitOrder(order, writer) {
var p = new BufferWriter(writer);
p.writeHash(order.hash);
Framer.renderTX(order.tx, true, p);
if (!writer)
p = p.render();
return p;
};
Framer.checkOrder = function checkOrder(order, writer) {
return Framer.submitOrder(order, writer);
};
Framer.reply = function reply(data, writer) {
var p = new BufferWriter(writer);
p.writeHash(data.hash);
p.writeU32(data.code || 0);
if (data.publicKey)
p.writeVarBytes(data.publicKey);
else
p.writeVarInt(0);
if (!writer)
p = p.render();
return p;
};
Framer.sendHeaders = function sendHeaders() {
return DUMMY;
};
Framer.haveWitness = function haveWitness() {
return DUMMY;
};
Framer.filterAdd = function filterAdd(data, writer) {
var p = new BufferWriter(writer);
p.writeVarBytes(data.data || data);
if (!writer)
p = p.render();
return p;
};
Framer.filterClear = function filterClear() {
return DUMMY;
};
// Total size and size of witness
Framer.block._sizes = function blockSizes(block) {
var writer = new BufferWriter();

View File

@ -119,44 +119,198 @@ Parser.prototype.parseHeader = function parseHeader(h) {
};
};
Parser.parseMempool = function parseMempool(p) {
return {};
};
Parser.parseSubmitOrder = function parseSubmitOrder(p) {
var hash, tx;
p = new BufferReader(p);
p.start();
return {
hash: p.readHash(),
tx: Parser.parseTX(p),
_size: p.end()
};
};
Parser.parseCheckOrder = function parseCheckOrder(p) {
return Parser.parseSubmitOrder(p);
};
Parser.parseReply = function parseReply(p) {
var hash, code, publicKey;
p = new BufferReader(p);
p.start();
return {
hash: p.readHash(),
code: p.readU32(),
publicKey: p.readVarBytes(),
_size: p.end()
};
};
Parser.parseSendHeaders = function parseSendHeaders(p) {
return {};
};
Parser.parseHaveWitness = function parseHaveWitness(p) {
return {};
};
Parser.parseGetAddr = function parseGetAddr(p) {
return {};
};
Parser.parseFilterLoad = function parseFilterLoad(p) {
return {};
};
Parser.parseFilterAdd = function parseFilterAdd(p) {
p = new BufferReader(p);
p.start();
return {
data: p.readVarBytes(),
_size: p.end()
};
};
Parser.parseFilterClear = function parseFilterClear(p) {
return {};
};
Parser.prototype.parsePayload = function parsePayload(cmd, p) {
if (cmd === 'version')
return Parser.parseVersion(p);
switch (cmd) {
case 'version':
return Parser.parseVersion(p);
case 'verack':
return Parser.parseVerack(p);
case 'mempool':
return Parser.parseMempool(p);
case 'getaddr':
return Parser.parseGetAddr(p);
case 'submitorder':
return Parser.parseSubmitOrder(p);
case 'checkorder':
return Parser.parseCheckOrder(p);
case 'reply':
return Parser.parseReply(p);
case 'sendheaders':
return Parser.parseSendHeaders(p);
case 'havewitness':
return Parser.parseHaveWitness(p);
case 'filterload':
return Parser.parseFilterLoad(p);
case 'filteradd':
return Parser.parseFilterAdd(p);
case 'filterclear':
return Parser.parseFilterClear(p);
case 'inv':
return Parser.parseInv(p);
case 'getdata':
return Parser.parseGetData(p);
case 'notfound':
return Parser.parseNotFound(p);
case 'getheaders':
return Parser.parseGetHeaders(p);
case 'getblocks':
return Parser.parseGetBlocks(p);
case 'merkleblock':
return Parser.parseMerkleBlock(p);
case 'headers':
return Parser.parseHeaders(p);
case 'block':
return Parser.parseBlockCompact(p);
case 'tx':
return Parser.parseTX(p);
case 'reject':
return Parser.parseReject(p);
case 'addr':
return Parser.parseAddr(p);
case 'ping':
return Parser.parsePing(p);
case 'pong':
return Parser.parsePong(p);
case 'alert':
return Parser.parseAlert(p);
case 'getutxos':
return Parser.parseGetUTXOs(p);
case 'utxos':
return Parser.parseUTXOs(p);
default:
utils.debug('Unknown packet: %s', cmd);
return p;
}
};
if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound')
return Parser.parseInvList(p);
Parser.parseGetUTXOs = function parseGetUTXOs(p) {
var mempool, prevout, count, i;
if (cmd === 'merkleblock')
return Parser.parseMerkleBlock(p);
p = new BufferReader(p);
p.start();
if (cmd === 'headers')
return Parser.parseHeaders(p);
mempool = p.readU8() === 1;
prevout = [];
count = p.readVarint();
if (cmd === 'block')
return Parser.parseBlockCompact(p);
for (i = 0; i < count; i++) {
prevout.push({
hash: p.readHash('hex'),
index: p.readU32()
});
}
if (cmd === 'tx')
return Parser.parseTX(p);
return {
mempool: mempool,
prevout: prevout,
_size: p.end()
};
};
if (cmd === 'reject')
return Parser.parseReject(p);
Parser.parseUTXOs = function parseUTXOs(p) {
var chainHeight, tip, map, count, coins;
var coin, version, height, i, notfound, ch, j;
if (cmd === 'addr')
return Parser.parseAddr(p);
p = new BufferReader(p);
p.start();
if (cmd === 'ping')
return Parser.parsePing(p);
chainHeight = p.readU32();
tip = p.readHash('hex');
map = p.readVarBytes();
count = p.readVarint();
coins = [];
notfound = [];
if (cmd === 'pong')
return Parser.parsePong(p);
for (i = 0; i < map.length; i++) {
ch = map[i];
for (j = 0; j < 8; j++) {
if ((ch & 1) === 0)
notfound.push(i + j);
ch >>>= 1;
}
}
if (cmd === 'alert')
return Parser.parseAlert(p);
for (i = 0; i < count; i++) {
version = p.readU32();
height = p.readU32();
if (cmd === 'utxo')
return Parser.parseUTXO(p);
if (height === 0x7fffffff)
height = -1;
return p;
coin = Parser.parseOutput(p);
coin.version = version;
coin.height = height;
coins.push(coin);
}
return {
height: chainHeight,
tip: tip,
map: map,
coins: coins,
notfound: notfound,
_size: p.end()
};
};
Parser.parsePing = function parsePing(p) {
@ -223,7 +377,58 @@ Parser.parseVersion = function parseVersion(p) {
};
};
Parser.parseInvList = function parseInvList(p) {
Parser.parseVerack = function parseVerack(p) {
return {};
};
Parser.parseNotFound = function parseNotFound(p) {
return Parser.parseInv(p);
};
Parser.parseGetData = function parseGetData(p) {
return Parser.parseInv(p);
};
Parser._parseGetBlocks = function _parseGetBlocks(p) {
var version, count, locator, i, stop;
p = new BufferReader(p);
p.start();
version = p.readU32();
count = p.readVarint();
locator = [];
for (i = 0; i < count; i++)
locator.push(p.readHash('hex'));
stop = p.readHash('hex');
if (stop === constants.nullHash)
stop = null;
return {
version: version,
locator: locator,
stop: stop,
_size: p.end()
};
};
Parser.parseGetBlocks = function parseGetBlocks(p) {
var data = Parser._parseGetBlocks(p);
assert(data.locator.length > 0, 'getblocks requires a locator.');
return data;
};
Parser.parseGetHeaders = function parseGetHeaders(p) {
var data = Parser._parseGetBlocks(p);
if (data.locator.length === 0)
data.locator = null;
return data;
};
Parser.parseInv = function parseInv(p) {
var items = [];
var i, count;
@ -308,6 +513,22 @@ Parser.parseHeaders = function parseHeaders(p) {
return headers;
};
Parser.parseBlockHeaders = function parseBlockHeaders(p) {
p = new BufferReader(p);
p.start();
return {
version: p.readU32(), // Technically signed
prevBlock: p.readHash('hex'),
merkleRoot: p.readHash('hex'),
ts: p.readU32(),
bits: p.readU32(),
nonce: p.readU32(),
totalTX: p.readVarint(),
_size: p.end()
}
};
Parser.parseBlock = function parseBlock(p) {
var txs = [];
var witnessSize = 0;

View File

@ -587,6 +587,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
continue;
}
if (op === opcodes.OP_1NEGATE) {
stack.push(STACK_NEGATE);
continue;
}
switch (op) {
case opcodes.OP_NOP:
case opcodes.OP_NOP1:
@ -601,10 +606,6 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
throw new ScriptError('Upgradable NOP used.', op, ip);
break;
}
case opcodes.OP_1NEGATE: {
stack.push(STACK_NEGATE);
break;
}
case opcodes.OP_IF:
case opcodes.OP_NOTIF: {
if (stack.length < 1)
@ -1047,13 +1048,13 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
if (stack.length === 0)
throw new ScriptError('Stack too small.', op, ip);
// NOTE: Bitcoind accepts 5 byte locktimes.
// 4 byte locktimes become useless in 2106.
locktime = Script.num(stack.top(-1), flags, 5).toNumber();
locktime = Script.num(stack.top(-1), flags, 5);
if (locktime < 0)
if (locktime.cmpn(0) < 0)
throw new ScriptError('Negative locktime.', op, ip);
locktime = locktime.uand(utils.U32).toNumber();
if (!Script.checkLocktime(locktime, tx, index))
throw new ScriptError('Locktime verification failed.', op, ip);
@ -1073,14 +1074,13 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
if (stack.length === 0)
throw new ScriptError('Stack too small.', op, ip);
// NOTE: Bitcoind accepts 5 byte locktimes.
// 4 byte locktimes become useless in 2106
// (will people still be using bcoin then?).
locktime = Script.num(stack.top(-1), flags, 4).toNumber();
locktime = Script.num(stack.top(-1), flags, 5);
if (locktime < 0)
if (locktime.cmpn(0) < 0)
throw new ScriptError('Negative sequence.', op, ip);
locktime = locktime.uand(utils.U32).toNumber();
if ((locktime & constants.sequenceLocktimeDisableFlag) !== 0)
break;
@ -1153,14 +1153,6 @@ Script.checkSequence = function checkSequence(sequence, tx, i) {
Script.bool = function bool(value) {
var i;
// Should never happen:
// if (typeof value === 'boolean')
// return value;
// Should never happen:
// if (utils.isFinite(value))
// return value !== 0;
if (bn.isBN(value))
return value.cmpn(0) !== 0;
@ -1210,10 +1202,10 @@ Script.num = function num(value, flags, size) {
// negative flag.
if (value[value.length - 1] & 0x80) {
if (utils.isNegZero(value, 'le')) {
value = new bn(0, 'le');
value = new bn(0);
} else {
value = new bn(value, 'le');
value = value.notn(value.bitLength()).addn(1).neg();
value = value.notn(size * 8).addn(1).neg();
}
} else {
value = new bn(value, 'le');
@ -1237,7 +1229,7 @@ Script.array = function(value) {
if (value.cmpn(0) === 0)
value = new bn(0);
else
value = value.neg().notn(value.bitLength()).subn(1);
value = value.neg().notn(value.byteLength() * 8).subn(1);
}
if (value.cmpn(0) === 0)
@ -1349,6 +1341,7 @@ Script.createMultisig = function createMultisig(keys, m, n) {
};
Script.createScripthash = function createScripthash(hash) {
assert(hash.length === 20);
return new Script([
opcodes.OP_HASH160,
hash,
@ -1357,6 +1350,8 @@ Script.createScripthash = function createScripthash(hash) {
};
Script.createNulldata = function createNulldata(flags) {
assert(Buffer.isBuffer(flags));
assert(flags.length <= constants.script.maxOpReturn, 'Nulldata too large.');
return new Script([
opcodes.OP_RETURN,
flags
@ -1366,11 +1361,12 @@ Script.createNulldata = function createNulldata(flags) {
Script.createWitnessProgram = function createWitnessProgram(version, data) {
assert(typeof version === 'number' && version >= 0 && version <= 16);
assert(Buffer.isBuffer(data));
assert(data.length === 20 || data.length === 32);
assert(data.length >= 2 && data.length <= 32);
return new Script([version === 0 ? 0 : version + 0x50, data]);
};
Script.createCommitment = function createCommitment(hash) {
assert(hash.length === 32);
return new Script([
opcodes.OP_RETURN,
Buffer.concat([new Buffer([0xaa, 0x21, 0xa9, 0xed]), hash])
@ -1859,8 +1855,6 @@ Script.createOutputScript = function(options) {
flags = options.flags;
if (typeof flags === 'string')
flags = new Buffer(flags, 'utf8');
assert(Buffer.isBuffer(flags));
assert(flags.length <= constants.script.maxOpReturn, 'Nulldata too large.');
return Script.createNulldata(flags);
}
@ -1875,9 +1869,6 @@ Script.createOutputScript = function(options) {
} else if (options.keys) {
m = options.m;
n = options.n || options.keys.length;
assert(m >= 1 && m <= n, 'm must be between 1 and n');
assert(n >= 1 && n <= (options.scriptHash ? 15 : 3),
'n must be between 1 and 15');
script = Script.createMultisig(options.keys, m, n);
} else if (Buffer.isBuffer(options.scriptHash)) {
if (options.version != null) {

View File

@ -45,6 +45,8 @@ SPVNode.prototype._init = function _init() {
// Pool needs access to the chain.
this.pool = new bcoin.pool(this, {
witness: this.network.witness,
selfish: true,
listen: false,
spv: true
});

View File

@ -59,7 +59,7 @@ function TX(data, block, index) {
if (block && this.ts === 0) {
if (block.type === 'merkleblock') {
if (block.hasTX(this.hash('hex')))
if (block.hasTX(this))
this.setBlock(block, index);
} else {
this.setBlock(block, index);
@ -118,6 +118,15 @@ TX.prototype.setBlock = function setBlock(block, index) {
this.block = block.hash('hex');
this.height = block.height;
this.index = index;
this.ps = 0;
};
TX.prototype.unsetBlock = function unsetBlock() {
this.ts = 0;
this.block = null;
this.height = -1;
this.index = -1;
this.ps = utils.now();
};
TX.prototype.hash = function hash(enc) {
@ -702,7 +711,8 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
if (prev && prev.isWitnessScripthash()) {
prev = input.witness.getRedeem();
cost += prev.getSigops(true);
if (prev)
cost += prev.getSigops(true);
} else {
cost += 0;
}

View File

@ -1444,21 +1444,6 @@ utils.cmp = function cmp(a, b) {
return 0;
};
utils.rcmp = function rcmp(a, b) {
var i;
assert(a.length === b.length);
for (i = a.length - 1; i >= 0; i--) {
if (a[i] < b[i])
return -1;
if (a[i] > b[i])
return 1;
}
return 0;
};
// memcmp in constant time (can only return true or false)
// https://cryptocoding.net/index.php/Coding_rules
// $ man 3 memcmp (see NetBSD's consttime_memequal)