@lodestar/beacon-node 1.45.0-dev.9ed373bf56 → 1.45.0-dev.aadf7a7167

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 (143) hide show
  1. package/lib/api/impl/beacon/index.d.ts +1 -1
  2. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  3. package/lib/api/impl/beacon/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +4 -0
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/index.d.ts +1 -1
  8. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/index.js +2 -2
  10. package/lib/api/impl/beacon/state/index.js.map +1 -1
  11. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  12. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  13. package/lib/api/impl/beacon/state/utils.js +9 -1
  14. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  15. package/lib/api/impl/debug/index.d.ts +1 -1
  16. package/lib/api/impl/debug/index.d.ts.map +1 -1
  17. package/lib/api/impl/debug/index.js +2 -2
  18. package/lib/api/impl/debug/index.js.map +1 -1
  19. package/lib/api/impl/lodestar/index.js +2 -2
  20. package/lib/api/impl/lodestar/index.js.map +1 -1
  21. package/lib/api/impl/proof/index.d.ts +1 -1
  22. package/lib/api/impl/proof/index.d.ts.map +1 -1
  23. package/lib/api/impl/proof/index.js +2 -2
  24. package/lib/api/impl/proof/index.js.map +1 -1
  25. package/lib/api/impl/utils.d.ts +23 -0
  26. package/lib/api/impl/utils.d.ts.map +1 -1
  27. package/lib/api/impl/utils.js +44 -1
  28. package/lib/api/impl/utils.js.map +1 -1
  29. package/lib/api/impl/validator/index.d.ts +0 -14
  30. package/lib/api/impl/validator/index.d.ts.map +1 -1
  31. package/lib/api/impl/validator/index.js +21 -56
  32. package/lib/api/impl/validator/index.js.map +1 -1
  33. package/lib/api/rest/base.d.ts.map +1 -1
  34. package/lib/api/rest/base.js +8 -0
  35. package/lib/api/rest/base.js.map +1 -1
  36. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/importBlock.js +1 -4
  38. package/lib/chain/blocks/importBlock.js.map +1 -1
  39. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  40. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  41. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  42. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  43. package/lib/chain/builderCircuitBreaker.js +59 -0
  44. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  45. package/lib/chain/chain.d.ts +2 -0
  46. package/lib/chain/chain.d.ts.map +1 -1
  47. package/lib/chain/chain.js +3 -0
  48. package/lib/chain/chain.js.map +1 -1
  49. package/lib/chain/errors/blockError.d.ts +7 -0
  50. package/lib/chain/errors/blockError.d.ts.map +1 -1
  51. package/lib/chain/errors/blockError.js +2 -0
  52. package/lib/chain/errors/blockError.js.map +1 -1
  53. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  54. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  55. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  56. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  57. package/lib/chain/interface.d.ts +2 -0
  58. package/lib/chain/interface.d.ts.map +1 -1
  59. package/lib/chain/interface.js.map +1 -1
  60. package/lib/chain/lightClient/index.d.ts.map +1 -1
  61. package/lib/chain/lightClient/index.js +11 -6
  62. package/lib/chain/lightClient/index.js.map +1 -1
  63. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  64. package/lib/chain/lightClient/proofs.js +28 -2
  65. package/lib/chain/lightClient/proofs.js.map +1 -1
  66. package/lib/chain/lightClient/types.d.ts +3 -1
  67. package/lib/chain/lightClient/types.d.ts.map +1 -1
  68. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  69. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  70. package/lib/chain/options.d.ts +4 -0
  71. package/lib/chain/options.d.ts.map +1 -1
  72. package/lib/chain/options.js.map +1 -1
  73. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  74. package/lib/chain/prepareNextSlot.js +12 -7
  75. package/lib/chain/prepareNextSlot.js.map +1 -1
  76. package/lib/chain/validation/block.d.ts.map +1 -1
  77. package/lib/chain/validation/block.js +52 -1
  78. package/lib/chain/validation/block.js.map +1 -1
  79. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  80. package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
  81. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  82. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  83. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  84. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  85. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  86. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  87. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  88. package/lib/execution/builder/http.d.ts +18 -0
  89. package/lib/execution/builder/http.d.ts.map +1 -1
  90. package/lib/execution/builder/http.js +20 -11
  91. package/lib/execution/builder/http.js.map +1 -1
  92. package/lib/metrics/metrics/beacon.d.ts +6 -0
  93. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  94. package/lib/metrics/metrics/beacon.js +18 -0
  95. package/lib/metrics/metrics/beacon.js.map +1 -1
  96. package/lib/network/gossip/encoding.d.ts +2 -2
  97. package/lib/network/gossip/encoding.d.ts.map +1 -1
  98. package/lib/network/gossip/encoding.js +15 -9
  99. package/lib/network/gossip/encoding.js.map +1 -1
  100. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  101. package/lib/network/gossip/gossipsub.js +2 -2
  102. package/lib/network/gossip/gossipsub.js.map +1 -1
  103. package/lib/network/gossip/topic.d.ts +74 -231
  104. package/lib/network/gossip/topic.d.ts.map +1 -1
  105. package/lib/network/gossip/topic.js +24 -1
  106. package/lib/network/gossip/topic.js.map +1 -1
  107. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  108. package/lib/network/processor/gossipHandlers.js +4 -0
  109. package/lib/network/processor/gossipHandlers.js.map +1 -1
  110. package/package.json +19 -19
  111. package/src/api/impl/beacon/index.ts +1 -1
  112. package/src/api/impl/beacon/pool/index.ts +5 -0
  113. package/src/api/impl/beacon/state/index.ts +3 -2
  114. package/src/api/impl/beacon/state/utils.ts +11 -0
  115. package/src/api/impl/debug/index.ts +3 -2
  116. package/src/api/impl/lodestar/index.ts +2 -2
  117. package/src/api/impl/proof/index.ts +2 -2
  118. package/src/api/impl/utils.ts +51 -1
  119. package/src/api/impl/validator/index.ts +21 -62
  120. package/src/api/rest/base.ts +9 -0
  121. package/src/chain/blocks/importBlock.ts +1 -6
  122. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  123. package/src/chain/builderCircuitBreaker.ts +84 -0
  124. package/src/chain/chain.ts +7 -0
  125. package/src/chain/errors/blockError.ts +3 -0
  126. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  127. package/src/chain/interface.ts +2 -0
  128. package/src/chain/lightClient/index.ts +12 -7
  129. package/src/chain/lightClient/proofs.ts +36 -1
  130. package/src/chain/lightClient/types.ts +3 -1
  131. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  132. package/src/chain/options.ts +4 -0
  133. package/src/chain/prepareNextSlot.ts +11 -7
  134. package/src/chain/validation/block.ts +69 -1
  135. package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
  136. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  137. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  138. package/src/execution/builder/http.ts +30 -17
  139. package/src/metrics/metrics/beacon.ts +19 -0
  140. package/src/network/gossip/encoding.ts +13 -7
  141. package/src/network/gossip/gossipsub.ts +7 -2
  142. package/src/network/gossip/topic.ts +31 -0
  143. package/src/network/processor/gossipHandlers.ts +5 -0
@@ -1,6 +1,22 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ExecutionStatus} from "@lodestar/fork-choice";
3
- import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
3
+ import {
4
+ ForkName,
5
+ MAX_ATTESTATIONS_ELECTRA,
6
+ MAX_ATTESTER_SLASHINGS_ELECTRA,
7
+ MAX_BLS_TO_EXECUTION_CHANGES,
8
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
9
+ MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
10
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
11
+ MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
12
+ MAX_PAYLOAD_ATTESTATIONS,
13
+ MAX_PROPOSER_SLASHINGS,
14
+ MAX_VOLUNTARY_EXITS,
15
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
16
+ isForkPostBellatrix,
17
+ isForkPostDeneb,
18
+ isForkPostGloas,
19
+ } from "@lodestar/params";
4
20
  import {
5
21
  computeEpochAtSlot,
6
22
  computeStartSlotAtEpoch,
@@ -164,6 +180,58 @@ export async function validateGossipBlock(
164
180
  });
165
181
  }
166
182
 
183
+ // [REJECT] The counts of `block.body.parent_execution_requests` are within
184
+ // their respective limits -- i.e. validate that
185
+ // `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`,
186
+ // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
187
+ // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
188
+ // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
189
+ // and
190
+ // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
191
+ // [REJECT] The counts of the block body operations are within their respective
192
+ // limits -- i.e. validate that
193
+ // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
194
+ // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
195
+ // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
196
+ // `len(block.body.deposits) == 0`,
197
+ // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
198
+ // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
199
+ // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
200
+ const body = (block as gloas.BeaconBlock).body;
201
+ const requests = body.parentExecutionRequests;
202
+ const countLimits: [string, number, number][] = [
203
+ ["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
204
+ ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
205
+ [
206
+ "parentExecutionRequests.consolidations",
207
+ requests.consolidations.length,
208
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
209
+ ],
210
+ [
211
+ "parentExecutionRequests.builderDeposits",
212
+ requests.builderDeposits.length,
213
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
214
+ ],
215
+ ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
216
+ ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
217
+ ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
218
+ ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
219
+ ["deposits", body.deposits.length, 0],
220
+ ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
221
+ ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
222
+ ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
223
+ ];
224
+ for (const [name, count, limit] of countLimits) {
225
+ if (count > limit) {
226
+ throw new BlockGossipError(GossipAction.REJECT, {
227
+ code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
228
+ name,
229
+ count,
230
+ limit,
231
+ });
232
+ }
233
+ }
234
+
167
235
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
168
236
  // This requires execution engine integration to verify the parent block hash
169
237
  }
@@ -1,4 +1,12 @@
1
1
  import {PayloadStatus} from "@lodestar/fork-choice";
2
+ import {
3
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
4
+ MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
5
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
6
+ MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
7
+ MAX_WITHDRAWALS_PER_PAYLOAD,
8
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
9
+ } from "@lodestar/params";
2
10
  import {
3
11
  computeStartSlotAtEpoch,
4
12
  getExecutionPayloadEnvelopeSignatureSet,
@@ -115,6 +123,37 @@ async function validateExecutionPayloadEnvelope(
115
123
  });
116
124
  }
117
125
 
126
+ // [REJECT] The counts of `execution_requests` are within their respective limits.
127
+ // New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
128
+ // are enforced here in gossip validation.
129
+ const {executionRequests} = envelope;
130
+ const requestCountLimits: [string, number, number][] = [
131
+ ["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
132
+ ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
133
+ ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
134
+ ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
135
+ ["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
136
+ ];
137
+ for (const [name, count, limit] of requestCountLimits) {
138
+ if (count > limit) {
139
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
140
+ code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
141
+ name,
142
+ count,
143
+ limit,
144
+ });
145
+ }
146
+ }
147
+
148
+ // [REJECT] The number of withdrawals is within the limit.
149
+ if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
150
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
151
+ code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
152
+ count: payload.withdrawals.length,
153
+ limit: MAX_WITHDRAWALS_PER_PAYLOAD,
154
+ });
155
+ }
156
+
118
157
  // Get the block state to verify the builder's signature.
119
158
  const blockState = await chain.regen
120
159
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
@@ -1,7 +1,7 @@
1
1
  import {BeaconConfig} from "@lodestar/config";
2
- import {DOMAIN_AGGREGATE_AND_PROOF, ForkSeq} from "@lodestar/params";
2
+ import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
3
3
  import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
- import {Epoch, SignedAggregateAndProof, ValidatorIndex, ssz} from "@lodestar/types";
4
+ import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";
5
5
 
6
6
  export function getAggregateAndProofSigningRoot(
7
7
  config: BeaconConfig,
@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
14
14
  const slot = computeStartSlotAtEpoch(epoch);
15
15
  const fork = config.getForkName(slot);
16
16
  const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
17
- const sszType = ForkSeq[fork] >= ForkSeq.electra ? ssz.electra.AggregateAndProof : ssz.phase0.AggregateAndProof;
17
+ const sszType = config.getForkTypes(slot).AggregateAndProof;
18
18
  return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
19
19
  }
20
20
 
@@ -1,14 +1,16 @@
1
1
  import {ContainerType, VectorCompositeType} from "@chainsafe/ssz";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {DatabaseController, Repository} from "@lodestar/db";
4
+ import {CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS} from "@lodestar/params";
4
5
  import {ssz} from "@lodestar/types";
5
6
  import {SyncCommitteeWitness} from "../../chain/lightClient/types.js";
6
7
  import {Bucket, getBucketNameByValue} from "../buckets.js";
7
8
 
8
- // We add a 1-byte prefix where 0 means pre-electra and 1 means post-electra
9
+ // We add a 1-byte prefix where 0 means pre-Electra, 1 means post-Electra, and 2 means post-Gloas.
9
10
  enum PrefixByte {
10
11
  PRE_ELECTRA = 0,
11
12
  POST_ELECTRA = 1,
13
+ POST_GLOAS = 2,
12
14
  }
13
15
 
14
16
  export const NUM_WITNESS = 4;
@@ -35,6 +37,35 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
35
37
  // Overrides for multi-fork
36
38
  encodeValue(value: SyncCommitteeWitness): Uint8Array {
37
39
  const numWitness = value.witness.length;
40
+ const hasGloasBranches =
41
+ value.currentSyncCommitteeBranch !== undefined || value.nextSyncCommitteeBranch !== undefined;
42
+
43
+ if (hasGloasBranches) {
44
+ if (
45
+ value.currentSyncCommitteeBranch?.length !== CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS ||
46
+ value.nextSyncCommitteeBranch?.length !== NEXT_SYNC_COMMITTEE_DEPTH_GLOAS
47
+ ) {
48
+ throw Error(
49
+ `Invalid post-Gloas sync committee branch lengths current=${value.currentSyncCommitteeBranch?.length} next=${value.nextSyncCommitteeBranch?.length}`
50
+ );
51
+ }
52
+
53
+ const type = new ContainerType({
54
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
55
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
56
+ currentSyncCommitteeRoot: ssz.Root,
57
+ nextSyncCommitteeRoot: ssz.Root,
58
+ });
59
+
60
+ const valueBytes = type.serialize({
61
+ currentSyncCommitteeBranch: value.currentSyncCommitteeBranch,
62
+ nextSyncCommitteeBranch: value.nextSyncCommitteeBranch,
63
+ currentSyncCommitteeRoot: value.currentSyncCommitteeRoot,
64
+ nextSyncCommitteeRoot: value.nextSyncCommitteeRoot,
65
+ });
66
+
67
+ return prefixData(PrefixByte.POST_GLOAS, valueBytes);
68
+ }
38
69
 
39
70
  if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) {
40
71
  throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`);
@@ -51,19 +82,25 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
51
82
  // We need to differentiate between post-electra and pre-electra witness
52
83
  // such that we can deserialize correctly
53
84
  const isPostElectra = numWitness === NUM_WITNESS_ELECTRA;
54
- const prefixByte = new Uint8Array(1);
55
- prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA;
56
-
57
- const prefixedData = new Uint8Array(1 + valueBytes.length);
58
- prefixedData.set(prefixByte, 0);
59
- prefixedData.set(valueBytes, 1);
60
-
61
- return prefixedData;
85
+ return prefixData(isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA, valueBytes);
62
86
  }
63
87
 
64
88
  decodeValue(data: Uint8Array): SyncCommitteeWitness {
65
89
  // First byte is written
66
90
  const prefix = data.subarray(0, 1);
91
+ const isPostGloas = prefix[0] === PrefixByte.POST_GLOAS;
92
+
93
+ if (isPostGloas) {
94
+ const type = new ContainerType({
95
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
96
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
97
+ currentSyncCommitteeRoot: ssz.Root,
98
+ nextSyncCommitteeRoot: ssz.Root,
99
+ });
100
+
101
+ return {witness: [], ...type.deserialize(data.subarray(1))};
102
+ }
103
+
67
104
  const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA;
68
105
 
69
106
  const type = new ContainerType({
@@ -75,3 +112,14 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
75
112
  return type.deserialize(data.subarray(1));
76
113
  }
77
114
  }
115
+
116
+ function prefixData(prefix: PrefixByte, valueBytes: Uint8Array): Uint8Array {
117
+ const prefixByte = new Uint8Array(1);
118
+ prefixByte[0] = prefix;
119
+
120
+ const prefixedData = new Uint8Array(1 + valueBytes.length);
121
+ prefixedData.set(prefixByte, 0);
122
+ prefixedData.set(valueBytes, 1);
123
+
124
+ return prefixedData;
125
+ }
@@ -70,6 +70,33 @@ export class NoBidReceived extends Error {
70
70
  }
71
71
  }
72
72
 
73
+ /**
74
+ * Beacon clients select randomized values from the following ranges when initializing
75
+ * the circuit breaker (so at boot time and once for each unique boot).
76
+ *
77
+ * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
78
+ * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
79
+ *
80
+ * The values are randomized per node so builders cannot predict when a given proposer will
81
+ * fall back to local blocks. With fixed thresholds a builder could withhold payloads right up
82
+ * to the limit without ever tripping the breaker.
83
+ */
84
+ export function getFaultInspectionParams(opts: {faultInspectionWindow?: number; allowedFaults?: number}): {
85
+ faultInspectionWindow: number;
86
+ allowedFaults: number;
87
+ } {
88
+ const faultInspectionWindow = Math.max(
89
+ opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
90
+ SLOTS_PER_EPOCH
91
+ );
92
+ // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
93
+ const allowedFaults = Math.min(
94
+ opts.allowedFaults ?? Math.floor(faultInspectionWindow / 4),
95
+ Math.floor(faultInspectionWindow / 4)
96
+ );
97
+ return {faultInspectionWindow, allowedFaults};
98
+ }
99
+
73
100
  /**
74
101
  * Additional duration to account for potential event loop lag which causes
75
102
  * builder blocks to be rejected even though the response was sent in time.
@@ -122,23 +149,9 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
122
149
  this.registrations = new ValidatorRegistrationCache();
123
150
  this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient;
124
151
 
125
- /**
126
- * Beacon clients select randomized values from the following ranges when initializing
127
- * the circuit breaker (so at boot time and once for each unique boot).
128
- *
129
- * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
130
- * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
131
- *
132
- */
133
- this.faultInspectionWindow = Math.max(
134
- opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
135
- SLOTS_PER_EPOCH
136
- );
137
- // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
138
- this.allowedFaults = Math.min(
139
- opts.allowedFaults ?? Math.floor(this.faultInspectionWindow / 4),
140
- Math.floor(this.faultInspectionWindow / 4)
141
- );
152
+ const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
153
+ this.faultInspectionWindow = faultInspectionWindow;
154
+ this.allowedFaults = allowedFaults;
142
155
  }
143
156
 
144
157
  updateStatus(status: BuilderStatus): void {
@@ -138,6 +138,25 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
138
138
  help: "Count of cached produced results",
139
139
  }),
140
140
 
141
+ builderCircuitBreaker: {
142
+ active: register.gauge({
143
+ name: "beacon_builder_circuit_breaker_active",
144
+ help: "Whether the builder circuit breaker is active (1) causing builder bids to be ignored",
145
+ }),
146
+ faults: register.gauge({
147
+ name: "beacon_builder_circuit_breaker_faults",
148
+ help: "Count of blocks with unrevealed payloads in the fault inspection window",
149
+ }),
150
+ blocksPresent: register.gauge({
151
+ name: "beacon_builder_circuit_breaker_blocks_present",
152
+ help: "Count of blocks present in the fault inspection window",
153
+ }),
154
+ payloadsRevealed: register.gauge({
155
+ name: "beacon_builder_circuit_breaker_payloads_revealed",
156
+ help: "Count of blocks with revealed payloads in the fault inspection window",
157
+ }),
158
+ },
159
+
141
160
  blockPayload: {
142
161
  payloadAdvancePrepTime: register.histogram({
143
162
  name: "beacon_block_payload_prepare_time",
@@ -9,7 +9,7 @@ import {ForkName} from "@lodestar/params";
9
9
  import {intToBytes} from "@lodestar/utils";
10
10
  import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js";
11
11
  import {Eth2GossipsubMetrics} from "./metrics.js";
12
- import {GossipTopicCache, getGossipSSZType} from "./topic.js";
12
+ import {GossipTopicCache, getGossipSSZMaxSize, getGossipSSZType} from "./topic.js";
13
13
 
14
14
  // Load WASM
15
15
  const xxhash = await xxhashFactory();
@@ -98,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
98
98
  export class DataTransformSnappy implements DataTransform {
99
99
  constructor(
100
100
  private readonly gossipTopicCache: GossipTopicCache,
101
- private readonly maxSizePerMessage: number,
101
+ private readonly maxPayloadSize: number,
102
102
  private readonly metrics: Eth2GossipsubMetrics | null
103
103
  ) {}
104
104
 
@@ -111,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
111
111
  inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
112
112
  // check uncompressed data length before we actually decompress
113
113
  const uncompressedDataLength = snappyWasm.decompress_len(data);
114
- if (uncompressedDataLength > this.maxSizePerMessage) {
115
- throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
116
- }
117
114
 
118
115
  const topic = this.gossipTopicCache.getTopic(topicStr);
119
116
  const sszType = getGossipSSZType(topic);
117
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
120
118
  this.metrics?.dataTransform.inbound.inc({type: topic.type});
121
119
 
122
120
  if (uncompressedDataLength < sszType.minSize) {
123
121
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
124
122
  }
123
+ if (uncompressedDataLength > maxSize) {
124
+ throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
125
+ }
125
126
  if (uncompressedDataLength > sszType.maxSize) {
126
127
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
127
128
  }
@@ -139,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
139
140
  */
140
141
  outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
141
142
  const topic = this.gossipTopicCache.getTopic(topicStr);
143
+ const sszType = getGossipSSZType(topic);
144
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
142
145
  this.metrics?.dataTransform.outbound.inc({type: topic.type});
143
- if (data.length > this.maxSizePerMessage) {
144
- throw Error(`ssz_snappy encoded data length ${data.length} > ${this.maxSizePerMessage}`);
146
+ if (data.length > maxSize) {
147
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${maxSize}`);
148
+ }
149
+ if (data.length > sszType.maxSize) {
150
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${sszType.maxSize}`);
145
151
  }
146
152
 
147
153
  // Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments
@@ -15,7 +15,12 @@ import {type Multiaddr, multiaddr} from "@multiformats/multiaddr";
15
15
  import {ENR} from "@chainsafe/enr";
16
16
  import {routes} from "@lodestar/api";
17
17
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
18
- import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
18
+ import {
19
+ ATTESTATION_SUBNET_COUNT,
20
+ MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
21
+ SLOTS_PER_EPOCH,
22
+ SYNC_COMMITTEE_SUBNET_COUNT,
23
+ } from "@lodestar/params";
19
24
  import {SubnetID} from "@lodestar/types";
20
25
  import {Logger, Map2d, Map2dArr} from "@lodestar/utils";
21
26
  import {RegistryMetricCreator} from "../../metrics/index.js";
@@ -179,7 +184,7 @@ export class Eth2Gossipsub {
179
184
  // Only send IDONTWANT messages if the message size is larger than this
180
185
  // This should be large enough to not send IDONTWANT for "small" messages
181
186
  // See https://github.com/ChainSafe/lodestar/pull/7077#issuecomment-2383679472
182
- idontwantMinDataSize: 16829,
187
+ idontwantMinDataSize: MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
183
188
  })(modules.libp2p.services.components) as GossipSubInternal;
184
189
 
185
190
  if (metrics) {
@@ -1,12 +1,19 @@
1
+ import {type CompositeTypeAny} from "@chainsafe/ssz";
1
2
  import {ForkDigestContext} from "@lodestar/config";
2
3
  import {
3
4
  ATTESTATION_SUBNET_COUNT,
4
5
  ForkName,
5
6
  ForkSeq,
7
+ MAX_ATTESTER_SLASHING_SIZE,
8
+ MAX_DATA_COLUMN_SIDECAR_SIZE,
9
+ MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
10
+ MAX_SIGNED_BEACON_BLOCK_SIZE,
11
+ MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE,
6
12
  SYNC_COMMITTEE_SUBNET_COUNT,
7
13
  isForkPostAltair,
8
14
  isForkPostElectra,
9
15
  isForkPostFulu,
16
+ isForkPostGloas,
10
17
  } from "@lodestar/params";
11
18
  import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types";
12
19
  import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js";
@@ -130,6 +137,30 @@ export function getGossipSSZType(topic: GossipTopic) {
130
137
  }
131
138
  }
132
139
 
140
+ /**
141
+ * Return the maximum uncompressed SSZ byte length accepted for a gossip object.
142
+ */
143
+ export function getGossipSSZMaxSize(topic: GossipTopic, maxPayloadSize: number, sszType?: CompositeTypeAny): number {
144
+ const {fork} = topic.boundary;
145
+ // Gloas progressive containers have broad theoretical SSZ max sizes; use the preset p2p bounds instead.
146
+ switch (topic.type) {
147
+ case GossipType.beacon_block:
148
+ return isForkPostGloas(fork) ? MAX_SIGNED_BEACON_BLOCK_SIZE : maxPayloadSize;
149
+ case GossipType.beacon_aggregate_and_proof:
150
+ return isForkPostGloas(fork) ? MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
151
+ case GossipType.attester_slashing:
152
+ return isForkPostGloas(fork) ? MAX_ATTESTER_SLASHING_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
153
+ case GossipType.data_column_sidecar:
154
+ return isForkPostGloas(fork) ? MAX_DATA_COLUMN_SIDECAR_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
155
+ case GossipType.execution_payload:
156
+ return maxPayloadSize;
157
+ case GossipType.execution_payload_bid:
158
+ return MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE;
159
+ default:
160
+ return (sszType ?? getGossipSSZType(topic)).maxSize;
161
+ }
162
+ }
163
+
133
164
  /**
134
165
  * Deserialize a gossip serialized data into an ssz object.
135
166
  */
@@ -1241,6 +1241,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1241
1241
  payloadAttestationMessage.data.payloadPresent,
1242
1242
  payloadAttestationMessage.data.blobDataAvailable
1243
1243
  );
1244
+
1245
+ chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
1246
+ version: config.getForkName(payloadAttestationMessage.data.slot),
1247
+ data: payloadAttestationMessage,
1248
+ });
1244
1249
  },
1245
1250
  [GossipType.execution_payload_bid]: async ({
1246
1251
  gossipData,