add feefilter packet. misc.

This commit is contained in:
Christopher Jeffrey 2016-05-18 17:32:35 -07:00
parent 07517ecd04
commit 3154a1c914
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 228 additions and 66 deletions

View File

@ -854,7 +854,7 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callb
self.total--;
if (limit) {
rate = Math.floor(entry.fees * 1000 / entry.size);
rate = bcoin.tx.getRate(entry.size, entry.fees);
rate += self.minReasonableFee;
if (rate > self.minFeeRate) {
self.minFeeRate = rate;

View File

@ -92,6 +92,8 @@ function Peer(pool, options) {
this.hashContinue = null;
this.filter = null;
this.relay = true;
this.localNonce = utils.nonce();
this.filterRate = -1;
this.challenge = null;
this.lastPong = 0;
@ -203,8 +205,6 @@ Peer.prototype._init = function init() {
self.emit('ack');
self.ts = utils.now();
self.write(self.framer.getAddr());
if (self.options.headers) {
if (self.version && self.version.version > 70012)
self.write(self.framer.sendHeaders());
@ -215,6 +215,16 @@ Peer.prototype._init = function init() {
self.write(self.framer.haveWitness());
}
self.write(self.framer.getAddr());
self.sendInv(self.pool.inv.list);
if (self.pool.options.filterRate != null) {
self.write(self.framer.feeFilter({
rate: self.pool.options.filterRate
}));
}
if (self.chain.isFull())
self.getMempool();
});
@ -222,7 +232,8 @@ Peer.prototype._init = function init() {
// Send hello
this.write(this.framer.version({
height: this.chain.height,
relay: this.options.relay
relay: this.options.relay,
nonce: this.localNonce
}));
};
@ -278,9 +289,18 @@ Peer.prototype.sendInv = function sendInv(items) {
if (!this.relay)
return;
if (!items)
return;
if (!Array.isArray(items))
items = [items];
if (items.length === 0)
return;
if (items.length > 50000)
items = items.slice(0, 50000);
if (this.filter)
items = items.map(this.isWatched, this);
@ -475,7 +495,7 @@ Peer.prototype._onPacket = function onPacket(packet) {
var payload = packet.payload;
if (this.lastBlock && cmd !== 'tx')
this._emitMerkle();
this._flushMerkle();
switch (cmd) {
case 'version':
@ -502,6 +522,8 @@ Peer.prototype._onPacket = function onPacket(packet) {
return this._handleGetUTXOs(payload);
case 'utxos':
return this._handleUTXOs(payload);
case 'feefilter':
return this._handleFeeFilter(payload);
case 'getblocks':
return this._handleGetBlocks(payload);
case 'getheaders':
@ -516,7 +538,7 @@ Peer.prototype._onPacket = function onPacket(packet) {
return this._handleFilterClear(payload);
case 'block':
payload = new bcoin.compactblock(payload);
this._emit(cmd, payload);
this.fire(cmd, payload);
break;
case 'merkleblock':
payload = new bcoin.merkleblock(payload);
@ -529,9 +551,9 @@ Peer.prototype._onPacket = function onPacket(packet) {
this.lastBlock.addTX(payload);
break;
}
this._emitMerkle();
this._flushMerkle();
}
this._emit(cmd, payload);
this.fire(cmd, payload);
break;
case 'sendheaders':
this.sendHeaders = true;
@ -542,28 +564,28 @@ Peer.prototype._onPacket = function onPacket(packet) {
this.response(cmd, payload);
break;
case 'verack':
this._emit(cmd, payload);
this.fire(cmd, payload);
break;
case 'notfound':
this._emit(cmd, payload);
this.fire(cmd, payload);
break;
default:
bcoin.debug('Unknown packet: %s', cmd);
this._emit(cmd, payload);
this.fire(cmd, payload);
break;
}
};
Peer.prototype._emit = function _emit(cmd, payload) {
Peer.prototype.fire = function fire(cmd, payload) {
if (this.response(cmd, payload))
return;
this.emit(cmd, payload);
};
Peer.prototype._emitMerkle = function _emitMerkle() {
Peer.prototype._flushMerkle = function _flushMerkle() {
if (this.lastBlock)
this._emit('merkleblock', this.lastBlock);
this.fire('merkleblock', this.lastBlock);
this.lastBlock = null;
};
@ -585,14 +607,21 @@ Peer.prototype._handleFilterLoad = function _handleFilterLoad(payload) {
};
Peer.prototype._handleFilterAdd = function _handleFilterAdd(payload) {
if (payload.data.length > constants.script.MAX_PUSH) {
this.setMisbehavior(100);
return;
}
if (this.filter)
this.filter.add(payload.data);
this.relay = true;
};
Peer.prototype._handleFilterClear = function _handleFilterClear(payload) {
if (this.filter)
this.filter.reset();
this.relay = true;
};
@ -601,7 +630,18 @@ Peer.prototype._handleUTXOs = function _handleUTXOs(payload) {
return new bcoin.coin(coin);
});
bcoin.debug('Received %d utxos from %s.', payload.coins.length, this.host);
this._emit('utxos', payload);
this.fire('utxos', payload);
};
Peer.prototype._handleFeeFilter = function _handleFeeFilter(payload) {
if (!(payload.rate >= 0 && payload.rate <= constants.MAX_MONEY)) {
this.setMisbehavior(100);
return;
}
this.filterRate = payload.rate;
this.fire('feefilter', payload);
};
/**
@ -876,6 +916,12 @@ Peer.prototype._handleVersion = function handleVersion(payload) {
var version = payload.version;
var services = payload.services;
if (payload.nonce.cmp(this.localNonce) === 0) {
this._error('We connected to ourself. Oops.');
this.setMisbehavior(100);
return;
}
if (version < constants.MIN_VERSION) {
this._error('Peer doesn\'t support required protocol version.');
this.setMisbehavior(100);
@ -957,18 +1003,15 @@ Peer.prototype._handleGetData = function handleGetData(items) {
var self = this;
var check = [];
var notfound = [];
var hash, entry, isWitness, value, i, item;
var i, item, entry, witness;
if (items.length > 50000)
return this._error('message getdata size() = %d', items.length);
for (i = 0; i < items.length; i++) {
item = items[i];
hash = item.hash;
entry = this.pool.inv.map[hash];
isWitness = (item.type & constants.WITNESS_MASK) !== 0;
value = null;
entry = this.pool.inv.map[item.hash];
witness = (item.type & constants.WITNESS_MASK) !== 0;
if (!entry) {
check.push(item);
@ -987,16 +1030,16 @@ Peer.prototype._handleGetData = function handleGetData(items) {
this.host,
entry.packetType,
utils.revHex(entry.key),
isWitness ? 'witness' : 'normal');
witness ? 'witness' : 'normal');
entry.sendTo(peer, isWitness);
entry.sendTo(peer, witness);
}
if (this.pool.options.selfish)
return;
utils.forEachSerial(check, function(item, next) {
var isWitness = item.type & constants.WITNESS_MASK;
var witness = item.type & constants.WITNESS_MASK;
var type = (item.type & ~constants.WITNESS_MASK) !== 0;
var hash = item.hash;
var i, tx, data;
@ -1006,16 +1049,26 @@ Peer.prototype._handleGetData = function handleGetData(items) {
notfound.push({ type: constants.inv.TX, hash: hash });
return next();
}
return self.mempool.getTX(hash, function(err, tx) {
return self.mempool.getEntry(hash, function(err, entry) {
if (err)
return next(err);
if (!tx) {
if (!entry) {
notfound.push({ type: constants.inv.TX, hash: hash });
return next();
}
if (isWitness)
tx = entry.tx;
// We should technically calculate this in
// the `mempool` handler, but it would be
// too slow.
if (self.filterRate !== -1) {
if (bcoin.tx.getRate(entry.size, entry.fees) < self.filterRate)
return next();
}
if (witness)
data = tx.renderWitness();
else
data = tx.renderNormal();
@ -1044,7 +1097,7 @@ Peer.prototype._handleGetData = function handleGetData(items) {
return next();
}
if (isWitness)
if (witness)
data = block.renderWitness();
else
data = block.renderNormal();
@ -1088,7 +1141,7 @@ Peer.prototype._handleGetData = function handleGetData(items) {
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
if (isWitness)
if (witness)
tx = tx.renderWitness();
else
tx = tx.renderNormal();
@ -1109,6 +1162,7 @@ Peer.prototype._handleGetData = function handleGetData(items) {
}
notfound.push({ type: type, hash: hash });
return next();
}, function(err) {
if (err)
@ -1174,37 +1228,33 @@ Peer.prototype._handlePong = function handlePong(data) {
Peer.prototype._handleGetAddr = function handleGetAddr() {
var hosts = {};
var peers = this.pool.peers.all;
var items = [];
var i, peer, ip, version;
var ts = utils.now() - (process.uptime() | 0);
var i, seed, version, peer;
if (this.pool.options.selfish)
return;
for (i = 0; i < peers.length; i++) {
peer = peers[i];
if (!peer.socket || !peer.socket.remoteAddress)
continue;
ip = peer.socket.remoteAddress;
version = utils.isIP(ip);
for (i = 0; i < this.pool.seeds.length; i++) {
seed = utils.parseHost(this.pool.seeds[i]);
seed = this.pool.getPeer(seed) || seed;
version = utils.isIP(seed.host);
if (!version)
continue;
if (hosts[ip])
if (hosts[seed.host])
continue;
hosts[ip] = true;
hosts[seed.host] = true;
items.push({
network: this.network,
ts: peer.ts,
services: peer.version ? peer.version.services : null,
ipv4: version === 4 ? ip : null,
ipv6: version === 6 ? ip : null,
port: peer.socket.remotePort || this.network.port
ts: seed.ts || ts,
services: seed.version ? seed.version.services : null,
ipv4: version === 4 ? seed.host : null,
ipv6: version === 6 ? seed.host : null,
port: seed.port || this.network.port
});
if (items.length === 1000)
@ -1289,7 +1339,7 @@ Peer.prototype.getHeaders = function getHeaders(locator, stop) {
this.host);
bcoin.debug('Height: %s, Hash: %s, Stop: %s',
locator && locator.length ? this.chain._getCachedHeight(locator[0]) : null,
locator && locator.length ? this.chain._getCachedHeight(locator[0]) : -1,
locator && locator.length ? utils.revHex(locator[0]) : 0,
stop ? utils.revHex(stop) : 0);

View File

@ -1018,10 +1018,6 @@ Pool.prototype._createPeer = function _createPeer(options) {
self.emit('version', version, peer);
});
peer.on('ack', function() {
peer.sendInv(self.inv.list);
});
return peer;
};
@ -2181,11 +2177,16 @@ Pool.prototype.isMisbehaving = function isMisbehaving(host) {
*/
Pool.prototype.reject = function reject(peer, obj, code, reason, score) {
var type;
if (obj) {
type = (obj instanceof bcoin.tx) ? 'tx' : 'block';
bcoin.debug('Rejecting %s %s from %s: ccode=%s reason=%s',
obj.type || 'block', obj.rhash, peer.host, code, reason);
type, obj.rhash, peer.host, code, reason);
peer.reject({
message: type,
ccode: code,
reason: reason,
data: obj.hash()
@ -2331,7 +2332,7 @@ function BroadcastItem(pool, item, callback) {
this.addCallback(callback);
this.start(item);
};
}
utils.inherits(BroadcastItem, EventEmitter);

View File

@ -377,6 +377,26 @@ Framer.prototype.addr = function addr(peers) {
return this.packet('addr', Framer.addr(peers));
};
/**
* Create an alert packet with a header.
* @param {Object} options - See {@link Framer.alert}.
* @returns {Buffer} alert packet.
*/
Framer.prototype.alert = function alert(options) {
return this.packet('alert', Framer.alert(options, this.network));
};
/**
* Create a feefilter packet with a header.
* @param {Object} options - See {@link Framer.feefilter}.
* @returns {Buffer} feefilter packet.
*/
Framer.prototype.feeFilter = function feeFilter(options) {
return this.packet('feefilter', Framer.feeFilter(options));
};
/**
* Serialize an address.
* @param {NetworkAddress} data
@ -432,6 +452,7 @@ Framer.version = function version(options, writer) {
var agent = options.agent || constants.USER_AGENT;
var remote = options.remote || {};
var local = options.local || {};
var nonce = options.nonce;
if (typeof agent === 'string')
agent = new Buffer(agent, 'ascii');
@ -445,12 +466,15 @@ Framer.version = function version(options, writer) {
if (local.services == null)
local.services = constants.LOCAL_SERVICES;
if (!nonce)
nonce = utils.nonce();
p.write32(constants.VERSION);
p.writeU64(constants.LOCAL_SERVICES);
p.write64(utils.now());
Framer.address(remote, false, p);
Framer.address(local, false, p);
p.writeU64(utils.nonce());
p.writeU64(nonce);
p.writeVarString(agent);
p.write32(options.height || 0);
p.writeU8(options.relay ? 1 : 0);
@ -1149,6 +1173,7 @@ Framer.addr = function addr(peers, writer) {
/**
* Create an alert packet (without a header).
* @param {AlertPacket} data
* @param {Network|NetworkType} network
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
*/
@ -1390,6 +1415,24 @@ Framer.filterClear = function filterClear() {
return DUMMY;
};
/**
* Create a feefilter packet (without a header).
* @param {FeeFilterPacket} data
* @param {BufferWriter?} writer - A buffer writer to continue writing from.
* @returns {Buffer} Returns a BufferWriter if `writer` was passed in.
*/
Framer.feeFilter = function feeFilter(data, network, writer) {
var p = new BufferWriter(writer);
p.write64(data.rate);
if (!writer)
p = p.render();
return p;
};
/**
* Calculate total block size and
* witness size without serializing.

View File

@ -111,11 +111,12 @@ Parser.prototype.parse = function parse(chunk) {
this.packet.payload = this.parsePayload(this.packet.cmd, this.packet.payload);
} catch (e) {
this.emit('error', e);
this.waiting = 24;
this.packet = null;
return;
}
if (this.packet.payload)
this.emit('packet', this.packet);
this.emit('packet', this.packet);
this.waiting = 24;
this.packet = null;
};
@ -349,6 +350,8 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) {
return Parser.parseGetUTXOs(p);
case 'utxos':
return Parser.parseUTXOs(p);
case 'feefilter':
return Parser.parseFeeFilter(p);
default:
bcoin.debug('Unknown packet: %s', cmd);
return p;
@ -472,16 +475,33 @@ Parser.parseVersion = function parseVersion(p) {
services = p.readU53();
ts = p.read53();
recv = Parser.parseAddress(p, false);
from = Parser.parseAddress(p, false);
nonce = p.readU64();
agent = p.readVarString('ascii');
height = p.read32();
if (p.left() > 0) {
from = Parser.parseAddress(p, false);
nonce = p.readU64();
} else {
from = {};
nonce = new bn(0);
}
if (p.left() > 0)
agent = p.readVarString('ascii', 256);
else
agent = '';
if (p.left() > 0)
height = p.read32();
else
height = 0;
if (p.left() > 0)
relay = p.readU8() === 1;
else
relay = true;
if (version === 10300)
version = 300;
assert(version >= 0, 'Version is negative.');
assert(ts >= 0, 'Timestamp is negative.');
assert(height >= 0, 'Height is negative.');
@ -1087,11 +1107,11 @@ Parser.parseReject = function parseReject(p) {
p = new BufferReader(p);
message = p.readVarString('ascii');
message = p.readVarString('ascii', 12);
ccode = p.readU8();
reason = p.readVarString('ascii');
reason = p.readVarString('ascii', 111);
if (p.left() >= 32)
if (message === 'block' || message === 'tx')
data = p.readHash('hex');
else
data = null;
@ -1227,6 +1247,19 @@ Parser.parseAlert = function parseAlert(p) {
};
};
/**
* Parse feefilter packet.
* @param {Buffer|BufferReader} p
* @returns {FeeFilterPacket}
*/
Parser.parseFeeFilter = function parseFeeFilter(p) {
p = new BufferReader(p);
return {
rate: p.read64N()
};
};
/*
* Expose
*/

View File

@ -543,11 +543,14 @@ BufferReader.prototype.readHash = function readHash(enc) {
/**
* Read string of a varint length.
* @param {String} enc - Any buffer-supported encoding.
* @param {Number?} limit - Size limit.
* @returns {String}
*/
BufferReader.prototype.readVarString = function readVarString(enc) {
return this.readString(enc, this.readVarint());
BufferReader.prototype.readVarString = function readVarString(enc, limit) {
var size = this.readVarint();
assert(!limit || size <= limit, 'String exceeds limit.');
return this.readString(enc, size);
};
/**

View File

@ -1515,6 +1515,20 @@ TX.prototype.getMinFee = function getMinFee(size, rate) {
return TX.getMinFee(size, rate);
};
/**
* Calculate the transaction's rate based on size
* and fees. Size will be calculated if not present.
* @param {Number?} size
* @returns {Rate}
*/
TX.prototype.getRate = function getRate(size) {
if (size == null)
size = this.maxSize();
return TX.getRate(size, this.getFee());
};
/**
* Calculate minimum fee based on rate and size.
* @param {Number?} size
@ -1536,6 +1550,17 @@ TX.getMinFee = function getMinFee(size, rate) {
return fee;
};
/**
* Calculate a fee rate based on size and fees.
* @param {Number} size
* @param {Amount} fee
* @returns {Rate}
*/
TX.getRate = function(size, fee) {
return Math.floor(fee * 1000 / size);
};
/**
* Calculate the minimum fee in order for the transaction
* to be relayable, but _round to the nearest kilobyte
@ -1723,6 +1748,7 @@ TX.prototype.inspect = function inspect() {
value: utils.btc(this.getOutputValue()),
fee: utils.btc(this.getFee()),
minFee: utils.btc(this.getMinFee()),
rate: utils.btc(this.getRate()),
confirmations: this.getConfirmations(),
priority: this.getPriority().priority.toString(10),
date: utils.date(this.ts || this.ps),

View File

@ -375,6 +375,12 @@
* @global
*/
/**
* @typedef {Object} FeeFilterPacket
* @property {Rate} rate
* @global
*/
/**
* One of `main`, `testnet`, `regtest`, `segnet3`, `segnet4`.
* @typedef {String} NetworkType