UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

159 lines 5.35 kB
import { GoodByeReasonCode } from "../../../constants/network.js"; import { COOL_DOWN_BEFORE_DECAY_MS, DEFAULT_SCORE, GOSSIPSUB_NEGATIVE_SCORE_WEIGHT, GOSSIPSUB_POSITIVE_SCORE_WEIGHT, HALFLIFE_DECAY_MS, MAX_SCORE, MIN_LODESTAR_SCORE_BEFORE_BAN, MIN_SCORE, NO_COOL_DOWN_APPLIED, } from "./constants.js"; import { ScoreState } from "./interface.js"; import { scoreToState } from "./utils.js"; /** * Manage score of a peer. */ export class RealScore { constructor() { this.lodestarScore = DEFAULT_SCORE; this.gossipScore = DEFAULT_SCORE; this.score = DEFAULT_SCORE; this.ignoreNegativeGossipScore = false; this.lastUpdate = Date.now(); } isCoolingDown() { return Date.now() < this.lastUpdate; } getScore() { return this.score; } getGossipScore() { return this.gossipScore; } add(scoreDelta) { let newScore = this.lodestarScore + scoreDelta; if (newScore > MAX_SCORE) newScore = MAX_SCORE; if (newScore < MIN_SCORE) newScore = MIN_SCORE; this.setLodestarScore(newScore); } applyReconnectionCoolDown(reason) { let coolDownMin = NO_COOL_DOWN_APPLIED; switch (reason) { // let scoring system handle score decay by itself case GoodByeReasonCode.BANNED: case GoodByeReasonCode.SCORE_TOO_LOW: return coolDownMin; case GoodByeReasonCode.INBOUND_DISCONNECT: case GoodByeReasonCode.TOO_MANY_PEERS: coolDownMin = 5; break; case GoodByeReasonCode.ERROR: case GoodByeReasonCode.CLIENT_SHUTDOWN: coolDownMin = 60; break; case GoodByeReasonCode.IRRELEVANT_NETWORK: coolDownMin = 240; break; } // set banning period to time in ms in the future from now this.lastUpdate = Date.now() + coolDownMin * 60 * 1000; return coolDownMin; } /** * Applies time-based logic such as decay rates to the score. * This function should be called periodically. * * Return the new score. */ update() { const nowMs = Date.now(); // Decay the current score // Using exponential decay based on a constant half life. const sinceLastUpdateMs = nowMs - this.lastUpdate; // If peer was banned, lastUpdate will be in the future if (sinceLastUpdateMs > 0) { this.lastUpdate = nowMs; // e^(-ln(2)/HL*t) const decayFactor = Math.exp(HALFLIFE_DECAY_MS * sinceLastUpdateMs); this.setLodestarScore(this.lodestarScore * decayFactor); } return this.lodestarScore; } updateGossipsubScore(newScore, ignore) { // we only update gossipsub if last_updated is in the past which means either the peer is // not banned or the BANNED_BEFORE_DECAY time is over. if (this.lastUpdate <= Date.now()) { this.gossipScore = newScore; this.ignoreNegativeGossipScore = ignore; } } getStat() { return { lodestarScore: this.lodestarScore, gossipScore: this.gossipScore, ignoreNegativeGossipScore: this.ignoreNegativeGossipScore, score: this.score, lastUpdate: this.lastUpdate, }; } /** * Updating lodestarScore should always go through this method, * so that we update this.score accordingly. */ setLodestarScore(newScore) { this.lodestarScore = newScore; this.updateState(); } /** * Compute the final score, ban peer if needed */ updateState() { const prevState = scoreToState(this.score); this.recomputeScore(); const newState = scoreToState(this.score); if (prevState !== ScoreState.Banned && newState === ScoreState.Banned) { // ban this peer for at least BANNED_BEFORE_DECAY_MS seconds this.lastUpdate = Date.now() + COOL_DOWN_BEFORE_DECAY_MS; } } /** * Compute the final score */ recomputeScore() { this.score = this.lodestarScore; if (this.score <= MIN_LODESTAR_SCORE_BEFORE_BAN) { // ignore all other scores, i.e. do nothing here return; } if (this.gossipScore >= 0) { this.score += this.gossipScore * GOSSIPSUB_POSITIVE_SCORE_WEIGHT; } else if (!this.ignoreNegativeGossipScore) { this.score += this.gossipScore * GOSSIPSUB_NEGATIVE_SCORE_WEIGHT; } } } /** An implementation of IPeerScore for testing */ export class MaxScore { getScore() { return MAX_SCORE; } getGossipScore() { return DEFAULT_SCORE; } isCoolingDown() { return false; } add() { } update() { return MAX_SCORE; } applyReconnectionCoolDown(_reason) { return NO_COOL_DOWN_APPLIED; } updateGossipsubScore() { } getStat() { return { lodestarScore: MAX_SCORE, gossipScore: DEFAULT_SCORE, ignoreNegativeGossipScore: false, score: MAX_SCORE, lastUpdate: Date.now(), }; } } //# sourceMappingURL=score.js.map