@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
140 lines • 4.59 kB
JavaScript
import { LinkedList } from "../../../util/array.js";
import { DropType, QueueType } from "./types.js";
// Having a drop ratio of 1 will empty the queue which is too severe
// Worse case drop 95% of the queue
const MAX_DROP_RATIO = 0.95;
/**
* Default gossip queue for all topics except for beacon_attestation
* Support LIFO and FIFO type.
*/
export class LinearGossipQueue {
constructor(opts) {
this.opts = opts;
this.list = new LinkedList();
// Increase _dropRatio gradually, retest its initial value if node is in good status
this._dropRatio = 0;
// this is to avoid the case we drop 90% of the queue, then queue is empty and we consider
// node is in good status
this.recentDrop = false;
// set recentDrop to false after we process up to maxLength items
this.processedCountSinceDrop = 0;
if (opts.dropOpts.type === DropType.ratio) {
const { start, step } = opts.dropOpts;
if (start <= 0 || start > 1) {
throw Error(`Invalid drop ratio start ${start} step ${step}`);
}
this._dropRatio = opts.dropOpts.start;
}
}
get length() {
return this.list.length;
}
get keySize() {
// this implementation doesn't support indexing
return 1;
}
// not implemented for this gossip queue
getDataAgeMs() {
return [];
}
get dropRatio() {
return this._dropRatio;
}
clear() {
this.list.clear();
}
/**
* Add item to gossip queue.
* Return number of items dropped
*/
add(item) {
// this signals the node is not overloaded anymore
if (this.opts.dropOpts.type === DropType.ratio && !this.recentDrop && this.length === 0) {
// reset drop ratio to see if node is comfortable with it
this._dropRatio = this.opts.dropOpts.start;
}
this.list.push(item);
if (this.list.length <= this.opts.maxLength) {
return 0;
}
// overload, need to drop more items
if (this.opts.dropOpts.type === DropType.count) {
return this.dropByCount(this.opts.dropOpts.count);
}
this.recentDrop = true;
const droppedCount = this.dropByRatio(this._dropRatio);
// increase drop ratio the next time queue is full
this._dropRatio = Math.min(MAX_DROP_RATIO, this._dropRatio + this.opts.dropOpts.step);
return droppedCount;
}
next() {
let item = null;
// LIFO -> pop() remove last item, FIFO -> shift() remove first item
switch (this.opts.type) {
case QueueType.LIFO:
item = this.list.pop();
break;
case QueueType.FIFO:
item = this.list.shift();
break;
}
// it's ok to mark recent drop as false if we dropped <50% of the queue the last time
if (this.opts.dropOpts.type === DropType.ratio && this.recentDrop && item !== null) {
this.processedCountSinceDrop++;
if (this.processedCountSinceDrop >= this.opts.maxLength) {
this.recentDrop = false;
this.processedCountSinceDrop = 0;
}
}
return item;
}
getAll() {
return this.list.toArray();
}
/**
* Drop up to some ratio of items from the queue
* ratio is from 0 to 1 inclusive
* Return number of items dropped
*/
dropByRatio(ratio) {
if (ratio < 0 || ratio > 1) {
throw Error(`Invalid ratio ${ratio}`);
}
if (ratio === 0) {
return 0;
}
if (ratio === 1) {
const numDeleted = this.length;
this.clear();
return numDeleted;
}
const count = Math.floor(this.list.length * ratio);
return this.dropByCount(count);
}
/**
* Drop up to some number of items from the queue
* Return number of items dropped
*/
dropByCount(count) {
if (count <= 0) {
return 0;
}
if (count >= this.length) {
const numDeleted = this.length;
this.clear();
return numDeleted;
}
let i = 0;
while (i < count && this.length > 0) {
if (this.opts.type === QueueType.LIFO) {
this.list.shift();
}
else {
this.list.pop();
}
i++;
}
return i;
}
}
//# sourceMappingURL=linear.js.map