pool: refactor and add compact block mode 1 support.
This commit is contained in:
parent
7543049180
commit
18c4a83ed3
@ -1678,10 +1678,10 @@ RejectPacket.fromRaw = function fromRaw(data, enc) {
|
|||||||
* @private
|
* @private
|
||||||
* @param {Number} code
|
* @param {Number} code
|
||||||
* @param {String} reason
|
* @param {String} reason
|
||||||
* @param {(TX|Block)?} obj
|
* @param {(TX|Block)?} msg
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RejectPacket.prototype.fromReason = function fromReason(code, reason, obj) {
|
RejectPacket.prototype.fromReason = function fromReason(code, reason, msg) {
|
||||||
if (typeof code === 'string')
|
if (typeof code === 'string')
|
||||||
code = RejectPacket.codes[code.toUpperCase()];
|
code = RejectPacket.codes[code.toUpperCase()];
|
||||||
|
|
||||||
@ -1695,9 +1695,9 @@ RejectPacket.prototype.fromReason = function fromReason(code, reason, obj) {
|
|||||||
this.code = code;
|
this.code = code;
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
|
|
||||||
if (obj) {
|
if (msg) {
|
||||||
this.message = (obj instanceof TX) ? 'tx' : 'block';
|
this.message = (msg instanceof TX) ? 'tx' : 'block';
|
||||||
this.hash = obj.hash('hex');
|
this.hash = msg.hash('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
107
lib/net/peer.js
107
lib/net/peer.js
@ -26,7 +26,6 @@ var BIP152 = require('./bip152');
|
|||||||
var Block = require('../primitives/block');
|
var Block = require('../primitives/block');
|
||||||
var TX = require('../primitives/tx');
|
var TX = require('../primitives/tx');
|
||||||
var encoding = require('../utils/encoding');
|
var encoding = require('../utils/encoding');
|
||||||
var errors = require('../protocol/errors');
|
|
||||||
var NetAddress = require('../primitives/netaddress');
|
var NetAddress = require('../primitives/netaddress');
|
||||||
var services = common.services;
|
var services = common.services;
|
||||||
var invTypes = InvItem.types;
|
var invTypes = InvItem.types;
|
||||||
@ -115,8 +114,9 @@ function Peer(pool) {
|
|||||||
this.bip150 = null;
|
this.bip150 = null;
|
||||||
this.compactMode = -1;
|
this.compactMode = -1;
|
||||||
this.compactWitness = false;
|
this.compactWitness = false;
|
||||||
this.lastMerkle = null;
|
this.merkleBlock = null;
|
||||||
this.waitingTX = 0;
|
this.merkleTime = -1;
|
||||||
|
this.merkleMatches = 0;
|
||||||
this.syncSent = false;
|
this.syncSent = false;
|
||||||
this.sentAddr = false;
|
this.sentAddr = false;
|
||||||
this.sentGetAddr = false;
|
this.sentGetAddr = false;
|
||||||
@ -270,13 +270,14 @@ Peer.prototype._init = function init() {
|
|||||||
|
|
||||||
this.parser.on('error', function(err) {
|
this.parser.on('error', function(err) {
|
||||||
self.error(err);
|
self.error(err);
|
||||||
self.reject(null, 'malformed', 'error parsing message', 10);
|
self.sendReject('malformed', 'error parsing message');
|
||||||
|
self.increaseBan(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.bip151) {
|
if (this.bip151) {
|
||||||
this.bip151.on('error', function(err) {
|
this.bip151.on('error', function(err) {
|
||||||
self.error(err);
|
self.error(err);
|
||||||
self.reject(null, 'malformed', 'error parsing message', 10);
|
self.destroy();
|
||||||
});
|
});
|
||||||
this.bip151.on('rekey', function() {
|
this.bip151.on('rekey', function() {
|
||||||
self.logger.debug('Rekeying with peer (%s).', self.hostname);
|
self.logger.debug('Rekeying with peer (%s).', self.hostname);
|
||||||
@ -649,7 +650,7 @@ Peer.prototype.announceBlock = function announceBlock(blocks) {
|
|||||||
// they're using compact block mode 1.
|
// they're using compact block mode 1.
|
||||||
if (this.compactMode === 1) {
|
if (this.compactMode === 1) {
|
||||||
this.invFilter.add(block.hash());
|
this.invFilter.add(block.hash());
|
||||||
this.sendCompactBlock(block, this.compactWitness);
|
this.sendCompactBlock(block);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -824,11 +825,12 @@ Peer.prototype.sendHeaders = function sendHeaders(items) {
|
|||||||
* Send a compact block.
|
* Send a compact block.
|
||||||
* @private
|
* @private
|
||||||
* @param {Block} block
|
* @param {Block} block
|
||||||
* @param {Boolean} witness
|
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.sendCompactBlock = function sendCompactBlock(block, witness) {
|
Peer.prototype.sendCompactBlock = function sendCompactBlock(block) {
|
||||||
|
var witness = this.compactWitness;
|
||||||
|
|
||||||
// Try again with a new nonce
|
// Try again with a new nonce
|
||||||
// if we get a siphash collision.
|
// if we get a siphash collision.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -1122,6 +1124,15 @@ Peer.prototype.maybeTimeout = function maybeTimeout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.merkleBlock) {
|
||||||
|
assert(this.merkleTime !== -1);
|
||||||
|
if (now > this.merkleTime + 60000) {
|
||||||
|
this.error('Peer is stalling (merkleblock).');
|
||||||
|
this.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.pool.syncing || this.chain.synced)
|
if (!this.pool.syncing || this.chain.synced)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -1509,26 +1520,10 @@ Peer.prototype.handlePacket = co(function* handlePacket(packet) {
|
|||||||
this.emit(packet.cmd, packet);
|
this.emit(packet.cmd, packet);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush merkle block once all matched
|
|
||||||
* txs have been received.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Peer.prototype.flushMerkle = co(function* flushMerkle() {
|
|
||||||
assert(this.lastMerkle);
|
|
||||||
|
|
||||||
yield this.pool.addBlock(this, this.lastMerkle);
|
|
||||||
|
|
||||||
this.lastMerkle = null;
|
|
||||||
this.waitingTX = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle `filterload` packet.
|
* Handle `filterload` packet.
|
||||||
* @private
|
* @private
|
||||||
* @param {FilterLoadPacket}
|
* @param {FilterLoadPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleFilterLoad = co(function* handleFilterLoad(packet) {
|
Peer.prototype.handleFilterLoad = co(function* handleFilterLoad(packet) {
|
||||||
@ -1546,7 +1541,7 @@ Peer.prototype.handleFilterLoad = co(function* handleFilterLoad(packet) {
|
|||||||
/**
|
/**
|
||||||
* Handle `filteradd` packet.
|
* Handle `filteradd` packet.
|
||||||
* @private
|
* @private
|
||||||
* @param {FilterAddPacket}
|
* @param {FilterAddPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleFilterAdd = co(function* handleFilterAdd(packet) {
|
Peer.prototype.handleFilterAdd = co(function* handleFilterAdd(packet) {
|
||||||
@ -2025,7 +2020,7 @@ Peer.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(packet) {
|
Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(packet) {
|
||||||
yield this.pool.handleGetBlockTxn(this, packet);
|
yield this.pool.handleBlockTxn(this, packet);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2037,24 +2032,22 @@ Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(packet) {
|
|||||||
|
|
||||||
Peer.prototype.sendGetHeaders = function sendGetHeaders(locator, stop) {
|
Peer.prototype.sendGetHeaders = function sendGetHeaders(locator, stop) {
|
||||||
var packet = new packets.GetHeadersPacket(locator, stop);
|
var packet = new packets.GetHeadersPacket(locator, stop);
|
||||||
var height = -1;
|
|
||||||
var hash = null;
|
var hash = null;
|
||||||
|
var end = null;
|
||||||
|
|
||||||
|
if (packet.locator.length > 0)
|
||||||
|
hash = util.revHex(packet.locator[0]);
|
||||||
|
|
||||||
|
if (stop)
|
||||||
|
end = util.revHex(stop);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Requesting headers packet from peer with getheaders (%s).',
|
'Requesting headers packet from peer with getheaders (%s).',
|
||||||
this.hostname);
|
this.hostname);
|
||||||
|
|
||||||
if (packet.locator.length > 0) {
|
|
||||||
height = this.chain.checkHeight(packet.locator[0]);
|
|
||||||
hash = util.revHex(packet.locator[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop)
|
|
||||||
stop = util.revHex(stop);
|
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Height: %d, Hash: %s, Stop: %s',
|
'Sending getheaders (hash=%s, stop=%s).',
|
||||||
height, hash, stop || null);
|
hash, end);
|
||||||
|
|
||||||
this.send(packet);
|
this.send(packet);
|
||||||
};
|
};
|
||||||
@ -2069,10 +2062,7 @@ Peer.prototype.sendGetBlocks = function getBlocks(locator, stop) {
|
|||||||
var packet = new packets.GetBlocksPacket(locator, stop);
|
var packet = new packets.GetBlocksPacket(locator, stop);
|
||||||
var height = -1;
|
var height = -1;
|
||||||
var hash = null;
|
var hash = null;
|
||||||
|
var end = null;
|
||||||
this.logger.debug(
|
|
||||||
'Requesting inv packet from peer with getblocks (%s).',
|
|
||||||
this.hostname);
|
|
||||||
|
|
||||||
if (packet.locator.length > 0) {
|
if (packet.locator.length > 0) {
|
||||||
height = this.chain.checkHeight(packet.locator[0]);
|
height = this.chain.checkHeight(packet.locator[0]);
|
||||||
@ -2080,11 +2070,15 @@ Peer.prototype.sendGetBlocks = function getBlocks(locator, stop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stop)
|
if (stop)
|
||||||
stop = util.revHex(stop);
|
end = util.revHex(stop);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Height: %d, Hash: %s, Stop: %s',
|
'Requesting inv packet from peer with getblocks (%s).',
|
||||||
height, hash, stop || null);
|
this.hostname);
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
'Sending getblocks (height=%d, hash=%s, stop=%s).',
|
||||||
|
height, hash, end);
|
||||||
|
|
||||||
this.send(packet);
|
this.send(packet);
|
||||||
};
|
};
|
||||||
@ -2115,15 +2109,15 @@ Peer.prototype.sendMempool = function sendMempool() {
|
|||||||
* Send `reject` to peer.
|
* Send `reject` to peer.
|
||||||
* @param {Number} code
|
* @param {Number} code
|
||||||
* @param {String} reason
|
* @param {String} reason
|
||||||
* @param {TX|Block} obj
|
* @param {TX|Block} msg
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.sendReject = function sendReject(code, reason, obj) {
|
Peer.prototype.sendReject = function sendReject(code, reason, msg) {
|
||||||
var reject = packets.RejectPacket.fromReason(code, reason, obj);
|
var reject = packets.RejectPacket.fromReason(code, reason, msg);
|
||||||
|
|
||||||
if (obj) {
|
if (msg) {
|
||||||
this.logger.debug('Rejecting %s %s (%s): ccode=%s reason=%s.',
|
this.logger.debug('Rejecting %s %s (%s): ccode=%s reason=%s.',
|
||||||
reject.message, obj.rhash(), this.hostname, code, reason);
|
reject.message, msg.rhash(), this.hostname, code, reason);
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug('Rejecting packet from %s: ccode=%s reason=%s.',
|
this.logger.debug('Rejecting packet from %s: ccode=%s reason=%s.',
|
||||||
this.hostname, code, reason);
|
this.hostname, code, reason);
|
||||||
@ -2138,12 +2132,13 @@ Peer.prototype.sendReject = function sendReject(code, reason, obj) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a `sendcmpct` packet.
|
* Send a `sendcmpct` packet.
|
||||||
|
* @param {Number} mode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.sendCompact = function sendCompact() {
|
Peer.prototype.sendCompact = function sendCompact(mode) {
|
||||||
var version = this.options.witness ? 2 : 1;
|
var version = this.options.witness ? 2 : 1;
|
||||||
this.logger.info('Initializing compact blocks (%s).', this.hostname);
|
this.logger.info('Initializing compact blocks (%s).', this.hostname);
|
||||||
this.send(new packets.SendCmpctPacket(0, version));
|
this.send(new packets.SendCmpctPacket(mode, version));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2174,14 +2169,14 @@ Peer.prototype.ban = function ban() {
|
|||||||
/**
|
/**
|
||||||
* Send a `reject` packet to peer.
|
* Send a `reject` packet to peer.
|
||||||
* @see Framer.reject
|
* @see Framer.reject
|
||||||
* @param {(TX|Block)?} obj
|
* @param {(TX|Block)?} msg
|
||||||
* @param {String} code - cccode.
|
* @param {String} code
|
||||||
* @param {String} reason
|
* @param {String} reason
|
||||||
* @param {Number} score
|
* @param {Number} score
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.reject = function reject(obj, code, reason, score) {
|
Peer.prototype.reject = function reject(msg, code, reason, score) {
|
||||||
this.sendReject(code, reason, obj);
|
this.sendReject(code, reason, msg);
|
||||||
this.increaseBan(score);
|
this.increaseBan(score);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
226
lib/net/pool.js
226
lib/net/pool.js
@ -34,7 +34,6 @@ var Map = require('../utils/map');
|
|||||||
var packets = require('./packets');
|
var packets = require('./packets');
|
||||||
var invTypes = InvItem.types;
|
var invTypes = InvItem.types;
|
||||||
var VerifyError = errors.VerifyError;
|
var VerifyError = errors.VerifyError;
|
||||||
var VerifyResult = errors.VerifyResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pool of peers for handling all network activity.
|
* A pool of peers for handling all network activity.
|
||||||
@ -104,7 +103,6 @@ function Pool(options) {
|
|||||||
|
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.syncing = false;
|
this.syncing = false;
|
||||||
this.feeRate = this.options.feeRate;
|
|
||||||
this.nonce = util.nonce();
|
this.nonce = util.nonce();
|
||||||
this.spvFilter = null;
|
this.spvFilter = null;
|
||||||
this.txFilter = null;
|
this.txFilter = null;
|
||||||
@ -180,26 +178,6 @@ Pool.prototype._init = function _init() {
|
|||||||
self.emit('block', block, entry);
|
self.emit('block', block, entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.chain.on('competitor', function(block, entry) {
|
|
||||||
self.emit('competitor', block, entry);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('fork', function(block, height, expected) {
|
|
||||||
self.emit('fork', block, height, expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('invalid', function(block, height) {
|
|
||||||
self.emit('invalid', block, height);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('exists', function(block, height) {
|
|
||||||
self.emit('exists', block, height);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('orphan', function(block, height) {
|
|
||||||
self.emit('orphan', block, height);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('reset', function() {
|
this.chain.on('reset', function() {
|
||||||
self.resetChain();
|
self.resetChain();
|
||||||
self.forceSync();
|
self.forceSync();
|
||||||
@ -896,7 +874,7 @@ Pool.prototype.handleOpen = function handleOpen(peer) {
|
|||||||
// We want compact blocks!
|
// We want compact blocks!
|
||||||
if (this.options.compact) {
|
if (this.options.compact) {
|
||||||
if (peer.version >= common.COMPACT_VERSION)
|
if (peer.version >= common.COMPACT_VERSION)
|
||||||
peer.sendCompact();
|
peer.sendCompact(this.options.blockMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find some more peers.
|
// Find some more peers.
|
||||||
@ -911,8 +889,8 @@ Pool.prototype.handleOpen = function handleOpen(peer) {
|
|||||||
this.announceList(peer);
|
this.announceList(peer);
|
||||||
|
|
||||||
// Set a fee rate filter.
|
// Set a fee rate filter.
|
||||||
if (this.feeRate !== -1)
|
if (this.options.feeRate !== -1)
|
||||||
peer.sendFeeRate(this.feeRate);
|
peer.sendFeeRate(this.options.feeRate);
|
||||||
|
|
||||||
// Start syncing the chain.
|
// Start syncing the chain.
|
||||||
peer.sync();
|
peer.sync();
|
||||||
@ -982,7 +960,7 @@ Pool.prototype.handleVersion = co(function* handleVersion(peer, packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Pool.prototype.handleVerack = co(function* handleVerack(peer, packet) {
|
Pool.prototype.handleVerack = co(function* handleVerack(peer, packet) {
|
||||||
;
|
this.emit('verack', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -993,7 +971,7 @@ Pool.prototype.handleVerack = co(function* handleVerack(peer, packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Pool.prototype.handlePing = co(function* handlePing(peer, packet) {
|
Pool.prototype.handlePing = co(function* handlePing(peer, packet) {
|
||||||
;
|
this.emit('ping', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1004,7 +982,7 @@ Pool.prototype.handlePing = co(function* handlePing(peer, packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Pool.prototype.handlePong = co(function* handlePong(peer, packet) {
|
Pool.prototype.handlePong = co(function* handlePong(peer, packet) {
|
||||||
;
|
this.emit('pong', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1051,6 +1029,8 @@ Pool.prototype.handleGetAddr = co(function* handleGetAddr(peer, packet) {
|
|||||||
peer.hostname);
|
peer.hostname);
|
||||||
|
|
||||||
peer.send(new packets.AddrPacket(items));
|
peer.send(new packets.AddrPacket(items));
|
||||||
|
|
||||||
|
this.emit('getaddr', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1066,8 +1046,6 @@ Pool.prototype.handleAddr = co(function* handleAddr(peer, packet) {
|
|||||||
var services = this.options.requiredServices;
|
var services = this.options.requiredServices;
|
||||||
var i, addr;
|
var i, addr;
|
||||||
|
|
||||||
this.emit('addr', addrs, peer);
|
|
||||||
|
|
||||||
for (i = 0; i < addrs.length; i++) {
|
for (i = 0; i < addrs.length; i++) {
|
||||||
addr = addrs[i];
|
addr = addrs[i];
|
||||||
|
|
||||||
@ -1096,6 +1074,8 @@ Pool.prototype.handleAddr = co(function* handleAddr(peer, packet) {
|
|||||||
peer.hostname);
|
peer.hostname);
|
||||||
|
|
||||||
this.fillOutbound();
|
this.fillOutbound();
|
||||||
|
|
||||||
|
this.emit('addr', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1159,13 +1139,13 @@ Pool.prototype._handleInv = co(function* handleInv(peer, packet) {
|
|||||||
unknown, peer.hostname);
|
unknown, peer.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('inv', items, peer);
|
|
||||||
|
|
||||||
if (blocks.length > 0)
|
if (blocks.length > 0)
|
||||||
yield this.handleBlockInv(peer, blocks);
|
yield this.handleBlockInv(peer, blocks);
|
||||||
|
|
||||||
if (txs.length > 0)
|
if (txs.length > 0)
|
||||||
yield this.handleTXInv(peer, txs);
|
yield this.handleTXInv(peer, txs);
|
||||||
|
|
||||||
|
this.emit('inv', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1361,7 +1341,7 @@ Pool.prototype.handleGetData = co(function* handleGetData(peer, packet) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.sendCompactBlock(block, peer.compactWitness);
|
peer.sendCompactBlock(block);
|
||||||
|
|
||||||
blocks++;
|
blocks++;
|
||||||
|
|
||||||
@ -1399,7 +1379,7 @@ Pool.prototype.handleGetData = co(function* handleGetData(peer, packet) {
|
|||||||
unknown, peer.hostname);
|
unknown, peer.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('getdata', items, peer);
|
this.emit('getdata', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1417,6 +1397,8 @@ Pool.prototype.handleNotFound = co(function* handleNotFound(peer, packet) {
|
|||||||
item = items[i];
|
item = items[i];
|
||||||
this.fulfill(peer, item.hash);
|
this.fulfill(peer, item.hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emit('notfound', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1462,6 +1444,8 @@ Pool.prototype.handleGetBlocks = co(function* handleGetBlocks(peer, packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer.sendInv(blocks);
|
peer.sendInv(blocks);
|
||||||
|
|
||||||
|
this.emit('getblocks', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1511,6 +1495,8 @@ Pool.prototype.handleGetHeaders = co(function* handleGetHeaders(peer, packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer.sendHeaders(headers);
|
peer.sendHeaders(headers);
|
||||||
|
|
||||||
|
this.emit('getheaders', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1595,13 +1581,13 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
|
|||||||
this.headerChain.push(node);
|
this.headerChain.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('headers', headers, peer);
|
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Received %s headers from peer (%s).',
|
'Received %s headers from peer (%s).',
|
||||||
headers.length,
|
headers.length,
|
||||||
peer.hostname);
|
peer.hostname);
|
||||||
|
|
||||||
|
this.emit('headers', packet, peer);
|
||||||
|
|
||||||
// Request the hashes we just added.
|
// Request the hashes we just added.
|
||||||
if (isCheckpoint) {
|
if (isCheckpoint) {
|
||||||
this.headerChain.shift();
|
this.headerChain.shift();
|
||||||
@ -1622,7 +1608,7 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Pool.prototype.handleSendHeaders = co(function* handleSendHeaders(peer, packet) {
|
Pool.prototype.handleSendHeaders = co(function* handleSendHeaders(peer, packet) {
|
||||||
this.emit('sendheader', packet, peer);
|
this.emit('sendheaders', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1645,7 +1631,7 @@ Pool.prototype.handleBlock = co(function* handleBlock(peer, packet) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle `block` packet. Attempt to add to chain.
|
* Attempt to add block to chain.
|
||||||
* @private
|
* @private
|
||||||
* @param {Peer} peer
|
* @param {Peer} peer
|
||||||
* @param {Block} block
|
* @param {Block} block
|
||||||
@ -1663,7 +1649,7 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle `block` packet. Attempt to add to chain (without a lock).
|
* Attempt to add block to chain (without a lock).
|
||||||
* @private
|
* @private
|
||||||
* @param {Peer} peer
|
* @param {Peer} peer
|
||||||
* @param {Block} block
|
* @param {Block} block
|
||||||
@ -1673,16 +1659,12 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) {
|
|||||||
Pool.prototype._addBlock = co(function* addBlock(peer, block) {
|
Pool.prototype._addBlock = co(function* addBlock(peer, block) {
|
||||||
var hash = block.hash('hex');
|
var hash = block.hash('hex');
|
||||||
var isCheckpoint = false;
|
var isCheckpoint = false;
|
||||||
var requested, node, checkpoint;
|
var node, checkpoint;
|
||||||
|
|
||||||
if (!this.syncing)
|
if (!this.syncing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
requested = this.fulfill(peer, hash);
|
if (!this.fulfill(peer, hash)) {
|
||||||
|
|
||||||
// Someone is sending us blocks without
|
|
||||||
// us requesting them.
|
|
||||||
if (!requested) {
|
|
||||||
peer.invFilter.add(block.hash());
|
peer.invFilter.add(block.hash());
|
||||||
this.logger.warning(
|
this.logger.warning(
|
||||||
'Received unrequested block: %s (%s).',
|
'Received unrequested block: %s (%s).',
|
||||||
@ -1817,23 +1799,28 @@ Pool.prototype.handleTX = co(function* handleTX(peer, packet) {
|
|||||||
|
|
||||||
Pool.prototype._handleTX = co(function* handleTX(peer, packet) {
|
Pool.prototype._handleTX = co(function* handleTX(peer, packet) {
|
||||||
var tx = packet.tx;
|
var tx = packet.tx;
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
var requested, missing;
|
||||||
|
|
||||||
if (peer.lastMerkle) {
|
if (peer.merkleBlock) {
|
||||||
assert(peer.waitingTX > 0);
|
assert(peer.merkleMatches > 0);
|
||||||
if (peer.lastMerkle.hasTX(tx)) {
|
if (peer.merkleBlock.hasTX(tx)) {
|
||||||
peer.lastMerkle.addTX(tx);
|
peer.merkleBlock.addTX(tx);
|
||||||
if (--peer.waitingTX === 0)
|
if (--peer.merkleMatches === 0) {
|
||||||
yield peer.flushMerkle();
|
yield this.addBlock(peer, peer.merkleBlock);
|
||||||
|
peer.merkleTime = -1;
|
||||||
|
peer.merkleBlock = null;
|
||||||
|
peer.merkleMatches = 0;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = tx.hash('hex');
|
requested = this.fulfill(peer, hash);
|
||||||
var requested = this.fulfill(peer, hash);
|
|
||||||
var missing;
|
|
||||||
|
|
||||||
if (!requested) {
|
if (!requested) {
|
||||||
this.logger.warning('Peer sent unrequested tx: %s (%s).',
|
this.logger.warning(
|
||||||
|
'Peer sent unrequested tx: %s (%s).',
|
||||||
tx.txid(), peer.hostname);
|
tx.txid(), peer.hostname);
|
||||||
|
|
||||||
peer.invFilter.add(tx.hash());
|
peer.invFilter.add(tx.hash());
|
||||||
@ -1917,7 +1904,7 @@ Pool.prototype.handleReject = co(function* handleReject(peer, packet) {
|
|||||||
|
|
||||||
Pool.prototype.handleMempool = co(function* handleMempool(peer, packet) {
|
Pool.prototype.handleMempool = co(function* handleMempool(peer, packet) {
|
||||||
var items = [];
|
var items = [];
|
||||||
var i, hashes;
|
var i, hash, hashes;
|
||||||
|
|
||||||
if (!this.mempool)
|
if (!this.mempool)
|
||||||
return;
|
return;
|
||||||
@ -1930,14 +1917,16 @@ Pool.prototype.handleMempool = co(function* handleMempool(peer, packet) {
|
|||||||
|
|
||||||
hashes = this.mempool.getSnapshot();
|
hashes = this.mempool.getSnapshot();
|
||||||
|
|
||||||
for (i = 0; i < hashes.length; i++)
|
for (i = 0; i < hashes.length; i++) {
|
||||||
items.push(new InvItem(invTypes.TX, hashes[i]));
|
hash = hashes[i];
|
||||||
|
items.push(new InvItem(invTypes.TX, hash));
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.debug('Sending mempool snapshot (%s).', peer.hostname);
|
this.logger.debug('Sending mempool snapshot (%s).', peer.hostname);
|
||||||
|
|
||||||
this.emit('mempool', peer);
|
|
||||||
|
|
||||||
peer.sendInv(items);
|
peer.sendInv(items);
|
||||||
|
|
||||||
|
this.emit('mempool', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1970,7 +1959,7 @@ Pool.prototype.handleFilterAdd = co(function* handleFilterAdd(peer, packet) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Pool.prototype.handleFilterClear = co(function* handleFilterClear(peer, packet) {
|
Pool.prototype.handleFilterClear = co(function* handleFilterClear(peer, packet) {
|
||||||
this.emit('filterclear', peer, packet);
|
this.emit('filterclear', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1982,7 +1971,10 @@ Pool.prototype.handleFilterClear = co(function* handleFilterClear(peer, packet)
|
|||||||
|
|
||||||
Pool.prototype.handleMerkleBlock = co(function* handleMerkleBlock(peer, packet) {
|
Pool.prototype.handleMerkleBlock = co(function* handleMerkleBlock(peer, packet) {
|
||||||
var block = packet.block;
|
var block = packet.block;
|
||||||
var ret = new VerifyResult();
|
var hash = block.hash('hex');
|
||||||
|
|
||||||
|
if (!this.syncing)
|
||||||
|
return;
|
||||||
|
|
||||||
// Potential DoS.
|
// Potential DoS.
|
||||||
if (!this.options.spv) {
|
if (!this.options.spv) {
|
||||||
@ -1993,7 +1985,15 @@ Pool.prototype.handleMerkleBlock = co(function* handleMerkleBlock(peer, packet)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peer.lastMerkle) {
|
if (!peer.requestMap.has(hash)) {
|
||||||
|
this.logger.warning(
|
||||||
|
'Peer sent an unrequested merkleblock (%s).',
|
||||||
|
peer.hostname);
|
||||||
|
peer.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peer.merkleBlock) {
|
||||||
this.logger.warning(
|
this.logger.warning(
|
||||||
'Peer sent a merkleblock prematurely (%s).',
|
'Peer sent a merkleblock prematurely (%s).',
|
||||||
peer.hostname);
|
peer.hostname);
|
||||||
@ -2001,19 +2001,24 @@ Pool.prototype.handleMerkleBlock = co(function* handleMerkleBlock(peer, packet)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block.verify(ret)) {
|
if (!block.verify()) {
|
||||||
this.logger.warning(
|
this.logger.warning(
|
||||||
'Peer sent an invalid merkleblock (%s).',
|
'Peer sent an invalid merkleblock (%s).',
|
||||||
peer.hostname);
|
peer.hostname);
|
||||||
peer.reject(block, 'invalid', ret.reason, ret.score);
|
peer.increaseBan(100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.lastMerkle = block;
|
if (block.matches.length === 0) {
|
||||||
peer.waitingTX = block.matches.length;
|
yield this.addBlock(peer, block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (peer.waitingTX === 0)
|
peer.merkleTime = util.ms();
|
||||||
yield peer.flushMerkle();
|
peer.merkleBlock = block;
|
||||||
|
peer.merkleMatches = block.matches.length;
|
||||||
|
|
||||||
|
this.emit('merkleblock', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2051,6 +2056,9 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
|||||||
var witness = this.options.witness;
|
var witness = this.options.witness;
|
||||||
var result;
|
var result;
|
||||||
|
|
||||||
|
if (!this.syncing)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!this.options.compact) {
|
if (!this.options.compact) {
|
||||||
this.logger.info('Peer sent unsolicited cmpctblock (%s).', peer.hostname);
|
this.logger.info('Peer sent unsolicited cmpctblock (%s).', peer.hostname);
|
||||||
return;
|
return;
|
||||||
@ -2061,13 +2069,33 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.compactBlocks.has(hash)) {
|
if (peer.compactBlocks.has(hash)) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Peer sent us a duplicate compact block (%s).',
|
'Peer sent us a duplicate compact block (%s).',
|
||||||
peer.hostname);
|
peer.hostname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.compactBlocks.has(hash)) {
|
||||||
|
this.logger.debug(
|
||||||
|
'Already waiting for compact block %s (%s).',
|
||||||
|
hash, peer.hostname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!peer.requestMap.has(hash)) {
|
||||||
|
if (this.options.blockMode !== 1) {
|
||||||
|
this.logger.warning(
|
||||||
|
'Peer sent us an unrequested compact block (%s).',
|
||||||
|
peer.hostname);
|
||||||
|
peer.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
peer.requestMap.insert(hash);
|
||||||
|
assert(!this.requestMap.has(hash));
|
||||||
|
this.requestMap.insert(hash);
|
||||||
|
}
|
||||||
|
|
||||||
if (!block.verify()) {
|
if (!block.verify()) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Peer sent an invalid compact block (%s).',
|
'Peer sent an invalid compact block (%s).',
|
||||||
@ -2086,10 +2114,12 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peer.compactBlocks.size >= 10) {
|
if (this.options.blockMode === 1) {
|
||||||
this.logger.warning('Compact block DoS attempt (%s).', peer.hostname);
|
if (peer.compactBlocks.size >= 10) {
|
||||||
peer.destroy();
|
this.logger.warning('Compact block DoS attempt (%s).', peer.hostname);
|
||||||
return;
|
peer.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!peer.compactBlocks.has(hash));
|
assert(!peer.compactBlocks.has(hash));
|
||||||
@ -2101,9 +2131,9 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
|||||||
'Received semi-full compact block %s (%s).',
|
'Received semi-full compact block %s (%s).',
|
||||||
block.rhash(), peer.hostname);
|
block.rhash(), peer.hostname);
|
||||||
|
|
||||||
this.emit('cmpctblock', block, peer);
|
|
||||||
|
|
||||||
peer.send(new packets.GetBlockTxnPacket(block.toRequest()));
|
peer.send(new packets.GetBlockTxnPacket(block.toRequest()));
|
||||||
|
|
||||||
|
this.emit('cmpctblock', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2147,11 +2177,11 @@ Pool.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(peer, packet)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('getblocktxn', req, peer);
|
|
||||||
|
|
||||||
res = BIP152.TXResponse.fromBlock(block, req);
|
res = BIP152.TXResponse.fromBlock(block, req);
|
||||||
|
|
||||||
peer.send(new packets.BlockTxnPacket(res, peer.compactWitness));
|
peer.send(new packets.BlockTxnPacket(res, peer.compactWitness));
|
||||||
|
|
||||||
|
this.emit('getblocktxn', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2161,7 +2191,7 @@ Pool.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(peer, packet)
|
|||||||
* @param {BlockTxnPacket} packet
|
* @param {BlockTxnPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
|
Pool.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
|
||||||
var res = packet.response;
|
var res = packet.response;
|
||||||
var block = peer.compactBlocks.get(res.hash);
|
var block = peer.compactBlocks.get(res.hash);
|
||||||
|
|
||||||
@ -2185,9 +2215,9 @@ Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
|
|||||||
'Filled compact block %s (%s).',
|
'Filled compact block %s (%s).',
|
||||||
block.rhash(), peer.hostname);
|
block.rhash(), peer.hostname);
|
||||||
|
|
||||||
this.emit('blocktxn', res, peer);
|
|
||||||
|
|
||||||
yield this.addBlock(peer, block.toBlock());
|
yield this.addBlock(peer, block.toBlock());
|
||||||
|
|
||||||
|
this.emit('blocktxn', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2197,7 +2227,7 @@ Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
|
|||||||
* @param {EncinitPacket} packet
|
* @param {EncinitPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleEncinit = co(function* handleEncinit(peer, packet) {
|
Pool.prototype.handleEncinit = co(function* handleEncinit(peer, packet) {
|
||||||
this.emit('encinit', packet, peer);
|
this.emit('encinit', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2208,7 +2238,7 @@ Peer.prototype.handleEncinit = co(function* handleEncinit(peer, packet) {
|
|||||||
* @param {EncackPacket} packet
|
* @param {EncackPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleEncack = co(function* handleEncack(peer, packet) {
|
Pool.prototype.handleEncack = co(function* handleEncack(peer, packet) {
|
||||||
this.emit('encack', packet, peer);
|
this.emit('encack', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2219,7 +2249,7 @@ Peer.prototype.handleEncack = co(function* handleEncack(peer, packet) {
|
|||||||
* @param {AuthChallengePacket} packet
|
* @param {AuthChallengePacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleAuthChallenge = co(function* handleAuthChallenge(peer, packet) {
|
Pool.prototype.handleAuthChallenge = co(function* handleAuthChallenge(peer, packet) {
|
||||||
this.emit('authchallenge', packet, peer);
|
this.emit('authchallenge', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2230,7 +2260,7 @@ Peer.prototype.handleAuthChallenge = co(function* handleAuthChallenge(peer, pack
|
|||||||
* @param {AuthReplyPacket} packet
|
* @param {AuthReplyPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleAuthReply = co(function* handleAuthReply(peer, packet) {
|
Pool.prototype.handleAuthReply = co(function* handleAuthReply(peer, packet) {
|
||||||
this.emit('authreply', packet, peer);
|
this.emit('authreply', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2241,7 +2271,7 @@ Peer.prototype.handleAuthReply = co(function* handleAuthReply(peer, packet) {
|
|||||||
* @param {AuthProposePacket} packet
|
* @param {AuthProposePacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleAuthPropose = co(function* handleAuthPropose(peer, packet) {
|
Pool.prototype.handleAuthPropose = co(function* handleAuthPropose(peer, packet) {
|
||||||
this.emit('authpropose', packet, peer);
|
this.emit('authpropose', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2252,8 +2282,8 @@ Peer.prototype.handleAuthPropose = co(function* handleAuthPropose(peer, packet)
|
|||||||
* @param {UnknownPacket} packet
|
* @param {UnknownPacket} packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Peer.prototype.handleUnknown = co(function* handleUnknown(peer, packet) {
|
Pool.prototype.handleUnknown = co(function* handleUnknown(peer, packet) {
|
||||||
this.logger.warning('Unknown packet: %s.', packet.cmd);
|
this.logger.warning('Unknown packet: %s (%s).', packet.cmd, peer.hostname);
|
||||||
this.emit('unknown', packet, peer);
|
this.emit('unknown', packet, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2780,20 +2810,6 @@ Pool.prototype.announceTX = function announceTX(msg) {
|
|||||||
peer.announceTX(msg);
|
peer.announceTX(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a fee rate filter for all peers.
|
|
||||||
* @param {Rate} rate
|
|
||||||
*/
|
|
||||||
|
|
||||||
Pool.prototype.setFeeRate = function setFeeRate(rate) {
|
|
||||||
var peer;
|
|
||||||
|
|
||||||
this.feeRate = rate;
|
|
||||||
|
|
||||||
for (peer = this.peers.head(); peer; peer = peer.next)
|
|
||||||
peer.sendFeeRate(rate);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to retrieve external IP from icanhazip.com.
|
* Attempt to retrieve external IP from icanhazip.com.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
@ -2898,6 +2914,7 @@ function PoolOptions(options) {
|
|||||||
this.seeds = this.network.seeds;
|
this.seeds = this.network.seeds;
|
||||||
this.nodes = [];
|
this.nodes = [];
|
||||||
this.invTimeout = 60000;
|
this.invTimeout = 60000;
|
||||||
|
this.blockMode = 0;
|
||||||
this.services = common.LOCAL_SERVICES;
|
this.services = common.LOCAL_SERVICES;
|
||||||
this.requiredServices = common.REQUIRED_SERVICES;
|
this.requiredServices = common.REQUIRED_SERVICES;
|
||||||
|
|
||||||
@ -3091,6 +3108,11 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
|||||||
this.invTimeout = options.invTimeout;
|
this.invTimeout = options.invTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.blockMode != null) {
|
||||||
|
assert(typeof options.blockMode === 'number');
|
||||||
|
this.blockMode = options.blockMode;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.witness) {
|
if (!this.witness) {
|
||||||
this.services &= ~common.services.WITNESS;
|
this.services &= ~common.services.WITNESS;
|
||||||
this.requiredServices &= ~common.services.WITNESS;
|
this.requiredServices &= ~common.services.WITNESS;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user