@lodestar/beacon-node 1.48.0-dev.dd957b093d → 1.48.0-dev.df6a2aa429
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 +0 -7
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +14 -0
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/config/index.d.ts.map +1 -1
- package/lib/api/impl/config/index.js +4 -1
- package/lib/api/impl/config/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +39 -39
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts +5 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +9 -16
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +8 -4
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +80 -15
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +8 -0
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/types.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +26 -11
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/chain.d.ts +7 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +69 -40
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +1 -1
- package/lib/chain/emitter.js +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +2 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +1 -0
- package/lib/chain/errors/gossipValidation.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.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/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +16 -0
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +7 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +22 -5
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +3 -0
- package/lib/execution/builder/validateBid.js.map +1 -1
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +4 -1
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +0 -3
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +0 -5
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +378 -278
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +22 -16
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +2 -1
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +24 -1
- package/lib/sync/range/range.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +0 -10
- package/src/api/impl/beacon/state/utils.ts +15 -0
- package/src/api/impl/config/index.ts +4 -1
- package/src/api/impl/validator/index.ts +44 -43
- package/src/chain/blocks/importBlock.ts +21 -18
- package/src/chain/blocks/index.ts +107 -21
- package/src/chain/blocks/types.ts +9 -0
- package/src/chain/blocks/utils/chainSegment.ts +26 -12
- package/src/chain/chain.ts +78 -50
- package/src/chain/emitter.ts +1 -1
- package/src/chain/errors/executionPayloadBid.ts +2 -0
- package/src/chain/errors/gossipValidation.ts +1 -0
- package/src/chain/interface.ts +2 -2
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/validation/executionPayloadBid.ts +18 -0
- package/src/chain/validatorMonitor.ts +24 -7
- package/src/execution/builder/validateBid.ts +4 -0
- package/src/execution/engine/mock.ts +4 -1
- package/src/metrics/metrics/lodestar.ts +0 -5
- package/src/network/processor/gossipHandlers.ts +487 -348
- package/src/network/processor/index.ts +24 -17
- package/src/network/reqresp/rateLimit.ts +2 -1
- package/src/sync/range/range.ts +23 -1
|
@@ -22,6 +22,7 @@ import { validateGossipProposerPreferences } from "../../chain/validation/propos
|
|
|
22
22
|
import { OpSource } from "../../chain/validatorMonitor.js";
|
|
23
23
|
import { kzgCommitmentToVersionedHash } from "../../util/blobs.js";
|
|
24
24
|
import { getBlobKzgCommitments, getDataColumnSidecarSlot } from "../../util/dataColumns.js";
|
|
25
|
+
import { callInNextEventLoop } from "../../util/eventLoop.js";
|
|
25
26
|
import { GossipType, } from "../gossip/interface.js";
|
|
26
27
|
import { sszDeserialize } from "../gossip/topic.js";
|
|
27
28
|
import { PeerAction } from "../peers/index.js";
|
|
@@ -520,8 +521,16 @@ function getSequentialHandlers(modules, options) {
|
|
|
520
521
|
const signedBlock = sszDeserialize(topic, serializedData);
|
|
521
522
|
try {
|
|
522
523
|
const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
|
|
523
|
-
|
|
524
|
-
|
|
524
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
525
|
+
callInNextEventLoop(() => {
|
|
526
|
+
try {
|
|
527
|
+
chain.serializedCache.set(signedBlock, serializedData);
|
|
528
|
+
handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
logger.debug("Error handling gossip block", { slot: signedBlock.message.slot, root: blockInput.blockRootHex }, e);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
525
534
|
}
|
|
526
535
|
catch (e) {
|
|
527
536
|
// Spec: IGNORE the block, ie not to re-publish to peers
|
|
@@ -534,6 +543,8 @@ function getSequentialHandlers(modules, options) {
|
|
|
534
543
|
// blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
|
|
535
544
|
const blockInput = chain.seenBlockInputCache.get(e.type.root);
|
|
536
545
|
if (blockInput) {
|
|
546
|
+
// We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
|
|
547
|
+
// happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
|
|
537
548
|
chain.serializedCache.set(signedBlock, serializedData);
|
|
538
549
|
// this is technically not a valid gossip block but gossip validation is a cheap subset of checks
|
|
539
550
|
// this runs the full state transition, so importing an equivocating-but-valid block here is safe.
|
|
@@ -553,26 +564,34 @@ function getSequentialHandlers(modules, options) {
|
|
|
553
564
|
throw new GossipActionError(GossipAction.REJECT, { code: "PRE_DENEB_BLOCK" });
|
|
554
565
|
}
|
|
555
566
|
const blockInput = await validateBeaconBlob(blobSidecar, topic.subnet, peerIdStr, seenTimestampSec);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
567
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
568
|
+
callInNextEventLoop(() => {
|
|
569
|
+
try {
|
|
570
|
+
chain.serializedCache.set(blobSidecar, serializedData);
|
|
571
|
+
if (!blockInput.hasBlockAndAllData()) {
|
|
572
|
+
const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
|
|
573
|
+
chain.logger.debug("Received gossip blob, waiting for full data availability", {
|
|
574
|
+
msToWait: cutoffTimeMs,
|
|
575
|
+
blobIndex: index,
|
|
576
|
+
...blockInput.getLogMeta(),
|
|
577
|
+
});
|
|
578
|
+
blockInput.waitForAllData(cutoffTimeMs).catch((_e) => {
|
|
579
|
+
chain.logger.debug("Waited for data after receiving gossip blob. Cut-off reached so attempting to fetch remainder of BlockInput", {
|
|
580
|
+
blobIndex: index,
|
|
581
|
+
...blockInput.getLogMeta(),
|
|
582
|
+
});
|
|
583
|
+
chain.emitter.emit(ChainEvent.incompleteBlockInput, {
|
|
584
|
+
blockInput,
|
|
585
|
+
peer: peerIdStr,
|
|
586
|
+
source: BlockInputSource.gossip,
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
logger.debug("Error handling gossip blob", { slot: blobSlot, root: blockInput.blockRootHex, index }, e);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
576
595
|
},
|
|
577
596
|
[GossipType.data_column_sidecar]: async ({ gossipData, topic, peerIdStr, seenTimestampSec, }) => {
|
|
578
597
|
const { fork } = topic.boundary;
|
|
@@ -592,50 +611,58 @@ function getSequentialHandlers(modules, options) {
|
|
|
592
611
|
}
|
|
593
612
|
// After gloas, data columns are tracked in PayloadEnvelopeInput
|
|
594
613
|
const payloadInput = await validatePayloadDataColumn(dataColumnSidecar, topic.subnet, peerIdStr, seenTimestampSec);
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
615
|
+
callInNextEventLoop(() => {
|
|
616
|
+
try {
|
|
617
|
+
chain.serializedCache.set(dataColumnSidecar, serializedData);
|
|
618
|
+
const payloadInputMeta = payloadInput.getLogMeta();
|
|
619
|
+
const { receivedColumns } = payloadInputMeta;
|
|
620
|
+
// it's not helpful to track every single column received
|
|
621
|
+
// instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
|
|
622
|
+
switch (receivedColumns) {
|
|
623
|
+
case 1:
|
|
624
|
+
case config.SAMPLES_PER_SLOT:
|
|
625
|
+
case 2 * config.SAMPLES_PER_SLOT:
|
|
626
|
+
case NUMBER_OF_COLUMNS / 4:
|
|
627
|
+
case NUMBER_OF_COLUMNS / 2:
|
|
628
|
+
case NUMBER_OF_COLUMNS:
|
|
629
|
+
metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
if (!payloadInput.hasComputedAllData()) {
|
|
633
|
+
// if we've received at least half of the columns, trigger reconstruction of the rest
|
|
634
|
+
if (receivedColumns >= NUMBER_OF_COLUMNS / 2) {
|
|
635
|
+
chain.columnReconstructionTracker.triggerColumnReconstruction(payloadInput);
|
|
636
|
+
}
|
|
637
|
+
chain.logger.debug("Received gossip data column, payload envelope input not yet complete", {
|
|
638
|
+
dataColumnIndex: index,
|
|
639
|
+
...payloadInputMeta,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
// NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
|
|
643
|
+
// envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
|
|
644
|
+
// payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
|
|
645
|
+
if (!payloadInput.isComplete()) {
|
|
646
|
+
const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
|
|
647
|
+
// do not await here to not delay gossip validation
|
|
648
|
+
payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
|
|
649
|
+
chain.logger.debug("Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope", {
|
|
650
|
+
dataColumnIndex: index,
|
|
651
|
+
...payloadInputMeta,
|
|
652
|
+
});
|
|
653
|
+
// TODO GLOAS: UnknownBlockSync to handle this event
|
|
654
|
+
chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
|
|
655
|
+
payloadInput,
|
|
656
|
+
peer: peerIdStr,
|
|
657
|
+
source: BlockInputSource.gossip,
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
}
|
|
614
661
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
// NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
|
|
621
|
-
// envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
|
|
622
|
-
// payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
|
|
623
|
-
if (!payloadInput.isComplete()) {
|
|
624
|
-
const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
|
|
625
|
-
// do not await here to not delay gossip validation
|
|
626
|
-
payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
|
|
627
|
-
chain.logger.debug("Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope", {
|
|
628
|
-
dataColumnIndex: index,
|
|
629
|
-
...payloadInputMeta,
|
|
630
|
-
});
|
|
631
|
-
// TODO GLOAS: UnknownBlockSync to handle this event
|
|
632
|
-
chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
|
|
633
|
-
payloadInput,
|
|
634
|
-
peer: peerIdStr,
|
|
635
|
-
source: BlockInputSource.gossip,
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
logger.debug("Error handling gossip data column", { slot: dataColumnSlot, root: payloadInput.blockRootHex, index }, e);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
639
666
|
}
|
|
640
667
|
else {
|
|
641
668
|
if (config.getForkSeq(dataColumnSlot) < ForkSeq.fulu) {
|
|
@@ -651,49 +678,57 @@ function getSequentialHandlers(modules, options) {
|
|
|
651
678
|
}
|
|
652
679
|
// Before gloas, data columns are tracked in BlockInput
|
|
653
680
|
const blockInput = await validateBeaconDataColumn(dataColumnSidecar, serializedData, topic.subnet, peerIdStr, seenTimestampSec);
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
681
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
682
|
+
callInNextEventLoop(() => {
|
|
683
|
+
try {
|
|
684
|
+
chain.serializedCache.set(dataColumnSidecar, serializedData);
|
|
685
|
+
const blockInputMeta = blockInput.getLogMeta();
|
|
686
|
+
const { receivedColumns } = blockInputMeta;
|
|
687
|
+
// it's not helpful to track every single column received
|
|
688
|
+
// instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
|
|
689
|
+
switch (receivedColumns) {
|
|
690
|
+
case 1:
|
|
691
|
+
case config.SAMPLES_PER_SLOT:
|
|
692
|
+
case 2 * config.SAMPLES_PER_SLOT:
|
|
693
|
+
case NUMBER_OF_COLUMNS / 4:
|
|
694
|
+
case NUMBER_OF_COLUMNS / 2:
|
|
695
|
+
case NUMBER_OF_COLUMNS:
|
|
696
|
+
metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
if (!blockInput.hasComputedAllData()) {
|
|
700
|
+
// immediately attempt fetch of data columns from execution engine
|
|
701
|
+
chain.getBlobsTracker.triggerGetBlobs(blockInput);
|
|
702
|
+
// if we've received at least half of the columns, trigger reconstruction of the rest
|
|
703
|
+
if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
|
|
704
|
+
chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!blockInput.hasBlockAndAllData()) {
|
|
708
|
+
const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
|
|
709
|
+
chain.logger.debug("Received gossip data column, waiting for full data availability", {
|
|
710
|
+
msToWait: cutoffTimeMs,
|
|
711
|
+
dataColumnIndex: index,
|
|
712
|
+
...blockInputMeta,
|
|
713
|
+
});
|
|
714
|
+
// do not await here to not delay gossip validation
|
|
715
|
+
blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
|
|
716
|
+
chain.logger.debug("Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput", {
|
|
717
|
+
dataColumnIndex: index,
|
|
718
|
+
...blockInputMeta,
|
|
719
|
+
});
|
|
720
|
+
chain.emitter.emit(ChainEvent.incompleteBlockInput, {
|
|
721
|
+
blockInput,
|
|
722
|
+
peer: peerIdStr,
|
|
723
|
+
source: BlockInputSource.gossip,
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
}
|
|
675
727
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
msToWait: cutoffTimeMs,
|
|
681
|
-
dataColumnIndex: index,
|
|
682
|
-
...blockInputMeta,
|
|
683
|
-
});
|
|
684
|
-
// do not await here to not delay gossip validation
|
|
685
|
-
blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
|
|
686
|
-
chain.logger.debug("Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput", {
|
|
687
|
-
dataColumnIndex: index,
|
|
688
|
-
...blockInputMeta,
|
|
689
|
-
});
|
|
690
|
-
chain.emitter.emit(ChainEvent.incompleteBlockInput, {
|
|
691
|
-
blockInput,
|
|
692
|
-
peer: peerIdStr,
|
|
693
|
-
source: BlockInputSource.gossip,
|
|
694
|
-
});
|
|
695
|
-
});
|
|
696
|
-
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
logger.debug("Error handling gossip data column", { slot: dataColumnSlot, root: blockInput.blockRootHex, index }, e);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
697
732
|
}
|
|
698
733
|
},
|
|
699
734
|
[GossipType.beacon_aggregate_and_proof]: async ({ gossipData, topic, seenTimestampSec, }) => {
|
|
@@ -710,62 +745,76 @@ function getSequentialHandlers(modules, options) {
|
|
|
710
745
|
}
|
|
711
746
|
throw e;
|
|
712
747
|
}
|
|
713
|
-
// Handler
|
|
748
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
714
749
|
const { indexedAttestation, committeeValidatorIndices, attDataRootHex } = validationResult;
|
|
715
|
-
|
|
716
|
-
const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
|
|
717
|
-
const insertOutcome = chain.aggregatedAttestationPool.add(aggregatedAttestation, attDataRootHex, indexedAttestation.attestingIndices.length, committeeValidatorIndices);
|
|
718
|
-
metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
719
|
-
if (!options.dontSendGossipAttestationsToForkchoice) {
|
|
750
|
+
callInNextEventLoop(() => {
|
|
720
751
|
try {
|
|
721
|
-
chain.
|
|
752
|
+
chain.validatorMonitor?.registerGossipAggregatedAttestation(seenTimestampSec, signedAggregateAndProof, indexedAttestation);
|
|
753
|
+
const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
|
|
754
|
+
const insertOutcome = chain.aggregatedAttestationPool.add(aggregatedAttestation, attDataRootHex, indexedAttestation.attestingIndices.length, committeeValidatorIndices);
|
|
755
|
+
metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
756
|
+
if (!options.dontSendGossipAttestationsToForkchoice) {
|
|
757
|
+
chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
|
|
758
|
+
}
|
|
759
|
+
chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
|
|
722
760
|
}
|
|
723
761
|
catch (e) {
|
|
724
|
-
logger.debug("Error
|
|
762
|
+
logger.debug("Error handling gossip aggregate and proof", {
|
|
763
|
+
slot: signedAggregateAndProof.message.aggregate.data.slot,
|
|
764
|
+
root: toRootHex(signedAggregateAndProof.message.aggregate.data.beaconBlockRoot),
|
|
765
|
+
}, e);
|
|
725
766
|
}
|
|
726
|
-
}
|
|
727
|
-
chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
|
|
767
|
+
});
|
|
728
768
|
},
|
|
729
769
|
[GossipType.attester_slashing]: async ({ gossipData, topic, }) => {
|
|
730
770
|
const { serializedData } = gossipData;
|
|
731
771
|
const { fork } = topic.boundary;
|
|
732
772
|
const attesterSlashing = sszDeserialize(topic, serializedData);
|
|
733
773
|
await validateGossipAttesterSlashing(chain, attesterSlashing);
|
|
734
|
-
// Handler
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
774
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
775
|
+
callInNextEventLoop(() => {
|
|
776
|
+
try {
|
|
777
|
+
chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
|
|
778
|
+
chain.forkChoice.onAttesterSlashing(attesterSlashing);
|
|
779
|
+
chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
|
|
780
|
+
}
|
|
781
|
+
catch (e) {
|
|
782
|
+
logger.debug("Error handling gossip attester slashing", { slot: attesterSlashing.attestation1.data.slot }, e);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
743
785
|
},
|
|
744
786
|
[GossipType.proposer_slashing]: async ({ gossipData, topic, }) => {
|
|
745
787
|
const { serializedData } = gossipData;
|
|
746
788
|
const proposerSlashing = sszDeserialize(topic, serializedData);
|
|
747
789
|
await validateGossipProposerSlashing(chain, proposerSlashing);
|
|
748
|
-
// Handler
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
790
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
791
|
+
callInNextEventLoop(() => {
|
|
792
|
+
try {
|
|
793
|
+
chain.opPool.insertProposerSlashing(proposerSlashing);
|
|
794
|
+
chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
|
|
795
|
+
}
|
|
796
|
+
catch (e) {
|
|
797
|
+
logger.debug("Error handling gossip proposer slashing", {
|
|
798
|
+
slot: proposerSlashing.signedHeader1.message.slot,
|
|
799
|
+
proposerIndex: proposerSlashing.signedHeader1.message.proposerIndex,
|
|
800
|
+
}, e);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
756
803
|
},
|
|
757
804
|
[GossipType.voluntary_exit]: async ({ gossipData, topic }) => {
|
|
758
805
|
const { serializedData } = gossipData;
|
|
759
806
|
const voluntaryExit = sszDeserialize(topic, serializedData);
|
|
760
807
|
await validateGossipVoluntaryExit(chain, voluntaryExit);
|
|
761
|
-
// Handler
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
808
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
809
|
+
callInNextEventLoop(() => {
|
|
810
|
+
try {
|
|
811
|
+
chain.opPool.insertVoluntaryExit(voluntaryExit);
|
|
812
|
+
chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
|
|
813
|
+
}
|
|
814
|
+
catch (e) {
|
|
815
|
+
logger.debug("Error handling gossip voluntary exit", { epoch: voluntaryExit.message.epoch, validatorIndex: voluntaryExit.message.validatorIndex }, e);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
769
818
|
},
|
|
770
819
|
[GossipType.sync_committee_contribution_and_proof]: async ({ gossipData, topic, }) => {
|
|
771
820
|
const { serializedData } = gossipData;
|
|
@@ -776,16 +825,21 @@ function getSequentialHandlers(modules, options) {
|
|
|
776
825
|
}
|
|
777
826
|
throw e;
|
|
778
827
|
});
|
|
779
|
-
// Handler
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
828
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
829
|
+
callInNextEventLoop(() => {
|
|
830
|
+
try {
|
|
831
|
+
chain.validatorMonitor?.registerGossipSyncContributionAndProof(contributionAndProof.message, syncCommitteeParticipantIndices);
|
|
832
|
+
const insertOutcome = chain.syncContributionAndProofPool.add(contributionAndProof.message, syncCommitteeParticipantIndices.length);
|
|
833
|
+
metrics?.opPool.syncContributionAndProofPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
834
|
+
chain.emitter.emit(routes.events.EventType.contributionAndProof, contributionAndProof);
|
|
835
|
+
}
|
|
836
|
+
catch (e) {
|
|
837
|
+
logger.debug("Error handling gossip contribution and proof", {
|
|
838
|
+
slot: contributionAndProof.message.contribution.slot,
|
|
839
|
+
subcommitteeIndex: contributionAndProof.message.contribution.subcommitteeIndex,
|
|
840
|
+
}, e);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
789
843
|
},
|
|
790
844
|
[GossipType.sync_committee]: async ({ gossipData, topic }) => {
|
|
791
845
|
const { serializedData } = gossipData;
|
|
@@ -801,16 +855,19 @@ function getSequentialHandlers(modules, options) {
|
|
|
801
855
|
}
|
|
802
856
|
throw e;
|
|
803
857
|
}
|
|
804
|
-
// Handler
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
858
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
859
|
+
// add for ALL positions this validator holds in the subcommittee
|
|
860
|
+
callInNextEventLoop(() => {
|
|
861
|
+
try {
|
|
862
|
+
for (const indexInSubcommittee of indicesInSubcommittee) {
|
|
863
|
+
const insertOutcome = chain.syncCommitteeMessagePool.add(subnet, syncCommittee, indexInSubcommittee);
|
|
864
|
+
metrics?.opPool.syncCommitteeMessagePoolInsertOutcome.inc({ insertOutcome });
|
|
865
|
+
}
|
|
809
866
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
867
|
+
catch (e) {
|
|
868
|
+
logger.debug("Error handling gossip sync committee", { slot: syncCommittee.slot, subnet, validatorIndex: syncCommittee.validatorIndex }, e);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
814
871
|
},
|
|
815
872
|
[GossipType.light_client_finality_update]: async ({ gossipData, topic, }) => {
|
|
816
873
|
const { serializedData } = gossipData;
|
|
@@ -827,14 +884,16 @@ function getSequentialHandlers(modules, options) {
|
|
|
827
884
|
const { serializedData } = gossipData;
|
|
828
885
|
const blsToExecutionChange = sszDeserialize(topic, serializedData);
|
|
829
886
|
await validateGossipBlsToExecutionChange(chain, blsToExecutionChange);
|
|
830
|
-
// Handler
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
887
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
888
|
+
callInNextEventLoop(() => {
|
|
889
|
+
try {
|
|
890
|
+
chain.opPool.insertBlsToExecutionChange(blsToExecutionChange);
|
|
891
|
+
chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
|
|
892
|
+
}
|
|
893
|
+
catch (e) {
|
|
894
|
+
logger.debug("Error handling gossip bls to execution change", { validatorIndex: blsToExecutionChange.message.validatorIndex }, e);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
838
897
|
},
|
|
839
898
|
[GossipType.execution_payload]: async ({ gossipData, topic, peerIdStr, seenTimestampSec, }) => {
|
|
840
899
|
const { serializedData } = gossipData;
|
|
@@ -878,103 +937,137 @@ function getSequentialHandlers(modules, options) {
|
|
|
878
937
|
blockRoot: blockRootHex,
|
|
879
938
|
});
|
|
880
939
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
940
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
941
|
+
callInNextEventLoop(() => {
|
|
942
|
+
try {
|
|
943
|
+
chain.serializedCache.set(signedEnvelope, serializedData);
|
|
944
|
+
payloadInput.addPayloadEnvelope({
|
|
945
|
+
envelope: signedEnvelope,
|
|
946
|
+
source: PayloadEnvelopeInputSource.gossip,
|
|
947
|
+
seenTimestampSec,
|
|
948
|
+
peerIdStr,
|
|
949
|
+
});
|
|
950
|
+
chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
|
|
951
|
+
slot,
|
|
952
|
+
builderIndex: envelope.builderIndex,
|
|
953
|
+
blockHash: toRootHex(envelope.payload.blockHash),
|
|
954
|
+
blockRoot: blockRootHex,
|
|
955
|
+
});
|
|
956
|
+
chain.processExecutionPayload(payloadInput, { validSignature: true }).catch((e) => {
|
|
957
|
+
// Adjust verbosity based on error type
|
|
958
|
+
let logLevel;
|
|
959
|
+
if (e instanceof PayloadError) {
|
|
960
|
+
switch (e.type.code) {
|
|
961
|
+
// BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
|
|
962
|
+
// already verified the block is in fork choice
|
|
963
|
+
case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
|
|
964
|
+
case PayloadErrorCode.MISS_BLOCK_STATE:
|
|
965
|
+
case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
|
|
966
|
+
// Errors might indicate an issue with our node or the connected EL client
|
|
967
|
+
logLevel = LogLevel.error;
|
|
968
|
+
break;
|
|
969
|
+
// INVALID_SIGNATURE should not happen, signature is verified during gossip validation
|
|
970
|
+
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
971
|
+
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
972
|
+
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
973
|
+
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
|
|
974
|
+
// Misbehaving peer, but could highlight an issue in another client
|
|
975
|
+
logLevel = LogLevel.warn;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
// Any unexpected error
|
|
905
981
|
logLevel = LogLevel.error;
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
// Misbehaving peer, but could highlight an issue in another client
|
|
913
|
-
logLevel = LogLevel.warn;
|
|
914
|
-
break;
|
|
915
|
-
}
|
|
982
|
+
}
|
|
983
|
+
metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
|
|
984
|
+
error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
|
|
985
|
+
});
|
|
986
|
+
chain.logger[logLevel]("Error processing execution payload from gossip", { slot, root: blockRootHex, peer: peerIdStr }, e);
|
|
987
|
+
});
|
|
916
988
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
logLevel = LogLevel.error;
|
|
989
|
+
catch (e) {
|
|
990
|
+
logger.debug("Error handling gossip execution payload", { slot, root: blockRootHex }, e);
|
|
920
991
|
}
|
|
921
|
-
metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
|
|
922
|
-
error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
|
|
923
|
-
});
|
|
924
|
-
chain.logger[logLevel]("Error processing execution payload from gossip", { slot, peer: peerIdStr, root: blockRootHex }, e);
|
|
925
992
|
});
|
|
926
993
|
},
|
|
927
994
|
[GossipType.payload_attestation_message]: async ({ gossipData, topic, seenTimestampSec, }) => {
|
|
928
995
|
const { serializedData } = gossipData;
|
|
929
996
|
const payloadAttestationMessage = sszDeserialize(topic, serializedData);
|
|
930
997
|
const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
|
|
934
|
-
const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
|
|
935
|
-
if (isNextSlotProposer) {
|
|
998
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
999
|
+
callInNextEventLoop(() => {
|
|
936
1000
|
try {
|
|
937
|
-
const
|
|
938
|
-
metrics?.
|
|
1001
|
+
const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
|
|
1002
|
+
metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, delaySec);
|
|
1003
|
+
const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
|
|
1004
|
+
const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
|
|
1005
|
+
if (isNextSlotProposer) {
|
|
1006
|
+
try {
|
|
1007
|
+
const insertOutcome = chain.payloadAttestationPool.add(payloadAttestationMessage, validationResult.attDataRootHex, validationResult.validatorCommitteeIndices);
|
|
1008
|
+
metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
1009
|
+
}
|
|
1010
|
+
catch (e) {
|
|
1011
|
+
logger.debug("Error adding to payloadAttestation pool", {}, e);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
chain.forkChoice.notifyPtcMessages(toRootHex(payloadAttestationMessage.data.beaconBlockRoot), payloadAttestationMessage.data.slot, validationResult.validatorCommitteeIndices, payloadAttestationMessage.data.payloadPresent, payloadAttestationMessage.data.blobDataAvailable);
|
|
1015
|
+
chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
|
|
1016
|
+
version: config.getForkName(payloadAttestationMessage.data.slot),
|
|
1017
|
+
data: payloadAttestationMessage,
|
|
1018
|
+
});
|
|
939
1019
|
}
|
|
940
1020
|
catch (e) {
|
|
941
|
-
logger.debug("Error
|
|
1021
|
+
logger.debug("Error handling gossip payload attestation message", {
|
|
1022
|
+
slot: payloadAttestationMessage.data.slot,
|
|
1023
|
+
root: toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
|
|
1024
|
+
}, e);
|
|
942
1025
|
}
|
|
943
|
-
}
|
|
944
|
-
chain.forkChoice.notifyPtcMessages(toRootHex(payloadAttestationMessage.data.beaconBlockRoot), payloadAttestationMessage.data.slot, validationResult.validatorCommitteeIndices, payloadAttestationMessage.data.payloadPresent, payloadAttestationMessage.data.blobDataAvailable);
|
|
945
|
-
chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
|
|
946
|
-
version: config.getForkName(payloadAttestationMessage.data.slot),
|
|
947
|
-
data: payloadAttestationMessage,
|
|
948
1026
|
});
|
|
949
1027
|
},
|
|
950
1028
|
[GossipType.execution_payload_bid]: async ({ gossipData, topic, seenTimestampSec, }) => {
|
|
951
1029
|
const { serializedData } = gossipData;
|
|
952
1030
|
const executionPayloadBid = sszDeserialize(topic, serializedData);
|
|
953
1031
|
const { proposerIndex } = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
|
|
954
|
-
//
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1032
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
1033
|
+
callInNextEventLoop(() => {
|
|
1034
|
+
try {
|
|
1035
|
+
// this could be negative, because it's most likely the bid of next slot comes at this clock slot
|
|
1036
|
+
const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
|
|
1037
|
+
metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, elapsedSec);
|
|
1038
|
+
// Handle valid payload bid by storing in a bid pool
|
|
1039
|
+
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
|
|
1040
|
+
metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
1041
|
+
chain.validatorMonitor?.registerExecutionPayloadBid(OpSource.gossip, proposerIndex, executionPayloadBid.message);
|
|
1042
|
+
chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
|
|
1043
|
+
version: config.getForkName(executionPayloadBid.message.slot),
|
|
1044
|
+
data: executionPayloadBid,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
catch (e) {
|
|
1048
|
+
logger.debug("Error handling gossip execution payload bid", {
|
|
1049
|
+
slot: executionPayloadBid.message.slot,
|
|
1050
|
+
root: toRootHex(executionPayloadBid.message.parentBlockRoot),
|
|
1051
|
+
proposerIndex,
|
|
1052
|
+
}, e);
|
|
1053
|
+
}
|
|
969
1054
|
});
|
|
970
1055
|
},
|
|
971
1056
|
[GossipType.proposer_preferences]: async ({ gossipData, topic, }) => {
|
|
972
1057
|
const { serializedData } = gossipData;
|
|
973
1058
|
const signedProposerPreferences = sszDeserialize(topic, serializedData);
|
|
974
1059
|
await validateGossipProposerPreferences(chain, signedProposerPreferences);
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1060
|
+
// Handler - deferred to next event loop so the validation result propagates first
|
|
1061
|
+
callInNextEventLoop(() => {
|
|
1062
|
+
try {
|
|
1063
|
+
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1064
|
+
version: config.getForkName(signedProposerPreferences.message.proposalSlot),
|
|
1065
|
+
data: signedProposerPreferences,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
catch (e) {
|
|
1069
|
+
logger.debug("Error handling gossip proposer preferences", { proposalSlot: signedProposerPreferences.message.proposalSlot }, e);
|
|
1070
|
+
}
|
|
978
1071
|
});
|
|
979
1072
|
},
|
|
980
1073
|
};
|
|
@@ -1001,43 +1094,9 @@ function getBatchHandlers(modules, options) {
|
|
|
1001
1094
|
subnet: param.topic.subnet,
|
|
1002
1095
|
}));
|
|
1003
1096
|
const { results: validationResults, batchableBls } = await validateGossipAttestationsSameAttData(fork, chain, validationParams);
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
continue;
|
|
1008
|
-
}
|
|
1009
|
-
// null means no error
|
|
1010
|
-
results.push(null);
|
|
1011
|
-
// Handler
|
|
1012
|
-
const { indexedAttestation, attDataRootHex, attestation, committeeIndex, validatorCommitteeIndex, committeeSize, } = validationResult.result;
|
|
1013
|
-
chain.validatorMonitor?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation);
|
|
1014
|
-
const { subnet } = validationResult.result;
|
|
1015
|
-
try {
|
|
1016
|
-
// Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
|
|
1017
|
-
// but don't add to attestation pool, to save CPU and RAM
|
|
1018
|
-
if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
|
|
1019
|
-
const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex, validatorCommitteeIndex, committeeSize);
|
|
1020
|
-
metrics?.opPool.attestationPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
catch (e) {
|
|
1024
|
-
logger.error("Error adding unaggregated attestation to pool", { subnet }, e);
|
|
1025
|
-
}
|
|
1026
|
-
if (!options.dontSendGossipAttestationsToForkchoice) {
|
|
1027
|
-
try {
|
|
1028
|
-
chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
|
|
1029
|
-
}
|
|
1030
|
-
catch (e) {
|
|
1031
|
-
logger.debug("Error adding gossip unaggregated attestation to forkchoice", { subnet }, e);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
if (isForkPostElectra(fork)) {
|
|
1035
|
-
chain.emitter.emit(routes.events.EventType.singleAttestation, attestation);
|
|
1036
|
-
}
|
|
1037
|
-
else {
|
|
1038
|
-
chain.emitter.emit(routes.events.EventType.attestation, attestation);
|
|
1039
|
-
chain.emitter.emit(routes.events.EventType.singleAttestation, toElectraSingleAttestation(attestation, indexedAttestation.attestingIndices[0]));
|
|
1040
|
-
}
|
|
1097
|
+
// verdicts computed synchronously and returned immediately
|
|
1098
|
+
for (const validationResult of validationResults) {
|
|
1099
|
+
results.push(validationResult.err ? validationResult.err : null);
|
|
1041
1100
|
}
|
|
1042
1101
|
if (batchableBls) {
|
|
1043
1102
|
metrics?.gossipAttestation.attestationBatchHistogram.observe(attestationCount);
|
|
@@ -1045,6 +1104,47 @@ function getBatchHandlers(modules, options) {
|
|
|
1045
1104
|
else {
|
|
1046
1105
|
metrics?.gossipAttestation.attestationNonBatchCount.inc(attestationCount);
|
|
1047
1106
|
}
|
|
1107
|
+
// Handler - deferred to next event loop so the validation result propagates first.
|
|
1108
|
+
callInNextEventLoop(() => {
|
|
1109
|
+
for (const [i, validationResult] of validationResults.entries()) {
|
|
1110
|
+
if (validationResult.err)
|
|
1111
|
+
continue;
|
|
1112
|
+
const { indexedAttestation, attDataRootHex, attestation, committeeIndex, validatorCommitteeIndex, committeeSize, subnet, } = validationResult.result;
|
|
1113
|
+
try {
|
|
1114
|
+
chain.validatorMonitor?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation);
|
|
1115
|
+
try {
|
|
1116
|
+
// Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
|
|
1117
|
+
// but don't add to attestation pool, to save CPU and RAM
|
|
1118
|
+
if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
|
|
1119
|
+
const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex, validatorCommitteeIndex, committeeSize);
|
|
1120
|
+
metrics?.opPool.attestationPool.gossipInsertOutcome.inc({ insertOutcome });
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
catch (e) {
|
|
1124
|
+
logger.debug("Error adding gossip unaggregated attestation to pool", { subnet }, e);
|
|
1125
|
+
}
|
|
1126
|
+
// Separate boundary: a pool insertion error above must not skip the fork-choice vote
|
|
1127
|
+
if (!options.dontSendGossipAttestationsToForkchoice) {
|
|
1128
|
+
try {
|
|
1129
|
+
chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
|
|
1130
|
+
}
|
|
1131
|
+
catch (e) {
|
|
1132
|
+
logger.debug("Error adding gossip unaggregated attestation to forkchoice", { subnet }, e);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (isForkPostElectra(fork)) {
|
|
1136
|
+
chain.emitter.emit(routes.events.EventType.singleAttestation, attestation);
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
chain.emitter.emit(routes.events.EventType.attestation, attestation);
|
|
1140
|
+
chain.emitter.emit(routes.events.EventType.singleAttestation, toElectraSingleAttestation(attestation, indexedAttestation.attestingIndices[0]));
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
catch (e) {
|
|
1144
|
+
logger.debug("Error handling gossip attestation", { slot: indexedAttestation.data.slot, subnet }, e);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1048
1148
|
return results;
|
|
1049
1149
|
},
|
|
1050
1150
|
};
|