@lodestar/state-transition 1.41.0-dev.702f7932c2 → 1.41.0-dev.988363dff3
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/isValidIndexedPayloadAttestation.js +1 -1
- package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts +1 -1
- package/lib/block/processDepositRequest.d.ts.map +1 -1
- package/lib/block/processDepositRequest.js +6 -5
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
- package/lib/block/processExecutionPayloadBid.js +5 -0
- package/lib/block/processExecutionPayloadBid.js.map +1 -1
- package/lib/block/processExecutionPayloadEnvelope.js +0 -9
- package/lib/block/processExecutionPayloadEnvelope.js.map +1 -1
- package/lib/block/processWithdrawals.d.ts.map +1 -1
- package/lib/block/processWithdrawals.js +9 -1
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/cache/epochCache.d.ts +3 -3
- package/lib/cache/epochCache.d.ts.map +1 -1
- package/lib/cache/epochCache.js +13 -13
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/signatureSets/indexedPayloadAttestation.d.ts +3 -4
- package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -1
- package/lib/signatureSets/indexedPayloadAttestation.js +4 -4
- package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +50 -0
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +10 -3
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/seed.d.ts +20 -4
- package/lib/util/seed.d.ts.map +1 -1
- package/lib/util/seed.js +80 -8
- package/lib/util/seed.js.map +1 -1
- package/package.json +7 -7
- package/src/block/isValidIndexedPayloadAttestation.ts +1 -1
- package/src/block/processDepositRequest.ts +8 -5
- package/src/block/processExecutionPayloadBid.ts +8 -0
- package/src/block/processExecutionPayloadEnvelope.ts +0 -15
- package/src/block/processWithdrawals.ts +10 -1
- package/src/cache/epochCache.ts +24 -24
- package/src/signatureSets/indexedPayloadAttestation.ts +4 -6
- package/src/slot/upgradeStateToGloas.ts +74 -0
- package/src/util/gloas.ts +11 -3
- package/src/util/seed.ts +106 -18
package/src/cache/epochCache.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import {
|
|
37
37
|
computeActivationExitEpoch,
|
|
38
38
|
computeEpochAtSlot,
|
|
39
|
+
computePayloadTimelinessCommitteesForEpoch,
|
|
39
40
|
computeProposers,
|
|
40
41
|
computeSyncPeriodAtEpoch,
|
|
41
42
|
getActivationChurnLimit,
|
|
@@ -43,7 +44,6 @@ import {
|
|
|
43
44
|
getSeed,
|
|
44
45
|
isActiveValidator,
|
|
45
46
|
isAggregatorFromCommitteeLength,
|
|
46
|
-
naiveGetPayloadTimlinessCommitteeIndices,
|
|
47
47
|
} from "../util/index.js";
|
|
48
48
|
import {
|
|
49
49
|
AttesterDuty,
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
computeSyncCommitteeCache,
|
|
65
65
|
getSyncCommitteeCache,
|
|
66
66
|
} from "./syncCommitteeCache.js";
|
|
67
|
-
import {BeaconStateAllForks, BeaconStateAltair,
|
|
67
|
+
import {BeaconStateAllForks, BeaconStateAltair, ShufflingGetter} from "./types.js";
|
|
68
68
|
|
|
69
69
|
/** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
|
|
70
70
|
export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
|
|
@@ -235,8 +235,8 @@ export class EpochCache {
|
|
|
235
235
|
nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
236
236
|
|
|
237
237
|
// TODO GLOAS: See if we need to cached PTC for prev/next epoch
|
|
238
|
-
// PTC for current epoch
|
|
239
|
-
|
|
238
|
+
// PTC for current epoch, computed eagerly at epoch transition
|
|
239
|
+
payloadTimelinessCommittees: Uint32Array[];
|
|
240
240
|
|
|
241
241
|
// TODO: Helper stats
|
|
242
242
|
syncPeriod: SyncPeriod;
|
|
@@ -275,7 +275,7 @@ export class EpochCache {
|
|
|
275
275
|
previousTargetUnslashedBalanceIncrements: number;
|
|
276
276
|
currentSyncCommitteeIndexed: SyncCommitteeCache;
|
|
277
277
|
nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
278
|
-
|
|
278
|
+
payloadTimelinessCommittees: Uint32Array[];
|
|
279
279
|
epoch: Epoch;
|
|
280
280
|
syncPeriod: SyncPeriod;
|
|
281
281
|
}) {
|
|
@@ -306,7 +306,7 @@ export class EpochCache {
|
|
|
306
306
|
this.previousTargetUnslashedBalanceIncrements = data.previousTargetUnslashedBalanceIncrements;
|
|
307
307
|
this.currentSyncCommitteeIndexed = data.currentSyncCommitteeIndexed;
|
|
308
308
|
this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed;
|
|
309
|
-
this.
|
|
309
|
+
this.payloadTimelinessCommittees = data.payloadTimelinessCommittees;
|
|
310
310
|
this.epoch = data.epoch;
|
|
311
311
|
this.syncPeriod = data.syncPeriod;
|
|
312
312
|
}
|
|
@@ -457,14 +457,14 @@ export class EpochCache {
|
|
|
457
457
|
nextSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
// Compute PTC for
|
|
461
|
-
let
|
|
460
|
+
// Compute PTC eagerly for all slots in the epoch
|
|
461
|
+
let payloadTimelinessCommittees: Uint32Array[] = [];
|
|
462
462
|
if (currentEpoch >= config.GLOAS_FORK_EPOCH) {
|
|
463
|
-
|
|
464
|
-
state
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
463
|
+
payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
|
|
464
|
+
state,
|
|
465
|
+
currentEpoch,
|
|
466
|
+
currentShuffling.committees,
|
|
467
|
+
effectiveBalanceIncrements
|
|
468
468
|
);
|
|
469
469
|
}
|
|
470
470
|
|
|
@@ -541,7 +541,7 @@ export class EpochCache {
|
|
|
541
541
|
currentTargetUnslashedBalanceIncrements,
|
|
542
542
|
currentSyncCommitteeIndexed,
|
|
543
543
|
nextSyncCommitteeIndexed,
|
|
544
|
-
|
|
544
|
+
payloadTimelinessCommittees,
|
|
545
545
|
epoch: currentEpoch,
|
|
546
546
|
syncPeriod: computeSyncPeriodAtEpoch(currentEpoch),
|
|
547
547
|
});
|
|
@@ -587,7 +587,7 @@ export class EpochCache {
|
|
|
587
587
|
currentTargetUnslashedBalanceIncrements: this.currentTargetUnslashedBalanceIncrements,
|
|
588
588
|
currentSyncCommitteeIndexed: this.currentSyncCommitteeIndexed,
|
|
589
589
|
nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed,
|
|
590
|
-
|
|
590
|
+
payloadTimelinessCommittees: this.payloadTimelinessCommittees,
|
|
591
591
|
epoch: this.epoch,
|
|
592
592
|
syncPeriod: this.syncPeriod,
|
|
593
593
|
});
|
|
@@ -698,11 +698,11 @@ export class EpochCache {
|
|
|
698
698
|
|
|
699
699
|
this.proposersPrevEpoch = this.proposers;
|
|
700
700
|
if (upcomingEpoch >= this.config.GLOAS_FORK_EPOCH) {
|
|
701
|
-
this.
|
|
702
|
-
state
|
|
703
|
-
|
|
704
|
-
this.
|
|
705
|
-
|
|
701
|
+
this.payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
|
|
702
|
+
state,
|
|
703
|
+
upcomingEpoch,
|
|
704
|
+
this.currentShuffling.committees,
|
|
705
|
+
this.effectiveBalanceIncrements
|
|
706
706
|
);
|
|
707
707
|
}
|
|
708
708
|
if (upcomingEpoch >= this.config.FULU_FORK_EPOCH) {
|
|
@@ -1022,18 +1022,18 @@ export class EpochCache {
|
|
|
1022
1022
|
return this.epoch >= this.config.ELECTRA_FORK_EPOCH;
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
-
getPayloadTimelinessCommittee(slot: Slot):
|
|
1025
|
+
getPayloadTimelinessCommittee(slot: Slot): Uint32Array {
|
|
1026
1026
|
const epoch = computeEpochAtSlot(slot);
|
|
1027
1027
|
|
|
1028
1028
|
if (epoch < this.config.GLOAS_FORK_EPOCH) {
|
|
1029
1029
|
throw new Error("Payload Timeliness Committee is not available before gloas fork");
|
|
1030
1030
|
}
|
|
1031
1031
|
|
|
1032
|
-
if (epoch
|
|
1033
|
-
|
|
1032
|
+
if (epoch !== this.epoch) {
|
|
1033
|
+
throw new Error(`Payload Timeliness Committee is not available for slot=${slot}`);
|
|
1034
1034
|
}
|
|
1035
1035
|
|
|
1036
|
-
|
|
1036
|
+
return this.payloadTimelinessCommittees[slot % SLOTS_PER_EPOCH];
|
|
1037
1037
|
}
|
|
1038
1038
|
|
|
1039
1039
|
getIndexedPayloadAttestation(
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import {BeaconConfig} from "@lodestar/config";
|
|
2
2
|
import {DOMAIN_PTC_ATTESTER} from "@lodestar/params";
|
|
3
|
-
import {
|
|
4
|
-
import {CachedBeaconStateGloas} from "../types.js";
|
|
3
|
+
import {gloas, ssz} from "@lodestar/types";
|
|
5
4
|
import {ISignatureSet, computeSigningRoot, createAggregateSignatureSetFromComponents} from "../util/index.js";
|
|
6
5
|
|
|
7
6
|
export function getIndexedPayloadAttestationSignatureSet(
|
|
8
|
-
|
|
7
|
+
config: BeaconConfig,
|
|
9
8
|
indexedPayloadAttestation: gloas.IndexedPayloadAttestation
|
|
10
9
|
): ISignatureSet {
|
|
11
10
|
return createAggregateSignatureSetFromComponents(
|
|
12
11
|
indexedPayloadAttestation.attestingIndices,
|
|
13
|
-
getPayloadAttestationDataSigningRoot(
|
|
12
|
+
getPayloadAttestationDataSigningRoot(config, indexedPayloadAttestation.data),
|
|
14
13
|
indexedPayloadAttestation.signature
|
|
15
14
|
);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export function getPayloadAttestationDataSigningRoot(
|
|
19
18
|
config: BeaconConfig,
|
|
20
|
-
stateSlot: Slot,
|
|
21
19
|
data: gloas.PayloadAttestationData
|
|
22
20
|
): Uint8Array {
|
|
23
|
-
const domain = config.getDomain(
|
|
21
|
+
const domain = config.getDomain(data.slot, DOMAIN_PTC_ATTESTER);
|
|
24
22
|
|
|
25
23
|
return computeSigningRoot(ssz.gloas.PayloadAttestationData, data, domain);
|
|
26
24
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
2
|
import {ssz} from "@lodestar/types";
|
|
3
|
+
import {toHex} from "@lodestar/utils";
|
|
4
|
+
import {isValidDepositSignature} from "../block/processDeposit.js";
|
|
5
|
+
import {applyDepositForBuilder} from "../block/processDepositRequest.js";
|
|
3
6
|
import {getCachedBeaconState} from "../cache/stateCache.js";
|
|
4
7
|
import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
|
|
8
|
+
import {isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
9
|
+
import {isValidatorKnown} from "../util/index.js";
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Upgrade a state from Fulu to Gloas.
|
|
@@ -64,6 +69,9 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
64
69
|
|
|
65
70
|
const stateGloas = getCachedBeaconState(stateGloasView, stateFulu);
|
|
66
71
|
|
|
72
|
+
// Process pending builder deposits at the fork boundary
|
|
73
|
+
onboardBuildersFromPendingDeposits(stateGloas);
|
|
74
|
+
|
|
67
75
|
stateGloas.commit();
|
|
68
76
|
// Clear cache to ensure the cache of fulu fields is not used by new gloas fields
|
|
69
77
|
// biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute
|
|
@@ -71,3 +79,69 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
71
79
|
|
|
72
80
|
return stateGloas;
|
|
73
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Applies any pending deposits for builders to onboard builders during the fork transition
|
|
85
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.2/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
|
|
86
|
+
*/
|
|
87
|
+
function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void {
|
|
88
|
+
// Track pubkeys of new validators to keep their deposits pending
|
|
89
|
+
const validatorPubkeys = new Set<string>();
|
|
90
|
+
|
|
91
|
+
// Track pubkeys of new builders added when applying deposits
|
|
92
|
+
const builderPubkeys = new Set<string>();
|
|
93
|
+
|
|
94
|
+
const remainingPendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
|
|
95
|
+
for (let i = 0; i < state.pendingDeposits.length; i++) {
|
|
96
|
+
const deposit = state.pendingDeposits.getReadonly(i);
|
|
97
|
+
|
|
98
|
+
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
|
|
99
|
+
const pubkeyHex = toHex(deposit.pubkey);
|
|
100
|
+
|
|
101
|
+
// Deposits for existing validators stay in pending queue
|
|
102
|
+
if (isValidatorKnown(state, validatorIndex) || validatorPubkeys.has(pubkeyHex)) {
|
|
103
|
+
remainingPendingDeposits.push(deposit);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// If the pubkey is associated with a builder that was created in a previous iteration
|
|
108
|
+
// or it is a builder deposit, try to apply the deposit to the new/existing builder
|
|
109
|
+
const isExistingBuilder = builderPubkeys.has(pubkeyHex);
|
|
110
|
+
const hasBuilderCredentials = isBuilderWithdrawalCredential(deposit.withdrawalCredentials);
|
|
111
|
+
if (isExistingBuilder || hasBuilderCredentials) {
|
|
112
|
+
const buildersLenBefore = state.builders.length;
|
|
113
|
+
applyDepositForBuilder(
|
|
114
|
+
state,
|
|
115
|
+
deposit.pubkey,
|
|
116
|
+
deposit.withdrawalCredentials,
|
|
117
|
+
deposit.amount,
|
|
118
|
+
deposit.signature,
|
|
119
|
+
deposit.slot
|
|
120
|
+
);
|
|
121
|
+
// Track newly added builders for subsequent iterations
|
|
122
|
+
if (!isExistingBuilder && state.builders.length > buildersLenBefore) {
|
|
123
|
+
builderPubkeys.add(pubkeyHex);
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If there is a pending deposit for a new validator that has a valid signature, track the
|
|
129
|
+
// pubkey so that subsequent builder deposits for the same pubkey stay in pending (applied to
|
|
130
|
+
// the validator later) rather than creating a builder. Deposits with invalid signatures are
|
|
131
|
+
// dropped here since they would fail in apply_pending_deposit anyway.
|
|
132
|
+
if (
|
|
133
|
+
isValidDepositSignature(
|
|
134
|
+
state.config,
|
|
135
|
+
deposit.pubkey,
|
|
136
|
+
deposit.withdrawalCredentials,
|
|
137
|
+
deposit.amount,
|
|
138
|
+
deposit.signature
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
141
|
+
validatorPubkeys.add(pubkeyHex);
|
|
142
|
+
remainingPendingDeposits.push(deposit);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
state.pendingDeposits = remainingPendingDeposits;
|
|
147
|
+
}
|
package/src/util/gloas.ts
CHANGED
|
@@ -28,12 +28,18 @@ export function getBuilderPaymentQuorumThreshold(state: CachedBeaconStateGloas):
|
|
|
28
28
|
return Math.floor(quorum / BUILDER_PAYMENT_THRESHOLD_DENOMINATOR);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function hasBuilderIndexFlag(index: number): boolean {
|
|
32
|
+
// Equivalent to `(index & BUILDER_INDEX_FLAG) != 0`
|
|
33
|
+
return Math.floor(index / BUILDER_INDEX_FLAG) % 2 === 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* Check if a validator index represents a builder (has the builder flag set).
|
|
33
38
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-is_builder_index
|
|
34
39
|
*/
|
|
35
40
|
export function isBuilderIndex(validatorIndex: number): boolean {
|
|
36
|
-
|
|
41
|
+
// Note: Can't use bitwise AND (&) because BUILDER_INDEX_FLAG exceeds 32 bits in JS bitwise operations.
|
|
42
|
+
return hasBuilderIndexFlag(validatorIndex);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
@@ -41,7 +47,8 @@ export function isBuilderIndex(validatorIndex: number): boolean {
|
|
|
41
47
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-convert_builder_index_to_validator_index
|
|
42
48
|
*/
|
|
43
49
|
export function convertBuilderIndexToValidatorIndex(builderIndex: BuilderIndex): ValidatorIndex {
|
|
44
|
-
|
|
50
|
+
// Note: Can't use bitwise OR (|) because BUILDER_INDEX_FLAG exceeds 32 bits in JS bitwise operations.
|
|
51
|
+
return hasBuilderIndexFlag(builderIndex) ? builderIndex : builderIndex + BUILDER_INDEX_FLAG;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
/**
|
|
@@ -49,7 +56,8 @@ export function convertBuilderIndexToValidatorIndex(builderIndex: BuilderIndex):
|
|
|
49
56
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-convert_validator_index_to_builder_index
|
|
50
57
|
*/
|
|
51
58
|
export function convertValidatorIndexToBuilderIndex(validatorIndex: ValidatorIndex): BuilderIndex {
|
|
52
|
-
|
|
59
|
+
// Note: Can't use bitwise AND (&) because BUILDER_INDEX_FLAG exceeds 32 bits in JS bitwise operations.
|
|
60
|
+
return hasBuilderIndexFlag(validatorIndex) ? validatorIndex - BUILDER_INDEX_FLAG : validatorIndex;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
/**
|
package/src/util/seed.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import {Bytes32, DomainType, Epoch, ValidatorIndex} from "@lodestar/types";
|
|
22
22
|
import {assert, bytesToBigInt, bytesToInt, intToBytes} from "@lodestar/utils";
|
|
23
23
|
import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
|
|
24
|
-
import {BeaconStateAllForks,
|
|
24
|
+
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
|
|
25
25
|
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js";
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -268,29 +268,117 @@ export function getNextSyncCommitteeIndices(
|
|
|
268
268
|
);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Compute PTC for all slots in an epoch eagerly.
|
|
273
|
+
*/
|
|
274
|
+
export function computePayloadTimelinessCommitteesForEpoch(
|
|
275
|
+
state: BeaconStateAllForks,
|
|
276
|
+
epoch: number,
|
|
277
|
+
committees: Uint32Array[][],
|
|
278
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
279
|
+
): Uint32Array[] {
|
|
277
280
|
const epochSeed = getSeed(state, epoch, DOMAIN_PTC_ATTESTER);
|
|
278
|
-
const startSlot =
|
|
279
|
-
const
|
|
281
|
+
const startSlot = epoch * SLOTS_PER_EPOCH;
|
|
282
|
+
const result: Uint32Array[] = new Array(SLOTS_PER_EPOCH);
|
|
283
|
+
|
|
284
|
+
// Pre-allocate slot seed buffer once, reuse across all slots
|
|
285
|
+
const slotSeedInput = new Uint8Array(epochSeed.length + 8);
|
|
286
|
+
slotSeedInput.set(epochSeed, 0);
|
|
287
|
+
const slotSeedView = new DataView(slotSeedInput.buffer, slotSeedInput.byteOffset, slotSeedInput.byteLength);
|
|
288
|
+
|
|
289
|
+
for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
|
|
290
|
+
const slot = startSlot + i;
|
|
291
|
+
// Write slot as little-endian uint64 (fits in uint32 range)
|
|
292
|
+
slotSeedView.setUint32(epochSeed.length, slot, true);
|
|
293
|
+
slotSeedView.setUint32(epochSeed.length + 4, 0, true);
|
|
294
|
+
const slotSeed = digest(slotSeedInput);
|
|
295
|
+
|
|
296
|
+
result[i] = computePayloadTimelinessCommitteeForSlot(slotSeed, committees[i], effectiveBalanceIncrements);
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
280
300
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Compute PTC for a single slot.
|
|
303
|
+
*/
|
|
304
|
+
export function computePayloadTimelinessCommitteeForSlot(
|
|
305
|
+
slotSeed: Uint8Array,
|
|
306
|
+
slotCommittees: Uint32Array[],
|
|
307
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements
|
|
308
|
+
): Uint32Array {
|
|
309
|
+
// Concatenate all committee Uint32Arrays for this slot
|
|
310
|
+
const totalLen = slotCommittees.reduce((sum, c) => sum + c.length, 0);
|
|
311
|
+
const allIndices = new Uint32Array(totalLen);
|
|
312
|
+
let offset = 0;
|
|
313
|
+
for (const c of slotCommittees) {
|
|
314
|
+
allIndices.set(c, offset);
|
|
315
|
+
offset += c.length;
|
|
316
|
+
}
|
|
317
|
+
return computePayloadTimelinessCommitteeIndices(effectiveBalanceIncrements, allIndices, slotSeed);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Optimized version of PTC indices computation.
|
|
322
|
+
* Avoids BigInt conversions and uses DataView for efficient byte reading.
|
|
323
|
+
*/
|
|
324
|
+
export function computePayloadTimelinessCommitteeIndices(
|
|
325
|
+
effectiveBalanceIncrements: EffectiveBalanceIncrements,
|
|
326
|
+
indices: Uint32Array,
|
|
327
|
+
seed: Uint8Array
|
|
328
|
+
): Uint32Array {
|
|
329
|
+
if (indices.length === 0) {
|
|
330
|
+
throw Error("Validator indices must not be empty");
|
|
289
331
|
}
|
|
290
332
|
|
|
291
|
-
|
|
333
|
+
const result = new Uint32Array(PTC_SIZE);
|
|
334
|
+
let resultLen = 0;
|
|
335
|
+
|
|
336
|
+
const MAX_RANDOM_VALUE = 0xffff; // 2^16 - 1
|
|
337
|
+
const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT;
|
|
338
|
+
const indicesLen = indices.length;
|
|
339
|
+
|
|
340
|
+
// Pre-allocate hash input buffer: seed + 8 bytes for block index
|
|
341
|
+
const hashInput = new Uint8Array(seed.length + 8);
|
|
342
|
+
hashInput.set(seed, 0);
|
|
343
|
+
const hashInputView = new DataView(hashInput.buffer, hashInput.byteOffset, hashInput.byteLength);
|
|
344
|
+
const seedLen = seed.length;
|
|
345
|
+
|
|
346
|
+
let i = 0;
|
|
347
|
+
let randomBytesView: DataView = new DataView(new ArrayBuffer(0));
|
|
348
|
+
let lastBlock = -1;
|
|
349
|
+
|
|
350
|
+
while (resultLen < PTC_SIZE) {
|
|
351
|
+
const candidateIndex = indices[i % indicesLen];
|
|
352
|
+
|
|
353
|
+
// Only recompute hash every 16 iterations
|
|
354
|
+
const block = i >>> 4; // Math.floor(i / 16)
|
|
355
|
+
if (block !== lastBlock) {
|
|
356
|
+
// Write block as little-endian uint64 (block always fits in uint32 range)
|
|
357
|
+
hashInputView.setUint32(seedLen, block, true);
|
|
358
|
+
hashInputView.setUint32(seedLen + 4, 0, true);
|
|
359
|
+
const randomBytes = digest(hashInput);
|
|
360
|
+
randomBytesView = new DataView(randomBytes.buffer, randomBytes.byteOffset, randomBytes.byteLength);
|
|
361
|
+
lastBlock = block;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const randomValue = randomBytesView.getUint16((i & 15) * 2, true);
|
|
365
|
+
|
|
366
|
+
const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex];
|
|
367
|
+
if (effectiveBalanceIncrement * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomValue) {
|
|
368
|
+
result[resultLen++] = candidateIndex;
|
|
369
|
+
}
|
|
370
|
+
i += 1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return result;
|
|
292
374
|
}
|
|
293
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Naive version of PTC indices computation.
|
|
378
|
+
* Used to verify the optimized `computePayloadTimelinessCommitteeIndices`.
|
|
379
|
+
*
|
|
380
|
+
* SLOW CODE - 🐢
|
|
381
|
+
*/
|
|
294
382
|
export function naiveComputePayloadTimelinessCommitteeIndices(
|
|
295
383
|
effectiveBalanceIncrements: EffectiveBalanceIncrements,
|
|
296
384
|
indices: ArrayLike<ValidatorIndex>,
|