@lodestar/beacon-node 1.45.0 → 1.46.0-dev.1cd9a671ea
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/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +186 -57
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +172 -88
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +6 -3
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -17
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +13 -5
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +44 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +48 -6
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +25 -4
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +14 -1
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/errors/proposerPreferences.js +2 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +11 -6
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +28 -2
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/lightClient/types.d.ts +3 -1
- package/lib/chain/lightClient/types.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/validation/aggregateAndProof.js +10 -6
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +10 -6
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +53 -4
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts +5 -0
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +31 -3
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts +9 -2
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +61 -9
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
- package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +2 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +15 -9
- 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 -2
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +74 -231
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +24 -1
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +4 -1
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +4 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +19 -11
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +12 -6
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +20 -11
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/util.d.ts +1 -0
- package/lib/network/util.d.ts.map +1 -1
- package/lib/network/util.js +4 -0
- package/lib/network/util.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +7 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +7 -2
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.js +1 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +18 -16
- package/src/api/impl/beacon/blocks/index.ts +221 -63
- package/src/api/impl/validator/index.ts +206 -98
- package/src/api/rest/base.ts +8 -3
- package/src/chain/blocks/importBlock.ts +0 -17
- package/src/chain/blocks/utils/chainSegment.ts +16 -5
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
- package/src/chain/chain.ts +61 -2
- package/src/chain/errors/blockError.ts +44 -7
- package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
- package/src/chain/errors/proposerPreferences.ts +16 -1
- package/src/chain/lightClient/index.ts +12 -7
- package/src/chain/lightClient/proofs.ts +36 -1
- package/src/chain/lightClient/types.ts +3 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
- package/src/chain/validation/aggregateAndProof.ts +12 -6
- package/src/chain/validation/attestation.ts +12 -6
- package/src/chain/validation/block.ts +69 -4
- package/src/chain/validation/executionPayloadBid.ts +33 -3
- package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
- package/src/chain/validation/proposerPreferences.ts +71 -9
- package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
- package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
- package/src/network/gossip/encoding.ts +13 -7
- package/src/network/gossip/gossipsub.ts +7 -2
- package/src/network/gossip/topic.ts +30 -0
- package/src/network/interface.ts +3 -1
- package/src/network/network.ts +24 -15
- package/src/network/processor/gossipHandlers.ts +12 -6
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
- package/src/network/reqresp/handlers/index.ts +21 -12
- package/src/network/util.ts +5 -0
- package/src/sync/sync.ts +10 -0
- package/src/sync/unknownBlock.ts +8 -2
- package/src/util/execution.ts +1 -1
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
computeEpochAtSlot,
|
|
19
19
|
computeTimeAtSlot,
|
|
20
|
+
isStatePostGloas,
|
|
20
21
|
reconstructSignedBlockContents,
|
|
21
22
|
signedBeaconBlockToBlinded,
|
|
22
23
|
signedBlockToSignedHeader,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
fulu,
|
|
32
33
|
gloas,
|
|
33
34
|
isDenebBlockContents,
|
|
35
|
+
isSignedExecutionPayloadEnvelopeContents,
|
|
34
36
|
sszTypesFor,
|
|
35
37
|
} from "@lodestar/types";
|
|
36
38
|
import {fromHex, sleep, toHex, toRootHex} from "@lodestar/utils";
|
|
@@ -38,9 +40,16 @@ import {BlockInputSource, isBlockInputBlobs, isBlockInputColumns} from "../../..
|
|
|
38
40
|
import {PayloadEnvelopeInputSource} from "../../../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
39
41
|
import {ImportBlockOpts} from "../../../../chain/blocks/types.js";
|
|
40
42
|
import {verifyBlocksInEpoch} from "../../../../chain/blocks/verifyBlock.js";
|
|
43
|
+
import {verifyExecutionPayloadEnvelope} from "../../../../chain/blocks/verifyExecutionPayloadEnvelope.js";
|
|
41
44
|
import {BeaconChain} from "../../../../chain/chain.js";
|
|
42
45
|
import {ChainEvent} from "../../../../chain/emitter.js";
|
|
43
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
BlockError,
|
|
48
|
+
BlockErrorCode,
|
|
49
|
+
BlockGossipError,
|
|
50
|
+
ExecutionPayloadEnvelopeError,
|
|
51
|
+
ExecutionPayloadEnvelopeErrorCode,
|
|
52
|
+
} from "../../../../chain/errors/index.js";
|
|
44
53
|
import {
|
|
45
54
|
BlockType,
|
|
46
55
|
ProduceFullBellatrix,
|
|
@@ -48,6 +57,7 @@ import {
|
|
|
48
57
|
ProduceFullFulu,
|
|
49
58
|
ProduceFullGloas,
|
|
50
59
|
} from "../../../../chain/produceBlock/index.js";
|
|
60
|
+
import {RegenCaller} from "../../../../chain/regen/index.js";
|
|
51
61
|
import {validateGossipBlock} from "../../../../chain/validation/block.js";
|
|
52
62
|
import {validateApiExecutionPayloadBid} from "../../../../chain/validation/executionPayloadBid.js";
|
|
53
63
|
import {validateApiExecutionPayloadEnvelope} from "../../../../chain/validation/executionPayloadEnvelope.js";
|
|
@@ -249,7 +259,7 @@ export function getBeaconBlockApi({
|
|
|
249
259
|
"api_reject_parent_unknown"
|
|
250
260
|
);
|
|
251
261
|
throw new BlockError(signedBlock, {
|
|
252
|
-
code: BlockErrorCode.
|
|
262
|
+
code: BlockErrorCode.PARENT_BLOCK_UNKNOWN,
|
|
253
263
|
parentRoot: toRootHex(signedBlock.message.parentRoot),
|
|
254
264
|
});
|
|
255
265
|
}
|
|
@@ -334,7 +344,8 @@ export function getBeaconBlockApi({
|
|
|
334
344
|
.catch((e) => {
|
|
335
345
|
if (
|
|
336
346
|
e instanceof BlockError &&
|
|
337
|
-
(e.type.code === BlockErrorCode.
|
|
347
|
+
(e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
|
|
348
|
+
e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN)
|
|
338
349
|
) {
|
|
339
350
|
chain.emitter.emit(ChainEvent.blockUnknownParent, {
|
|
340
351
|
blockInput: blockForImport,
|
|
@@ -345,7 +356,9 @@ export function getBeaconBlockApi({
|
|
|
345
356
|
throw e;
|
|
346
357
|
}),
|
|
347
358
|
];
|
|
348
|
-
const sentPeersArr = await promiseAllMaybeAsync<number | void>(
|
|
359
|
+
const sentPeersArr = await promiseAllMaybeAsync<{sentPeers: number; alreadyPublished: boolean} | number | void>(
|
|
360
|
+
publishPromises
|
|
361
|
+
);
|
|
349
362
|
|
|
350
363
|
if (isForkPostGloas(fork)) {
|
|
351
364
|
// After gloas, data columns are not published with the block but when publishing the execution payload envelope
|
|
@@ -356,10 +369,13 @@ export function getBeaconBlockApi({
|
|
|
356
369
|
// + 1 because we publish to beacon_block first
|
|
357
370
|
for (let i = 0; i < dataColumnSidecars.length; i++) {
|
|
358
371
|
// + 1 because we publish to beacon_block first
|
|
359
|
-
const sentPeers = sentPeersArr[i + 1] as number;
|
|
360
|
-
// sent peers could be 0 as we set `allowPublishToZeroTopicPeers=true` in network.publishDataColumnSidecar()
|
|
372
|
+
const {sentPeers, alreadyPublished} = sentPeersArr[i + 1] as {sentPeers: number; alreadyPublished: boolean};
|
|
373
|
+
// sent peers could be 0 as we set `allowPublishToZeroTopicPeers=true` in network.publishDataColumnSidecar()
|
|
361
374
|
metrics?.dataColumns.sentPeersPerSubnet.observe(sentPeers);
|
|
362
|
-
|
|
375
|
+
// A duplicate publish (alreadyPublished=true) means the column is already propagating on the network —
|
|
376
|
+
// expected in self-build flows where peers gossip columns back to us before we publish the envelope.
|
|
377
|
+
// Only warn when we genuinely failed to reach any peer. See https://github.com/ChainSafe/lodestar/issues/9527.
|
|
378
|
+
if (sentPeers === 0 && !alreadyPublished) {
|
|
363
379
|
columnsPublishedWithZeroPeers++;
|
|
364
380
|
}
|
|
365
381
|
}
|
|
@@ -651,8 +667,22 @@ export function getBeaconBlockApi({
|
|
|
651
667
|
publishBlockV2,
|
|
652
668
|
publishBlindedBlockV2,
|
|
653
669
|
|
|
654
|
-
async publishExecutionPayloadEnvelope({
|
|
670
|
+
async publishExecutionPayloadEnvelope({signedEnvelopeOrContents, broadcastValidation}) {
|
|
655
671
|
const seenTimestampSec = Date.now() / 1000;
|
|
672
|
+
|
|
673
|
+
const blobDataIncluded = isSignedExecutionPayloadEnvelopeContents(signedEnvelopeOrContents);
|
|
674
|
+
let signedExecutionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope;
|
|
675
|
+
// Blobs and KZG proofs submitted alongside the envelope in the stateless flow
|
|
676
|
+
let submittedContents: {kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs} | null = null;
|
|
677
|
+
|
|
678
|
+
if (blobDataIncluded) {
|
|
679
|
+
signedExecutionPayloadEnvelope = signedEnvelopeOrContents.signedExecutionPayloadEnvelope;
|
|
680
|
+
submittedContents = {kzgProofs: signedEnvelopeOrContents.kzgProofs, blobs: signedEnvelopeOrContents.blobs};
|
|
681
|
+
} else {
|
|
682
|
+
// Stateful flow, blobs and KZG proofs are attached from the block production cache below
|
|
683
|
+
signedExecutionPayloadEnvelope = signedEnvelopeOrContents;
|
|
684
|
+
}
|
|
685
|
+
|
|
656
686
|
const envelope = signedExecutionPayloadEnvelope.message;
|
|
657
687
|
const slot = envelope.payload.slotNumber;
|
|
658
688
|
const fork = config.getForkName(slot);
|
|
@@ -663,7 +693,6 @@ export function getBeaconBlockApi({
|
|
|
663
693
|
throw new ApiError(400, `publishExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
|
|
664
694
|
}
|
|
665
695
|
|
|
666
|
-
// TODO GLOAS: review checks, do we want to implement `broadcast_validation`?
|
|
667
696
|
let block = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.EMPTY);
|
|
668
697
|
if (block === null) {
|
|
669
698
|
// Only wait if the envelope is for the current slot
|
|
@@ -683,39 +712,172 @@ export function getBeaconBlockApi({
|
|
|
683
712
|
throw new ApiError(400, `Envelope slot ${slot} does not match block slot ${block.slot}`);
|
|
684
713
|
}
|
|
685
714
|
|
|
686
|
-
await validateApiExecutionPayloadEnvelope(chain, signedExecutionPayloadEnvelope);
|
|
687
|
-
|
|
688
715
|
const isSelfBuild = envelope.builderIndex === BUILDER_INDEX_SELF_BUILD;
|
|
689
|
-
|
|
716
|
+
const cachedResult = chain.blockProductionCache.get(blockRootHex);
|
|
717
|
+
// Only use the cached result if it contains payload data, blocks committing to a bid are cached without it
|
|
718
|
+
const cachedGloasResult =
|
|
719
|
+
cachedResult !== undefined &&
|
|
720
|
+
isForkPostGloas(cachedResult.fork) &&
|
|
721
|
+
cachedResult.type === BlockType.Full &&
|
|
722
|
+
(cachedResult as ProduceFullGloas).executionPayload !== undefined
|
|
723
|
+
? (cachedResult as ProduceFullGloas)
|
|
724
|
+
: undefined;
|
|
725
|
+
|
|
726
|
+
broadcastValidation = broadcastValidation ?? routes.beacon.BroadcastValidation.gossip;
|
|
727
|
+
const valLogMeta = {
|
|
728
|
+
slot,
|
|
729
|
+
blockRoot: blockRootHex,
|
|
730
|
+
blockHash: blockHashHex,
|
|
731
|
+
builderIndex: envelope.builderIndex,
|
|
732
|
+
isSelfBuild,
|
|
733
|
+
blobDataIncluded,
|
|
734
|
+
broadcastValidation,
|
|
735
|
+
...(submittedContents !== null ? {submittedBlobs: submittedContents.blobs.length} : {}),
|
|
736
|
+
};
|
|
737
|
+
try {
|
|
738
|
+
switch (broadcastValidation) {
|
|
739
|
+
case routes.beacon.BroadcastValidation.none: {
|
|
740
|
+
chain.logger.debug("Skipping broadcast validation of execution payload envelope", valLogMeta);
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
690
743
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
744
|
+
case routes.beacon.BroadcastValidation.gossip: {
|
|
745
|
+
await validateApiExecutionPayloadEnvelope(chain, signedExecutionPayloadEnvelope);
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
case routes.beacon.BroadcastValidation.consensusAndEquivocation:
|
|
750
|
+
case routes.beacon.BroadcastValidation.consensus: {
|
|
751
|
+
await validateApiExecutionPayloadEnvelope(chain, signedExecutionPayloadEnvelope);
|
|
752
|
+
|
|
753
|
+
// Verify the envelope against the post-block state
|
|
754
|
+
const blockState = await chain.regen
|
|
755
|
+
.getBlockSlotState(block, block.slot, {dontTransferCache: true}, RegenCaller.restApi)
|
|
756
|
+
.catch((e) => {
|
|
757
|
+
chain.logger.debug("Failed to regenerate block state for consensus checks", valLogMeta, e as Error);
|
|
758
|
+
return null;
|
|
759
|
+
});
|
|
760
|
+
if (blockState === null || !isStatePostGloas(blockState)) {
|
|
761
|
+
throw new ApiError(
|
|
762
|
+
500,
|
|
763
|
+
`Unable to regenerate block state for consensus checks slot=${slot} blockRoot=${blockRootHex}`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
// Signature and executionRequestsRoot are already verified by gossip validation above
|
|
768
|
+
verifyExecutionPayloadEnvelope(chain.config, blockState, envelope, {verifyExecutionRequestsRoot: false});
|
|
769
|
+
} catch (error) {
|
|
770
|
+
chain.logger.error(
|
|
771
|
+
"Consensus checks failed while publishing execution payload envelope",
|
|
772
|
+
valLogMeta,
|
|
773
|
+
error as Error
|
|
774
|
+
);
|
|
775
|
+
throw new ApiError(400, (error as Error).message);
|
|
776
|
+
}
|
|
777
|
+
chain.logger.debug("Consensus validated while publishing execution payload envelope", valLogMeta);
|
|
778
|
+
|
|
779
|
+
// TODO GLOAS: check the block is not a proposer equivocation before publishing the envelope
|
|
780
|
+
if (broadcastValidation === routes.beacon.BroadcastValidation.consensusAndEquivocation) {
|
|
781
|
+
const message = `Equivocation checks not yet implemented for broadcastValidation=${broadcastValidation}`;
|
|
782
|
+
if (chain.opts.broadcastValidationStrictness === "error") {
|
|
783
|
+
throw Error(message);
|
|
784
|
+
}
|
|
785
|
+
chain.logger.warn(message, valLogMeta);
|
|
786
|
+
}
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
default: {
|
|
791
|
+
const message = `Broadcast validation of ${broadcastValidation} type not implemented yet`;
|
|
792
|
+
if (chain.opts.broadcastValidationStrictness === "error") {
|
|
793
|
+
throw Error(message);
|
|
794
|
+
}
|
|
795
|
+
chain.logger.warn(message, valLogMeta);
|
|
796
|
+
}
|
|
699
797
|
}
|
|
700
|
-
|
|
701
|
-
|
|
798
|
+
} catch (error) {
|
|
799
|
+
if (
|
|
800
|
+
error instanceof ExecutionPayloadEnvelopeError &&
|
|
801
|
+
error.type.code === ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN
|
|
802
|
+
) {
|
|
803
|
+
// The envelope may already be known, e.g. received via gossip from another node in a
|
|
804
|
+
// multi node setup, this is benign and treated as a successful publish (same as blocks)
|
|
805
|
+
if (submittedContents === null) {
|
|
806
|
+
chain.logger.debug("Ignoring already-known execution payload envelope during publishing", valLogMeta);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
// The envelope may have been gossiped without its data columns being published, e.g. if
|
|
810
|
+
// another beacon node failed mid-publish, still publish columns from the submitted blobs
|
|
811
|
+
chain.logger.debug("Publishing data columns of already-known execution payload envelope", valLogMeta);
|
|
812
|
+
} else {
|
|
813
|
+
throw error;
|
|
702
814
|
}
|
|
815
|
+
}
|
|
703
816
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
817
|
+
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
|
|
818
|
+
if (!payloadInput) {
|
|
819
|
+
// The block is awaited above (queuing if the envelope arrived first), and both the API and
|
|
820
|
+
// gossip import paths seed the PayloadEnvelopeInput before importing the block, so the input
|
|
821
|
+
// should exist here.
|
|
822
|
+
throw new ApiError(404, `PayloadEnvelopeInput not found for slot=${slot} blockRoot=${blockRootHex}`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
let dataColumnSidecars: gloas.DataColumnSidecar[] = [];
|
|
826
|
+
let cells: fulu.Cell[][] | undefined;
|
|
827
|
+
let kzgProofs: deneb.KZGProofs | undefined;
|
|
828
|
+
let dataColumnTimer: (() => number) | undefined;
|
|
829
|
+
|
|
830
|
+
if (submittedContents !== null) {
|
|
831
|
+
// Validate submitted blob data against bid commitments before computing data column sidecars
|
|
832
|
+
const expectedBlobCount = payloadInput.getVersionedHashes().length;
|
|
833
|
+
if (submittedContents.blobs.length !== expectedBlobCount) {
|
|
834
|
+
throw new ApiError(
|
|
835
|
+
400,
|
|
836
|
+
`Submitted blob count does not match bid commitments submitted=${submittedContents.blobs.length} expected=${expectedBlobCount}`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const expectedProofCount = expectedBlobCount * NUMBER_OF_COLUMNS;
|
|
840
|
+
if (submittedContents.kzgProofs.length !== expectedProofCount) {
|
|
841
|
+
throw new ApiError(
|
|
842
|
+
400,
|
|
843
|
+
`Submitted KZG proof count does not match bid commitments submitted=${submittedContents.kzgProofs.length} expected=${expectedProofCount}`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
if (submittedContents.blobs.length > 0) {
|
|
847
|
+
// If the block was produced by this node, reuse the cached cells and only time the
|
|
848
|
+
// metric when cells are actually computed from the submitted blobs
|
|
849
|
+
if (cachedGloasResult?.cells) {
|
|
850
|
+
cells = cachedGloasResult.cells;
|
|
851
|
+
} else {
|
|
852
|
+
dataColumnTimer = metrics?.peerDas.dataColumnSidecarComputationTime.startTimer();
|
|
853
|
+
cells = submittedContents.blobs.map((blob) => kzg.computeCells(blob));
|
|
854
|
+
}
|
|
855
|
+
kzgProofs = submittedContents.kzgProofs;
|
|
856
|
+
}
|
|
857
|
+
} else if (cachedGloasResult !== undefined) {
|
|
858
|
+
if (cachedGloasResult.cells && cachedGloasResult.blobsBundle.commitments.length > 0) {
|
|
859
|
+
cells = cachedGloasResult.cells;
|
|
860
|
+
kzgProofs = cachedGloasResult.blobsBundle.proofs;
|
|
716
861
|
}
|
|
717
862
|
} else {
|
|
718
|
-
//
|
|
863
|
+
// An envelope without blob data can only be published via the beacon node that cached them at block production
|
|
864
|
+
if (payloadInput.getVersionedHashes().length > 0) {
|
|
865
|
+
throw new ApiError(
|
|
866
|
+
400,
|
|
867
|
+
`No cached blob data to attach to execution payload envelope for block root ${blockRootHex}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (cells !== undefined && kzgProofs !== undefined && cells.length > 0) {
|
|
873
|
+
const proofs = kzgProofs;
|
|
874
|
+
const cellsAndProofs = cells.map((rowCells, rowIndex) => ({
|
|
875
|
+
cells: rowCells,
|
|
876
|
+
proofs: proofs.slice(rowIndex * NUMBER_OF_COLUMNS, (rowIndex + 1) * NUMBER_OF_COLUMNS),
|
|
877
|
+
}));
|
|
878
|
+
|
|
879
|
+
dataColumnSidecars = getGloasDataColumnSidecars(slot, envelope.beaconBlockRoot, cellsAndProofs);
|
|
880
|
+
dataColumnTimer?.();
|
|
719
881
|
}
|
|
720
882
|
|
|
721
883
|
// If called near a slot boundary (e.g. late in slot N-1), hold briefly so gossip aligns with slot N.
|
|
@@ -724,21 +886,18 @@ export function getBeaconBlockApi({
|
|
|
724
886
|
await sleep(msToBlockSlot);
|
|
725
887
|
}
|
|
726
888
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
889
|
+
if (payloadInput.hasPayloadEnvelope()) {
|
|
890
|
+
// The envelope may have been added while this request was being validated, e.g. via gossip
|
|
891
|
+
chain.logger.debug("Execution payload envelope already added during publishing", valLogMeta);
|
|
892
|
+
} else {
|
|
893
|
+
payloadInput.addPayloadEnvelope({
|
|
894
|
+
envelope: signedExecutionPayloadEnvelope,
|
|
895
|
+
source: PayloadEnvelopeInputSource.api,
|
|
896
|
+
seenTimestampSec,
|
|
897
|
+
peerIdStr: undefined,
|
|
898
|
+
});
|
|
733
899
|
}
|
|
734
900
|
|
|
735
|
-
payloadInput.addPayloadEnvelope({
|
|
736
|
-
envelope: signedExecutionPayloadEnvelope,
|
|
737
|
-
source: PayloadEnvelopeInputSource.api,
|
|
738
|
-
seenTimestampSec,
|
|
739
|
-
peerIdStr: undefined,
|
|
740
|
-
});
|
|
741
|
-
|
|
742
901
|
if (dataColumnSidecars.length > 0) {
|
|
743
902
|
for (const columnSidecar of dataColumnSidecars) {
|
|
744
903
|
payloadInput.addColumn({
|
|
@@ -750,31 +909,28 @@ export function getBeaconBlockApi({
|
|
|
750
909
|
}
|
|
751
910
|
}
|
|
752
911
|
|
|
753
|
-
const valLogMeta = {
|
|
754
|
-
slot,
|
|
755
|
-
blockRoot: blockRootHex,
|
|
756
|
-
blockHash: blockHashHex,
|
|
757
|
-
builderIndex: envelope.builderIndex,
|
|
758
|
-
isSelfBuild,
|
|
759
|
-
dataColumns: dataColumnSidecars.length,
|
|
760
|
-
};
|
|
761
|
-
|
|
762
912
|
const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
|
|
763
913
|
metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.api}, delaySec);
|
|
764
914
|
chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.api, delaySec, signedExecutionPayloadEnvelope);
|
|
765
915
|
|
|
766
|
-
chain.logger.info("Publishing execution payload envelope",
|
|
916
|
+
chain.logger.info("Publishing execution payload envelope", {
|
|
917
|
+
...valLogMeta,
|
|
918
|
+
dataColumns: dataColumnSidecars.length,
|
|
919
|
+
});
|
|
767
920
|
|
|
768
921
|
const publishPromises = [
|
|
769
922
|
// Gossip the signed execution payload envelope first
|
|
770
923
|
() => network.publishSignedExecutionPayloadEnvelope(signedExecutionPayloadEnvelope),
|
|
771
|
-
//
|
|
924
|
+
// Publish all data column sidecars
|
|
772
925
|
...dataColumnSidecars.map((dataColumnSidecar) => () => network.publishDataColumnSidecar(dataColumnSidecar)),
|
|
773
|
-
// Import execution payload. Signature
|
|
926
|
+
// Import execution payload. Signature is verified during broadcast validation, an already
|
|
927
|
+
// known envelope was verified by its original source and `none` deliberately skips validation
|
|
774
928
|
() => chain.processExecutionPayload(payloadInput, {validSignature: true}),
|
|
775
929
|
];
|
|
776
930
|
|
|
777
|
-
const publishPromise = promiseAllMaybeAsync<number | void>(
|
|
931
|
+
const publishPromise = promiseAllMaybeAsync<{sentPeers: number; alreadyPublished: boolean} | number | void>(
|
|
932
|
+
publishPromises
|
|
933
|
+
);
|
|
778
934
|
|
|
779
935
|
chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
|
|
780
936
|
slot,
|
|
@@ -790,9 +946,11 @@ export function getBeaconBlockApi({
|
|
|
790
946
|
let columnsPublishedWithZeroPeers = 0;
|
|
791
947
|
// Skip first entry (envelope); the final entry is processExecutionPayload(), which returns void.
|
|
792
948
|
for (let i = 0; i < dataColumnSidecars.length; i++) {
|
|
793
|
-
const sentPeers = sentPeersArr[i + 1] as number;
|
|
949
|
+
const {sentPeers, alreadyPublished} = sentPeersArr[i + 1] as {sentPeers: number; alreadyPublished: boolean};
|
|
794
950
|
metrics?.dataColumns.sentPeersPerSubnet.observe(sentPeers);
|
|
795
|
-
|
|
951
|
+
// A duplicate publish means the column is already propagating — expected in self-build flows.
|
|
952
|
+
// Only warn when we genuinely failed to reach any peer. See https://github.com/ChainSafe/lodestar/issues/9527.
|
|
953
|
+
if (sentPeers === 0 && !alreadyPublished) {
|
|
796
954
|
columnsPublishedWithZeroPeers++;
|
|
797
955
|
}
|
|
798
956
|
}
|