@lodestar/state-transition 1.46.0-dev.1cd9a671ea → 1.46.0-dev.40d3816715
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/block/index.d.ts.map +1 -1
- package/lib/block/index.js +3 -2
- package/lib/block/index.js.map +1 -1
- package/lib/block/processAttestations.d.ts +2 -2
- package/lib/block/processAttestations.d.ts.map +1 -1
- package/lib/block/processAttestations.js +2 -2
- package/lib/block/processAttestations.js.map +1 -1
- package/lib/block/processAttestationsAltair.d.ts +3 -3
- package/lib/block/processAttestationsAltair.d.ts.map +1 -1
- package/lib/block/processAttestationsAltair.js +7 -4
- package/lib/block/processAttestationsAltair.js.map +1 -1
- package/lib/block/processExecutionPayloadBid.d.ts +2 -2
- package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
- package/lib/block/processExecutionPayloadBid.js +2 -0
- package/lib/block/processExecutionPayloadBid.js.map +1 -1
- package/lib/block/processOperations.d.ts +2 -2
- package/lib/block/processOperations.d.ts.map +1 -1
- package/lib/block/processOperations.js +2 -2
- package/lib/block/processOperations.js.map +1 -1
- package/lib/block/processWithdrawalRequest.d.ts.map +1 -1
- package/lib/block/processWithdrawalRequest.js +2 -2
- package/lib/block/processWithdrawalRequest.js.map +1 -1
- package/lib/cache/epochCache.d.ts.map +1 -1
- package/lib/cache/epochCache.js +18 -19
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/rewards/blockRewards.d.ts.map +1 -1
- package/lib/rewards/blockRewards.js +7 -4
- package/lib/rewards/blockRewards.js.map +1 -1
- package/lib/slot/upgradeStateToAltair.js +1 -1
- package/lib/slot/upgradeStateToAltair.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +1 -1
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/util/clock.d.ts +55 -0
- package/lib/util/clock.d.ts.map +1 -0
- package/lib/util/clock.js +142 -0
- package/lib/util/clock.js.map +1 -0
- package/lib/util/index.d.ts +1 -0
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +1 -0
- package/lib/util/index.js.map +1 -1
- package/package.json +13 -13
- package/src/block/index.ts +4 -3
- package/src/block/processAttestations.ts +10 -2
- package/src/block/processAttestationsAltair.ts +10 -4
- package/src/block/processExecutionPayloadBid.ts +5 -2
- package/src/block/processOperations.ts +3 -2
- package/src/block/processWithdrawalRequest.ts +6 -2
- package/src/cache/epochCache.ts +19 -22
- package/src/rewards/blockRewards.ts +20 -5
- package/src/slot/upgradeStateToAltair.ts +1 -0
- package/src/slot/upgradeStateToGloas.ts +1 -1
- package/src/util/clock.ts +183 -0
- package/src/util/index.ts +1 -0
package/src/cache/epochCache.ts
CHANGED
|
@@ -408,12 +408,17 @@ export class EpochCache {
|
|
|
408
408
|
|
|
409
409
|
const nextShuffling = cachedNextShuffling ?? computeEpochShuffling(state, nextActiveIndices, nextEpoch);
|
|
410
410
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
let proposers: number[];
|
|
411
|
+
let proposers: ValidatorIndex[];
|
|
412
|
+
let proposersNextEpoch: ProposersDeferred;
|
|
414
413
|
if (currentEpoch >= config.FULU_FORK_EPOCH) {
|
|
415
|
-
//
|
|
416
|
-
|
|
414
|
+
// After fulu, use state.proposerLookahead for current and next epoch proposers.
|
|
415
|
+
// Computing from the unfiltered active shuffling would include slashed validators in gloas.
|
|
416
|
+
const proposerLookahead = (state as CachedBeaconStateFulu).proposerLookahead.getAll();
|
|
417
|
+
proposers = proposerLookahead.slice(0, SLOTS_PER_EPOCH);
|
|
418
|
+
proposersNextEpoch = {
|
|
419
|
+
computed: true,
|
|
420
|
+
indexes: proposerLookahead.slice(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH * 2),
|
|
421
|
+
};
|
|
417
422
|
} else {
|
|
418
423
|
// We need to calculate Pre-fulu
|
|
419
424
|
// Allow to create CachedBeaconState for empty states, or no active validators
|
|
@@ -421,18 +426,17 @@ export class EpochCache {
|
|
|
421
426
|
currentShuffling.activeIndices.length > 0
|
|
422
427
|
? computeProposers(
|
|
423
428
|
config.getForkSeqAtEpoch(currentEpoch),
|
|
424
|
-
|
|
429
|
+
getSeed(state, currentEpoch, DOMAIN_BEACON_PROPOSER),
|
|
425
430
|
currentShuffling,
|
|
426
431
|
effectiveBalanceIncrements
|
|
427
432
|
)
|
|
428
433
|
: [];
|
|
434
|
+
proposersNextEpoch = {
|
|
435
|
+
computed: false,
|
|
436
|
+
seed: getSeed(state, nextEpoch, DOMAIN_BEACON_PROPOSER),
|
|
437
|
+
};
|
|
429
438
|
}
|
|
430
439
|
|
|
431
|
-
const proposersNextEpoch: ProposersDeferred = {
|
|
432
|
-
computed: false,
|
|
433
|
-
seed: getSeed(state, nextEpoch, DOMAIN_BEACON_PROPOSER),
|
|
434
|
-
};
|
|
435
|
-
|
|
436
440
|
// Only after altair, compute the indices of the current sync committee
|
|
437
441
|
const afterAltairFork = currentEpoch >= config.ALTAIR_FORK_EPOCH;
|
|
438
442
|
|
|
@@ -713,17 +717,10 @@ export class EpochCache {
|
|
|
713
717
|
// Populate proposer cache with lookahead from state
|
|
714
718
|
const proposerLookahead = (state as CachedBeaconStateFulu).proposerLookahead.getAll();
|
|
715
719
|
this.proposers = proposerLookahead.slice(0, SLOTS_PER_EPOCH);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
indexes: proposerLookahead.slice(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH * 2),
|
|
721
|
-
};
|
|
722
|
-
} else {
|
|
723
|
-
// This should not happen unless MIN_SEED_LOOKAHEAD is set to 0
|
|
724
|
-
// this ensures things don't break if the proposer lookahead is not long enough
|
|
725
|
-
this.proposersNextEpoch = {computed: false, seed: getSeed(state, epochAfterUpcoming, DOMAIN_BEACON_PROPOSER)};
|
|
726
|
-
}
|
|
720
|
+
this.proposersNextEpoch = {
|
|
721
|
+
computed: true,
|
|
722
|
+
indexes: proposerLookahead.slice(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH * 2),
|
|
723
|
+
};
|
|
727
724
|
} else {
|
|
728
725
|
// Need to calculate proposers pre-fulu
|
|
729
726
|
const upcomingProposerSeed = getSeed(state, upcomingEpoch, DOMAIN_BEACON_PROPOSER);
|
|
@@ -4,11 +4,17 @@ import {
|
|
|
4
4
|
WHISTLEBLOWER_REWARD_QUOTIENT,
|
|
5
5
|
WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA,
|
|
6
6
|
isForkPostElectra,
|
|
7
|
+
isForkPostGloas,
|
|
7
8
|
} from "@lodestar/params";
|
|
8
|
-
import {BeaconBlock, altair, phase0, rewards} from "@lodestar/types";
|
|
9
|
+
import {BeaconBlock, Slot, altair, phase0, rewards} from "@lodestar/types";
|
|
9
10
|
import {processAttestationsAltair} from "../block/processAttestationsAltair.js";
|
|
10
11
|
import {RewardCache} from "../cache/rewardCache.js";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
CachedBeaconStateAllForks,
|
|
14
|
+
CachedBeaconStateAltair,
|
|
15
|
+
CachedBeaconStateGloas,
|
|
16
|
+
CachedBeaconStatePhase0,
|
|
17
|
+
} from "../cache/stateCache.js";
|
|
12
18
|
import {getAttesterSlashableIndices} from "../util/attestation.js";
|
|
13
19
|
|
|
14
20
|
type SubRewardValue = number; // All reward values should be integer
|
|
@@ -36,10 +42,18 @@ export async function computeBlockRewards(
|
|
|
36
42
|
let syncAggregateReward = cachedSyncAggregateReward;
|
|
37
43
|
|
|
38
44
|
if (blockAttestationReward === 0) {
|
|
45
|
+
const parentSlot = isForkPostGloas(fork)
|
|
46
|
+
? (preState as CachedBeaconStateGloas).latestExecutionPayloadBid.slot
|
|
47
|
+
: null;
|
|
39
48
|
blockAttestationReward =
|
|
40
49
|
fork === ForkName.phase0
|
|
41
50
|
? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, preState as CachedBeaconStatePhase0)
|
|
42
|
-
: computeBlockAttestationRewardAltair(
|
|
51
|
+
: computeBlockAttestationRewardAltair(
|
|
52
|
+
config,
|
|
53
|
+
block as altair.BeaconBlock,
|
|
54
|
+
preState as CachedBeaconStateAltair,
|
|
55
|
+
parentSlot
|
|
56
|
+
);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
if (syncAggregateReward === 0) {
|
|
@@ -79,12 +93,13 @@ function computeBlockAttestationRewardPhase0(
|
|
|
79
93
|
function computeBlockAttestationRewardAltair(
|
|
80
94
|
config: BeaconConfig,
|
|
81
95
|
block: altair.BeaconBlock,
|
|
82
|
-
preState: CachedBeaconStateAltair
|
|
96
|
+
preState: CachedBeaconStateAltair,
|
|
97
|
+
parentSlot: Slot | null
|
|
83
98
|
): SubRewardValue {
|
|
84
99
|
const fork = config.getForkSeq(block.slot);
|
|
85
100
|
const {attestations} = block.body;
|
|
86
101
|
|
|
87
|
-
processAttestationsAltair(fork, preState, attestations, false);
|
|
102
|
+
processAttestationsAltair(fork, preState, attestations, parentSlot, false);
|
|
88
103
|
|
|
89
104
|
return preState.proposerRewards.attestations;
|
|
90
105
|
}
|
|
@@ -70,7 +70,7 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
70
70
|
stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
|
|
71
71
|
stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
|
|
72
72
|
stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
|
|
73
|
-
stateGloasView.latestExecutionPayloadBid.gasLimit = stateFulu.latestExecutionPayloadHeader.gasLimit;
|
|
73
|
+
stateGloasView.latestExecutionPayloadBid.gasLimit = BigInt(stateFulu.latestExecutionPayloadHeader.gasLimit);
|
|
74
74
|
stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(
|
|
75
75
|
ssz.gloas.ExecutionRequests.defaultValue()
|
|
76
76
|
);
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
+
import {GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
3
|
+
import {Epoch, Slot, TimeSeconds} from "@lodestar/types";
|
|
4
|
+
import {ErrorAborted, Logger, isErrorAborted, sleep} from "@lodestar/utils";
|
|
5
|
+
import {computeEpochAtSlot} from "./epoch.js";
|
|
6
|
+
import {computeTimeAtSlot, getCurrentSlot} from "./slot.js";
|
|
7
|
+
|
|
8
|
+
type RunEveryFn = (slot: Slot, signal: AbortSignal) => Promise<void>;
|
|
9
|
+
|
|
10
|
+
export type ClockOptions = {
|
|
11
|
+
skipSlots?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface IClock {
|
|
15
|
+
readonly genesisTime: number;
|
|
16
|
+
readonly secondsPerSlot: number;
|
|
17
|
+
|
|
18
|
+
readonly currentEpoch: number;
|
|
19
|
+
|
|
20
|
+
start(signal: AbortSignal): void;
|
|
21
|
+
runEverySlot(fn: (slot: Slot, signal: AbortSignal) => Promise<void>): void;
|
|
22
|
+
runEveryEpoch(fn: (epoch: Epoch, signal: AbortSignal) => Promise<void>): void;
|
|
23
|
+
msToSlot(slot: Slot): number;
|
|
24
|
+
msFromSlot(slot: Slot): number;
|
|
25
|
+
secFromSlot(slot: Slot): number;
|
|
26
|
+
getCurrentSlot(): Slot;
|
|
27
|
+
getCurrentEpoch(): Epoch;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum TimeItem {
|
|
31
|
+
Slot,
|
|
32
|
+
Epoch,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Clock implements IClock {
|
|
36
|
+
readonly genesisTime: number;
|
|
37
|
+
readonly secondsPerSlot: number;
|
|
38
|
+
private readonly config: ChainForkConfig;
|
|
39
|
+
private readonly logger: Logger;
|
|
40
|
+
private readonly fns: {timeItem: TimeItem; fn: RunEveryFn}[] = [];
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
config: ChainForkConfig,
|
|
44
|
+
logger: Logger,
|
|
45
|
+
private readonly opts: {genesisTime: number} & ClockOptions
|
|
46
|
+
) {
|
|
47
|
+
this.genesisTime = opts.genesisTime;
|
|
48
|
+
this.secondsPerSlot = config.SLOT_DURATION_MS / 1000;
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.logger = logger;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get currentEpoch(): Epoch {
|
|
54
|
+
return computeEpochAtSlot(getCurrentSlot(this.config, this.genesisTime));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
start(signal: AbortSignal): void {
|
|
58
|
+
for (const {timeItem, fn} of this.fns) {
|
|
59
|
+
this.runAtMostEvery(timeItem, signal, fn).catch((e: Error) => {
|
|
60
|
+
if (!isErrorAborted(e)) {
|
|
61
|
+
this.logger.error("runAtMostEvery", {}, e);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getCurrentSlot(): Slot {
|
|
68
|
+
return getCurrentSlot(this.config, this.genesisTime);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getCurrentEpoch(): Epoch {
|
|
72
|
+
return computeEpochAtSlot(getCurrentSlot(this.config, this.genesisTime));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
runEverySlot(fn: RunEveryFn): void {
|
|
76
|
+
this.fns.push({timeItem: TimeItem.Slot, fn});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
runEveryEpoch(fn: RunEveryFn): void {
|
|
80
|
+
this.fns.push({timeItem: TimeItem.Epoch, fn});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Milliseconds from now to a specific slot */
|
|
84
|
+
msToSlot(slot: Slot): number {
|
|
85
|
+
return computeTimeAtSlot(this.config, slot, this.genesisTime) * 1000 - Date.now();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Milliseconds elapsed from a specific slot to now */
|
|
89
|
+
msFromSlot(slot: Slot): number {
|
|
90
|
+
return Date.now() - computeTimeAtSlot(this.config, slot, this.genesisTime) * 1000;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Seconds elapsed from a specific slot to now */
|
|
94
|
+
secFromSlot(slot: Slot): number {
|
|
95
|
+
return Date.now() / 1000 - computeTimeAtSlot(this.config, slot, this.genesisTime);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* If a task happens to take more than one slot to run, we might skip a slot. This is unfortunate,
|
|
100
|
+
* however the alternative is to *always* process every slot, which has the chance of creating a
|
|
101
|
+
* theoretically unlimited backlog of tasks. It was a conscious decision to choose to drop tasks
|
|
102
|
+
* on an overloaded/latent system rather than overload it even more.
|
|
103
|
+
*/
|
|
104
|
+
private async runAtMostEvery(timeItem: TimeItem, signal: AbortSignal, fn: RunEveryFn): Promise<void> {
|
|
105
|
+
// Run immediately first
|
|
106
|
+
let slot = getCurrentSlot(this.config, this.genesisTime);
|
|
107
|
+
let slotOrEpoch = timeItem === TimeItem.Slot ? slot : computeEpochAtSlot(slot);
|
|
108
|
+
while (!signal.aborted) {
|
|
109
|
+
// Must catch fn() to ensure `sleep()` is awaited both for resolve and reject
|
|
110
|
+
const task = fn(slotOrEpoch, signal).catch((e: Error) => {
|
|
111
|
+
if (!isErrorAborted(e)) this.logger.error("Error on runEvery fn", {}, e);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (timeItem !== TimeItem.Slot || this.opts.skipSlots !== false) {
|
|
115
|
+
// await response to only continue with next task if current task finished within slot
|
|
116
|
+
await task;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await sleep(this.timeUntilNext(timeItem), signal);
|
|
121
|
+
// calling getCurrentSlot here may not be correct when we're close to the next slot
|
|
122
|
+
// it's safe to call getCurrentSlotAround after we sleep
|
|
123
|
+
const nextSlot = getCurrentSlotAround(this.config, this.genesisTime);
|
|
124
|
+
|
|
125
|
+
if (timeItem === TimeItem.Slot) {
|
|
126
|
+
if (nextSlot > slot + 1) {
|
|
127
|
+
// It's not very likely that we skip more than one slot as HTTP timeout is set
|
|
128
|
+
// to SLOT_DURATION_MS so we will fail task before skipping another slot.
|
|
129
|
+
this.logger.warn("Skipped slot due to task taking more than one slot to run", {
|
|
130
|
+
skippedSlot: slot + 1,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
slotOrEpoch = nextSlot;
|
|
134
|
+
} else {
|
|
135
|
+
slotOrEpoch = computeEpochAtSlot(nextSlot);
|
|
136
|
+
}
|
|
137
|
+
slot = nextSlot;
|
|
138
|
+
} catch (e) {
|
|
139
|
+
if (e instanceof ErrorAborted) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private timeUntilNext(timeItem: TimeItem): number {
|
|
148
|
+
const milliSecondsPerSlot = this.config.SLOT_DURATION_MS;
|
|
149
|
+
const msFromGenesis = Date.now() - this.genesisTime * 1000;
|
|
150
|
+
|
|
151
|
+
if (timeItem === TimeItem.Slot) {
|
|
152
|
+
if (msFromGenesis >= 0) {
|
|
153
|
+
return milliSecondsPerSlot - (msFromGenesis % milliSecondsPerSlot);
|
|
154
|
+
}
|
|
155
|
+
return Math.abs(msFromGenesis) % milliSecondsPerSlot;
|
|
156
|
+
}
|
|
157
|
+
const milliSecondsPerEpoch = SLOTS_PER_EPOCH * milliSecondsPerSlot;
|
|
158
|
+
if (msFromGenesis >= 0) {
|
|
159
|
+
return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch);
|
|
160
|
+
}
|
|
161
|
+
return Math.abs(msFromGenesis) % milliSecondsPerEpoch;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Same to the spec but we use Math.round instead of Math.floor.
|
|
167
|
+
*/
|
|
168
|
+
export function getCurrentSlotAround(config: ChainForkConfig, genesisTime: TimeSeconds): Slot {
|
|
169
|
+
const diffInSeconds = Date.now() / 1000 - genesisTime;
|
|
170
|
+
const slotsSinceGenesis = Math.round((diffInSeconds * 1000) / config.SLOT_DURATION_MS);
|
|
171
|
+
return GENESIS_SLOT + slotsSinceGenesis;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// function useEventStream() {
|
|
175
|
+
// this.stream = this.events.getEventStream([BeaconEventType.BLOCK, BeaconEventType.HEAD, BeaconEventType.CHAIN_REORG]);
|
|
176
|
+
// pipeToEmitter(this.stream, this).catch((e: Error) => {
|
|
177
|
+
// this.logger.error("Error on stream pipe", {}, e);
|
|
178
|
+
// });
|
|
179
|
+
|
|
180
|
+
// // On stop
|
|
181
|
+
// this.stream.stop();
|
|
182
|
+
// this.stream = null;
|
|
183
|
+
// }
|
package/src/util/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./balance.js";
|
|
|
6
6
|
export * from "./blindedBlock.js";
|
|
7
7
|
export * from "./blockRoot.js";
|
|
8
8
|
export * from "./capella.js";
|
|
9
|
+
export * from "./clock.js";
|
|
9
10
|
export * from "./computeAnchorCheckpoint.js";
|
|
10
11
|
export * from "./deposit.js";
|
|
11
12
|
export * from "./domain.js";
|