@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
156 lines • 8.6 kB
JavaScript
import { ExecutionStatus } from "@lodestar/fork-choice";
import { EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH } from "@lodestar/params";
import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
import { computeEpochAtSlot, isExecutionCachedStateType, isMergeTransitionComplete } from "@lodestar/state-transition";
import { ErrorAborted, prettyBytes, prettyBytesShort, sleep } from "@lodestar/utils";
import { ExecutionEngineState } from "../execution/index.js";
import { SyncState } from "../sync/index.js";
import { prettyTimeDiffSec } from "../util/time.js";
import { TimeSeries } from "../util/timeSeries.js";
/** Create a warning log whenever the peer count is at or below this value */
const WARN_PEER_COUNT = 1;
/**
* Runs a notifier service that periodically logs information about the node.
*/
export async function runNodeNotifier(modules) {
const { network, chain, sync, config, logger, signal } = modules;
const headSlotTimeSeries = new TimeSeries({ maxPoints: 10 });
const tdTimeSeries = new TimeSeries({ maxPoints: 50 });
const SLOTS_PER_SYNC_COMMITTEE_PERIOD = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
let hasLowPeerCount = false;
let isFirstTime = true;
try {
while (!signal.aborted) {
const connectedPeerCount = network.getConnectedPeerCount();
if (connectedPeerCount <= WARN_PEER_COUNT) {
// Only log once and prevent peer count warning on startup
if (!hasLowPeerCount && !isFirstTime) {
logger.warn("Low peer count", { peers: connectedPeerCount });
hasLowPeerCount = true;
}
}
else {
hasLowPeerCount = false;
}
const clockSlot = chain.clock.currentSlot;
const clockEpoch = computeEpochAtSlot(clockSlot);
if (clockEpoch >= config.BELLATRIX_FORK_EPOCH && computeStartSlotAtEpoch(clockEpoch) === clockSlot) {
if (chain.executionEngine.state === ExecutionEngineState.OFFLINE) {
logger.warn("Execution client is offline");
}
else if (chain.executionEngine.state === ExecutionEngineState.AUTH_FAILED) {
logger.error("Execution client authentication failed. Verify if the JWT secret matches on both clients");
}
}
const headInfo = chain.forkChoice.getHead();
const headState = chain.getHeadState();
const finalizedEpoch = headState.finalizedCheckpoint.epoch;
const finalizedRoot = headState.finalizedCheckpoint.root;
const headSlot = headInfo.slot;
headSlotTimeSeries.addPoint(headSlot, Math.floor(Date.now() / 1000));
const peersRow = `peers: ${connectedPeerCount}`;
const clockSlotRow = `slot: ${clockSlot}`;
// Give info about empty slots if head < clock
const skippedSlots = clockSlot - headInfo.slot;
// headDiffInfo to have space suffix if its a non empty string
const headDiffInfo = skippedSlots > 0 ? (skippedSlots > 1000 ? `${headInfo.slot} ` : `(slot -${skippedSlots}) `) : "";
const headRow = `head: ${headDiffInfo}${prettyBytes(headInfo.blockRoot)}`;
const executionInfo = getHeadExecutionInfo(config, clockEpoch, headState, headInfo);
const finalizedCheckpointRow = `finalized: ${prettyBytes(finalizedRoot)}:${finalizedEpoch}`;
// Log in TD progress in separate line to not clutter regular status update.
// This line will only exist between BELLATRIX_FORK_EPOCH and TTD, a window of some days / weeks max.
// Notifier log lines must be kept at a reasonable max width otherwise it's very hard to read
const tdProgress = chain.eth1.getTDProgress();
if (tdProgress !== null && !tdProgress.ttdHit) {
tdTimeSeries.addPoint(tdProgress.tdDiffScaled, tdProgress.timestamp);
const timestampTDD = tdTimeSeries.computeY0Point();
// It is possible to get ttd estimate with an error at imminent merge
const secToTTD = Math.max(Math.floor(timestampTDD - Date.now() / 1000), 0);
const timeLeft = Number.isFinite(secToTTD) ? prettyTimeDiffSec(secToTTD) : "?";
logger.info(`TTD in ${timeLeft} current TD ${tdProgress.td} / ${tdProgress.ttd}`);
}
let nodeState;
switch (sync.state) {
case SyncState.SyncingFinalized:
case SyncState.SyncingHead: {
const slotsPerSecond = Math.max(headSlotTimeSeries.computeLinearSpeed(), 0);
const distance = Math.max(clockSlot - headSlot, 0);
const secondsLeft = distance / slotsPerSecond;
const timeLeft = Number.isFinite(secondsLeft) ? prettyTimeDiffSec(secondsLeft) : "?";
// Syncing - time left - speed - head - finalized - clock - peers
nodeState = [
"Syncing",
`${timeLeft} left`,
`${slotsPerSecond.toPrecision(3)} slots/s`,
clockSlotRow,
headRow,
...executionInfo,
finalizedCheckpointRow,
peersRow,
];
break;
}
case SyncState.Synced: {
// Synced - clock - head - finalized - peers
nodeState = ["Synced", clockSlotRow, headRow, ...executionInfo, finalizedCheckpointRow, peersRow];
break;
}
case SyncState.Stalled: {
// Searching peers - peers - head - finalized - clock
nodeState = ["Searching peers", peersRow, clockSlotRow, headRow, ...executionInfo, finalizedCheckpointRow];
}
}
logger.info(nodeState.join(" - "));
// Log important chain time-based events
// Log sync committee change
if (clockEpoch > config.ALTAIR_FORK_EPOCH && clockSlot % SLOTS_PER_SYNC_COMMITTEE_PERIOD === 0) {
const period = Math.floor(clockEpoch / EPOCHS_PER_SYNC_COMMITTEE_PERIOD);
logger.info(`New sync committee period ${period}`);
}
// Log halfway through each slot
await sleep(timeToNextHalfSlot(config, chain, isFirstTime), signal);
isFirstTime = false;
}
}
catch (e) {
if (e instanceof ErrorAborted) {
return; // Ok
}
logger.error("Node notifier error", {}, e);
}
}
function timeToNextHalfSlot(config, chain, isFirstTime) {
const msPerSlot = config.SECONDS_PER_SLOT * 1000;
const msPerHalfSlot = msPerSlot / 2;
const msFromGenesis = Date.now() - chain.genesisTime * 1000;
const msToNextSlot = msFromGenesis < 0
? // For future genesis time, calculate time left in the slot
-msFromGenesis % msPerSlot
: // For past genesis time, calculate time until the next slot
msPerSlot - (msFromGenesis % msPerSlot);
if (isFirstTime) {
// at the 1st time we may miss middle of the current clock slot
return msToNextSlot > msPerHalfSlot ? msToNextSlot - msPerHalfSlot : msToNextSlot + msPerHalfSlot;
}
// after the 1st time always wait until middle of next clock slot
return msToNextSlot + msPerHalfSlot;
}
function getHeadExecutionInfo(config, clockEpoch, headState, headInfo) {
if (clockEpoch < config.BELLATRIX_FORK_EPOCH) {
return [];
}
const executionStatusStr = headInfo.executionStatus.toLowerCase();
// Add execution status to notifier only if head is on/post bellatrix
if (isExecutionCachedStateType(headState)) {
if (isMergeTransitionComplete(headState)) {
const executionPayloadHashInfo = headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadBlockHash : "empty";
const executionPayloadNumberInfo = headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadNumber : NaN;
return [
`exec-block: ${executionStatusStr}(${executionPayloadNumberInfo} ${prettyBytesShort(executionPayloadHashInfo)})`,
];
}
return [`exec-block: ${executionStatusStr}`];
}
return [];
}
//# sourceMappingURL=notifier.js.map