compact block relay support.
This commit is contained in:
parent
a85f3edde5
commit
95067ba938
1
bin/node
1
bin/node
@ -17,6 +17,7 @@ var node = new bcoin.fullnode({
|
||||
logFile: true,
|
||||
db: 'leveldb',
|
||||
prune: process.argv.indexOf('--prune') !== -1,
|
||||
compact: process.argv.indexOf('--compact') !== -1,
|
||||
useCheckpoints: process.argv.indexOf('--checkpoints') !== -1,
|
||||
selfish: process.argv.indexOf('--selfish') !== -1,
|
||||
headers: process.argv.indexOf('--headers') !== -1,
|
||||
|
||||
@ -39,6 +39,7 @@ function CompactBlock(options) {
|
||||
this.count = 0;
|
||||
this.k0 = null;
|
||||
this.k1 = null;
|
||||
this.timeout = null;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
@ -126,7 +127,7 @@ CompactBlock.fromRaw = function fromRaw(data, enc) {
|
||||
return new CompactBlock().fromRaw(data);
|
||||
};
|
||||
|
||||
CompactBlock.prototype.toRaw = function toRaw(writer) {
|
||||
CompactBlock.prototype.toRaw = function toRaw(witness, writer) {
|
||||
var p = bcoin.writer(writer);
|
||||
var i, id, lo, hi, ptx;
|
||||
|
||||
@ -155,7 +156,10 @@ CompactBlock.prototype.toRaw = function toRaw(writer) {
|
||||
for (i = 0; i < this.ptx.length; i++) {
|
||||
ptx = this.ptx[i];
|
||||
p.writeVarint2(ptx[0]);
|
||||
ptx[1].toRaw(p);
|
||||
if (witness)
|
||||
ptx[1].toRaw(p);
|
||||
else
|
||||
ptx[1].toNormal(p);
|
||||
}
|
||||
|
||||
if (!writer)
|
||||
@ -353,6 +357,18 @@ CompactBlock.fromBlock = function fromBlock(block, nonce) {
|
||||
return new CompactBlock().fromBlock(block, nonce);
|
||||
};
|
||||
|
||||
CompactBlock.prototype.startTimeout = function startTimeout(time, callback) {
|
||||
assert(this.timeout == null);
|
||||
this.timeout = setTimeout(callback, time);
|
||||
};
|
||||
|
||||
CompactBlock.prototype.stopTimeout = function stopTimeout() {
|
||||
if (this.timeout != null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a BlockTransactionsRequest (bip152): `getblocktxn` packet.
|
||||
* @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
|
||||
@ -527,10 +543,10 @@ BlockTX.prototype.toRaw = function toRaw(witness, writer) {
|
||||
|
||||
for (i = 0; i < this.txs.length; i++) {
|
||||
tx = this.txs[i];
|
||||
if (!witness)
|
||||
tx.toNormal(p);
|
||||
else
|
||||
if (witness)
|
||||
tx.toRaw(p);
|
||||
else
|
||||
tx.toNormal(p);
|
||||
}
|
||||
|
||||
if (!writer)
|
||||
|
||||
@ -148,6 +148,8 @@ function Environment() {
|
||||
this.mtx = require('./mtx');
|
||||
this.txdb = require('./txdb');
|
||||
this.abstractblock = require('./abstractblock');
|
||||
this.bip151 = require('./bip151');
|
||||
this.bip152 = require('./bip152');
|
||||
this.memblock = require('./memblock');
|
||||
this.block = require('./block');
|
||||
this.merkleblock = require('./merkleblock');
|
||||
|
||||
@ -95,6 +95,7 @@ function Fullnode(options) {
|
||||
witness: this.network.witness,
|
||||
selfish: this.options.selfish,
|
||||
headers: this.options.headers,
|
||||
compact: this.options.compact,
|
||||
proxyServer: this.options.proxyServer,
|
||||
preferredSeed: this.options.preferredSeed,
|
||||
spv: false
|
||||
|
||||
@ -1148,7 +1148,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx) {
|
||||
var prevout = {};
|
||||
var i, hash, input, prev;
|
||||
|
||||
if (tx.getSize() > 5000) {
|
||||
if (tx.getSize() > 99999) {
|
||||
this.logger.debug('Ignoring large orphan: %s', tx.rhash);
|
||||
this.emit('bad orphan', tx);
|
||||
return;
|
||||
|
||||
@ -106,6 +106,9 @@ function Peer(pool, options) {
|
||||
this.syncSent = false;
|
||||
this.loader = options.loader || false;
|
||||
this._connectTimeout = null;
|
||||
this.compactMode = null;
|
||||
this.compactBlocks = {};
|
||||
this.sentAddr = false;
|
||||
|
||||
this.challenge = null;
|
||||
this.lastPong = -1;
|
||||
@ -266,6 +269,10 @@ Peer.prototype._onConnect = function _onConnect() {
|
||||
self.write(self.framer.haveWitness());
|
||||
}
|
||||
|
||||
// We want compact blocks!
|
||||
if (self.options.compact && self.version.hasCompact())
|
||||
self.sendCompact();
|
||||
|
||||
// Find some more peers.
|
||||
self.write(self.framer.getAddr());
|
||||
|
||||
@ -697,8 +704,17 @@ Peer.prototype.getData = function getData(items) {
|
||||
|
||||
for (i = 0; i < items.length; i++) {
|
||||
item = items[i];
|
||||
|
||||
if (item.toInv)
|
||||
item = item.toInv();
|
||||
|
||||
if (this.options.compact
|
||||
&& this.compactMode
|
||||
&& item.isBlock()
|
||||
&& !item.hasWitness()) {
|
||||
item.type = constants.inv.CMPCT_BLOCK;
|
||||
}
|
||||
|
||||
data[i] = item;
|
||||
}
|
||||
|
||||
@ -792,6 +808,18 @@ Peer.prototype._onPacket = function onPacket(packet) {
|
||||
case 'notfound':
|
||||
this.fire(cmd, payload);
|
||||
break;
|
||||
case 'encinit':
|
||||
return this._handleEncinit(payload);
|
||||
case 'encack':
|
||||
return this._handleEncack(payload);
|
||||
case 'sendcmpct':
|
||||
return this._handleSendCmpct(payload);
|
||||
case 'cmpctblock':
|
||||
return this._handleCmpctBlock(payload);
|
||||
case 'getblocktxn':
|
||||
return this._handleGetBlockTxn(payload);
|
||||
case 'blocktxn':
|
||||
return this._handleBlockTxn(payload);
|
||||
default:
|
||||
this.logger.warning('Unknown packet: %s.', cmd);
|
||||
this.fire(cmd, payload);
|
||||
@ -1341,6 +1369,48 @@ Peer.prototype._handleMempool = function _handleMempool() {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a block/tx either from the broadcast map, mempool, or blockchain.
|
||||
* @param {InvItem} item
|
||||
* @param {Function} callback - Returns
|
||||
* [Error, {@link Block}|{@link MempoolEntry}].
|
||||
*/
|
||||
|
||||
Peer.prototype._getItem = function _getItem(item, callback) {
|
||||
var entry = this.pool.inv.map[item.hash];
|
||||
|
||||
if (entry) {
|
||||
this.logger.debug(
|
||||
'Peer requested %s %s as a %s packet (%s).',
|
||||
entry.type === constants.inv.TX ? 'tx' : 'block',
|
||||
utils.revHex(entry.hash),
|
||||
item.hasWitness() ? 'witness' : 'normal',
|
||||
this.hostname);
|
||||
|
||||
entry.ack(peer);
|
||||
|
||||
if (entry.msg)
|
||||
return callback(null, entry.msg);
|
||||
}
|
||||
|
||||
if (this.options.selfish)
|
||||
return callback();
|
||||
|
||||
if (item.isTX()) {
|
||||
if (!this.mempool)
|
||||
return callback();
|
||||
return this.mempool.getEntry(item.hash, callback);
|
||||
}
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
return callback();
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
return callback();
|
||||
|
||||
return this.chain.db.getBlock(item.hash, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `getdata` packet.
|
||||
* @private
|
||||
@ -1349,9 +1419,7 @@ Peer.prototype._handleMempool = function _handleMempool() {
|
||||
|
||||
Peer.prototype._handleGetData = function _handleGetData(items) {
|
||||
var self = this;
|
||||
var check = [];
|
||||
var notfound = [];
|
||||
var i, item, entry, witness;
|
||||
var notFound = [];
|
||||
|
||||
var unlock = this.locker.lock(_handleGetData, [items]);
|
||||
if (!unlock)
|
||||
@ -1370,68 +1438,31 @@ Peer.prototype._handleGetData = function _handleGetData(items) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// Hit the broadcast queue first.
|
||||
for (i = 0; i < items.length; i++) {
|
||||
item = items[i];
|
||||
entry = this.pool.inv.map[item.hash];
|
||||
witness = (item.type & constants.WITNESS_MASK) !== 0;
|
||||
utils.forEachSerial(items, function(item, next) {
|
||||
var i, tx, block;
|
||||
|
||||
// If the item isn't present, queue it up
|
||||
// to be checked in the mempool or chain.
|
||||
if (!entry) {
|
||||
check.push(item);
|
||||
continue;
|
||||
}
|
||||
self._getItem(item, function(err, entry) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if ((item.type & ~constants.WITNESS_MASK) !== entry.type) {
|
||||
self.logger.debug(
|
||||
'Peer requested an existing item with the wrong type (%s).',
|
||||
this.hostname);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
'Peer requested %s %s as a %s packet (%s).',
|
||||
entry.type === constants.inv.TX ? 'tx' : 'block',
|
||||
utils.revHex(entry.hash),
|
||||
witness ? 'witness' : 'normal',
|
||||
this.hostname);
|
||||
|
||||
// Try to send the data.
|
||||
// If the item was announce-only (i.e. the data
|
||||
// isn't actually contained in the broadcast
|
||||
// item), check this on the mempool/chain.
|
||||
if (!entry.send(this, witness))
|
||||
check.push(item);
|
||||
}
|
||||
|
||||
if (this.pool.options.selfish)
|
||||
return done();
|
||||
|
||||
// Item wasn't found in broadcast queue.
|
||||
// Check the mempool and chain.
|
||||
utils.forEachSerial(check, function(item, next) {
|
||||
var type = item.type & ~constants.WITNESS_MASK;
|
||||
var witness = (item.type & constants.WITNESS_MASK) !== 0;
|
||||
var hash = item.hash;
|
||||
var i, tx, data;
|
||||
|
||||
if (type === constants.inv.TX) {
|
||||
if (!self.mempool) {
|
||||
notfound.push(new InvItem(constants.inv.TX, hash));
|
||||
if (!entry) {
|
||||
notFound.push(item);
|
||||
return next();
|
||||
}
|
||||
return self.mempool.getEntry(hash, function(err, entry) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!entry) {
|
||||
notfound.push(new InvItem(constants.inv.TX, hash));
|
||||
if (item.isTX()) {
|
||||
tx = entry.tx;
|
||||
|
||||
// Coinbases are an insta-ban from any node.
|
||||
// This should technically never happen, but
|
||||
// it's worth keeping here just in case. A
|
||||
// 24-hour ban from any node is rough.
|
||||
if (tx.isCoinbase()) {
|
||||
notFound.push(item);
|
||||
self.logger.warning('Failsafe: tried to relay a coinbase.');
|
||||
return next();
|
||||
}
|
||||
|
||||
tx = entry.tx;
|
||||
|
||||
// We should technically calculate this in
|
||||
// the `mempool` handler, but it would be
|
||||
// too slow.
|
||||
@ -1440,105 +1471,93 @@ Peer.prototype._handleGetData = function _handleGetData(items) {
|
||||
return next();
|
||||
}
|
||||
|
||||
data = witness
|
||||
? self.framer.witnessTX(tx)
|
||||
: self.framer.tx(tx);
|
||||
if (item.hasWitness())
|
||||
self.write(self.framer.witnessTX(tx));
|
||||
else
|
||||
self.write(self.framer.tx(tx));
|
||||
|
||||
self.write(data);
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
if (type === constants.inv.BLOCK) {
|
||||
if (self.chain.db.options.spv) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
return next();
|
||||
}
|
||||
if (self.chain.db.options.prune) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
return next();
|
||||
}
|
||||
return self.chain.db.getBlock(hash, function(err, block) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!block) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
block = entry;
|
||||
|
||||
switch (item.type) {
|
||||
case constants.inv.BLOCK:
|
||||
case constants.inv.WITNESS_BLOCK:
|
||||
if (item.hasWitness())
|
||||
self.write(self.framer.witnessBlock(block));
|
||||
else
|
||||
self.write(self.framer.block(block));
|
||||
break;
|
||||
case constants.inv.FILTERED_BLOCK:
|
||||
case constants.inv.WITNESS_FILTERED_BLOCK:
|
||||
if (!self.spvFilter) {
|
||||
notFound.push(item);
|
||||
return next();
|
||||
}
|
||||
|
||||
block = block.toMerkle(self.spvFilter);
|
||||
|
||||
self.write(self.framer.merkleBlock(block));
|
||||
|
||||
for (i = 0; i < block.txs.length; i++) {
|
||||
tx = block.txs[i];
|
||||
|
||||
if (item.hasWitness())
|
||||
self.write(self.framer.witnessTX(tx));
|
||||
else
|
||||
self.write(self.framer.tx(tx));
|
||||
}
|
||||
|
||||
break;
|
||||
case constants.inv.CMPCT_BLOCK:
|
||||
// Fallback to full block.
|
||||
if (block.height < self.chain.tip.height - 10) {
|
||||
self.write(self.framer.block(block));
|
||||
break;
|
||||
}
|
||||
|
||||
// Try again with a new nonce
|
||||
// if we get a siphash collision.
|
||||
for (;;) {
|
||||
try {
|
||||
block = bcoin.bip152.CompactBlock.fromBlock(block);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
self.write(self.framer.cmpctBlock(block));
|
||||
break;
|
||||
default:
|
||||
self.logger.warning(
|
||||
'Peer sent an unknown getdata type: %s (%s).',
|
||||
item.type,
|
||||
self.hostname);
|
||||
notFound.push(item);
|
||||
return next();
|
||||
}
|
||||
|
||||
data = witness
|
||||
? self.framer.witnessBlock(block)
|
||||
: self.framer.block(block);
|
||||
|
||||
self.write(data);
|
||||
|
||||
if (hash === self.hashContinue) {
|
||||
self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash));
|
||||
self.hashContinue = null;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
if (type === constants.inv.FILTERED_BLOCK) {
|
||||
if (self.chain.db.options.spv) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
return next();
|
||||
}
|
||||
if (self.chain.db.options.prune) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
return next();
|
||||
|
||||
if (item.hash === self.hashContinue) {
|
||||
self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash));
|
||||
self.hashContinue = null;
|
||||
}
|
||||
return self.chain.db.getBlock(hash, function(err, block) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!block) {
|
||||
notfound.push(new InvItem(constants.inv.BLOCK, hash));
|
||||
return next();
|
||||
}
|
||||
|
||||
block = block.toMerkle(self.spvFilter);
|
||||
|
||||
self.write(self.framer.merkleBlock(block));
|
||||
|
||||
for (i = 0; i < block.txs.length; i++) {
|
||||
tx = block.txs[i];
|
||||
|
||||
tx = witness
|
||||
? self.framer.witnessTX(tx)
|
||||
: self.framer.tx(tx);
|
||||
|
||||
self.write(tx);
|
||||
}
|
||||
|
||||
if (hash === self.hashContinue) {
|
||||
self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash));
|
||||
self.hashContinue = null;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
notfound.push(new InvItem(type, hash));
|
||||
|
||||
return next();
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
return done(err);
|
||||
|
||||
self.logger.debug(
|
||||
'Served %d items with getdata (notfound=%d) (%s).',
|
||||
items.length - notfound.length,
|
||||
notfound.length,
|
||||
items.length - notFound.length,
|
||||
notFound.length,
|
||||
self.hostname);
|
||||
|
||||
if (notfound.length > 0)
|
||||
self.write(self.framer.notFound(notfound));
|
||||
if (notFound.length > 0)
|
||||
self.write(self.framer.notFound(notFound));
|
||||
|
||||
done();
|
||||
});
|
||||
@ -1628,6 +1647,13 @@ Peer.prototype._handleGetAddr = function _handleGetAddr() {
|
||||
if (this.pool.options.selfish)
|
||||
return;
|
||||
|
||||
if (this.sentAddr) {
|
||||
this.logger.debug('Ignoring repeated getaddr (%s).', this.hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sentAddr = true;
|
||||
|
||||
for (i = 0; i < this.pool.hosts.length; i++) {
|
||||
host = this.pool.hosts[i];
|
||||
|
||||
@ -1746,6 +1772,188 @@ Peer.prototype._handleAlert = function _handleAlert(alert) {
|
||||
this.fire('alert', alert);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `encinit` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleEncinit = function _handleEncinit(payload) {
|
||||
this.fire('encinit', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `encack` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleEncack = function _handleEncack(payload) {
|
||||
this.fire('encack', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `sendcmpct` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleSendCmpct = function _handleSendCmpct(payload) {
|
||||
if (payload.version !== 1) {
|
||||
// Ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.mode !== 0) {
|
||||
// Ignore (we can't do mode 1 yet).
|
||||
return;
|
||||
}
|
||||
|
||||
this.compactMode = payload;
|
||||
this.fire('sendcmpct', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `cmpctblock` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleCmpctBlock = function _handleCmpctBlock(block) {
|
||||
var self = this;
|
||||
var hash = block.hash('hex');
|
||||
|
||||
function done(err) {
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
return;
|
||||
}
|
||||
self.fire('cmpctblock', block);
|
||||
}
|
||||
|
||||
if (!this.options.compact) {
|
||||
this.logger.info('Peer sent unsolicited cmpctblock (%s).', this.hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.mempool) {
|
||||
this.logger.warning('Requesting compact blocks without a mempool!');
|
||||
return done();
|
||||
}
|
||||
|
||||
if (this.compactBlocks[hash]) {
|
||||
this.logger.debug(
|
||||
'Peer sent us a duplicate compact block (%s).',
|
||||
this.hostname);
|
||||
return done();
|
||||
}
|
||||
|
||||
// Sort of a lock too.
|
||||
this.compactBlocks[hash] = block;
|
||||
|
||||
block.fillMempool(this.mempool, function(err, result) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
if (result) {
|
||||
delete self.compactBlocks[hash];
|
||||
self.emit('block', block.toBlock());
|
||||
return;
|
||||
}
|
||||
|
||||
self.write(self.framer.getBlockTxn(block.toRequest()));
|
||||
|
||||
block.startTimeout(10000, function() {
|
||||
self.logger.debug(
|
||||
'Compact block timed out: %s (%s).',
|
||||
block.rhash, self.hostname);
|
||||
delete self.compactBlocks[hash];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `getblocktxn` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleGetBlockTxn = function _handleGetBlockTxn(payload) {
|
||||
var self = this;
|
||||
var blockTX;
|
||||
|
||||
function done(err) {
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
return;
|
||||
}
|
||||
self.fire('blocktxn', payload);
|
||||
}
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
return done();
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
return done();
|
||||
|
||||
if (this.options.selfish)
|
||||
return done();
|
||||
|
||||
this.chain.db.getBlock(payload.hash, function(err, block) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
if (!block) {
|
||||
self.logger.info(
|
||||
'Peer sent getblocktxn for non-existent block (%s).',
|
||||
self.hostname);
|
||||
self.setMisbehavior(100);
|
||||
return done();
|
||||
}
|
||||
|
||||
if (block.height < self.chain.tip.height - 15) {
|
||||
self.logger.info(
|
||||
'Peer sent a getblocktxn for a block > 15 deep (%s)',
|
||||
self.hostname);
|
||||
return done();
|
||||
}
|
||||
|
||||
blockTX = bcoin.bip152.BlockTX.fromBlock(block, payload);
|
||||
|
||||
self.write(self.framer.blockTxn(blockTX));
|
||||
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle `blocktxn` packet.
|
||||
* @private
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
Peer.prototype._handleBlockTxn = function _handleBlockTxn(payload) {
|
||||
var block = this.compactBlocks[payload.hash];
|
||||
|
||||
if (!block) {
|
||||
this.logger.info('Peer sent unsolicited blocktxn (%s).', this.hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
this.fire('getblocktxn', payload);
|
||||
|
||||
block.stopTimeout();
|
||||
delete this.compactBlocks[payload.hash];
|
||||
|
||||
if (!block.fillMissing(payload.txs)) {
|
||||
this.setMisbehavior(100);
|
||||
this.logger.info('Peer sent non-full blocktxn (%s).', this.hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('block', block.toBlock());
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an `alert` to peer.
|
||||
* @param {AlertPacket} alert
|
||||
@ -1836,6 +2044,15 @@ Peer.prototype.sendReject = function sendReject(code, reason, obj) {
|
||||
this.write(this.framer.reject(reject));
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a `sendcmpct` packet.
|
||||
*/
|
||||
|
||||
Peer.prototype.sendCompact = function sendCompact() {
|
||||
var cmpct = new bcoin.bip152.SendCompact(0, 1);
|
||||
this.write(this.framer.sendCmpct(cmpct));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the peer is misbehaving (banScore >= 100).
|
||||
* @returns {Boolean}
|
||||
|
||||
@ -572,7 +572,8 @@ Pool.prototype._addLoader = function _addLoader() {
|
||||
loader: true,
|
||||
network: true,
|
||||
spv: this.options.spv,
|
||||
witness: this.options.witness
|
||||
witness: this.options.witness,
|
||||
compact: this.options.compact
|
||||
});
|
||||
|
||||
this.logger.info('Added loader peer (%s).', peer.hostname);
|
||||
@ -973,7 +974,8 @@ Pool.prototype._createPeer = function _createPeer(options) {
|
||||
network: options.network,
|
||||
spv: options.spv,
|
||||
witness: options.witness,
|
||||
headers: this.options.headers
|
||||
headers: this.options.headers,
|
||||
compact: this.options.compact
|
||||
});
|
||||
|
||||
peer.once('close', function() {
|
||||
@ -1275,7 +1277,8 @@ Pool.prototype._addLeech = function _addLeech(socket) {
|
||||
socket: socket,
|
||||
network: false,
|
||||
spv: false,
|
||||
witness: false
|
||||
witness: false,
|
||||
compact: false
|
||||
});
|
||||
|
||||
this.logger.info('Added leech peer (%s).', peer.hostname);
|
||||
@ -1316,7 +1319,8 @@ Pool.prototype._addPeer = function _addPeer() {
|
||||
host: host,
|
||||
network: true,
|
||||
spv: this.options.spv,
|
||||
witness: this.options.witness
|
||||
witness: this.options.witness,
|
||||
compact: this.options.compact
|
||||
});
|
||||
|
||||
this.peers.pending.push(peer);
|
||||
@ -2046,7 +2050,7 @@ LoadRequest.prototype.destroy = function destroy() {
|
||||
*/
|
||||
|
||||
LoadRequest.prototype._onTimeout = function _onTimeout() {
|
||||
if (this.type === this.pool.block.type
|
||||
if (this.type !== this.pool.tx.type
|
||||
&& this.peer === this.pool.peers.load) {
|
||||
this.pool.logger.debug(
|
||||
'Loader took too long serving a block. Finding a new one.');
|
||||
@ -2267,47 +2271,6 @@ BroadcastItem.prototype.finish = function finish(err) {
|
||||
this.callback.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the item to a peer.
|
||||
* @param {Peer} peer
|
||||
* @param {Boolean} witness - Whether to use the witness serialization.
|
||||
* @returns {Boolean} Whether the data is available to be sent.
|
||||
*/
|
||||
|
||||
BroadcastItem.prototype.send = function send(peer, witness) {
|
||||
var data;
|
||||
|
||||
if (!this.msg) {
|
||||
this.ack(peer);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.type === constants.inv.TX) {
|
||||
// Failsafe - we never want to relay coinbases.
|
||||
// They are an insta-ban from any bitcoind node.
|
||||
if (this.msg.isCoinbase()) {
|
||||
peer.write(peer.framer.notFound([this.toInv()]));
|
||||
this.pool.logger.warning('Failsafe: tried to relay a coinbase.');
|
||||
this.finish(new Error('Coinbase.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
data = witness
|
||||
? peer.framer.witnessTX(this.msg)
|
||||
: peer.framer.tx(this.msg);
|
||||
} else {
|
||||
data = witness
|
||||
? peer.framer.witnessBlock(this.msg)
|
||||
: peer.framer.block(this.msg);
|
||||
}
|
||||
|
||||
peer.write(data);
|
||||
|
||||
this.ack(peer);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle an ack from a peer.
|
||||
* @param {Peer} peer
|
||||
|
||||
@ -27,7 +27,7 @@ exports.MIN_VERSION = 70001;
|
||||
* @default
|
||||
*/
|
||||
|
||||
exports.VERSION = 70012;
|
||||
exports.VERSION = 70014;
|
||||
|
||||
/**
|
||||
* Max message size (~4mb with segwit, formerly 2mb)
|
||||
|
||||
@ -404,6 +404,66 @@ Framer.prototype.feeFilter = function feeFilter(options) {
|
||||
return this.packet('feefilter', Framer.feeFilter(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an encinit packet with a header.
|
||||
* @param {Object} options - See {@link Framer.encinit}.
|
||||
* @returns {Buffer} encinit packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.encinit = function encinit(options) {
|
||||
return this.packet('encinit', Framer.encinit(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an encack packet with a header.
|
||||
* @param {Object} options - See {@link Framer.encack}.
|
||||
* @returns {Buffer} encack packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.encack = function encack(options) {
|
||||
return this.packet('encack', Framer.encack(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sendcmpct packet with a header.
|
||||
* @param {Object} options - See {@link Framer.sendCmpct}.
|
||||
* @returns {Buffer} sendCmpct packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.sendCmpct = function sendCmpct(options) {
|
||||
return this.packet('sendcmpct', Framer.sendCmpct(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a cmpctblock packet with a header.
|
||||
* @param {Object} options - See {@link Framer.cmpctBlock}.
|
||||
* @returns {Buffer} cmpctBlock packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.cmpctBlock = function cmpctBlock(options) {
|
||||
return this.packet('cmpctblock', Framer.cmpctBlock(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a getblocktxn packet with a header.
|
||||
* @param {Object} options - See {@link Framer.getBlockTxn}.
|
||||
* @returns {Buffer} getBlockTxn packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.getBlockTxn = function getBlockTxn(options) {
|
||||
return this.packet('getblocktxn', Framer.getBlockTxn(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a blocktxn packet with a header.
|
||||
* @param {Object} options - See {@link Framer.blockTxn}.
|
||||
* @returns {Buffer} blockTxn packet.
|
||||
*/
|
||||
|
||||
Framer.prototype.blockTxn = function blockTxn(options) {
|
||||
return this.packet('blocktxn', Framer.blockTxn(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a version packet (without a header).
|
||||
* @param {VersionPacket} options
|
||||
@ -868,6 +928,72 @@ Framer.feeFilter = function feeFilter(data, writer) {
|
||||
return p;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an encinit packet (without a header).
|
||||
* @param {BIP151} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.encinit = function encinit(data, writer) {
|
||||
return data.toEncinit(writer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an encinit packet (without a header).
|
||||
* @param {BIP151} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.encack = function encack(data, writer) {
|
||||
return data.toEncack(writer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sendcmpct packet (without a header).
|
||||
* @param {SendCompact} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.sendCmpct = function sendCmpct(data, writer) {
|
||||
return data.toRaw(writer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a cmpctblock packet (without a header).
|
||||
* @param {CompactBlock} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.cmpctBlock = function cmpctBlock(data, writer) {
|
||||
return data.toRaw(false, writer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a getblocktxn packet (without a header).
|
||||
* @param {BlockTXRequest} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.getBlockTxn = function getBlockTxn(data, writer) {
|
||||
return data.toRaw(writer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a blocktxn packet (without a header).
|
||||
* @param {BlockTX} data
|
||||
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
|
||||
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
|
||||
*/
|
||||
|
||||
Framer.blockTxn = function blockTxn(data, writer) {
|
||||
return data.toRaw(false, writer);
|
||||
};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
@ -176,6 +176,15 @@ VersionPacket.prototype.hasHeaders = function hasHeaders() {
|
||||
return this.version >= 31800;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the protocol version supports bip152.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
VersionPacket.prototype.hasCompact = function hasCompact() {
|
||||
return this.version >= 70014;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from serialized data.
|
||||
* @private
|
||||
@ -285,12 +294,45 @@ InvItem.fromRaw = function fromRaw(data, enc) {
|
||||
return new InvItem().fromRaw(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the inv item is a block.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
InvItem.prototype.isBlock = function isBlock() {
|
||||
switch (this.type) {
|
||||
case constants.inv.BLOCK:
|
||||
case constants.inv.WITNESS_BLOCK:
|
||||
case constants.inv.FILTERED_BLOCK:
|
||||
case constants.inv.WITNESS_FILTERED_BLOCK:
|
||||
case constants.inv.CMPCT_BLOCK:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the inv item is a tx.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
InvItem.prototype.isTX = function isTX() {
|
||||
switch (this.type) {
|
||||
case constants.inv.TX:
|
||||
case constants.inv.WITNESS_TX:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the inv item has the witness bit set.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
InvItem.prototype.isWitness = function isWitness() {
|
||||
InvItem.prototype.hasWitness = function hasWitness() {
|
||||
return (this.type & constants.WITNESS_MASK) !== 0;
|
||||
};
|
||||
|
||||
|
||||
@ -343,6 +343,18 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) {
|
||||
return Parser.parseUTXOs(p);
|
||||
case 'feefilter':
|
||||
return Parser.parseFeeFilter(p);
|
||||
case 'encinit':
|
||||
return Parser.parseEncinit(p);
|
||||
case 'encack':
|
||||
return Parser.parseEncack(p);
|
||||
case 'sendcmpct':
|
||||
return Parser.parseSendCmpct(p);
|
||||
case 'cmpctblock':
|
||||
return Parser.parseCmpctBlock(p);
|
||||
case 'getblocktxn':
|
||||
return Parser.parseGetBlockTxn(p);
|
||||
case 'blocktxn':
|
||||
return Parser.parseBlockTxn(p);
|
||||
default:
|
||||
return p;
|
||||
}
|
||||
@ -654,6 +666,32 @@ Parser.parseFeeFilter = function parseFeeFilter(p) {
|
||||
};
|
||||
};
|
||||
|
||||
Parser.parseEncinit = function parseEncinit(p) {
|
||||
// Handled elsewhere.
|
||||
return p;
|
||||
};
|
||||
|
||||
Parser.parseEncack = function parseEncack(p) {
|
||||
// Handled elsewhere.
|
||||
return p;
|
||||
};
|
||||
|
||||
Parser.parseSendCmpct = function parseSendCmpct(p) {
|
||||
return bcoin.bip152.SendCompact.fromRaw(p);
|
||||
};
|
||||
|
||||
Parser.parseCmpctBlock = function parseCmpctBlock(p) {
|
||||
return bcoin.bip152.CompactBlock.fromRaw(p);
|
||||
};
|
||||
|
||||
Parser.parseGetBlockTxn = function parseGetBlockTxn(p) {
|
||||
return bcoin.bip152.BlockTXRequest.fromRaw(p);
|
||||
};
|
||||
|
||||
Parser.parseBlockTxn = function parseBlockTxn(p) {
|
||||
return bcoin.bip152.BlockTX.fromRaw(p);
|
||||
};
|
||||
|
||||
/**
|
||||
* Packet
|
||||
* @constructor
|
||||
|
||||
Loading…
Reference in New Issue
Block a user