Skip to content

Commit

Permalink
Merge 5916733 into 2fcecd6
Browse files Browse the repository at this point in the history
  • Loading branch information
ensi321 committed Sep 17, 2024
2 parents 2fcecd6 + 5916733 commit 03f054b
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 66 deletions.
5 changes: 4 additions & 1 deletion packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
PubkeyIndexMap,
EpochShuffling,
computeEndSlotAtEpoch,
getSlotFractionFromInterval,
SlotInterval,
} from "@lodestar/state-transition";
import {BeaconConfig} from "@lodestar/config";
import {
Expand Down Expand Up @@ -226,7 +228,8 @@ export class BeaconChain implements IBeaconChain {

if (!clock) clock = new Clock({config, genesisTime: this.genesisTime, signal});

const preAggregateCutOffTime = (2 / 3) * this.config.SECONDS_PER_SLOT;
const preAggregateCutOffTime =
getSlotFractionFromInterval(SlotInterval.AGGREGATION_PROPAGATION) * this.config.SECONDS_PER_SLOT;
this.attestationPool = new AttestationPool(
config,
clock,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {phase0, Epoch, RootHex} from "@lodestar/types";
import {CachedBeaconStateAllForks, computeStartSlotAtEpoch, getBlockRootAtSlot} from "@lodestar/state-transition";
import {
CachedBeaconStateAllForks,
ONE_INTERVAL_OF_SLOT,
computeStartSlotAtEpoch,
getBlockRootAtSlot,
} from "@lodestar/state-transition";
import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils";
import {routes} from "@lodestar/api";
import {loadCachedBeaconState} from "@lodestar/state-transition";
import {INTERVALS_PER_SLOT} from "@lodestar/params";
import {Metrics} from "../../metrics/index.js";
import {IClock} from "../../util/clock.js";
import {ShufflingCache} from "../shufflingCache.js";
Expand Down Expand Up @@ -489,14 +493,14 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
}

const blockSlot = state.slot;
const twoThirdsSlot = (2 * state.config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT;
const twoIntervalsFromSlot = 2 * ONE_INTERVAL_OF_SLOT * state.config.SECONDS_PER_SLOT;
// we always have clock in production, fallback value is only for test
const secFromSlot = this.clock?.secFromSlot(blockSlot) ?? twoThirdsSlot;
const secToTwoThirdsSlot = twoThirdsSlot - secFromSlot;
if (secToTwoThirdsSlot > 0) {
const secFromSlot = this.clock?.secFromSlot(blockSlot) ?? twoIntervalsFromSlot;
const secToTwoIntervalsFromSlot = twoIntervalsFromSlot - secFromSlot;
if (secToTwoIntervalsFromSlot > 0) {
// 2/3 of slot is the most free time of every slot, take that chance to persist checkpoint states
// normally it should only persist checkpoint states at 2/3 of slot 0 of epoch
await sleep(secToTwoThirdsSlot * 1000, this.signal);
await sleep(secToTwoIntervalsFromSlot * 1000, this.signal);
} else if (!this.processLateBlock) {
// normally the block persist happens at 2/3 of slot 0 of epoch, if it's already late then just skip to allow other tasks to run
// there are plenty of chances in the same epoch to persist checkpoint states, also if block is late it could be reorged
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function validateLightClientFinalityUpdate(
}

// [IGNORE] The finality_update is received after the block at signature_slot was given enough time to propagate
// through the network -- i.e. validate that one-third of finality_update.signature_slot has transpired
// through the network -- i.e. validate that first interval (1 / INTERVALS_PER_SLOT) of finality_update.signature_slot has transpired
// (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
if (updateReceivedTooEarly(config, chain.genesisTime, gossipedFinalityUpdate)) {
throw new LightClientError(GossipAction.IGNORE, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ChainForkConfig} from "@lodestar/config";
import {computeTimeAtSlot} from "@lodestar/state-transition";
import {computeTimeAtSlot, ONE_INTERVAL_OF_SLOT} from "@lodestar/state-transition";
import {LightClientOptimisticUpdate} from "@lodestar/types";
import {IBeaconChain} from "../interface.js";
import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js";
Expand All @@ -26,7 +26,7 @@ export function validateLightClientOptimisticUpdate(
}

// [IGNORE] The optimistic_update is received after the block at signature_slot was given enough time to propagate
// through the network -- i.e. validate that one-third of optimistic_update.signature_slot has transpired
// through the network -- i.e. validate that one interval of optimistic_update.signature_slot has transpired
// (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
if (updateReceivedTooEarly(config, chain.genesisTime, gossipedOptimisticUpdate)) {
throw new LightClientError(GossipAction.IGNORE, {
Expand All @@ -53,15 +53,16 @@ export function validateLightClientOptimisticUpdate(
* xxx|------- (x is not okay)
*
* [IGNORE] The *update is received after the block at signature_slot was given enough time to propagate
* through the network -- i.e. validate that one-third of *update.signature_slot has transpired
* through the network -- i.e. validate that one interval of *update.signature_slot has transpired
* (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
*/
export function updateReceivedTooEarly(
config: ChainForkConfig,
genesisTime: number,
update: Pick<LightClientOptimisticUpdate, "signatureSlot">
): boolean {
const signatureSlot13TimestampMs = computeTimeAtSlot(config, update.signatureSlot + 1 / 3, genesisTime) * 1000;
const signatureSlot13TimestampMs =
computeTimeAtSlot(config, update.signatureSlot + ONE_INTERVAL_OF_SLOT, genesisTime) * 1000;
const earliestAllowedTimestampMs = signatureSlot13TimestampMs - MAXIMUM_GOSSIP_CLOCK_DISPARITY;
return Date.now() < earliestAllowedTimestampMs;
}
27 changes: 19 additions & 8 deletions packages/beacon-node/src/metrics/validatorMonitor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
computeEpochAtSlot,
computeTimeAtSlotInterval,
parseAttesterFlags,
CachedBeaconStateAllForks,
CachedBeaconStateAltair,
parseParticipationFlags,
computeStartSlotAtEpoch,
getBlockRootAtSlot,
ParticipationFlags,
SlotInterval,
} from "@lodestar/state-transition";
import {LogData, LogHandler, LogLevel, Logger, MapDef, MapDefMax, toRootHex} from "@lodestar/utils";
import {BeaconBlock, RootHex, altair, deneb} from "@lodestar/types";
Expand Down Expand Up @@ -384,7 +386,8 @@ export function createValidatorMonitor(
registerBeaconBlock(src, seenTimestampSec, block) {
const validator = validators.get(block.proposerIndex);
// Returns the delay between the start of `block.slot` and `seenTimestamp`.
const delaySec = seenTimestampSec - (genesisTime + block.slot * config.SECONDS_PER_SLOT);
const delaySec =
seenTimestampSec - computeTimeAtSlotInterval(config, block.slot, SlotInterval.BLOCK_PROPAGATION, genesisTime);
metrics.gossipBlock.elapsedTimeTillReceived.observe(delaySec);
if (validator) {
metrics.validatorMonitor.beaconBlockTotal.inc({src});
Expand Down Expand Up @@ -428,7 +431,9 @@ export function createValidatorMonitor(
onPoolSubmitUnaggregatedAttestation(seenTimestampSec, indexedAttestation, subnet, sentPeers) {
const data = indexedAttestation.data;
// Returns the duration between when the attestation `data` could be produced (1/3rd through the slot) and `seenTimestamp`.
const delaySec = seenTimestampSec - (genesisTime + (data.slot + 1 / 3) * config.SECONDS_PER_SLOT);
const delaySec =
seenTimestampSec -
computeTimeAtSlotInterval(config, data.slot, SlotInterval.ATTESTATION_PROPAGATION, genesisTime);
for (const index of indexedAttestation.attestingIndices) {
const validator = validators.get(index);
if (validator) {
Expand Down Expand Up @@ -460,8 +465,10 @@ export function createValidatorMonitor(
const src = OpSource.gossip;
const data = indexedAttestation.data;
const epoch = computeEpochAtSlot(data.slot);
// Returns the duration between when the attestation `data` could be produced (1/3rd through the slot) and `seenTimestamp`.
const delaySec = seenTimestampSec - (genesisTime + (data.slot + 1 / 3) * config.SECONDS_PER_SLOT);
// Returns the duration between when the attestation `data` could be produced (one interval through the slot) and `seenTimestamp`.
const delaySec =
seenTimestampSec -
computeTimeAtSlotInterval(config, data.slot, SlotInterval.ATTESTATION_PROPAGATION, genesisTime);

for (const index of indexedAttestation.attestingIndices) {
const validator = validators.get(index);
Expand All @@ -477,8 +484,10 @@ export function createValidatorMonitor(

onPoolSubmitAggregatedAttestation(seenTimestampSec, indexedAttestation, sentPeers) {
const data = indexedAttestation.data;
// Returns the duration between when a `AggregateAndproof` with `data` could be produced (2/3rd through the slot) and `seenTimestamp`.
const delaySec = seenTimestampSec - (genesisTime + (data.slot + 2 / 3) * config.SECONDS_PER_SLOT);
// Returns the duration between when a `AggregateAndproof` with `data` could be produced (two intervals through the slot) and `seenTimestamp`.
const delaySec =
seenTimestampSec -
computeTimeAtSlotInterval(config, data.slot, SlotInterval.AGGREGATION_PROPAGATION, genesisTime);

for (const index of indexedAttestation.attestingIndices) {
const validator = validators.get(index);
Expand All @@ -504,8 +513,10 @@ export function createValidatorMonitor(
const src = OpSource.gossip;
const data = indexedAttestation.data;
const epoch = computeEpochAtSlot(data.slot);
// Returns the duration between when a `AggregateAndProof` with `data` could be produced (2/3rd through the slot) and `seenTimestamp`.
const delaySec = seenTimestampSec - (genesisTime + (data.slot + 2 / 3) * config.SECONDS_PER_SLOT);
// Returns the duration between when a `AggregateAndProof` with `data` could be produced (two intervals through the slot) and `seenTimestamp`.
const delaySec =
seenTimestampSec -
computeTimeAtSlotInterval(config, data.slot, SlotInterval.AGGREGATION_PROPAGATION, genesisTime);

const aggregatorIndex = signedAggregateAndProof.message.aggregatorIndex;
const validatorAggregator = validators.get(aggregatorIndex);
Expand Down
14 changes: 7 additions & 7 deletions packages/beacon-node/src/network/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from "@lodestar/types";
import {routes} from "@lodestar/api";
import {ResponseIncoming} from "@lodestar/reqresp";
import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params";
import {ForkSeq, INTERVALS_PER_SLOT, MAX_BLOBS_PER_BLOCK} from "@lodestar/params";
import {Metrics, RegistryMetricCreator} from "../metrics/index.js";
import {IBeaconChain} from "../chain/index.js";
import {IBeaconDb} from "../db/interface.js";
Expand Down Expand Up @@ -588,9 +588,9 @@ export class Network implements INetwork {
// TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember())

try {
// messages SHOULD be broadcast after one-third of slot has transpired
// messages SHOULD be broadcast after one interval of slot has transpired
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#sync-committee
await this.waitOneThirdOfSlot(finalityUpdate.signatureSlot);
await this.waitOneIntervalOfSlot(finalityUpdate.signatureSlot);
await this.publishLightClientFinalityUpdate(finalityUpdate);
} catch (e) {
// Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
Expand All @@ -605,9 +605,9 @@ export class Network implements INetwork {
// TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember())

try {
// messages SHOULD be broadcast after one-third of slot has transpired
// messages SHOULD be broadcast after one interval of slot has transpired
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#sync-committee
await this.waitOneThirdOfSlot(optimisticUpdate.signatureSlot);
await this.waitOneIntervalOfSlot(optimisticUpdate.signatureSlot);
await this.publishLightClientOptimisticUpdate(optimisticUpdate);
} catch (e) {
// Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
Expand All @@ -618,8 +618,8 @@ export class Network implements INetwork {
}
};

private waitOneThirdOfSlot = async (slot: number): Promise<void> => {
const secAtSlot = computeTimeAtSlot(this.config, slot + 1 / 3, this.chain.genesisTime);
private waitOneIntervalOfSlot = async (slot: number): Promise<void> => {
const secAtSlot = computeTimeAtSlot(this.config, slot + 1 / INTERVALS_PER_SLOT, this.chain.genesisTime);
const msToSlot = secAtSlot * 1000 - Date.now();
await sleep(msToSlot, this.controller.signal);
};
Expand Down
1 change: 1 addition & 0 deletions packages/state-transition/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from "./shufflingDecisionRoot.js";
export * from "./signatureSets.js";
export * from "./signingRoot.js";
export * from "./slot.js";
export * from "./slotInterval.js";
export * from "./syncCommittee.js";
export * from "./validator.js";
export * from "./weakSubjectivity.js";
Expand Down
11 changes: 11 additions & 0 deletions packages/state-transition/src/util/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {ChainConfig} from "@lodestar/config";
import {GENESIS_SLOT, INTERVALS_PER_SLOT} from "@lodestar/params";
import {Slot, Epoch, TimeSeconds} from "@lodestar/types";
import {computeStartSlotAtEpoch, computeEpochAtSlot} from "./epoch.js";
import {SlotInterval, calculateSlotWithInterval} from "./slotInterval.js";

export function getSlotsSinceGenesis(config: ChainConfig, genesisTime: TimeSeconds): Slot {
const diffInSeconds = Date.now() / 1000 - genesisTime;
Expand All @@ -21,6 +22,16 @@ export function computeTimeAtSlot(config: ChainConfig, slot: Slot, genesisTime:
return genesisTime + slot * config.SECONDS_PER_SLOT;
}

/** Compute time in seconds of the beginnng of interval of slot */
export function computeTimeAtSlotInterval(
config: ChainConfig,
slot: Slot,
interval: SlotInterval,
genesisTime: TimeSeconds
): TimeSeconds {
return genesisTime + calculateSlotWithInterval(slot, interval) * config.SECONDS_PER_SLOT;
}

export function getCurrentInterval(config: ChainConfig, secondsIntoSlot: number): number {
const timePerInterval = Math.floor(config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT);
return Math.floor(secondsIntoSlot / timePerInterval);
Expand Down
39 changes: 39 additions & 0 deletions packages/state-transition/src/util/slotInterval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {INTERVALS_PER_SLOT} from "@lodestar/params";
import {Slot} from "@lodestar/types";
/**
* SlotInterval defines intervals within a slot.
* For example, attestation propagation happens at the beginning of interval 1 (second interval) of the slot
* whereas block propagation happens at the beginning of interval 0 (first interval) of the slot.
*
* This can also be interpret as seconds into the slot.
* For example, block propagation happens at (0 * SECONDS_PER_SLOT / INTERVALS_PER_SLOT) seconds into the slot
* and attestation propagation happens (1 * SECONDS_PER_SLOT / INTERVALS_PER_SLOT) seconds into the slot
*
* Some intervals might have several validator actions eg. aggregate and sync aggregate both happening at the beginning
* inteval 2.
*/
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
export enum SlotInterval {
BLOCK_PROPAGATION = 0,
SYNC_ATTESTATION_PROPAGATION = 0,
BEACON_COMMITTEE_SELECTION = 0,
ATTESTATION_PROPAGATION = 1,
SYNC_COMMITTEE_SELECTION = 1,
AGGREGATION_PROPAGATION = 2,
SYNC_AGGREGATE_PROPAGATION = 2,
}

export const ONE_INTERVAL_OF_SLOT = 1 / INTERVALS_PER_SLOT;

/** Return a decimal slot given slot and interval */
export function calculateSlotWithInterval(slot: Slot, interval: SlotInterval): Slot {
return slot + getSlotFractionFromInterval(interval);
}

/** Return a fraction of a slot given interval */
export function getSlotFractionFromInterval(interval: SlotInterval): Slot {
return interval / INTERVALS_PER_SLOT;
}
export function endOfInterval(interval: SlotInterval): Slot {
return interval + 1;
}
4 changes: 2 additions & 2 deletions packages/validator/src/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitD
syncCommitteeStepCallProduceMessage: register.histogram({
name: "vc_sync_committee_step_call_produce_message_seconds",
help: "Time between 1/3 of slot and call produce message",
// Max wait time is 1 / 3 of slot
// Max wait time is 1 / INTERVALS_PER_SLOT of slot
buckets: [0.5, 1, 2, 3, 6, 12],
}),
syncCommitteeStepCallPublishMessage: register.histogram({
Expand All @@ -86,7 +86,7 @@ export function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitD
syncCommitteeStepCallProduceAggregate: register.histogram({
name: "vc_sync_committee_step_call_produce_aggregate_seconds",
help: "Time between 2/3 of slot and call produce aggregate",
// Min wait time is 2 / 3 of slot
// Min wait time is 2 / INTERVAL_PER_SLOTS of slot
buckets: [0.5, 1, 2, 3, 6, 12],
}),
syncCommitteeStepCallPublishAggregate: register.histogram({
Expand Down
Loading

0 comments on commit 03f054b

Please sign in to comment.