UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

138 lines 7.36 kB
import { computeEpochAtSlot, computeStartSlotAtEpoch } from "@lodestar/state-transition"; export { PeerSyncType }; /** The type of peer relative to our current state */ var PeerSyncType; (function (PeerSyncType) { /** The peer is on our chain and is fully synced with respect to our chain */ PeerSyncType["FullySynced"] = "FullySynced"; /** The peer has a greater knowledge of the chain than us that warrants a full sync */ PeerSyncType["Advanced"] = "Advanced"; /** A peer is behind in the sync and not useful to us for downloading blocks */ PeerSyncType["Behind"] = "Behind"; })(PeerSyncType || (PeerSyncType = {})); // Cache Object.keys iteration for faster loops in metrics export const peerSyncTypes = Object.keys(PeerSyncType); function withinRangeOf(value, target, range) { return value >= target - range && value <= target + range; } export function getPeerSyncType(local, remote, forkChoice, slotImportTolerance) { // Aux vars: Inclusive boundaries of the range to consider a peer's head synced to ours. const nearRangeStart = local.headSlot - slotImportTolerance; const nearRangeEnd = local.headSlot + slotImportTolerance; if (remote.finalizedEpoch < local.finalizedEpoch) { // The node has a lower finalized epoch, their chain is not useful to us. There are two // cases where a node can have a lower finalized epoch: // // ## The node is on the same chain // // If a node is on the same chain but has a lower finalized epoch, their head must be // lower than ours. Therefore, we have nothing to request from them. // // ## The node is on a fork // // If a node is on a fork that has a lower finalized epoch, switching to that fork would // cause us to revert a finalized block. This is not permitted, therefore we have no // interest in their blocks. // // We keep these peers to allow them to sync from us. return PeerSyncType.Behind; } if (remote.finalizedEpoch > local.finalizedEpoch) { if ( // Peer is in next epoch, and head is within range => SYNCED (local.finalizedEpoch + 1 === remote.finalizedEpoch && withinRangeOf(remote.headSlot, local.headSlot, slotImportTolerance)) || // Peer's head is known => SYNCED forkChoice.hasBlock(remote.headRoot)) { return PeerSyncType.FullySynced; } return PeerSyncType.Advanced; } // remote.finalizedEpoch == local.finalizedEpoch // NOTE: if a peer has our same `finalizedEpoch` with a different `finalized_root` // they are not considered relevant and won't be propagated to sync. // Check if the peer is the peer is inside the tolerance range to be considered synced. if (remote.headSlot < nearRangeStart) { return PeerSyncType.Behind; } if (remote.headSlot > nearRangeEnd && !forkChoice.hasBlock(remote.headRoot)) { // This peer has a head ahead enough of ours and we have no knowledge of their best block. return PeerSyncType.Advanced; } // This peer is either in the tolerance range, or ahead us with an already rejected block. return PeerSyncType.FullySynced; } export { RangeSyncType }; var RangeSyncType; (function (RangeSyncType) { /** A finalized chain sync should be started with this peer */ RangeSyncType["Finalized"] = "Finalized"; /** A head chain sync should be started with this peer */ RangeSyncType["Head"] = "Head"; })(RangeSyncType || (RangeSyncType = {})); // Cache Object.keys iteration for faster loops in metrics export const rangeSyncTypes = Object.keys(RangeSyncType); /** * Check if a peer requires a finalized chain sync. Only if: * - The remotes finalized epoch is greater than our current finalized epoch and we have * not seen the finalized hash before */ export function getRangeSyncType(local, remote, forkChoice) { if (remote.finalizedEpoch > local.finalizedEpoch && !forkChoice.hasBlock(remote.finalizedRoot)) { return RangeSyncType.Finalized; } return RangeSyncType.Head; } export function getRangeSyncTarget(local, remote, chain) { const forkChoice = chain.forkChoice; // finalized sync if (remote.finalizedEpoch > local.finalizedEpoch && !forkChoice.hasBlock(remote.finalizedRoot)) { return { // If RangeSyncType.Finalized, the range of blocks fetchable from startEpoch and target must allow to switch // to RangeSyncType.Head // // finalizedRoot is a block with slot <= computeStartSlotAtEpoch(finalizedEpoch). // If finalizedEpoch does not start with a skipped slot, the SyncChain with this target MUST process the // first block of the next epoch in order to trigger the condition above `forkChoice.hasBlock(remote.finalizedRoot)` // and do a Head sync. // // When doing a finalized sync, we'll process blocks up to the finalized checkpoint, which does not allow to // finalize that checkpoint. Instead, our head will be the finalized checkpoint and our finalized checkpoint will // be some older checkpoint. After completing a finalized SyncChain: // // (== finalized, -- non-finalized) // Remote ====================================================|----------------| // Local =====================================|--------------| syncType: RangeSyncType.Finalized, startEpoch: local.finalizedEpoch, target: { slot: computeStartSlotAtEpoch(remote.finalizedEpoch), root: remote.finalizedRoot, }, }; } // we don't want to sync from epoch < minEpoch // if we boot from an unfinalized checkpoint state, we don't want to sync before anchorStateLatestBlockSlot // if we boot from a finalized checkpoint state, anchorStateLatestBlockSlot is trusted and we also don't want to sync before it const minEpoch = Math.max(remote.finalizedEpoch, computeEpochAtSlot(chain.anchorStateLatestBlockSlot)); // head sync return { syncType: RangeSyncType.Head, // The new peer has the same finalized `remote.finalizedEpoch == local.finalizedEpoch` since // previous filters should prevent a peer with an earlier finalized chain from reaching here. // // By default and during stable network conditions, the head sync always starts from // the finalized epoch (even though it's the head sync) because finalized epoch is < local head. // This is to prevent the issue noted here https://github.com/ChainSafe/lodestar/pull/7509#discussion_r1984353063. // // During non-finality of the network, when starting from an unfinalized checkpoint state, we don't want // to sync before anchorStateLatestBlockSlot as finalized epoch is too far away. Local head will also be // the same to that value at startup, the head sync always starts from anchorStateLatestBlockSlot in this case. startEpoch: Math.min(computeEpochAtSlot(local.headSlot), minEpoch), target: { slot: remote.headSlot, root: remote.headRoot, }, }; } //# sourceMappingURL=remoteSyncType.js.map