@lodestar/state-transition 1.37.0 → 1.38.0-dev.255e56fb68

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.
Files changed (139) hide show
  1. package/lib/block/index.d.ts +4 -1
  2. package/lib/block/index.d.ts.map +1 -1
  3. package/lib/block/index.js +19 -8
  4. package/lib/block/index.js.map +1 -1
  5. package/lib/block/isValidIndexedPayloadAttestation.d.ts +4 -0
  6. package/lib/block/isValidIndexedPayloadAttestation.d.ts.map +1 -0
  7. package/lib/block/isValidIndexedPayloadAttestation.js +14 -0
  8. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -0
  9. package/lib/block/processAttestationPhase0.d.ts.map +1 -1
  10. package/lib/block/processAttestationPhase0.js +6 -1
  11. package/lib/block/processAttestationPhase0.js.map +1 -1
  12. package/lib/block/processAttestationsAltair.d.ts +3 -3
  13. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  14. package/lib/block/processAttestationsAltair.js +46 -4
  15. package/lib/block/processAttestationsAltair.js.map +1 -1
  16. package/lib/block/processConsolidationRequest.d.ts +3 -2
  17. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  18. package/lib/block/processConsolidationRequest.js +2 -2
  19. package/lib/block/processConsolidationRequest.js.map +1 -1
  20. package/lib/block/processDepositRequest.d.ts +2 -2
  21. package/lib/block/processDepositRequest.d.ts.map +1 -1
  22. package/lib/block/processDepositRequest.js.map +1 -1
  23. package/lib/block/processExecutionPayloadBid.d.ts +5 -0
  24. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -0
  25. package/lib/block/processExecutionPayloadBid.js +89 -0
  26. package/lib/block/processExecutionPayloadBid.js.map +1 -0
  27. package/lib/block/processExecutionPayloadEnvelope.d.ts +4 -0
  28. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -0
  29. package/lib/block/processExecutionPayloadEnvelope.js +118 -0
  30. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -0
  31. package/lib/block/processOperations.d.ts.map +1 -1
  32. package/lib/block/processOperations.js +8 -2
  33. package/lib/block/processOperations.js.map +1 -1
  34. package/lib/block/processPayloadAttestation.d.ts +4 -0
  35. package/lib/block/processPayloadAttestation.d.ts.map +1 -0
  36. package/lib/block/processPayloadAttestation.js +16 -0
  37. package/lib/block/processPayloadAttestation.js.map +1 -0
  38. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  39. package/lib/block/processProposerSlashing.js +16 -1
  40. package/lib/block/processProposerSlashing.js.map +1 -1
  41. package/lib/block/processVoluntaryExit.js +1 -1
  42. package/lib/block/processVoluntaryExit.js.map +1 -1
  43. package/lib/block/processWithdrawalRequest.d.ts +2 -2
  44. package/lib/block/processWithdrawalRequest.d.ts.map +1 -1
  45. package/lib/block/processWithdrawalRequest.js +1 -1
  46. package/lib/block/processWithdrawalRequest.js.map +1 -1
  47. package/lib/block/processWithdrawals.d.ts +4 -3
  48. package/lib/block/processWithdrawals.d.ts.map +1 -1
  49. package/lib/block/processWithdrawals.js +89 -19
  50. package/lib/block/processWithdrawals.js.map +1 -1
  51. package/lib/cache/epochCache.d.ts +5 -1
  52. package/lib/cache/epochCache.d.ts.map +1 -1
  53. package/lib/cache/epochCache.js +34 -1
  54. package/lib/cache/epochCache.js.map +1 -1
  55. package/lib/epoch/index.d.ts +4 -2
  56. package/lib/epoch/index.d.ts.map +1 -1
  57. package/lib/epoch/index.js +10 -1
  58. package/lib/epoch/index.js.map +1 -1
  59. package/lib/epoch/processBuilderPendingPayments.d.ts +6 -0
  60. package/lib/epoch/processBuilderPendingPayments.d.ts.map +1 -0
  61. package/lib/epoch/processBuilderPendingPayments.js +28 -0
  62. package/lib/epoch/processBuilderPendingPayments.js.map +1 -0
  63. package/lib/index.d.ts +1 -1
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js.map +1 -1
  66. package/lib/signatureSets/index.d.ts +3 -2
  67. package/lib/signatureSets/index.d.ts.map +1 -1
  68. package/lib/signatureSets/index.js +3 -2
  69. package/lib/signatureSets/index.js.map +1 -1
  70. package/lib/signatureSets/indexedAttestation.d.ts +1 -1
  71. package/lib/signatureSets/indexedAttestation.d.ts.map +1 -1
  72. package/lib/signatureSets/indexedAttestation.js +5 -3
  73. package/lib/signatureSets/indexedAttestation.js.map +1 -1
  74. package/lib/signatureSets/indexedPayloadAttestation.d.ts +6 -0
  75. package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -0
  76. package/lib/signatureSets/indexedPayloadAttestation.js +11 -0
  77. package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -0
  78. package/lib/slot/index.d.ts +2 -1
  79. package/lib/slot/index.d.ts.map +1 -1
  80. package/lib/slot/index.js +6 -2
  81. package/lib/slot/index.js.map +1 -1
  82. package/lib/slot/upgradeStateToAltair.js +1 -1
  83. package/lib/slot/upgradeStateToAltair.js.map +1 -1
  84. package/lib/slot/upgradeStateToGloas.d.ts +0 -1
  85. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  86. package/lib/slot/upgradeStateToGloas.js +47 -5
  87. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  88. package/lib/stateTransition.js +4 -3
  89. package/lib/stateTransition.js.map +1 -1
  90. package/lib/util/electra.d.ts +5 -5
  91. package/lib/util/electra.d.ts.map +1 -1
  92. package/lib/util/electra.js +2 -1
  93. package/lib/util/electra.js.map +1 -1
  94. package/lib/util/epoch.d.ts +3 -3
  95. package/lib/util/epoch.d.ts.map +1 -1
  96. package/lib/util/epoch.js.map +1 -1
  97. package/lib/util/gloas.d.ts +11 -0
  98. package/lib/util/gloas.d.ts.map +1 -0
  99. package/lib/util/gloas.js +35 -0
  100. package/lib/util/gloas.js.map +1 -0
  101. package/lib/util/seed.d.ts +5 -1
  102. package/lib/util/seed.d.ts.map +1 -1
  103. package/lib/util/seed.js +33 -1
  104. package/lib/util/seed.js.map +1 -1
  105. package/lib/util/validator.d.ts +2 -2
  106. package/lib/util/validator.d.ts.map +1 -1
  107. package/lib/util/validator.js +14 -1
  108. package/lib/util/validator.js.map +1 -1
  109. package/package.json +6 -6
  110. package/src/block/index.ts +35 -14
  111. package/src/block/isValidIndexedPayloadAttestation.ts +23 -0
  112. package/src/block/processAttestationPhase0.ts +5 -1
  113. package/src/block/processAttestationsAltair.ts +62 -5
  114. package/src/block/processConsolidationRequest.ts +6 -5
  115. package/src/block/processDepositRequest.ts +5 -2
  116. package/src/block/processExecutionPayloadBid.ts +120 -0
  117. package/src/block/processExecutionPayloadEnvelope.ts +181 -0
  118. package/src/block/processOperations.ts +16 -4
  119. package/src/block/processPayloadAttestation.ts +25 -0
  120. package/src/block/processProposerSlashing.ts +24 -3
  121. package/src/block/processVoluntaryExit.ts +1 -1
  122. package/src/block/processWithdrawalRequest.ts +4 -4
  123. package/src/block/processWithdrawals.ts +118 -27
  124. package/src/cache/epochCache.ts +58 -1
  125. package/src/epoch/index.ts +12 -0
  126. package/src/epoch/processBuilderPendingPayments.ts +31 -0
  127. package/src/index.ts +2 -0
  128. package/src/signatureSets/index.ts +4 -2
  129. package/src/signatureSets/indexedAttestation.ts +8 -8
  130. package/src/signatureSets/indexedPayloadAttestation.ts +24 -0
  131. package/src/slot/index.ts +11 -3
  132. package/src/slot/upgradeStateToAltair.ts +2 -1
  133. package/src/slot/upgradeStateToGloas.ts +49 -5
  134. package/src/stateTransition.ts +4 -4
  135. package/src/util/electra.ts +15 -6
  136. package/src/util/epoch.ts +6 -3
  137. package/src/util/gloas.ts +58 -0
  138. package/src/util/seed.ts +57 -1
  139. package/src/util/validator.ts +21 -2
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.37.0",
14
+ "version": "1.38.0-dev.255e56fb68",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -62,10 +62,10 @@
62
62
  "@chainsafe/pubkey-index-map": "^3.0.0",
63
63
  "@chainsafe/ssz": "^1.2.2",
64
64
  "@chainsafe/swap-or-not-shuffle": "^1.2.1",
65
- "@lodestar/config": "^1.37.0",
66
- "@lodestar/params": "^1.37.0",
67
- "@lodestar/types": "^1.37.0",
68
- "@lodestar/utils": "^1.37.0",
65
+ "@lodestar/config": "1.38.0-dev.255e56fb68",
66
+ "@lodestar/params": "1.38.0-dev.255e56fb68",
67
+ "@lodestar/types": "1.38.0-dev.255e56fb68",
68
+ "@lodestar/utils": "1.38.0-dev.255e56fb68",
69
69
  "bigint-buffer": "^1.1.5"
70
70
  },
71
71
  "keywords": [
@@ -74,5 +74,5 @@
74
74
  "beacon",
75
75
  "blockchain"
76
76
  ],
77
- "gitHead": "eaf5bc974a483104665b6ed57b1a15e2fd48f730"
77
+ "gitHead": "a6f5bc4398f508565cd5cf856c0d6946571d5465"
78
78
  }
@@ -1,14 +1,22 @@
1
- import {ForkSeq} from "@lodestar/params";
1
+ import {ForkPostGloas, ForkSeq} from "@lodestar/params";
2
2
  import {BeaconBlock, BlindedBeaconBlock, altair, capella} from "@lodestar/types";
3
3
  import {BeaconStateTransitionMetrics} from "../metrics.js";
4
- import {CachedBeaconStateAllForks, CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js";
4
+ import {
5
+ CachedBeaconStateAllForks,
6
+ CachedBeaconStateBellatrix,
7
+ CachedBeaconStateCapella,
8
+ CachedBeaconStateGloas,
9
+ } from "../types.js";
5
10
  import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js";
6
11
  import {BlockExternalData, DataAvailabilityStatus} from "./externalData.js";
7
12
  import {processBlobKzgCommitments} from "./processBlobKzgCommitments.js";
8
13
  import {processBlockHeader} from "./processBlockHeader.js";
9
14
  import {processEth1Data} from "./processEth1Data.js";
10
15
  import {processExecutionPayload} from "./processExecutionPayload.js";
16
+ import {processExecutionPayloadBid} from "./processExecutionPayloadBid.ts";
17
+ import {processExecutionPayloadEnvelope} from "./processExecutionPayloadEnvelope.ts";
11
18
  import {processOperations} from "./processOperations.js";
19
+ import {processPayloadAttestation} from "./processPayloadAttestation.ts";
12
20
  import {processRandao} from "./processRandao.js";
13
21
  import {processSyncAggregate} from "./processSyncCommittee.js";
14
22
  import {processWithdrawals} from "./processWithdrawals.js";
@@ -22,6 +30,9 @@ export {
22
30
  processEth1Data,
23
31
  processSyncAggregate,
24
32
  processWithdrawals,
33
+ processExecutionPayloadBid,
34
+ processPayloadAttestation,
35
+ processExecutionPayloadEnvelope,
25
36
  };
26
37
 
27
38
  export * from "./externalData.js";
@@ -41,23 +52,33 @@ export function processBlock(
41
52
 
42
53
  processBlockHeader(state, block);
43
54
 
44
- // The call to the process_execution_payload must happen before the call to the process_randao as the former depends
45
- // on the randao_mix computed with the reveal of the previous block.
46
- if (fork >= ForkSeq.bellatrix && isExecutionEnabled(state as CachedBeaconStateBellatrix, block)) {
55
+ if (fork >= ForkSeq.gloas) {
56
+ // After gloas, processWithdrawals does not take a payload parameter
57
+ processWithdrawals(fork, state as CachedBeaconStateGloas);
58
+ } else if (fork >= ForkSeq.capella) {
47
59
  const fullOrBlindedPayload = getFullOrBlindedPayload(block);
48
- // TODO Deneb: Allow to disable withdrawals for interop testing
49
- // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals
50
- if (fork >= ForkSeq.capella) {
51
- processWithdrawals(
52
- fork,
53
- state as CachedBeaconStateCapella,
54
- fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload
55
- );
56
- }
60
+ processWithdrawals(
61
+ fork,
62
+ state as CachedBeaconStateCapella,
63
+ fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload
64
+ );
65
+ }
57
66
 
67
+ // The call to the process_execution_payload must happen before the call to the process_randao as the former depends
68
+ // on the randao_mix computed with the reveal of the previous block.
69
+ // TODO GLOAS: We call processExecutionPayload somewhere else post-gloas
70
+ if (
71
+ fork >= ForkSeq.bellatrix &&
72
+ fork < ForkSeq.gloas &&
73
+ isExecutionEnabled(state as CachedBeaconStateBellatrix, block)
74
+ ) {
58
75
  processExecutionPayload(fork, state as CachedBeaconStateBellatrix, block.body, externalData);
59
76
  }
60
77
 
78
+ if (fork >= ForkSeq.gloas) {
79
+ processExecutionPayloadBid(state as CachedBeaconStateGloas, block as BeaconBlock<ForkPostGloas>);
80
+ }
81
+
61
82
  processRandao(state, block, verifySignatures);
62
83
  processEth1Data(state, block.body.eth1Data);
63
84
  processOperations(fork, state, block.body, opts, metrics);
@@ -0,0 +1,23 @@
1
+ import {gloas} from "@lodestar/types";
2
+ import {getIndexedPayloadAttestationSignatureSet} from "../signatureSets/index.ts";
3
+ import {CachedBeaconStateGloas} from "../types.js";
4
+ import {verifySignatureSet} from "../util/index.ts";
5
+
6
+ export function isValidIndexedPayloadAttestation(
7
+ state: CachedBeaconStateGloas,
8
+ indexedPayloadAttestation: gloas.IndexedPayloadAttestation,
9
+ verifySignature: boolean
10
+ ): boolean {
11
+ const indices = indexedPayloadAttestation.attestingIndices;
12
+ const isSorted = indices.every((val, i, arr) => i === 0 || arr[i - 1] <= val);
13
+
14
+ if (indices.length === 0 || !isSorted) {
15
+ return false;
16
+ }
17
+
18
+ if (verifySignature) {
19
+ return verifySignatureSet(getIndexedPayloadAttestationSignatureSet(state, indexedPayloadAttestation));
20
+ }
21
+
22
+ return true;
23
+ }
@@ -86,7 +86,11 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo
86
86
  }
87
87
 
88
88
  if (fork >= ForkSeq.electra) {
89
- assert.equal(data.index, 0, `AttestationData.index must be zero: index=${data.index}`);
89
+ if (fork >= ForkSeq.gloas) {
90
+ assert.lt(data.index, 2, `AttestationData.index must be 0 or 1: index=${data.index}`);
91
+ } else {
92
+ assert.equal(data.index, 0, `AttestationData.index must be 0: index=${data.index}`);
93
+ }
90
94
  const attestationElectra = attestation as electra.Attestation;
91
95
  const committeeIndices = attestationElectra.committeeBits.getTrueBitIndexes();
92
96
 
@@ -1,9 +1,11 @@
1
1
  import {byteArrayEquals} from "@chainsafe/ssz";
2
2
  import {
3
+ EFFECTIVE_BALANCE_INCREMENT,
3
4
  ForkSeq,
4
5
  MIN_ATTESTATION_INCLUSION_DELAY,
5
6
  PROPOSER_WEIGHT,
6
7
  SLOTS_PER_EPOCH,
8
+ SLOTS_PER_HISTORICAL_ROOT,
7
9
  TIMELY_HEAD_FLAG_INDEX,
8
10
  TIMELY_HEAD_WEIGHT,
9
11
  TIMELY_SOURCE_FLAG_INDEX,
@@ -16,7 +18,8 @@ import {Attestation, Epoch, phase0} from "@lodestar/types";
16
18
  import {intSqrt} from "@lodestar/utils";
17
19
  import {BeaconStateTransitionMetrics} from "../metrics.js";
18
20
  import {getAttestationWithIndicesSignatureSet} from "../signatureSets/indexedAttestation.js";
19
- import {CachedBeaconStateAltair} from "../types.js";
21
+ import {CachedBeaconStateAltair, CachedBeaconStateGloas} from "../types.js";
22
+ import {isAttestationSameSlot, isAttestationSameSlotRootCache} from "../util/gloas.ts";
20
23
  import {increaseBalance, verifySignatureSet} from "../util/index.js";
21
24
  import {RootCache} from "../util/rootCache.js";
22
25
  import {checkpointToStr, isTimelyTarget, validateAttestation} from "./processAttestationPhase0.js";
@@ -31,7 +34,7 @@ const SLOTS_PER_EPOCH_SQRT = intSqrt(SLOTS_PER_EPOCH);
31
34
 
32
35
  export function processAttestationsAltair(
33
36
  fork: ForkSeq,
34
- state: CachedBeaconStateAltair,
37
+ state: CachedBeaconStateAltair | CachedBeaconStateGloas,
35
38
  attestations: Attestation[],
36
39
  verifySignature = true,
37
40
  metrics?: BeaconStateTransitionMetrics | null
@@ -46,6 +49,9 @@ export function processAttestationsAltair(
46
49
  let proposerReward = 0;
47
50
  let newSeenAttesters = 0;
48
51
  let newSeenAttestersEffectiveBalance = 0;
52
+
53
+ const builderWeightMap: Map<number, number> = new Map();
54
+
49
55
  for (const attestation of attestations) {
50
56
  const data = attestation.data;
51
57
 
@@ -66,13 +72,16 @@ export function processAttestationsAltair(
66
72
 
67
73
  const inCurrentEpoch = data.target.epoch === currentEpoch;
68
74
  const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation;
75
+ // Count how much additional weight added to current or previous epoch's builder pending payment (in ETH increment)
76
+ let paymentWeightToAdd = 0;
69
77
 
70
78
  const flagsAttestation = getAttestationParticipationStatus(
71
79
  fork,
72
80
  data,
73
81
  stateSlot - data.slot,
74
82
  epochCtx.epoch,
75
- rootCache
83
+ rootCache,
84
+ fork >= ForkSeq.gloas ? (state as CachedBeaconStateGloas).executionPayloadAvailability.toBoolArray() : null
76
85
  );
77
86
 
78
87
  // For each participant, update their participation
@@ -121,12 +130,35 @@ export function processAttestationsAltair(
121
130
  }
122
131
  }
123
132
  }
133
+
134
+ if (fork >= ForkSeq.gloas && flagsNewSet !== 0 && isAttestationSameSlot(state as CachedBeaconStateGloas, data)) {
135
+ paymentWeightToAdd += effectiveBalanceIncrements[validatorIndex];
136
+ }
124
137
  }
125
138
 
126
139
  // Do the discrete math inside the loop to ensure a deterministic result
127
140
  const totalIncrements = totalBalanceIncrementsWithWeight;
128
141
  const proposerRewardNumerator = totalIncrements * state.epochCtx.baseRewardPerIncrement;
129
142
  proposerReward += Math.floor(proposerRewardNumerator / PROPOSER_REWARD_DOMINATOR);
143
+
144
+ if (fork >= ForkSeq.gloas) {
145
+ const builderPendingPaymentIndex = inCurrentEpoch
146
+ ? SLOTS_PER_EPOCH + (data.slot % SLOTS_PER_EPOCH)
147
+ : data.slot % SLOTS_PER_EPOCH;
148
+
149
+ const existingWeight =
150
+ builderWeightMap.get(builderPendingPaymentIndex) ??
151
+ (state as CachedBeaconStateGloas).builderPendingPayments.get(builderPendingPaymentIndex).weight;
152
+ const updatedWeight = existingWeight + paymentWeightToAdd * EFFECTIVE_BALANCE_INCREMENT;
153
+ builderWeightMap.set(builderPendingPaymentIndex, updatedWeight);
154
+ }
155
+ }
156
+
157
+ for (const [index, weight] of builderWeightMap) {
158
+ const payment = (state as CachedBeaconStateGloas).builderPendingPayments.get(index);
159
+ if (payment.withdrawal.amount > 0) {
160
+ payment.weight = weight;
161
+ }
130
162
  }
131
163
 
132
164
  metrics?.newSeenAttestersPerBlock.set(newSeenAttesters);
@@ -145,7 +177,8 @@ export function getAttestationParticipationStatus(
145
177
  data: phase0.AttestationData,
146
178
  inclusionDelay: number,
147
179
  currentEpoch: Epoch,
148
- rootCache: RootCache
180
+ rootCache: RootCache,
181
+ executionPayloadAvailability: boolean[] | null
149
182
  ): number {
150
183
  const justifiedCheckpoint =
151
184
  data.target.epoch === currentEpoch ? rootCache.currentJustifiedCheckpoint : rootCache.previousJustifiedCheckpoint;
@@ -168,9 +201,33 @@ export function getAttestationParticipationStatus(
168
201
  const isMatchingTarget = byteArrayEquals(data.target.root, rootCache.getBlockRoot(data.target.epoch));
169
202
 
170
203
  // a timely head is only be set if the target is _also_ matching
171
- const isMatchingHead =
204
+ // In gloas, this is called `head_root_matches`
205
+ let isMatchingHead =
172
206
  isMatchingTarget && byteArrayEquals(data.beaconBlockRoot, rootCache.getBlockRootAtSlot(data.slot));
173
207
 
208
+ if (fork >= ForkSeq.gloas) {
209
+ let isMatchingPayload = false;
210
+
211
+ if (isAttestationSameSlotRootCache(rootCache, data)) {
212
+ if (data.index !== 0) {
213
+ throw new Error("Attesting same slot must indicate empty payload");
214
+ }
215
+ isMatchingPayload = true;
216
+ } else {
217
+ if (executionPayloadAvailability === null) {
218
+ throw new Error("Must supply executionPayloadAvailability post-gloas");
219
+ }
220
+
221
+ if (data.index !== 0 && data.index !== 1) {
222
+ throw new Error(`data index must be 0 or 1 index=${data.index}`);
223
+ }
224
+
225
+ isMatchingPayload = Boolean(data.index) === executionPayloadAvailability[data.slot % SLOTS_PER_HISTORICAL_ROOT];
226
+ }
227
+
228
+ isMatchingHead = isMatchingHead && isMatchingPayload;
229
+ }
230
+
174
231
  let flags = 0;
175
232
  if (isMatchingSource && inclusionDelay <= SLOTS_PER_EPOCH_SQRT) flags |= TIMELY_SOURCE;
176
233
  if (isMatchingTarget && isTimelyTarget(fork, inclusionDelay)) flags |= TIMELY_TARGET;
@@ -1,6 +1,6 @@
1
- import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params";
1
+ import {FAR_FUTURE_EPOCH, ForkSeq, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params";
2
2
  import {electra, ssz} from "@lodestar/types";
3
- import {CachedBeaconStateElectra} from "../types.js";
3
+ import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
4
4
  import {hasEth1WithdrawalCredential} from "../util/capella.js";
5
5
  import {
6
6
  hasCompoundingWithdrawalCredential,
@@ -13,7 +13,8 @@ import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidat
13
13
 
14
14
  // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
15
15
  export function processConsolidationRequest(
16
- state: CachedBeaconStateElectra,
16
+ fork: ForkSeq,
17
+ state: CachedBeaconStateElectra | CachedBeaconStateGloas,
17
18
  consolidationRequest: electra.ConsolidationRequest
18
19
  ): void {
19
20
  const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
@@ -82,7 +83,7 @@ export function processConsolidationRequest(
82
83
  }
83
84
 
84
85
  // Verify the source has no pending withdrawals in the queue
85
- if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) {
86
+ if (getPendingBalanceToWithdraw(fork, state, sourceIndex) > 0) {
86
87
  return;
87
88
  }
88
89
 
@@ -103,7 +104,7 @@ export function processConsolidationRequest(
103
104
  * Determine if we should set consolidation target validator to compounding credential
104
105
  */
105
106
  function isValidSwitchToCompoundRequest(
106
- state: CachedBeaconStateElectra,
107
+ state: CachedBeaconStateElectra | CachedBeaconStateGloas,
107
108
  consolidationRequest: electra.ConsolidationRequest
108
109
  ): boolean {
109
110
  const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
@@ -1,8 +1,11 @@
1
1
  import {UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
2
2
  import {electra, ssz} from "@lodestar/types";
3
- import {CachedBeaconStateElectra} from "../types.js";
3
+ import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
4
4
 
5
- export function processDepositRequest(state: CachedBeaconStateElectra, depositRequest: electra.DepositRequest): void {
5
+ export function processDepositRequest(
6
+ state: CachedBeaconStateElectra | CachedBeaconStateGloas,
7
+ depositRequest: electra.DepositRequest
8
+ ): void {
6
9
  if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
7
10
  state.depositRequestsStartIndex = depositRequest.index;
8
11
  }
@@ -0,0 +1,120 @@
1
+ import {PublicKey, Signature, verify} from "@chainsafe/blst";
2
+ import {byteArrayEquals} from "@chainsafe/ssz";
3
+ import {
4
+ DOMAIN_BEACON_BUILDER,
5
+ FAR_FUTURE_EPOCH,
6
+ ForkPostGloas,
7
+ MIN_ACTIVATION_BALANCE,
8
+ SLOTS_PER_EPOCH,
9
+ } from "@lodestar/params";
10
+ import {BeaconBlock, gloas, ssz} from "@lodestar/types";
11
+ import {toHex, toRootHex} from "@lodestar/utils";
12
+ import {G2_POINT_AT_INFINITY} from "../constants/constants.ts";
13
+ import {CachedBeaconStateGloas} from "../types.ts";
14
+ import {hasBuilderWithdrawalCredential} from "../util/gloas.ts";
15
+ import {computeSigningRoot, getCurrentEpoch, getRandaoMix, isActiveValidator} from "../util/index.ts";
16
+
17
+ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block: BeaconBlock<ForkPostGloas>): void {
18
+ const signedBid = block.body.signedExecutionPayloadBid;
19
+ const bid = signedBid.message;
20
+ const {builderIndex, value: amount} = bid;
21
+ const builder = state.validators.getReadonly(builderIndex);
22
+
23
+ // For self-builds, amount must be zero regardless of withdrawal credential prefix
24
+ if (builderIndex === block.proposerIndex) {
25
+ if (amount !== 0) {
26
+ throw Error(`Invalid execution payload bid: self-build with non-zero amount ${amount}`);
27
+ }
28
+ if (!byteArrayEquals(signedBid.signature, G2_POINT_AT_INFINITY)) {
29
+ throw Error("Invalid execution payload bid: self-build with non-zero signature");
30
+ }
31
+ // Non-self builds require builder withdrawal credential
32
+ } else {
33
+ if (!hasBuilderWithdrawalCredential(builder.withdrawalCredentials)) {
34
+ throw Error(`Invalid execution payload bid: builder ${builderIndex} does not have builder withdrawal credential`);
35
+ }
36
+
37
+ if (!verifyExecutionPayloadBidSignature(state, builder.pubkey, signedBid)) {
38
+ throw Error(`Invalid execution payload bid: invalid signature for builder ${builderIndex}`);
39
+ }
40
+ }
41
+
42
+ if (!isActiveValidator(builder, getCurrentEpoch(state))) {
43
+ throw Error(`Invalid execution payload bid: builder ${builderIndex} is not active`);
44
+ }
45
+
46
+ if (builder.slashed) {
47
+ throw Error(`Invalid execution payload bid: builder ${builderIndex} is slashed`);
48
+ }
49
+
50
+ const pendingPayments = state.builderPendingPayments
51
+ .getAllReadonly()
52
+ .filter((payment) => payment.withdrawal.builderIndex === builderIndex)
53
+ .reduce((acc, payment) => acc + payment.withdrawal.amount, 0);
54
+ const pendingWithdrawals = state.builderPendingWithdrawals
55
+ .getAllReadonly()
56
+ .filter((withdrawal) => withdrawal.builderIndex === builderIndex)
57
+ .reduce((acc, withdrawal) => acc + withdrawal.amount, 0);
58
+
59
+ if (
60
+ amount !== 0 &&
61
+ state.balances.get(builderIndex) < amount + pendingPayments + pendingWithdrawals + MIN_ACTIVATION_BALANCE
62
+ ) {
63
+ throw Error("Insufficient builder balance");
64
+ }
65
+
66
+ if (bid.slot !== block.slot) {
67
+ throw Error(`Bid slot ${bid.slot} does not match block slot ${block.slot}`);
68
+ }
69
+
70
+ if (!byteArrayEquals(bid.parentBlockHash, state.latestBlockHash)) {
71
+ throw Error(
72
+ `Parent block hash ${toRootHex(bid.parentBlockHash)} of bid does not match state's latest block hash ${toRootHex(state.latestBlockHash)}`
73
+ );
74
+ }
75
+
76
+ if (!byteArrayEquals(bid.parentBlockRoot, block.parentRoot)) {
77
+ throw Error(
78
+ `Parent block root ${toRootHex(bid.parentBlockRoot)} of bid does not match block's parent root ${toRootHex(block.parentRoot)}`
79
+ );
80
+ }
81
+
82
+ const stateRandao = getRandaoMix(state, getCurrentEpoch(state));
83
+ if (!byteArrayEquals(bid.prevRandao, stateRandao)) {
84
+ throw Error(`Prev randao ${toHex(bid.prevRandao)} of bid does not match state's randao mix ${toHex(stateRandao)}`);
85
+ }
86
+
87
+ if (amount > 0) {
88
+ const pendingPaymentView = ssz.gloas.BuilderPendingPayment.toViewDU({
89
+ weight: 0,
90
+ withdrawal: ssz.gloas.BuilderPendingWithdrawal.toViewDU({
91
+ feeRecipient: bid.feeRecipient,
92
+ amount,
93
+ builderIndex,
94
+ withdrawableEpoch: FAR_FUTURE_EPOCH,
95
+ }),
96
+ });
97
+
98
+ state.builderPendingPayments.set(SLOTS_PER_EPOCH + (bid.slot % SLOTS_PER_EPOCH), pendingPaymentView);
99
+ }
100
+
101
+ state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid);
102
+ }
103
+
104
+ function verifyExecutionPayloadBidSignature(
105
+ state: CachedBeaconStateGloas,
106
+ pubkey: Uint8Array,
107
+ signedBid: gloas.SignedExecutionPayloadBid
108
+ ): boolean {
109
+ const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_BUILDER);
110
+ const signingRoot = computeSigningRoot(ssz.gloas.ExecutionPayloadBid, signedBid.message, domain);
111
+
112
+ try {
113
+ const publicKey = PublicKey.fromBytes(pubkey);
114
+ const signature = Signature.fromBytes(signedBid.signature, true);
115
+
116
+ return verify(signingRoot, publicKey, signature);
117
+ } catch (_e) {
118
+ return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature
119
+ }
120
+ }
@@ -0,0 +1,181 @@
1
+ import {PublicKey, Signature, verify} from "@chainsafe/blst";
2
+ import {byteArrayEquals} from "@chainsafe/ssz";
3
+ import {DOMAIN_BEACON_BUILDER, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
4
+ import {gloas, ssz} from "@lodestar/types";
5
+ import {toHex, toRootHex} from "@lodestar/utils";
6
+ import {CachedBeaconStateGloas} from "../types.ts";
7
+ import {computeExitEpochAndUpdateChurn, computeSigningRoot, computeTimeAtSlot} from "../util/index.ts";
8
+ import {processConsolidationRequest} from "./processConsolidationRequest.ts";
9
+ import {processDepositRequest} from "./processDepositRequest.ts";
10
+ import {processWithdrawalRequest} from "./processWithdrawalRequest.ts";
11
+
12
+ // This function does not call execution engine to verify payload. Need to call it from other place
13
+ export function processExecutionPayloadEnvelope(
14
+ state: CachedBeaconStateGloas,
15
+ signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
16
+ verify: boolean
17
+ ): void {
18
+ const envelope = signedEnvelope.message;
19
+ const payload = envelope.payload;
20
+ const fork = state.config.getForkSeq(envelope.slot);
21
+
22
+ if (verify) {
23
+ const builderIndex = envelope.builderIndex;
24
+ const pubkey = state.validators.getReadonly(builderIndex).pubkey;
25
+
26
+ if (!verifyExecutionPayloadEnvelopeSignature(state, pubkey, signedEnvelope)) {
27
+ throw new Error("Payload Envelope has invalid signature");
28
+ }
29
+ }
30
+
31
+ validateExecutionPayloadEnvelope(state, envelope);
32
+
33
+ const requests = envelope.executionRequests;
34
+
35
+ for (const deposit of requests.deposits) {
36
+ processDepositRequest(state, deposit);
37
+ }
38
+
39
+ for (const withdrawal of requests.withdrawals) {
40
+ processWithdrawalRequest(fork, state, withdrawal);
41
+ }
42
+
43
+ for (const consolidation of requests.consolidations) {
44
+ processConsolidationRequest(fork, state, consolidation);
45
+ }
46
+
47
+ // Queue the builder payment
48
+ const paymentIndex = SLOTS_PER_EPOCH + (state.slot % SLOTS_PER_EPOCH);
49
+ const payment = state.builderPendingPayments.get(paymentIndex).clone();
50
+ const amount = payment.withdrawal.amount;
51
+
52
+ if (amount > 0) {
53
+ const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, BigInt(amount));
54
+
55
+ payment.withdrawal.withdrawableEpoch = exitQueueEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
56
+ state.builderPendingWithdrawals.push(payment.withdrawal);
57
+ }
58
+
59
+ state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
60
+
61
+ // Cache the execution payload hash
62
+ state.executionPayloadAvailability.set(state.slot % SLOTS_PER_HISTORICAL_ROOT, true);
63
+ state.latestBlockHash = payload.blockHash;
64
+
65
+ if (verify && !byteArrayEquals(envelope.stateRoot, state.hashTreeRoot())) {
66
+ throw new Error(
67
+ `Envelope's state root does not match state envelope=${toRootHex(envelope.stateRoot)} state=${toRootHex(state.hashTreeRoot())}`
68
+ );
69
+ }
70
+ }
71
+
72
+ function validateExecutionPayloadEnvelope(
73
+ state: CachedBeaconStateGloas,
74
+ envelope: gloas.ExecutionPayloadEnvelope
75
+ ): void {
76
+ const payload = envelope.payload;
77
+
78
+ if (byteArrayEquals(state.latestBlockHeader.stateRoot, ssz.Root.defaultValue())) {
79
+ const previousStateRoot = state.hashTreeRoot();
80
+ state.latestBlockHeader.stateRoot = previousStateRoot;
81
+ }
82
+
83
+ // Verify consistency with the beacon block
84
+ if (!byteArrayEquals(envelope.beaconBlockRoot, state.latestBlockHeader.hashTreeRoot())) {
85
+ throw new Error(
86
+ `Envelope's block is not the latest block header envelope=${toRootHex(envelope.beaconBlockRoot)} latestBlockHeader=${toRootHex(state.latestBlockHeader.hashTreeRoot())}`
87
+ );
88
+ }
89
+
90
+ // Verify consistency with the beacon block
91
+ if (envelope.slot !== state.slot) {
92
+ throw new Error(`Slot mismatch between envelope and state envelope=${envelope.slot} state=${state.slot}`);
93
+ }
94
+
95
+ const committedBid = state.latestExecutionPayloadBid;
96
+ // Verify consistency with the committed bid
97
+ if (envelope.builderIndex !== committedBid.builderIndex) {
98
+ throw new Error(
99
+ `Builder index mismatch between envelope and committed bid envelope=${envelope.builderIndex} committedBid=${committedBid.builderIndex}`
100
+ );
101
+ }
102
+
103
+ // Verify consistency with the committed bid
104
+ const envelopeKzgRoot = ssz.deneb.BlobKzgCommitments.hashTreeRoot(envelope.blobKzgCommitments);
105
+ if (!byteArrayEquals(committedBid.blobKzgCommitmentsRoot, envelopeKzgRoot)) {
106
+ throw new Error(
107
+ `Kzg commitment root mismatch between envelope and committed bid envelope=${toRootHex(envelopeKzgRoot)} committedBid=${toRootHex(committedBid.blobKzgCommitmentsRoot)}`
108
+ );
109
+ }
110
+
111
+ // Verify the withdrawals root
112
+ const envelopeWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(envelope.payload.withdrawals);
113
+ if (!byteArrayEquals(state.latestWithdrawalsRoot, envelopeWithdrawalsRoot)) {
114
+ throw new Error(
115
+ `Withdrawals root mismatch between envelope and latest withdrawals root envelope=${toRootHex(envelopeWithdrawalsRoot)} latestWithdrawalRoot=${toRootHex(state.latestWithdrawalsRoot)}`
116
+ );
117
+ }
118
+
119
+ // Verify the gas_limit
120
+ if (Number(committedBid.gasLimit) !== payload.gasLimit) {
121
+ throw new Error(
122
+ `Gas limit mismatch between envelope's payload and committed bid envelope=${payload.gasLimit} committedBid=${Number(committedBid.gasLimit)}`
123
+ );
124
+ }
125
+
126
+ // Verify the block hash
127
+ if (!byteArrayEquals(committedBid.blockHash, payload.blockHash)) {
128
+ throw new Error(
129
+ `Block hash mismatch between envelope's payload and committed bid envelope=${toRootHex(payload.blockHash)} committedBid=${toRootHex(committedBid.blockHash)}`
130
+ );
131
+ }
132
+
133
+ // Verify consistency of the parent hash with respect to the previous execution payload
134
+ if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) {
135
+ throw new Error(
136
+ `Parent hash mismatch between envelope's payload and state envelope=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}`
137
+ );
138
+ }
139
+
140
+ // Verify prev_randao matches committed bid
141
+ if (!byteArrayEquals(committedBid.prevRandao, payload.prevRandao)) {
142
+ throw new Error(
143
+ `Prev randao mismatch between committed bid and payload committedBid=${toHex(committedBid.prevRandao)} payload=${toHex(payload.prevRandao)}`
144
+ );
145
+ }
146
+
147
+ // Verify timestamp
148
+ if (payload.timestamp !== computeTimeAtSlot(state.config, state.slot, state.genesisTime)) {
149
+ throw new Error(
150
+ `Timestamp mismatch between envelope's payload and state envelope=${payload.timestamp} state=${computeTimeAtSlot(state.config, state.slot, state.genesisTime)}`
151
+ );
152
+ }
153
+
154
+ // Verify commitments are under limit
155
+ const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(state.epochCtx.epoch);
156
+ if (envelope.blobKzgCommitments.length > maxBlobsPerBlock) {
157
+ throw new Error(
158
+ `Kzg commitments exceed limit commitment.length=${envelope.blobKzgCommitments.length} limit=${maxBlobsPerBlock}`
159
+ );
160
+ }
161
+
162
+ // Skipped: Verify the execution payload is valid
163
+ }
164
+
165
+ function verifyExecutionPayloadEnvelopeSignature(
166
+ state: CachedBeaconStateGloas,
167
+ pubkey: Uint8Array,
168
+ signedEnvelope: gloas.SignedExecutionPayloadEnvelope
169
+ ): boolean {
170
+ const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_BUILDER);
171
+ const signingRoot = computeSigningRoot(ssz.gloas.ExecutionPayloadEnvelope, signedEnvelope.message, domain);
172
+
173
+ try {
174
+ const publicKey = PublicKey.fromBytes(pubkey);
175
+ const signature = Signature.fromBytes(signedEnvelope.signature, true);
176
+
177
+ return verify(signingRoot, publicKey, signature);
178
+ } catch (_e) {
179
+ return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature
180
+ }
181
+ }