@lodestar/beacon-node 1.45.0 → 1.46.0-dev.82546f11da
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
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
ssz,
|
|
51
51
|
} from "@lodestar/types";
|
|
52
52
|
import {
|
|
53
|
+
GWEI_TO_WEI,
|
|
53
54
|
TimeoutError,
|
|
54
55
|
defer,
|
|
55
56
|
formatWeiToEth,
|
|
@@ -101,7 +102,7 @@ import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProdu
|
|
|
101
102
|
* A cutoff of 2 seconds gives enough time and if there are unexpected delays it ensures we publish
|
|
102
103
|
* in time as proposals post 4 seconds into the slot will likely be orphaned due to proposer boost reorg.
|
|
103
104
|
*
|
|
104
|
-
* TODO GLOAS: re-evaluate cutoff timing
|
|
105
|
+
* TODO GLOAS: re-evaluate cutoff timing due to attestation deadline changes in gloas
|
|
105
106
|
*/
|
|
106
107
|
const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
|
|
107
108
|
/** Overall timeout for execution and block production apis */
|
|
@@ -528,6 +529,16 @@ export function getValidatorApi(
|
|
|
528
529
|
builderBoostFactor?: bigint,
|
|
529
530
|
{feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
|
|
530
531
|
): Promise<ProduceBlindedBlockOrBlockContentsRes> {
|
|
532
|
+
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
|
|
533
|
+
if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
|
|
534
|
+
logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
|
|
535
|
+
builderSelection = routes.validator.BuilderSelection.BuilderAlways;
|
|
536
|
+
}
|
|
537
|
+
builderBoostFactor = builderBoostFactor ?? BigInt(100);
|
|
538
|
+
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
|
|
539
|
+
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
|
|
540
|
+
}
|
|
541
|
+
|
|
531
542
|
notWhileSyncing(chain, sync.state);
|
|
532
543
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
533
544
|
|
|
@@ -538,36 +549,12 @@ export function getValidatorApi(
|
|
|
538
549
|
metrics?.blockProductionSlotDelta.set(slot - parentSlot);
|
|
539
550
|
|
|
540
551
|
const fork = config.getForkName(slot);
|
|
541
|
-
// set some sensible opts
|
|
542
|
-
// builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled
|
|
543
|
-
// and the actual selection will be determined using builderBoostFactor passed by the validator
|
|
544
|
-
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
|
|
545
|
-
builderBoostFactor = builderBoostFactor ?? BigInt(100);
|
|
546
|
-
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
|
|
547
|
-
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
|
|
548
|
-
}
|
|
549
552
|
|
|
550
553
|
const isBuilderEnabled =
|
|
551
554
|
ForkSeq[fork] >= ForkSeq.bellatrix &&
|
|
552
555
|
chain.executionBuilder !== undefined &&
|
|
553
556
|
builderSelection !== routes.validator.BuilderSelection.ExecutionOnly;
|
|
554
557
|
|
|
555
|
-
// At any point either the builder or execution or both flows should be active.
|
|
556
|
-
//
|
|
557
|
-
// Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager
|
|
558
|
-
// configurations could cause a validator pubkey to have builder disabled with builder selection builder only
|
|
559
|
-
// (TODO: independently make sure such an options update is not successful for a validator pubkey)
|
|
560
|
-
//
|
|
561
|
-
// So if builder is disabled ignore builder selection of builder only if caused by user mistake
|
|
562
|
-
// https://github.com/ChainSafe/lodestar/issues/6338
|
|
563
|
-
const isEngineEnabled = !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly;
|
|
564
|
-
|
|
565
|
-
if (!isEngineEnabled && !isBuilderEnabled) {
|
|
566
|
-
throw Error(
|
|
567
|
-
`Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}`
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
558
|
const graffitiBytes = toGraffitiBytes(
|
|
572
559
|
getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
|
|
573
560
|
private: opts.private,
|
|
@@ -582,7 +569,6 @@ export function getValidatorApi(
|
|
|
582
569
|
fork,
|
|
583
570
|
builderSelection,
|
|
584
571
|
isBuilderEnabled,
|
|
585
|
-
isEngineEnabled,
|
|
586
572
|
strictFeeRecipientCheck,
|
|
587
573
|
// winston logger doesn't like bigint
|
|
588
574
|
builderBoostFactor: `${builderBoostFactor}`,
|
|
@@ -608,27 +594,25 @@ export function getValidatorApi(
|
|
|
608
594
|
})
|
|
609
595
|
: Promise.reject(new Error("Builder disabled"));
|
|
610
596
|
|
|
611
|
-
const enginePromise =
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
})
|
|
631
|
-
: Promise.reject(new Error("Engine disabled"));
|
|
597
|
+
const enginePromise = produceEngineBlockContents(slot, randaoReveal, graffitiBytes, {
|
|
598
|
+
feeRecipient,
|
|
599
|
+
strictFeeRecipientCheck,
|
|
600
|
+
commonBlockBodyPromise,
|
|
601
|
+
parentBlock,
|
|
602
|
+
}).then((engineBlock) => {
|
|
603
|
+
// Once the engine returns a block, in the event of either:
|
|
604
|
+
// - suspected builder censorship
|
|
605
|
+
// - builder boost factor set to 0 or builder selection `executionalways`
|
|
606
|
+
// we don't need to wait for builder block as engine block will always be selected
|
|
607
|
+
if (
|
|
608
|
+
engineBlock.shouldOverrideBuilder ||
|
|
609
|
+
builderBoostFactor === BigInt(0) ||
|
|
610
|
+
builderSelection === routes.validator.BuilderSelection.ExecutionAlways
|
|
611
|
+
) {
|
|
612
|
+
controller.abort();
|
|
613
|
+
}
|
|
614
|
+
return engineBlock;
|
|
615
|
+
});
|
|
632
616
|
|
|
633
617
|
// Calculate cutoff time based on start of the slot
|
|
634
618
|
const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
|
|
@@ -675,30 +659,24 @@ export function getValidatorApi(
|
|
|
675
659
|
throw Error("Builder and engine both failed to produce the block within timeout");
|
|
676
660
|
}
|
|
677
661
|
|
|
678
|
-
if (builder.status === "pending" && !isEngineEnabled) {
|
|
679
|
-
throw Error("Builder failed to produce the block within timeout");
|
|
680
|
-
}
|
|
681
|
-
|
|
682
662
|
if (engine.status === "pending" && !isBuilderEnabled) {
|
|
683
663
|
throw Error("Engine failed to produce the block within timeout");
|
|
684
664
|
}
|
|
685
665
|
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
{
|
|
691
|
-
...loggerContext,
|
|
692
|
-
durationMs: engine.durationMs,
|
|
693
|
-
},
|
|
694
|
-
engine.reason
|
|
695
|
-
);
|
|
696
|
-
} else if (engine.status === "pending") {
|
|
697
|
-
logger.warn("Engine failed to produce the block within cutoff time", {
|
|
666
|
+
if (engine.status === "rejected") {
|
|
667
|
+
logger.warn(
|
|
668
|
+
"Engine failed to produce the block",
|
|
669
|
+
{
|
|
698
670
|
...loggerContext,
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
|
|
671
|
+
durationMs: engine.durationMs,
|
|
672
|
+
},
|
|
673
|
+
engine.reason
|
|
674
|
+
);
|
|
675
|
+
} else if (engine.status === "pending") {
|
|
676
|
+
logger.warn("Engine failed to produce the block within cutoff time", {
|
|
677
|
+
...loggerContext,
|
|
678
|
+
cutoffMs,
|
|
679
|
+
});
|
|
702
680
|
}
|
|
703
681
|
|
|
704
682
|
if (isBuilderEnabled) {
|
|
@@ -727,14 +705,12 @@ export function getValidatorApi(
|
|
|
727
705
|
}
|
|
728
706
|
|
|
729
707
|
if (builder.status === "rejected" && engine.status === "rejected") {
|
|
730
|
-
throw Error(
|
|
731
|
-
`${isBuilderEnabled && isEngineEnabled ? "Builder and engine both" : isBuilderEnabled ? "Builder" : "Engine"} failed to produce the block`
|
|
732
|
-
);
|
|
708
|
+
throw Error(`${isBuilderEnabled ? "Builder and engine both" : "Engine"} failed to produce the block`);
|
|
733
709
|
}
|
|
734
710
|
|
|
735
711
|
// handle shouldOverrideBuilder separately
|
|
736
|
-
if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder) {
|
|
737
|
-
logger.
|
|
712
|
+
if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder && isBuilderEnabled) {
|
|
713
|
+
logger.warn("Selected engine block: censorship suspected in builder blocks", {
|
|
738
714
|
...loggerContext,
|
|
739
715
|
durationMs: engine.durationMs,
|
|
740
716
|
shouldOverrideBuilder: engine.value.shouldOverrideBuilder,
|
|
@@ -751,11 +727,9 @@ export function getValidatorApi(
|
|
|
751
727
|
|
|
752
728
|
if (builder.status === "fulfilled" && engine.status !== "fulfilled") {
|
|
753
729
|
const reason =
|
|
754
|
-
|
|
755
|
-
? BuilderBlockSelectionReason.
|
|
756
|
-
:
|
|
757
|
-
? BuilderBlockSelectionReason.EnginePending
|
|
758
|
-
: BuilderBlockSelectionReason.EngineError;
|
|
730
|
+
engine.status === "pending"
|
|
731
|
+
? BuilderBlockSelectionReason.EnginePending
|
|
732
|
+
: BuilderBlockSelectionReason.EngineError;
|
|
759
733
|
|
|
760
734
|
logger.info("Selected builder block: no engine block produced", {
|
|
761
735
|
reason,
|
|
@@ -865,13 +839,31 @@ export function getValidatorApi(
|
|
|
865
839
|
return {data, meta};
|
|
866
840
|
},
|
|
867
841
|
|
|
868
|
-
async produceBlockV4({
|
|
842
|
+
async produceBlockV4({
|
|
843
|
+
slot,
|
|
844
|
+
randaoReveal,
|
|
845
|
+
graffiti,
|
|
846
|
+
feeRecipient,
|
|
847
|
+
includePayload,
|
|
848
|
+
builderSelection,
|
|
849
|
+
builderBoostFactor,
|
|
850
|
+
}) {
|
|
869
851
|
const fork = config.getForkName(slot);
|
|
870
852
|
|
|
871
853
|
if (!isForkPostGloas(fork)) {
|
|
872
854
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
873
855
|
}
|
|
874
856
|
|
|
857
|
+
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
|
|
858
|
+
if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
|
|
859
|
+
logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
|
|
860
|
+
builderSelection = routes.validator.BuilderSelection.BuilderAlways;
|
|
861
|
+
}
|
|
862
|
+
builderBoostFactor = builderBoostFactor ?? BigInt(100);
|
|
863
|
+
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
|
|
864
|
+
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
|
|
865
|
+
}
|
|
866
|
+
|
|
875
867
|
notWhileSyncing(chain, sync.state);
|
|
876
868
|
await waitForSlot(slot);
|
|
877
869
|
|
|
@@ -888,15 +880,17 @@ export function getValidatorApi(
|
|
|
888
880
|
})
|
|
889
881
|
);
|
|
890
882
|
|
|
891
|
-
// TODO GLOAS:
|
|
892
|
-
// the user control bid source preferences and value comparison. Also add external builder api
|
|
893
|
-
// support when it is implemented.
|
|
883
|
+
// TODO GLOAS: add external builder api support when it is implemented
|
|
894
884
|
const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
|
|
895
885
|
const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
|
|
886
|
+
// Bids are only skipped entirely with executiononly or while the circuit breaker is active,
|
|
887
|
+
// other engine-preferring selections still build a block with the best bid as fallback in
|
|
888
|
+
// case local production fails
|
|
896
889
|
const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
|
|
897
|
-
const builderBid =
|
|
898
|
-
|
|
899
|
-
|
|
890
|
+
const builderBid =
|
|
891
|
+
builderSelection === routes.validator.BuilderSelection.ExecutionOnly || circuitBreakerActive
|
|
892
|
+
? null
|
|
893
|
+
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
900
894
|
|
|
901
895
|
const logCtx = {
|
|
902
896
|
slot,
|
|
@@ -904,6 +898,8 @@ export function getValidatorApi(
|
|
|
904
898
|
parentBlockRoot: parentBlockRootHex,
|
|
905
899
|
parentBlockHash: parentBlock.executionPayloadBlockHash,
|
|
906
900
|
fork,
|
|
901
|
+
builderSelection,
|
|
902
|
+
builderBoostFactor,
|
|
907
903
|
circuitBreakerActive,
|
|
908
904
|
...(builderBid !== null
|
|
909
905
|
? {
|
|
@@ -940,33 +936,101 @@ export function getValidatorApi(
|
|
|
940
936
|
return fn().finally(() => t?.({source}));
|
|
941
937
|
};
|
|
942
938
|
|
|
943
|
-
//
|
|
944
|
-
|
|
945
|
-
|
|
939
|
+
// Calculate cutoff time based on start of the slot, ensures a slow local payload build does
|
|
940
|
+
// not delay the proposal when a builder bid block is available (and vice versa)
|
|
941
|
+
const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
|
|
942
|
+
|
|
943
|
+
// use abort controller to stop waiting for the bid block if the engine block will be selected
|
|
944
|
+
const controller = new AbortController();
|
|
945
|
+
|
|
946
|
+
const enginePromise: ReturnType<typeof chain.produceBlock> = timed(ProducedBlockSource.engine, () =>
|
|
947
|
+
chain.produceBlock(baseAttrs)
|
|
948
|
+
).then((engineBlock) => {
|
|
949
|
+
// No need to wait for the bid block if the engine block will always be selected due to
|
|
950
|
+
// suspected builder censorship, a builder boost factor of 0 or executionalways selection
|
|
951
|
+
if (
|
|
952
|
+
engineBlock.shouldOverrideBuilder ||
|
|
953
|
+
builderBoostFactor === BigInt(0) ||
|
|
954
|
+
builderSelection === routes.validator.BuilderSelection.ExecutionAlways
|
|
955
|
+
) {
|
|
956
|
+
controller.abort();
|
|
957
|
+
}
|
|
958
|
+
return engineBlock;
|
|
959
|
+
});
|
|
960
|
+
const bidPromise: ReturnType<typeof chain.produceBlock> =
|
|
946
961
|
builderBid !== null
|
|
947
962
|
? timed(ProducedBlockSource.builder, () => chain.produceBlock({...baseAttrs, builderBid}))
|
|
948
|
-
: Promise.reject()
|
|
949
|
-
|
|
963
|
+
: Promise.reject(new Error("No builder bid available"));
|
|
964
|
+
|
|
965
|
+
const [engineResult, bidResult] = await resolveOrRacePromises([enginePromise, bidPromise], {
|
|
966
|
+
resolveTimeoutMs: cutoffMs,
|
|
967
|
+
raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
|
|
968
|
+
signal: controller.signal,
|
|
969
|
+
});
|
|
950
970
|
|
|
951
971
|
let bestResult: typeof engineResult | null = null;
|
|
952
972
|
let source: ProducedBlockSource = ProducedBlockSource.engine;
|
|
953
|
-
|
|
973
|
+
|
|
974
|
+
// handle shouldOverrideBuilder separately
|
|
975
|
+
if (engineResult.status === "fulfilled" && engineResult.value.shouldOverrideBuilder && builderBid !== null) {
|
|
976
|
+
source = ProducedBlockSource.engine;
|
|
977
|
+
bestResult = engineResult;
|
|
978
|
+
metrics?.blockProductionSelectionResults.inc({
|
|
979
|
+
source: ProducedBlockSource.engine,
|
|
980
|
+
reason: EngineBlockSelectionReason.BuilderCensorship,
|
|
981
|
+
});
|
|
982
|
+
logger.warn("Selected local block: censorship suspected in builder bid", logCtx);
|
|
983
|
+
} else if (engineResult.status === "fulfilled" && bidResult.status === "fulfilled") {
|
|
984
|
+
const result = selectBlockProductionSource({
|
|
985
|
+
builderSelection,
|
|
986
|
+
builderBoostFactor,
|
|
987
|
+
engineExecutionPayloadValue: engineResult.value.executionPayloadValue,
|
|
988
|
+
// The bid value is the payment to the proposer, in Gwei
|
|
989
|
+
builderExecutionPayloadValue: BigInt(builderBid?.message.value ?? 0) * GWEI_TO_WEI,
|
|
990
|
+
});
|
|
991
|
+
source = result.source;
|
|
992
|
+
metrics?.blockProductionSelectionResults.inc(result);
|
|
993
|
+
logger.info(`Selected ${source} block`, {reason: result.reason, ...logCtx});
|
|
994
|
+
bestResult = source === ProducedBlockSource.builder ? bidResult : engineResult;
|
|
995
|
+
} else if (bidResult.status === "fulfilled") {
|
|
954
996
|
source = ProducedBlockSource.builder;
|
|
955
997
|
bestResult = bidResult;
|
|
956
|
-
|
|
998
|
+
const reason =
|
|
999
|
+
engineResult.status === "pending"
|
|
1000
|
+
? BuilderBlockSelectionReason.EnginePending
|
|
1001
|
+
: BuilderBlockSelectionReason.EngineError;
|
|
1002
|
+
metrics?.blockProductionSelectionResults.inc({source: ProducedBlockSource.builder, reason});
|
|
1003
|
+
logger.info("Selected builder bid block: no local block produced", {
|
|
1004
|
+
reason,
|
|
1005
|
+
...logCtx,
|
|
1006
|
+
error: engineResult.status === "rejected" ? (engineResult.reason as Error).message : undefined,
|
|
1007
|
+
});
|
|
957
1008
|
} else if (engineResult.status === "fulfilled") {
|
|
958
1009
|
source = ProducedBlockSource.engine;
|
|
959
1010
|
bestResult = engineResult;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1011
|
+
const reason =
|
|
1012
|
+
builderBid === null
|
|
1013
|
+
? EngineBlockSelectionReason.BuilderNoBid
|
|
1014
|
+
: bidResult.status === "pending"
|
|
1015
|
+
? EngineBlockSelectionReason.BuilderPending
|
|
1016
|
+
: EngineBlockSelectionReason.BuilderError;
|
|
1017
|
+
metrics?.blockProductionSelectionResults.inc({source: ProducedBlockSource.engine, reason});
|
|
1018
|
+
logger.info("Selected local block: no builder bid block produced", {
|
|
1019
|
+
reason,
|
|
1020
|
+
...logCtx,
|
|
1021
|
+
error: bidResult.status === "rejected" ? (bidResult.reason as Error).message : undefined,
|
|
1022
|
+
});
|
|
963
1023
|
}
|
|
964
1024
|
|
|
965
1025
|
if (bestResult === null || bestResult.status !== "fulfilled") {
|
|
966
|
-
const engineReason = engineResult.status === "rejected" ? engineResult.reason :
|
|
967
|
-
const bidReason =
|
|
968
|
-
logger.error("Block production failed", {
|
|
969
|
-
|
|
1026
|
+
const engineReason = engineResult.status === "rejected" ? engineResult.reason : engineResult.status;
|
|
1027
|
+
const bidReason = bidResult.status === "rejected" ? bidResult.reason : bidResult.status;
|
|
1028
|
+
logger.error("Block production failed", {
|
|
1029
|
+
...logCtx,
|
|
1030
|
+
engineReason: String(engineReason),
|
|
1031
|
+
bidReason: String(bidReason),
|
|
1032
|
+
});
|
|
1033
|
+
throw Error(`Block production failed: engine=${String(engineReason)} builder=${String(bidReason)}`);
|
|
970
1034
|
}
|
|
971
1035
|
|
|
972
1036
|
const {block, executionPayloadValue, consensusBlockValue} = bestResult.value;
|
|
@@ -987,9 +1051,41 @@ export function getValidatorApi(
|
|
|
987
1051
|
void chain.persistBlock(block, "produced_engine_block");
|
|
988
1052
|
}
|
|
989
1053
|
|
|
1054
|
+
// Include the payload for self-builds unless disabled (stateless flow)
|
|
1055
|
+
const isSelfBuild = source === ProducedBlockSource.engine;
|
|
1056
|
+
if (isSelfBuild && includePayload) {
|
|
1057
|
+
const produceResult = chain.blockProductionCache.get(blockRoot);
|
|
1058
|
+
if (
|
|
1059
|
+
produceResult === undefined ||
|
|
1060
|
+
!isForkPostGloas(produceResult.fork) ||
|
|
1061
|
+
produceResult.type !== BlockType.Full
|
|
1062
|
+
) {
|
|
1063
|
+
throw Error(`Missing cached block production result for produced block slot=${slot} blockRoot=${blockRoot}`);
|
|
1064
|
+
}
|
|
1065
|
+
const {executionPayload, executionRequests, blobsBundle, parentBlockRoot} = produceResult as ProduceFullGloas;
|
|
1066
|
+
|
|
1067
|
+
const blockContents: gloas.BlockContents = {
|
|
1068
|
+
block: block as gloas.BeaconBlock,
|
|
1069
|
+
executionPayloadEnvelope: {
|
|
1070
|
+
payload: executionPayload,
|
|
1071
|
+
executionRequests,
|
|
1072
|
+
builderIndex: BUILDER_INDEX_SELF_BUILD,
|
|
1073
|
+
beaconBlockRoot: fromHex(blockRoot),
|
|
1074
|
+
parentBeaconBlockRoot: parentBlockRoot,
|
|
1075
|
+
},
|
|
1076
|
+
kzgProofs: blobsBundle.proofs,
|
|
1077
|
+
blobs: blobsBundle.blobs,
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
return {
|
|
1081
|
+
data: blockContents,
|
|
1082
|
+
meta: {version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: true},
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
990
1086
|
return {
|
|
991
1087
|
data: block as gloas.BeaconBlock,
|
|
992
|
-
meta: {version: fork, consensusBlockValue},
|
|
1088
|
+
meta: {version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: false},
|
|
993
1089
|
};
|
|
994
1090
|
},
|
|
995
1091
|
|
|
@@ -1798,6 +1894,18 @@ export function getValidatorApi(
|
|
|
1798
1894
|
|
|
1799
1895
|
const {executionPayload, executionRequests, parentBlockRoot} = produceResult as ProduceFullGloas;
|
|
1800
1896
|
|
|
1897
|
+
if (executionPayload === undefined) {
|
|
1898
|
+
// Blocks committing to a builder bid are cached as full but without payload data
|
|
1899
|
+
throw new ApiError(404, `No local execution payload cached for block root ${blockRootHex}`);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (executionPayload.slotNumber !== slot) {
|
|
1903
|
+
throw new ApiError(
|
|
1904
|
+
404,
|
|
1905
|
+
`Cached execution payload is for slot=${executionPayload.slotNumber}, requested slot=${slot}`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1801
1909
|
const envelope: gloas.ExecutionPayloadEnvelope = {
|
|
1802
1910
|
payload: executionPayload,
|
|
1803
1911
|
executionRequests: executionRequests,
|
package/src/api/rest/base.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {parse as parseQueryString} from "qs";
|
|
|
5
5
|
import {addSszContentTypeParser} from "@lodestar/api/server";
|
|
6
6
|
import {NUMBER_OF_COLUMNS} from "@lodestar/params";
|
|
7
7
|
import {ErrorAborted, Gauge, Histogram, Logger} from "@lodestar/utils";
|
|
8
|
+
import {GossipActionError} from "../../chain/errors/gossipValidation.js";
|
|
8
9
|
import {isLocalhostIP} from "../../util/ip.js";
|
|
9
10
|
import {ApiError, FailureList, IndexedError, NodeIsSyncing} from "../impl/errors.js";
|
|
10
11
|
import {HttpActiveSocketsTracker, SocketMetrics} from "./activeSockets.js";
|
|
@@ -121,8 +122,8 @@ export class RestApiServer {
|
|
|
121
122
|
};
|
|
122
123
|
void res.status(err.statusCode).send(payload);
|
|
123
124
|
} else {
|
|
124
|
-
// Convert
|
|
125
|
-
const statusCode = err instanceof ApiError ? err.statusCode : 500;
|
|
125
|
+
// Convert known request errors into status codes
|
|
126
|
+
const statusCode = err instanceof ApiError ? err.statusCode : err instanceof GossipActionError ? 400 : 500;
|
|
126
127
|
const payload: ErrorResponse = {code: statusCode, message: err.message, stacktraces};
|
|
127
128
|
void res.status(statusCode).send(payload);
|
|
128
129
|
}
|
|
@@ -170,7 +171,11 @@ export class RestApiServer {
|
|
|
170
171
|
|
|
171
172
|
const operationId = getOperationId(req);
|
|
172
173
|
|
|
173
|
-
if (
|
|
174
|
+
if (
|
|
175
|
+
err instanceof ApiError ||
|
|
176
|
+
err instanceof GossipActionError ||
|
|
177
|
+
[INVALID_MEDIA_TYPE_CODE, SCHEMA_VALIDATION_ERROR_CODE].includes(err.code)
|
|
178
|
+
) {
|
|
174
179
|
this.logger.warn(`Req ${req.id} ${operationId} failed`, {reason: err.message});
|
|
175
180
|
} else {
|
|
176
181
|
this.logger.error(`Req ${req.id} ${operationId} error`, {}, err);
|
|
@@ -2,7 +2,6 @@ import {BitArray} from "@chainsafe/ssz";
|
|
|
2
2
|
import {routes} from "@lodestar/api";
|
|
3
3
|
import {
|
|
4
4
|
AncestorStatus,
|
|
5
|
-
EpochDifference,
|
|
6
5
|
ExecutionStatus,
|
|
7
6
|
ForkChoiceError,
|
|
8
7
|
ForkChoiceErrorCode,
|
|
@@ -21,7 +20,6 @@ import {
|
|
|
21
20
|
IBeaconStateView,
|
|
22
21
|
RootCache,
|
|
23
22
|
computeEpochAtSlot,
|
|
24
|
-
computeStartSlotAtEpoch,
|
|
25
23
|
computeTimeAtSlot,
|
|
26
24
|
isStartSlotOfEpoch,
|
|
27
25
|
isStatePostAltair,
|
|
@@ -301,21 +299,6 @@ export async function importBlock(
|
|
|
301
299
|
// Set head state as strong reference
|
|
302
300
|
this.regen.updateHeadState(newHead, postState);
|
|
303
301
|
|
|
304
|
-
try {
|
|
305
|
-
this.emitter.emit(routes.events.EventType.head, {
|
|
306
|
-
block: newHead.blockRoot,
|
|
307
|
-
epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(newHead.slot)) === newHead.slot,
|
|
308
|
-
slot: newHead.slot,
|
|
309
|
-
state: newHead.stateRoot,
|
|
310
|
-
previousDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.previous),
|
|
311
|
-
currentDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.current),
|
|
312
|
-
executionOptimistic: isOptimisticBlock(newHead),
|
|
313
|
-
});
|
|
314
|
-
} catch (e) {
|
|
315
|
-
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
|
|
316
|
-
this.logger.debug("Error emitting head event", {slot: newHead.slot, root: newHead.blockRoot}, e as Error);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
302
|
const delaySec = this.clock.secFromSlot(newHead.slot);
|
|
320
303
|
this.logger.verbose("New chain head", {
|
|
321
304
|
slot: newHead.slot,
|
|
@@ -86,11 +86,22 @@ export function assertLinearChainSegment(
|
|
|
86
86
|
// Maybe the previous slot's FULL envelope was orphaned — try falling back.
|
|
87
87
|
// If even prevExecHash doesn't match, the segment is non-linear.
|
|
88
88
|
if (bidParentHash !== prevExecHash) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
// i === 0 compares against the seeded fork-choice parent (segment boundary); i > 0
|
|
90
|
+
// compares against the in-segment predecessor's payload (broken link inside the segment).
|
|
91
|
+
throw new BlockError(
|
|
92
|
+
block,
|
|
93
|
+
i === 0
|
|
94
|
+
? {
|
|
95
|
+
code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
|
|
96
|
+
parentRoot: toRootHex(block.message.parentRoot),
|
|
97
|
+
parentBlockHash: bidParentHash,
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS,
|
|
101
|
+
parentBlockHash: bidParentHash,
|
|
102
|
+
expectedBlockHash: currentExecHash,
|
|
103
|
+
}
|
|
104
|
+
);
|
|
94
105
|
}
|
|
95
106
|
if (lastFullSlot !== null && payloadEnvelopes !== null) {
|
|
96
107
|
const orphanedInput = payloadEnvelopes.get(lastFullSlot);
|
|
@@ -201,7 +201,7 @@ export async function verifyBlockExecutionPayload(
|
|
|
201
201
|
invalidateFromParentBlockHash: toRootHex(executionPayloadEnabled.parentHash),
|
|
202
202
|
};
|
|
203
203
|
const execError = new BlockError(block, {
|
|
204
|
-
code: BlockErrorCode.
|
|
204
|
+
code: BlockErrorCode.EXECUTION_ENGINE_INVALID,
|
|
205
205
|
execStatus: execResult.status,
|
|
206
206
|
errorMessage: execResult.validationError ?? "",
|
|
207
207
|
});
|
|
@@ -94,7 +94,7 @@ export function verifyBlocksSanityChecks(
|
|
|
94
94
|
const parentRoot = toRootHex(block.message.parentRoot);
|
|
95
95
|
const parentBlockDefaultStatus = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
|
|
96
96
|
if (!parentBlockDefaultStatus) {
|
|
97
|
-
throw new BlockError(block, {code: BlockErrorCode.
|
|
97
|
+
throw new BlockError(block, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
parentBlock = parentBlockDefaultStatus;
|
|
@@ -100,8 +100,8 @@ export function verifyExecutionPayloadEnvelope(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Verify consistency with expected withdrawals
|
|
103
|
-
const payloadWithdrawalsRoot = ssz.
|
|
104
|
-
const expectedWithdrawalsRoot = ssz.
|
|
103
|
+
const payloadWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(payload.withdrawals);
|
|
104
|
+
const expectedWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals);
|
|
105
105
|
if (!byteArrayEquals(payloadWithdrawalsRoot, expectedWithdrawalsRoot)) {
|
|
106
106
|
throw new Error(
|
|
107
107
|
`Withdrawals mismatch between payload and expected payload=${toRootHex(payloadWithdrawalsRoot)} expected=${toRootHex(expectedWithdrawalsRoot)}`
|
package/src/chain/chain.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import {PrivateKey} from "@libp2p/interface";
|
|
3
3
|
import {Type} from "@chainsafe/ssz";
|
|
4
|
+
import {routes} from "@lodestar/api";
|
|
4
5
|
import {BeaconConfig} from "@lodestar/config";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CheckpointWithHex,
|
|
8
|
+
EpochDifference,
|
|
9
|
+
ForkChoiceStateGetter,
|
|
10
|
+
IForkChoice,
|
|
11
|
+
PayloadStatus,
|
|
12
|
+
ProtoBlock,
|
|
13
|
+
UpdateHeadOpt,
|
|
14
|
+
} from "@lodestar/fork-choice";
|
|
6
15
|
import {LoggerNode} from "@lodestar/logger/node";
|
|
7
16
|
import {
|
|
8
17
|
EFFECTIVE_BALANCE_INCREMENT,
|
|
@@ -1179,7 +1188,56 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1179
1188
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller});
|
|
1180
1189
|
|
|
1181
1190
|
try {
|
|
1182
|
-
|
|
1191
|
+
const prevHead = this.forkChoice.getHead();
|
|
1192
|
+
const head = this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicalHead}).head;
|
|
1193
|
+
|
|
1194
|
+
const headRootChanged = head.blockRoot !== prevHead.blockRoot;
|
|
1195
|
+
|
|
1196
|
+
if (!headRootChanged && prevHead.payloadStatus === head.payloadStatus) {
|
|
1197
|
+
return head;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
try {
|
|
1201
|
+
const previousDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.previous);
|
|
1202
|
+
const currentDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.current);
|
|
1203
|
+
const epochTransition = computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot;
|
|
1204
|
+
const executionOptimistic = isOptimisticBlock(head);
|
|
1205
|
+
|
|
1206
|
+
if (headRootChanged) {
|
|
1207
|
+
this.emitter.emit(routes.events.EventType.head, {
|
|
1208
|
+
block: head.blockRoot,
|
|
1209
|
+
epochTransition,
|
|
1210
|
+
slot: head.slot,
|
|
1211
|
+
state: head.stateRoot,
|
|
1212
|
+
previousDutyDependentRoot,
|
|
1213
|
+
currentDutyDependentRoot,
|
|
1214
|
+
executionOptimistic,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
this.emitter.emit(routes.events.EventType.headV2, {
|
|
1219
|
+
version: this.config.getForkName(head.slot),
|
|
1220
|
+
data: {
|
|
1221
|
+
slot: head.slot,
|
|
1222
|
+
block: head.blockRoot,
|
|
1223
|
+
state: head.stateRoot,
|
|
1224
|
+
payloadStatus: head.payloadStatus === PayloadStatus.FULL ? "full" : "empty",
|
|
1225
|
+
epochTransition,
|
|
1226
|
+
currentEpochDependentRoot: previousDutyDependentRoot,
|
|
1227
|
+
nextEpochDependentRoot: currentDutyDependentRoot,
|
|
1228
|
+
executionOptimistic,
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
} catch (e) {
|
|
1232
|
+
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
|
|
1233
|
+
this.logger.debug(
|
|
1234
|
+
"Error emitting head/head_v2 event",
|
|
1235
|
+
{slot: head.slot, root: head.blockRoot, headRootChanged},
|
|
1236
|
+
e as Error
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
return head;
|
|
1183
1241
|
} catch (e) {
|
|
1184
1242
|
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
|
|
1185
1243
|
throw e;
|
|
@@ -1209,6 +1267,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1209
1267
|
const secFromSlot = this.clock.secFromSlot(slot);
|
|
1210
1268
|
|
|
1211
1269
|
try {
|
|
1270
|
+
// Do not emit head event here, when proposing we rely on the one emitted when importing our own block
|
|
1212
1271
|
const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({
|
|
1213
1272
|
mode: UpdateHeadOpt.GetProposerHead,
|
|
1214
1273
|
secFromSlot,
|