@lodestar/beacon-node 1.45.0-dev.d7859e978b → 1.45.0-dev.e6c77a6488
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/api/impl/beacon/index.d.ts +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +6 -43
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +48 -5
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +13 -3
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +48 -4
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +2 -2
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +2 -2
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/proof/index.d.ts +1 -1
- package/lib/api/impl/proof/index.d.ts.map +1 -1
- package/lib/api/impl/proof/index.js +2 -2
- package/lib/api/impl/proof/index.js.map +1 -1
- package/lib/api/impl/utils.d.ts +23 -0
- package/lib/api/impl/utils.d.ts.map +1 -1
- package/lib/api/impl/utils.js +44 -1
- package/lib/api/impl/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +0 -14
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +62 -61
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +10 -8
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +17 -17
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +1 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +1 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +1 -0
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -0
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +4 -12
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +0 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +0 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +1 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +7 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +6 -0
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +21 -6
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +14 -5
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/core/metrics.d.ts +65 -0
- package/lib/network/core/metrics.d.ts.map +1 -1
- package/lib/network/core/metrics.js +94 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/discv5/utils.d.ts.map +1 -1
- package/lib/network/discv5/utils.js +12 -3
- package/lib/network/discv5/utils.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +7 -0
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +2 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +6 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -3
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/peers/discover.d.ts.map +1 -1
- package/lib/network/peers/discover.js +17 -12
- package/lib/network/peers/discover.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +10 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/score/score.d.ts +3 -1
- package/lib/network/peers/score/score.d.ts.map +1 -1
- package/lib/network/peers/score/score.js +6 -1
- package/lib/network/peers/score/score.js.map +1 -1
- package/lib/network/peers/score/store.d.ts.map +1 -1
- package/lib/network/peers/score/store.js +6 -1
- package/lib/network/peers/score/store.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +14 -0
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +32 -16
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/range/chain.d.ts +4 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +3 -3
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +10 -0
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +25 -19
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/util/graffiti.d.ts +15 -0
- package/lib/util/graffiti.d.ts.map +1 -1
- package/lib/util/graffiti.js +55 -0
- package/lib/util/graffiti.js.map +1 -1
- package/package.json +18 -18
- package/src/api/impl/beacon/index.ts +1 -1
- package/src/api/impl/beacon/pool/index.ts +5 -53
- package/src/api/impl/beacon/state/index.ts +55 -3
- package/src/api/impl/beacon/state/utils.ts +68 -4
- package/src/api/impl/debug/index.ts +3 -2
- package/src/api/impl/lodestar/index.ts +2 -2
- package/src/api/impl/proof/index.ts +2 -2
- package/src/api/impl/utils.ts +51 -1
- package/src/api/impl/validator/index.ts +70 -66
- package/src/chain/blocks/importBlock.ts +19 -10
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +17 -17
- package/src/chain/chain.ts +0 -3
- package/src/chain/interface.ts +0 -2
- package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
- package/src/chain/options.ts +2 -0
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/seenCache/index.ts +0 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -0
- package/src/chain/shufflingCache.ts +23 -7
- package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/chain/validation/proposerPreferences.ts +15 -5
- package/src/network/core/metrics.ts +94 -0
- package/src/network/discv5/utils.ts +11 -3
- package/src/network/gossip/encoding.ts +7 -0
- package/src/network/gossip/gossipsub.ts +2 -1
- package/src/network/gossip/topic.ts +28 -4
- package/src/network/peers/discover.ts +16 -11
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/peers/score/score.ts +8 -1
- package/src/network/peers/score/store.ts +8 -1
- package/src/network/peers/utils/prioritizePeers.ts +15 -0
- package/src/network/processor/gossipHandlers.ts +35 -16
- package/src/sync/range/chain.ts +4 -5
- package/src/sync/range/range.ts +10 -0
- package/src/sync/utils/downloadByRange.ts +26 -19
- package/src/util/graffiti.ts +78 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
- package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
- package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
|
@@ -7,6 +7,7 @@ import {MapDef} from "@lodestar/utils";
|
|
|
7
7
|
import {shuffle} from "../../../util/shuffle.js";
|
|
8
8
|
import {sortBy} from "../../../util/sortBy.js";
|
|
9
9
|
import {NetworkCoreMetrics} from "../../core/metrics.js";
|
|
10
|
+
import {SubnetType} from "../../metadata.js";
|
|
10
11
|
import {RequestedSubnet} from "./subnetMap.js";
|
|
11
12
|
|
|
12
13
|
/** Target number of peers we'd like to have connected to a given long-lived subnet */
|
|
@@ -189,6 +190,18 @@ export function prioritizePeers(
|
|
|
189
190
|
|
|
190
191
|
const connectedPeerCount = connectedPeers.length;
|
|
191
192
|
|
|
193
|
+
// Track the outbound peers invariant (we keep at least OUTBOUND_PEERS_RATIO outbound peers).
|
|
194
|
+
// Report 0 when there are no peers so the gauge doesn't go stale at its last value.
|
|
195
|
+
if (metrics) {
|
|
196
|
+
let outboundPeerCount = 0;
|
|
197
|
+
for (const peer of connectedPeers) {
|
|
198
|
+
if (peer.direction === "outbound") {
|
|
199
|
+
outboundPeerCount++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
metrics.peerManager.outboundPeersRatio.set(connectedPeerCount > 0 ? outboundPeerCount / connectedPeerCount : 0);
|
|
203
|
+
}
|
|
204
|
+
|
|
192
205
|
if (connectedPeerCount < targetPeers) {
|
|
193
206
|
// Need more peers.
|
|
194
207
|
// Instead of attempting to connect the exact amount necessary this will overshoot a little since the success
|
|
@@ -256,6 +269,7 @@ function requestSubnetPeers(
|
|
|
256
269
|
|
|
257
270
|
for (const {subnet, toSlot} of activeAttnets) {
|
|
258
271
|
const peersInSubnet = peersPerSubnet.get(subnet) ?? 0;
|
|
272
|
+
metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.attnets}, peersInSubnet);
|
|
259
273
|
if (peersInSubnet < targetSubnetPeers) {
|
|
260
274
|
// We need more peers
|
|
261
275
|
attnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peersInSubnet});
|
|
@@ -282,6 +296,7 @@ function requestSubnetPeers(
|
|
|
282
296
|
|
|
283
297
|
for (const {subnet, toSlot} of activeSyncnets) {
|
|
284
298
|
const peersInSubnet = peersPerSubnet.get(subnet) ?? 0;
|
|
299
|
+
metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.syncnets}, peersInSubnet);
|
|
285
300
|
if (peersInSubnet < targetSubnetPeers) {
|
|
286
301
|
// We need more peers
|
|
287
302
|
syncnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peersInSubnet});
|
|
@@ -165,6 +165,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
165
165
|
// tracked in https://github.com/ChainSafe/lodestar/issues/7957
|
|
166
166
|
|
|
167
167
|
const logCtx = {
|
|
168
|
+
slot,
|
|
168
169
|
currentSlot: chain.clock.currentSlot,
|
|
169
170
|
peerId: peerIdStr,
|
|
170
171
|
delaySec,
|
|
@@ -184,20 +185,24 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
184
185
|
seenTimestampSec,
|
|
185
186
|
peerIdStr,
|
|
186
187
|
});
|
|
188
|
+
|
|
189
|
+
// Optimistically seed the payload-envelope cache too, mirroring seenBlockInputCache above.
|
|
190
|
+
// This ensures we have PayloadEnvelopeInput, even through "PARENT_UNKNOWN" error
|
|
191
|
+
// see https://github.com/ChainSafe/lodestar/issues/9475
|
|
192
|
+
if (isForkPostGloas(fork)) {
|
|
193
|
+
chain.seenPayloadEnvelopeInputCache.add({
|
|
194
|
+
blockRootHex,
|
|
195
|
+
block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
|
|
196
|
+
forkName: fork,
|
|
197
|
+
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
198
|
+
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
199
|
+
timeCreatedSec: seenTimestampSec,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
187
203
|
try {
|
|
188
204
|
const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
|
|
189
205
|
|
|
190
|
-
if (isForkPostGloas(fork)) {
|
|
191
|
-
chain.seenPayloadEnvelopeInputCache.add({
|
|
192
|
-
blockRootHex,
|
|
193
|
-
block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
|
|
194
|
-
forkName: fork,
|
|
195
|
-
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
196
|
-
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
197
|
-
timeCreatedSec: seenTimestampSec,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
206
|
const blockInputMeta = blockInput.getLogMeta();
|
|
202
207
|
|
|
203
208
|
const recvToValidation = Date.now() / 1000 - seenTimestampSec;
|
|
@@ -234,12 +239,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
234
239
|
throw e;
|
|
235
240
|
}
|
|
236
241
|
|
|
237
|
-
|
|
238
|
-
|
|
242
|
+
// IGNORE means the block is acceptable (e.g. FUTURE_SLOT, ALREADY_KNOWN), just not propagated.
|
|
243
|
+
// Keep the optimistically-added cache entries; they are pruned on finalization. Only REJECT
|
|
244
|
+
// (provably invalid) and unexpected errors prune below.
|
|
245
|
+
if (e.action === GossipAction.IGNORE) {
|
|
246
|
+
throw e;
|
|
239
247
|
}
|
|
248
|
+
|
|
249
|
+
chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
|
|
240
250
|
}
|
|
241
251
|
|
|
252
|
+
// REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
|
|
253
|
+
// both caches, keeping them consistent.
|
|
242
254
|
chain.seenBlockInputCache.prune(blockRootHex);
|
|
255
|
+
if (isForkPostGloas(fork)) {
|
|
256
|
+
chain.seenPayloadEnvelopeInputCache.prune(blockRootHex);
|
|
257
|
+
}
|
|
243
258
|
throw e;
|
|
244
259
|
}
|
|
245
260
|
}
|
|
@@ -1127,9 +1142,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1127
1142
|
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
|
|
1128
1143
|
|
|
1129
1144
|
logger.debug("Received gossip payload envelope", {
|
|
1145
|
+
slot,
|
|
1130
1146
|
currentSlot: chain.clock.currentSlot,
|
|
1131
1147
|
peerId: peerIdStr,
|
|
1132
|
-
slot,
|
|
1133
1148
|
blockRoot: toRootHex(envelope.beaconBlockRoot),
|
|
1134
1149
|
delaySec,
|
|
1135
1150
|
});
|
|
@@ -1226,6 +1241,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1226
1241
|
payloadAttestationMessage.data.payloadPresent,
|
|
1227
1242
|
payloadAttestationMessage.data.blobDataAvailable
|
|
1228
1243
|
);
|
|
1244
|
+
|
|
1245
|
+
chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
|
|
1246
|
+
version: config.getForkName(payloadAttestationMessage.data.slot),
|
|
1247
|
+
data: payloadAttestationMessage,
|
|
1248
|
+
});
|
|
1229
1249
|
},
|
|
1230
1250
|
[GossipType.execution_payload_bid]: async ({
|
|
1231
1251
|
gossipData,
|
|
@@ -1258,9 +1278,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1258
1278
|
const signedProposerPreferences = sszDeserialize(topic, serializedData);
|
|
1259
1279
|
await validateGossipProposerPreferences(chain, signedProposerPreferences);
|
|
1260
1280
|
|
|
1261
|
-
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
1262
1281
|
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1263
|
-
version:
|
|
1282
|
+
version: config.getForkName(signedProposerPreferences.message.proposalSlot),
|
|
1264
1283
|
data: signedProposerPreferences,
|
|
1265
1284
|
});
|
|
1266
1285
|
},
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -186,10 +186,6 @@ export class SyncChain {
|
|
|
186
186
|
this.logger = logger;
|
|
187
187
|
this.logId = `${syncType}-${nextChainId++}`;
|
|
188
188
|
|
|
189
|
-
if (metrics) {
|
|
190
|
-
metrics.syncRange.headSyncPeers.addCollect(() => this.scrapeMetrics(metrics));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
189
|
// Trigger event on parent class
|
|
194
190
|
this.sync().then(
|
|
195
191
|
() => fns.onEnd(null, this.target),
|
|
@@ -775,7 +771,10 @@ export class SyncChain {
|
|
|
775
771
|
});
|
|
776
772
|
}
|
|
777
773
|
|
|
778
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Called by `RangeSync`'s to avoid collecting metrics of removed chains.
|
|
776
|
+
*/
|
|
777
|
+
scrapeMetrics(metrics: Metrics): void {
|
|
779
778
|
const syncPeersMetric =
|
|
780
779
|
this.syncType === RangeSyncType.Finalized
|
|
781
780
|
? metrics.syncRange.finalizedSyncPeers
|
package/src/sync/range/range.ts
CHANGED
|
@@ -105,6 +105,16 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
105
105
|
|
|
106
106
|
if (metrics) {
|
|
107
107
|
metrics.syncStatus.addCollect(() => this.scrapeMetrics(metrics));
|
|
108
|
+
metrics.syncRange.headSyncPeers.addCollect(() => {
|
|
109
|
+
// Gauges retain their last set value, so a removed chain would keep reporting stale
|
|
110
|
+
// per-column peer counts (finalizedSyncPeers especially, as it is rarely recreated once
|
|
111
|
+
// synced)
|
|
112
|
+
metrics.syncRange.headSyncPeers.reset();
|
|
113
|
+
metrics.syncRange.finalizedSyncPeers.reset();
|
|
114
|
+
for (const syncChain of this.chains.values()) {
|
|
115
|
+
syncChain.scrapeMetrics(metrics);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
120
|
|
|
@@ -142,6 +142,7 @@ export function cacheByRangeResponses({
|
|
|
142
142
|
const blockRootHex = toRootHex(blockRoot);
|
|
143
143
|
|
|
144
144
|
const existing = updatedBatchBlocks.get(block.message.slot);
|
|
145
|
+
let blockInput: IBlockInput;
|
|
145
146
|
if (existing) {
|
|
146
147
|
// In practice this code block shouldn't be reached because we shouldn't be refetching a block we already have, see Batch#getRequests.
|
|
147
148
|
// Will throw if root hex does not match (meaning we are following the wrong chain)
|
|
@@ -155,8 +156,9 @@ export function cacheByRangeResponses({
|
|
|
155
156
|
},
|
|
156
157
|
{throwOnDuplicateAdd: false}
|
|
157
158
|
);
|
|
159
|
+
blockInput = existing;
|
|
158
160
|
} else {
|
|
159
|
-
|
|
161
|
+
blockInput = cache.getByBlock({
|
|
160
162
|
block,
|
|
161
163
|
blockRootHex,
|
|
162
164
|
source,
|
|
@@ -165,6 +167,21 @@ export function cacheByRangeResponses({
|
|
|
165
167
|
});
|
|
166
168
|
updatedBatchBlocks.set(blockInput.slot, blockInput);
|
|
167
169
|
}
|
|
170
|
+
|
|
171
|
+
// Seed seenPayloadEnvelopeInputCache the instant we have the block, before the blob loop (or any
|
|
172
|
+
// later step) can throw and abort the batch — otherwise a gloas block would sit in
|
|
173
|
+
// seenBlockInputCache but unseeded here, and payload-by-root sync would later throw "Missing
|
|
174
|
+
// PayloadEnvelopeInput for known block" (see issue #9306). add() is idempotent.
|
|
175
|
+
if (isForkPostGloas(blockInput.forkName)) {
|
|
176
|
+
seenPayloadEnvelopeInputCache.add({
|
|
177
|
+
blockRootHex: blockInput.blockRootHex,
|
|
178
|
+
block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
|
|
179
|
+
forkName: blockInput.forkName,
|
|
180
|
+
sampledColumns: custodyConfig.sampledColumns,
|
|
181
|
+
custodyColumns: custodyConfig.custodyColumns,
|
|
182
|
+
timeCreatedSec: seenTimestampSec,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
|
|
170
187
|
for (const {blockRoot, blobSidecars} of responses.validatedBlobSidecars ?? []) {
|
|
@@ -205,22 +222,6 @@ export function cacheByRangeResponses({
|
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
|
|
208
|
-
// Seed seenPayloadEnvelopeInputCache for every gloas block in the batch, regardless of whether
|
|
209
|
-
// the peer returned its envelope. Without this, a block returned without its envelope would be
|
|
210
|
-
// imported with no cache entry, and later payload-by-root sync would throw
|
|
211
|
-
// "Missing PayloadEnvelopeInput for known block" (see issue #9306).
|
|
212
|
-
for (const blockInput of updatedBatchBlocks.values()) {
|
|
213
|
-
if (!blockInput.hasBlock() || !isForkPostGloas(blockInput.forkName)) continue;
|
|
214
|
-
seenPayloadEnvelopeInputCache.add({
|
|
215
|
-
blockRootHex: blockInput.blockRootHex,
|
|
216
|
-
block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
|
|
217
|
-
forkName: blockInput.forkName,
|
|
218
|
-
sampledColumns: custodyConfig.sampledColumns,
|
|
219
|
-
custodyColumns: custodyConfig.custodyColumns,
|
|
220
|
-
timeCreatedSec: seenTimestampSec,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
225
|
let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
|
|
225
226
|
existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
|
|
226
227
|
if (downloadedPayloadEnvelopes !== null) {
|
|
@@ -229,8 +230,8 @@ export function cacheByRangeResponses({
|
|
|
229
230
|
const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
|
|
230
231
|
const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
|
|
231
232
|
if (payloadInput === undefined) {
|
|
232
|
-
// Unreachable given the loop above seeded an entry for every gloas block in
|
|
233
|
-
// for the parent block, it's populated at BeaconChain init
|
|
233
|
+
// Unreachable given the validatedBlocks loop above seeded an entry for every gloas block in
|
|
234
|
+
// the batch. for the parent block, it's populated at BeaconChain init
|
|
234
235
|
throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
|
|
235
236
|
}
|
|
236
237
|
|
|
@@ -980,6 +981,12 @@ export async function validateColumnsByRangeResponse(
|
|
|
980
981
|
missingIndices: prettyPrintIndices(request.columns),
|
|
981
982
|
})
|
|
982
983
|
);
|
|
984
|
+
// Gloas: the bid commits to blobs even for payloads that get orphaned, whose columns are
|
|
985
|
+
// pruned and unservable. Skip instead of truncating + re-requesting forever; DA is enforced
|
|
986
|
+
// on envelope processing. Pre-Gloas commitments are canonical, so keep strict.
|
|
987
|
+
if (isForkPostGloas(forkName)) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
983
990
|
break;
|
|
984
991
|
}
|
|
985
992
|
|
package/src/util/graffiti.ts
CHANGED
|
@@ -37,3 +37,81 @@ export function getDefaultGraffiti(
|
|
|
37
37
|
// No EL client info available. We still want to include CL info albeit not spec compliant
|
|
38
38
|
return `${consensusClientVersion.code}${consensusClientVersion.commit.slice(0, 4)}`;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
function appendLongestFittingSuffix(userGraffiti: string, suffixes: string[]): string {
|
|
42
|
+
const userGraffitiBytes = Buffer.byteLength(userGraffiti, "utf8");
|
|
43
|
+
if (userGraffitiBytes >= GRAFFITI_SIZE) {
|
|
44
|
+
return userGraffiti;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const availableBytes = GRAFFITI_SIZE - userGraffitiBytes;
|
|
48
|
+
|
|
49
|
+
for (const suffix of suffixes) {
|
|
50
|
+
if (Buffer.byteLength(suffix, "utf8") <= availableBytes) {
|
|
51
|
+
return `${userGraffiti}${suffix}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return userGraffiti;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Appends the richest available client watermark that fits after user graffiti.
|
|
60
|
+
*
|
|
61
|
+
* Tiers are:
|
|
62
|
+
* - full EL/CL watermark, e.g. " BU9b0eLS80c2"
|
|
63
|
+
* - EL/CL client codes, e.g. " BULS"
|
|
64
|
+
* - CL client code, e.g. " LS"
|
|
65
|
+
*/
|
|
66
|
+
export function appendClientInfoToGraffiti(
|
|
67
|
+
userGraffiti: string,
|
|
68
|
+
consensusClientVersion: ClientVersion,
|
|
69
|
+
executionClientVersion: ClientVersion | null | undefined,
|
|
70
|
+
opts: {private?: boolean} = {}
|
|
71
|
+
): string {
|
|
72
|
+
if (opts.private) {
|
|
73
|
+
return userGraffiti;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Graffiti supplied via the beacon API is decoded from a fixed 32-byte field (see
|
|
77
|
+
// fromGraffitiHex) and arrives right-padded with NUL bytes. Trim only trailing padding
|
|
78
|
+
// NULs; a NUL that appears in the middle of the string is data, not padding.
|
|
79
|
+
let end = userGraffiti.length;
|
|
80
|
+
while (end > 0 && userGraffiti.charCodeAt(end - 1) === 0) {
|
|
81
|
+
end--;
|
|
82
|
+
}
|
|
83
|
+
const graffiti = userGraffiti.slice(0, end);
|
|
84
|
+
|
|
85
|
+
const fullClientInfo = getDefaultGraffiti(consensusClientVersion, executionClientVersion, {private: false});
|
|
86
|
+
if (graffiti.length === 0) {
|
|
87
|
+
return fullClientInfo;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const suffixes =
|
|
91
|
+
executionClientVersion != null
|
|
92
|
+
? [
|
|
93
|
+
` ${fullClientInfo}`,
|
|
94
|
+
` ${executionClientVersion.code}${consensusClientVersion.code}`,
|
|
95
|
+
` ${consensusClientVersion.code}`,
|
|
96
|
+
]
|
|
97
|
+
: [` ${fullClientInfo}`, ` ${consensusClientVersion.code}`];
|
|
98
|
+
|
|
99
|
+
return appendLongestFittingSuffix(graffiti, suffixes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getBlockGraffiti(
|
|
103
|
+
userGraffiti: string | undefined,
|
|
104
|
+
consensusClientVersion: ClientVersion,
|
|
105
|
+
executionClientVersion: ClientVersion | null | undefined,
|
|
106
|
+
opts: {private?: boolean; graffitiAppend?: boolean}
|
|
107
|
+
): string {
|
|
108
|
+
if (userGraffiti === undefined) {
|
|
109
|
+
return getDefaultGraffiti(consensusClientVersion, executionClientVersion, opts);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (opts.graffitiAppend === false) {
|
|
113
|
+
return userGraffiti;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return appendClientInfoToGraffiti(userGraffiti, consensusClientVersion, executionClientVersion, opts);
|
|
117
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { RootHex, Slot, ValidatorIndex } from "@lodestar/types";
|
|
2
|
-
/**
|
|
3
|
-
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
4
|
-
*/
|
|
5
|
-
export declare class SeenProposerPreferences {
|
|
6
|
-
private readonly validatorByDependentRootBySlot;
|
|
7
|
-
isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean;
|
|
8
|
-
add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void;
|
|
9
|
-
/**
|
|
10
|
-
* Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
|
|
11
|
-
* passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
|
|
12
|
-
* on each slot tick.
|
|
13
|
-
*/
|
|
14
|
-
prune(currentSlot: Slot): void;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=seenProposerPreferences.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"seenProposerPreferences.d.ts","sourceRoot":"","sources":["../../../src/chain/seenCache/seenProposerPreferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAG9D;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,8BAA8B,CAE7C;IAEF,OAAO,CAAC,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAE3F;IAED,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,CAEpF;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAM7B;CACF"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { MapDef } from "@lodestar/utils";
|
|
2
|
-
/**
|
|
3
|
-
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
4
|
-
*/
|
|
5
|
-
export class SeenProposerPreferences {
|
|
6
|
-
validatorByDependentRootBySlot = new MapDef(() => new Map());
|
|
7
|
-
isKnown(dependentRoot, proposalSlot, validatorIndex) {
|
|
8
|
-
return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
|
|
9
|
-
}
|
|
10
|
-
add(dependentRoot, proposalSlot, validatorIndex) {
|
|
11
|
-
this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
|
|
15
|
-
* passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
|
|
16
|
-
* on each slot tick.
|
|
17
|
-
*/
|
|
18
|
-
prune(currentSlot) {
|
|
19
|
-
for (const slot of this.validatorByDependentRootBySlot.keys()) {
|
|
20
|
-
if (slot < currentSlot) {
|
|
21
|
-
this.validatorByDependentRootBySlot.delete(slot);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
//# sourceMappingURL=seenProposerPreferences.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"seenProposerPreferences.js","sourceRoot":"","sources":["../../../src/chain/seenCache/seenProposerPreferences.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAEvC;;GAEG;AACH,MAAM,OAAO,uBAAuB;IACjB,8BAA8B,GAAG,IAAI,MAAM,CAC1D,GAAG,EAAE,CAAC,IAAI,GAAG,EAA2B,CACzC,CAAC;IAEF,OAAO,CAAC,aAAsB,EAAE,YAAkB,EAAE,cAA8B,EAAW;QAC3F,OAAO,IAAI,CAAC,8BAA8B,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,cAAc,CAAC;IAAA,CACrG;IAED,GAAG,CAAC,aAAsB,EAAE,YAAkB,EAAE,cAA8B,EAAQ;QACpF,IAAI,CAAC,8BAA8B,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAAA,CACnG;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAiB,EAAQ;QAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9D,IAAI,IAAI,GAAG,WAAW,EAAE,CAAC;gBACvB,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IAAA,CACF;CACF"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
|
-
import {MapDef} from "@lodestar/utils";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
6
|
-
*/
|
|
7
|
-
export class SeenProposerPreferences {
|
|
8
|
-
private readonly validatorByDependentRootBySlot = new MapDef<Slot, Map<RootHex, ValidatorIndex>>(
|
|
9
|
-
() => new Map<RootHex, ValidatorIndex>()
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
|
|
13
|
-
return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
|
|
17
|
-
this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
|
|
22
|
-
* passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
|
|
23
|
-
* on each slot tick.
|
|
24
|
-
*/
|
|
25
|
-
prune(currentSlot: Slot): void {
|
|
26
|
-
for (const slot of this.validatorByDependentRootBySlot.keys()) {
|
|
27
|
-
if (slot < currentSlot) {
|
|
28
|
-
this.validatorByDependentRootBySlot.delete(slot);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|