@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
100 lines (87 loc) • 4.13 kB
text/typescript
import {ForkName, isForkPostFulu} from "@lodestar/params";
import {ForkDigest, Root, Slot, Status, fulu, ssz} from "@lodestar/types";
import {toHex, toRootHex} from "@lodestar/utils";
// TODO: Why this value? (From Lighthouse)
const FUTURE_SLOT_TOLERANCE = 1;
export enum IrrelevantPeerCode {
INCOMPATIBLE_FORKS = "IRRELEVANT_PEER_INCOMPATIBLE_FORKS",
DIFFERENT_CLOCKS = "IRRELEVANT_PEER_DIFFERENT_CLOCKS",
DIFFERENT_FINALIZED = "IRRELEVANT_PEER_DIFFERENT_FINALIZED",
NO_EARLIEST_AVAILABLE_SLOT = "NO_EARLIEST_AVAILABLE_SLOT",
}
type IrrelevantPeerType =
| {code: IrrelevantPeerCode.INCOMPATIBLE_FORKS; ours: ForkDigest; theirs: ForkDigest}
| {code: IrrelevantPeerCode.DIFFERENT_CLOCKS; slotDiff: number}
| {code: IrrelevantPeerCode.NO_EARLIEST_AVAILABLE_SLOT}
| {code: IrrelevantPeerCode.DIFFERENT_FINALIZED; expectedRoot: Root; remoteRoot: Root};
/**
* Process a `Status` message to determine if a peer is relevant to us. If the peer is
* irrelevant the reason is returned.
*/
export function assertPeerRelevance(
forkName: ForkName,
remote: Status,
local: Status,
currentSlot: Slot
): IrrelevantPeerType | null {
// The node is on a different network/fork
if (!ssz.ForkDigest.equals(local.forkDigest, remote.forkDigest)) {
return {
code: IrrelevantPeerCode.INCOMPATIBLE_FORKS,
ours: local.forkDigest,
theirs: remote.forkDigest,
};
}
// The remote's head is on a slot that is significantly ahead of what we consider the
// current slot. This could be because they are using a different genesis time, or that
// their or our system's clock is incorrect.
const slotDiff = remote.headSlot - Math.max(currentSlot, 0);
if (slotDiff > FUTURE_SLOT_TOLERANCE) {
return {code: IrrelevantPeerCode.DIFFERENT_CLOCKS, slotDiff};
}
// The remote's finalized epoch is less than or equal to ours, but the block root is
// different to the one in our chain. Therefore, the node is on a different chain and we
// should not communicate with them.
if (
remote.finalizedEpoch <= local.finalizedEpoch &&
!isZeroRoot(remote.finalizedRoot) &&
!isZeroRoot(local.finalizedRoot)
) {
// NOTE: due to preferring to not access chain state here, we can't check the finalized root against our history.
// The impact of not doing check is low: peers that are behind us we can't confirm they are in the same chain as us.
// In the worst case they will attempt to sync from us, fail and disconnect. The ENR fork check should be sufficient
// to differentiate most peers in normal network conditions.
const remoteRoot = remote.finalizedRoot;
const expectedRoot = remote.finalizedEpoch === local.finalizedEpoch ? local.finalizedRoot : null;
if (expectedRoot !== null && !ssz.Root.equals(remoteRoot, expectedRoot)) {
return {
code: IrrelevantPeerCode.DIFFERENT_FINALIZED,
expectedRoot: expectedRoot, // forkChoice returns Tree BranchNode which the logger prints as {}
remoteRoot: remoteRoot,
};
}
}
if (isForkPostFulu(forkName) && (remote as fulu.Status).earliestAvailableSlot === undefined) {
return {
code: IrrelevantPeerCode.NO_EARLIEST_AVAILABLE_SLOT,
};
}
// Note: Accept request status finalized checkpoint in the future, we do not know if it is a true finalized root
return null;
}
export function isZeroRoot(root: Root): boolean {
const ZERO_ROOT = ssz.Root.defaultValue();
return ssz.Root.equals(root, ZERO_ROOT);
}
export function renderIrrelevantPeerType(type: IrrelevantPeerType): string {
switch (type.code) {
case IrrelevantPeerCode.INCOMPATIBLE_FORKS:
return `INCOMPATIBLE_FORKS ours: ${toHex(type.ours)} theirs: ${toHex(type.theirs)}`;
case IrrelevantPeerCode.DIFFERENT_CLOCKS:
return `DIFFERENT_CLOCKS slotDiff: ${type.slotDiff}`;
case IrrelevantPeerCode.DIFFERENT_FINALIZED:
return `DIFFERENT_FINALIZED root: ${toRootHex(type.remoteRoot)} expected: ${toRootHex(type.expectedRoot)}`;
case IrrelevantPeerCode.NO_EARLIEST_AVAILABLE_SLOT:
return "No earliestAvailableSlot announced via peer Status";
}
}