@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
134 lines • 5.88 kB
JavaScript
import EventEmitter from "node:events";
import { computeEpochAtSlot, computeTimeAtSlot, getCurrentSlot } from "@lodestar/state-transition";
import { ErrorAborted } from "@lodestar/utils";
import { MAXIMUM_GOSSIP_CLOCK_DISPARITY } from "../constants/constants.js";
export var ClockEvent;
(function (ClockEvent) {
/**
* This event signals the start of a new slot, and that subsequent calls to `clock.currentSlot` will equal `slot`.
* This event is guaranteed to be emitted every `SECONDS_PER_SLOT` seconds.
*/
ClockEvent["slot"] = "clock:slot";
/**
* This event signals the start of a new epoch, and that subsequent calls to `clock.currentEpoch` will return `epoch`.
* This event is guaranteed to be emitted every `SECONDS_PER_SLOT * SLOTS_PER_EPOCH` seconds.
*/
ClockEvent["epoch"] = "clock:epoch";
})(ClockEvent || (ClockEvent = {}));
/**
* A local clock, the clock time is assumed to be trusted
*/
export class Clock extends EventEmitter {
constructor({ config, genesisTime, signal }) {
super();
this.onNextSlot = (slot) => {
const clockSlot = slot ?? getCurrentSlot(this.config, this.genesisTime);
// process multiple clock slots in the case the main thread has been saturated for > SECONDS_PER_SLOT
while (this._currentSlot < clockSlot && !this.signal.aborted) {
const previousSlot = this._currentSlot;
this._currentSlot++;
this.emit(ClockEvent.slot, this._currentSlot);
const previousEpoch = computeEpochAtSlot(previousSlot);
const currentEpoch = computeEpochAtSlot(this._currentSlot);
if (previousEpoch < currentEpoch) {
this.emit(ClockEvent.epoch, currentEpoch);
}
}
if (!this.signal.aborted) {
//recursively invoke onNextSlot
this.timeoutId = setTimeout(this.onNextSlot, this.msUntilNextSlot());
}
};
this.config = config;
this.genesisTime = genesisTime;
this.timeoutId = setTimeout(this.onNextSlot, this.msUntilNextSlot());
this.signal = signal;
this._currentSlot = getCurrentSlot(this.config, this.genesisTime);
this.signal.addEventListener("abort", () => clearTimeout(this.timeoutId), { once: true });
}
get currentSlot() {
const slot = getCurrentSlot(this.config, this.genesisTime);
if (slot > this._currentSlot) {
clearTimeout(this.timeoutId);
this.onNextSlot(slot);
}
return slot;
}
/**
* If it's too close to next slot given MAXIMUM_GOSSIP_CLOCK_DISPARITY, return currentSlot + 1.
* Otherwise return currentSlot
*/
get currentSlotWithGossipDisparity() {
const currentSlot = this.currentSlot;
const nextSlotTime = computeTimeAtSlot(this.config, currentSlot + 1, this.genesisTime) * 1000;
return nextSlotTime - Date.now() < MAXIMUM_GOSSIP_CLOCK_DISPARITY ? currentSlot + 1 : currentSlot;
}
get currentEpoch() {
return computeEpochAtSlot(this.currentSlot);
}
/** Returns the slot if the internal clock were advanced by `toleranceSec`. */
slotWithFutureTolerance(toleranceSec) {
// this is the same to getting slot at now + toleranceSec
return getCurrentSlot(this.config, this.genesisTime - toleranceSec);
}
/** Returns the slot if the internal clock were reversed by `toleranceSec`. */
slotWithPastTolerance(toleranceSec) {
// this is the same to getting slot at now - toleranceSec
return getCurrentSlot(this.config, this.genesisTime + toleranceSec);
}
/**
* Check if a slot is current slot given MAXIMUM_GOSSIP_CLOCK_DISPARITY.
*/
isCurrentSlotGivenGossipDisparity(slot) {
const currentSlot = this.currentSlot;
if (currentSlot === slot) {
return true;
}
const nextSlotTime = computeTimeAtSlot(this.config, currentSlot + 1, this.genesisTime) * 1000;
// we're too close to next slot, accept next slot
if (nextSlotTime - Date.now() < MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
return slot === currentSlot + 1;
}
const currentSlotTime = computeTimeAtSlot(this.config, currentSlot, this.genesisTime) * 1000;
// we've just passed the current slot, accept previous slot
if (Date.now() - currentSlotTime < MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
return slot === currentSlot - 1;
}
return false;
}
async waitForSlot(slot) {
if (this.signal.aborted) {
throw new ErrorAborted();
}
if (this.currentSlot >= slot) {
return;
}
return new Promise((resolve, reject) => {
const onSlot = (clockSlot) => {
if (clockSlot >= slot) {
onDone();
}
};
const onDone = () => {
this.off(ClockEvent.slot, onSlot);
this.signal.removeEventListener("abort", onAbort);
resolve();
};
const onAbort = () => {
this.off(ClockEvent.slot, onSlot);
reject(new ErrorAborted());
};
this.on(ClockEvent.slot, onSlot);
this.signal.addEventListener("abort", onAbort, { once: true });
});
}
secFromSlot(slot, toSec = Date.now() / 1000) {
return toSec - (this.genesisTime + slot * this.config.SECONDS_PER_SLOT);
}
msUntilNextSlot() {
const milliSecondsPerSlot = this.config.SECONDS_PER_SLOT * 1000;
const diffInMilliSeconds = Date.now() - this.genesisTime * 1000;
return milliSecondsPerSlot - (diffInMilliSeconds % milliSecondsPerSlot);
}
}
//# sourceMappingURL=clock.js.map