@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
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
|
|
2
|
+
import {GENESIS_SLOT, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
3
|
import {
|
|
3
4
|
computeEpochAtSlot,
|
|
5
|
+
computeStartSlotAtEpoch,
|
|
4
6
|
createSingleSignatureSetFromComponents,
|
|
5
7
|
getProposerPreferencesSigningRoot,
|
|
6
8
|
} from "@lodestar/state-transition";
|
|
7
|
-
import {ValidatorIndex, gloas} from "@lodestar/types";
|
|
9
|
+
import {Epoch, Slot, ValidatorIndex, gloas} from "@lodestar/types";
|
|
8
10
|
import {toRootHex} from "@lodestar/utils";
|
|
9
11
|
import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
|
|
10
12
|
import {IBeaconChain} from "../index.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Validates a gossiped `SignedProposerPreferences` per
|
|
14
|
-
* https://github.com/ethereum/consensus-specs/blob/
|
|
16
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/p2p-interface.md#new-proposer_preferences
|
|
15
17
|
*/
|
|
16
18
|
export async function validateGossipProposerPreferences(
|
|
17
19
|
chain: IBeaconChain,
|
|
@@ -22,9 +24,9 @@ export async function validateGossipProposerPreferences(
|
|
|
22
24
|
const dependentRootHex = toRootHex(dependentRoot);
|
|
23
25
|
const proposalEpoch = computeEpochAtSlot(proposalSlot);
|
|
24
26
|
|
|
25
|
-
// [IGNORE] `preferences.proposal_slot` is
|
|
27
|
+
// [IGNORE] `preferences.proposal_slot` is within the proposer lookahead.
|
|
26
28
|
const currentEpoch = chain.clock.currentEpoch;
|
|
27
|
-
if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch +
|
|
29
|
+
if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + MIN_SEED_LOOKAHEAD) {
|
|
28
30
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
29
31
|
code: ProposerPreferencesErrorCode.INVALID_EPOCH,
|
|
30
32
|
proposalSlot,
|
|
@@ -44,21 +46,56 @@ export async function validateGossipProposerPreferences(
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
// [IGNORE] The block with root `dependent_root` has been seen by the node.
|
|
49
|
+
const dependentBlock = chain.forkChoice.getBlockHexDefaultStatus(dependentRootHex);
|
|
50
|
+
if (dependentBlock === null) {
|
|
51
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
52
|
+
code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT,
|
|
53
|
+
proposalSlot,
|
|
54
|
+
dependentRoot: dependentRootHex,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dependentEpoch = proposalEpoch - MIN_SEED_LOOKAHEAD;
|
|
59
|
+
const dependentRootSlot = getDependentRootSlot(dependentEpoch);
|
|
60
|
+
|
|
61
|
+
// [REJECT] The slot of the block with root `preferences.dependent_root` is strictly less than
|
|
62
|
+
// `compute_start_slot_at_epoch(compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD)`.
|
|
63
|
+
if (dependentBlock.slot > dependentRootSlot) {
|
|
64
|
+
throw new ProposerPreferencesError(GossipAction.REJECT, {
|
|
65
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT_SLOT,
|
|
66
|
+
dependentRoot: dependentRootHex,
|
|
67
|
+
dependentBlockSlot: dependentBlock.slot,
|
|
68
|
+
dependentRootSlot,
|
|
69
|
+
dependentEpoch,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// [IGNORE] `is_valid_dependent_root(store, preferences.dependent_root, epoch)` returns `True`,
|
|
74
|
+
// where `epoch` is `compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD`.
|
|
75
|
+
if (!isValidDependentRoot(chain.forkChoice, dependentBlock, dependentEpoch)) {
|
|
76
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
77
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT,
|
|
78
|
+
dependentRoot: dependentRootHex,
|
|
79
|
+
dependentBlockSlot: dependentBlock.slot,
|
|
80
|
+
dependentEpoch,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
// Resolve the proposer lookahead for the message's branch via head state (fast path) or
|
|
48
85
|
// the previous-root checkpoint state (populated by `processSlotsToNearestCheckpoint` for
|
|
49
|
-
// any imported branch crossing into `
|
|
86
|
+
// any imported branch crossing into `dependentEpoch`). The head-state path also handles
|
|
50
87
|
// narrow timing windows where the checkpoint state isn't yet populated.
|
|
51
88
|
const headState = chain.getHeadState();
|
|
52
89
|
let proposers: ValidatorIndex[] | null = null;
|
|
53
90
|
if (headState.epoch === proposalEpoch && headState.currentDecisionRoot === dependentRootHex) {
|
|
54
91
|
proposers = headState.currentProposers;
|
|
55
|
-
} else if (headState.epoch ===
|
|
92
|
+
} else if (headState.epoch === dependentEpoch && headState.nextDecisionRoot === dependentRootHex) {
|
|
56
93
|
proposers = headState.nextProposers;
|
|
57
94
|
} else {
|
|
58
95
|
// Sync lookup only to not trigger disk reload from gossip input.
|
|
59
|
-
const checkpointState = chain.regen.getCheckpointStateSync({epoch:
|
|
96
|
+
const checkpointState = chain.regen.getCheckpointStateSync({epoch: dependentEpoch, rootHex: dependentRootHex});
|
|
60
97
|
if (checkpointState !== null) {
|
|
61
|
-
// State is at `
|
|
98
|
+
// State is at `dependentEpoch`, so proposers for `proposalSlot` (next epoch from
|
|
62
99
|
// the state's perspective) live in `nextProposers`.
|
|
63
100
|
proposers = checkpointState.nextProposers;
|
|
64
101
|
}
|
|
@@ -118,3 +155,28 @@ export async function validateGossipProposerPreferences(
|
|
|
118
155
|
|
|
119
156
|
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
120
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check whether `dependentBlock` is a viable shuffling-dependent root for `epoch`:
|
|
161
|
+
* it is before the epoch boundary and either has a direct child at or after
|
|
162
|
+
* the boundary, or is the current fork-choice head.
|
|
163
|
+
*/
|
|
164
|
+
export function isValidDependentRoot(forkChoice: IForkChoice, dependentBlock: ProtoBlock, epoch: Epoch): boolean {
|
|
165
|
+
const epochStartSlot = computeStartSlotAtEpoch(epoch);
|
|
166
|
+
if (dependentBlock.slot > getDependentRootSlot(epoch)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const block of forkChoice.getBlockSummariesByParentRoot(dependentBlock.blockRoot)) {
|
|
171
|
+
if (block.slot >= epochStartSlot) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return dependentBlock.blockRoot === forkChoice.getHeadRoot();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Return the slot used to select a dependent root for `epoch`, clamped to genesis. */
|
|
180
|
+
function getDependentRootSlot(epoch: Epoch): Slot {
|
|
181
|
+
return Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch) - 1);
|
|
182
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
-
import {DOMAIN_AGGREGATE_AND_PROOF
|
|
2
|
+
import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
|
|
3
3
|
import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
-
import {Epoch, SignedAggregateAndProof, ValidatorIndex
|
|
4
|
+
import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";
|
|
5
5
|
|
|
6
6
|
export function getAggregateAndProofSigningRoot(
|
|
7
7
|
config: BeaconConfig,
|
|
@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
|
|
|
14
14
|
const slot = computeStartSlotAtEpoch(epoch);
|
|
15
15
|
const fork = config.getForkName(slot);
|
|
16
16
|
const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
|
|
17
|
-
const sszType =
|
|
17
|
+
const sszType = config.getForkTypes(slot).AggregateAndProof;
|
|
18
18
|
return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {ContainerType, VectorCompositeType} from "@chainsafe/ssz";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {DatabaseController, Repository} from "@lodestar/db";
|
|
4
|
+
import {CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS} from "@lodestar/params";
|
|
4
5
|
import {ssz} from "@lodestar/types";
|
|
5
6
|
import {SyncCommitteeWitness} from "../../chain/lightClient/types.js";
|
|
6
7
|
import {Bucket, getBucketNameByValue} from "../buckets.js";
|
|
7
8
|
|
|
8
|
-
// We add a 1-byte prefix where 0 means pre-
|
|
9
|
+
// We add a 1-byte prefix where 0 means pre-Electra, 1 means post-Electra, and 2 means post-Gloas.
|
|
9
10
|
enum PrefixByte {
|
|
10
11
|
PRE_ELECTRA = 0,
|
|
11
12
|
POST_ELECTRA = 1,
|
|
13
|
+
POST_GLOAS = 2,
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const NUM_WITNESS = 4;
|
|
@@ -35,6 +37,35 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
35
37
|
// Overrides for multi-fork
|
|
36
38
|
encodeValue(value: SyncCommitteeWitness): Uint8Array {
|
|
37
39
|
const numWitness = value.witness.length;
|
|
40
|
+
const hasGloasBranches =
|
|
41
|
+
value.currentSyncCommitteeBranch !== undefined || value.nextSyncCommitteeBranch !== undefined;
|
|
42
|
+
|
|
43
|
+
if (hasGloasBranches) {
|
|
44
|
+
if (
|
|
45
|
+
value.currentSyncCommitteeBranch?.length !== CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS ||
|
|
46
|
+
value.nextSyncCommitteeBranch?.length !== NEXT_SYNC_COMMITTEE_DEPTH_GLOAS
|
|
47
|
+
) {
|
|
48
|
+
throw Error(
|
|
49
|
+
`Invalid post-Gloas sync committee branch lengths current=${value.currentSyncCommitteeBranch?.length} next=${value.nextSyncCommitteeBranch?.length}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const type = new ContainerType({
|
|
54
|
+
currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
55
|
+
nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
56
|
+
currentSyncCommitteeRoot: ssz.Root,
|
|
57
|
+
nextSyncCommitteeRoot: ssz.Root,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const valueBytes = type.serialize({
|
|
61
|
+
currentSyncCommitteeBranch: value.currentSyncCommitteeBranch,
|
|
62
|
+
nextSyncCommitteeBranch: value.nextSyncCommitteeBranch,
|
|
63
|
+
currentSyncCommitteeRoot: value.currentSyncCommitteeRoot,
|
|
64
|
+
nextSyncCommitteeRoot: value.nextSyncCommitteeRoot,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return prefixData(PrefixByte.POST_GLOAS, valueBytes);
|
|
68
|
+
}
|
|
38
69
|
|
|
39
70
|
if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) {
|
|
40
71
|
throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`);
|
|
@@ -51,19 +82,25 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
51
82
|
// We need to differentiate between post-electra and pre-electra witness
|
|
52
83
|
// such that we can deserialize correctly
|
|
53
84
|
const isPostElectra = numWitness === NUM_WITNESS_ELECTRA;
|
|
54
|
-
|
|
55
|
-
prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA;
|
|
56
|
-
|
|
57
|
-
const prefixedData = new Uint8Array(1 + valueBytes.length);
|
|
58
|
-
prefixedData.set(prefixByte, 0);
|
|
59
|
-
prefixedData.set(valueBytes, 1);
|
|
60
|
-
|
|
61
|
-
return prefixedData;
|
|
85
|
+
return prefixData(isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA, valueBytes);
|
|
62
86
|
}
|
|
63
87
|
|
|
64
88
|
decodeValue(data: Uint8Array): SyncCommitteeWitness {
|
|
65
89
|
// First byte is written
|
|
66
90
|
const prefix = data.subarray(0, 1);
|
|
91
|
+
const isPostGloas = prefix[0] === PrefixByte.POST_GLOAS;
|
|
92
|
+
|
|
93
|
+
if (isPostGloas) {
|
|
94
|
+
const type = new ContainerType({
|
|
95
|
+
currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
96
|
+
nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
|
|
97
|
+
currentSyncCommitteeRoot: ssz.Root,
|
|
98
|
+
nextSyncCommitteeRoot: ssz.Root,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {witness: [], ...type.deserialize(data.subarray(1))};
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA;
|
|
68
105
|
|
|
69
106
|
const type = new ContainerType({
|
|
@@ -75,3 +112,14 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
|
|
|
75
112
|
return type.deserialize(data.subarray(1));
|
|
76
113
|
}
|
|
77
114
|
}
|
|
115
|
+
|
|
116
|
+
function prefixData(prefix: PrefixByte, valueBytes: Uint8Array): Uint8Array {
|
|
117
|
+
const prefixByte = new Uint8Array(1);
|
|
118
|
+
prefixByte[0] = prefix;
|
|
119
|
+
|
|
120
|
+
const prefixedData = new Uint8Array(1 + valueBytes.length);
|
|
121
|
+
prefixedData.set(prefixByte, 0);
|
|
122
|
+
prefixedData.set(valueBytes, 1);
|
|
123
|
+
|
|
124
|
+
return prefixedData;
|
|
125
|
+
}
|
|
@@ -9,7 +9,7 @@ import {ForkName} from "@lodestar/params";
|
|
|
9
9
|
import {intToBytes} from "@lodestar/utils";
|
|
10
10
|
import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js";
|
|
11
11
|
import {Eth2GossipsubMetrics} from "./metrics.js";
|
|
12
|
-
import {GossipTopicCache, getGossipSSZType} from "./topic.js";
|
|
12
|
+
import {GossipTopicCache, getGossipSSZMaxSize, getGossipSSZType} from "./topic.js";
|
|
13
13
|
|
|
14
14
|
// Load WASM
|
|
15
15
|
const xxhash = await xxhashFactory();
|
|
@@ -98,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
|
|
|
98
98
|
export class DataTransformSnappy implements DataTransform {
|
|
99
99
|
constructor(
|
|
100
100
|
private readonly gossipTopicCache: GossipTopicCache,
|
|
101
|
-
private readonly
|
|
101
|
+
private readonly maxPayloadSize: number,
|
|
102
102
|
private readonly metrics: Eth2GossipsubMetrics | null
|
|
103
103
|
) {}
|
|
104
104
|
|
|
@@ -111,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
|
|
|
111
111
|
inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
|
|
112
112
|
// check uncompressed data length before we actually decompress
|
|
113
113
|
const uncompressedDataLength = snappyWasm.decompress_len(data);
|
|
114
|
-
if (uncompressedDataLength > this.maxSizePerMessage) {
|
|
115
|
-
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
|
|
116
|
-
}
|
|
117
114
|
|
|
118
115
|
const topic = this.gossipTopicCache.getTopic(topicStr);
|
|
119
116
|
const sszType = getGossipSSZType(topic);
|
|
117
|
+
const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
|
|
120
118
|
this.metrics?.dataTransform.inbound.inc({type: topic.type});
|
|
121
119
|
|
|
122
120
|
if (uncompressedDataLength < sszType.minSize) {
|
|
123
121
|
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
|
|
124
122
|
}
|
|
123
|
+
if (uncompressedDataLength > maxSize) {
|
|
124
|
+
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
|
|
125
|
+
}
|
|
125
126
|
if (uncompressedDataLength > sszType.maxSize) {
|
|
126
127
|
throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
|
|
127
128
|
}
|
|
@@ -139,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
|
|
|
139
140
|
*/
|
|
140
141
|
outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
|
|
141
142
|
const topic = this.gossipTopicCache.getTopic(topicStr);
|
|
143
|
+
const sszType = getGossipSSZType(topic);
|
|
144
|
+
const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
|
|
142
145
|
this.metrics?.dataTransform.outbound.inc({type: topic.type});
|
|
143
|
-
if (data.length >
|
|
144
|
-
throw Error(`ssz_snappy encoded data length ${data.length} > ${
|
|
146
|
+
if (data.length > maxSize) {
|
|
147
|
+
throw Error(`ssz_snappy encoded data length ${data.length} > ${maxSize}`);
|
|
148
|
+
}
|
|
149
|
+
if (data.length > sszType.maxSize) {
|
|
150
|
+
throw Error(`ssz_snappy encoded data length ${data.length} > ${sszType.maxSize}`);
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
// Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments
|
|
@@ -15,7 +15,12 @@ import {type Multiaddr, multiaddr} from "@multiformats/multiaddr";
|
|
|
15
15
|
import {ENR} from "@chainsafe/enr";
|
|
16
16
|
import {routes} from "@lodestar/api";
|
|
17
17
|
import {BeaconConfig, ForkBoundary} from "@lodestar/config";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ATTESTATION_SUBNET_COUNT,
|
|
20
|
+
MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
|
|
21
|
+
SLOTS_PER_EPOCH,
|
|
22
|
+
SYNC_COMMITTEE_SUBNET_COUNT,
|
|
23
|
+
} from "@lodestar/params";
|
|
19
24
|
import {SubnetID} from "@lodestar/types";
|
|
20
25
|
import {Logger, Map2d, Map2dArr} from "@lodestar/utils";
|
|
21
26
|
import {RegistryMetricCreator} from "../../metrics/index.js";
|
|
@@ -179,7 +184,7 @@ export class Eth2Gossipsub {
|
|
|
179
184
|
// Only send IDONTWANT messages if the message size is larger than this
|
|
180
185
|
// This should be large enough to not send IDONTWANT for "small" messages
|
|
181
186
|
// See https://github.com/ChainSafe/lodestar/pull/7077#issuecomment-2383679472
|
|
182
|
-
idontwantMinDataSize:
|
|
187
|
+
idontwantMinDataSize: MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
|
|
183
188
|
})(modules.libp2p.services.components) as GossipSubInternal;
|
|
184
189
|
|
|
185
190
|
if (metrics) {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import {type CompositeTypeAny} from "@chainsafe/ssz";
|
|
1
2
|
import {ForkDigestContext} from "@lodestar/config";
|
|
2
3
|
import {
|
|
3
4
|
ATTESTATION_SUBNET_COUNT,
|
|
4
5
|
ForkName,
|
|
5
6
|
ForkSeq,
|
|
7
|
+
MAX_ATTESTER_SLASHING_SIZE,
|
|
8
|
+
MAX_DATA_COLUMN_SIDECAR_SIZE,
|
|
9
|
+
MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
|
|
10
|
+
MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE,
|
|
6
11
|
SYNC_COMMITTEE_SUBNET_COUNT,
|
|
7
12
|
isForkPostAltair,
|
|
8
13
|
isForkPostElectra,
|
|
9
14
|
isForkPostFulu,
|
|
15
|
+
isForkPostGloas,
|
|
10
16
|
} from "@lodestar/params";
|
|
11
17
|
import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types";
|
|
12
18
|
import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js";
|
|
@@ -130,6 +136,30 @@ export function getGossipSSZType(topic: GossipTopic) {
|
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Return the maximum uncompressed SSZ byte length accepted for a gossip object.
|
|
141
|
+
*/
|
|
142
|
+
export function getGossipSSZMaxSize(topic: GossipTopic, maxPayloadSize: number, sszType?: CompositeTypeAny): number {
|
|
143
|
+
const {fork} = topic.boundary;
|
|
144
|
+
// Gloas progressive containers have broad theoretical SSZ max sizes; use the preset p2p bounds instead.
|
|
145
|
+
switch (topic.type) {
|
|
146
|
+
case GossipType.beacon_block:
|
|
147
|
+
return maxPayloadSize;
|
|
148
|
+
case GossipType.beacon_aggregate_and_proof:
|
|
149
|
+
return isForkPostGloas(fork) ? MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
|
|
150
|
+
case GossipType.attester_slashing:
|
|
151
|
+
return isForkPostGloas(fork) ? MAX_ATTESTER_SLASHING_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
|
|
152
|
+
case GossipType.data_column_sidecar:
|
|
153
|
+
return isForkPostGloas(fork) ? MAX_DATA_COLUMN_SIDECAR_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
|
|
154
|
+
case GossipType.execution_payload:
|
|
155
|
+
return maxPayloadSize;
|
|
156
|
+
case GossipType.execution_payload_bid:
|
|
157
|
+
return MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE;
|
|
158
|
+
default:
|
|
159
|
+
return (sszType ?? getGossipSSZType(topic)).maxSize;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
133
163
|
/**
|
|
134
164
|
* Deserialize a gossip serialized data into an ssz object.
|
|
135
165
|
*/
|
package/src/network/interface.ts
CHANGED
|
@@ -103,7 +103,9 @@ export interface INetwork extends INetworkCorePublic {
|
|
|
103
103
|
publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number>;
|
|
104
104
|
publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise<number>;
|
|
105
105
|
publishBeaconAttestation(attestation: SingleAttestation, subnet: SubnetID): Promise<number>;
|
|
106
|
-
publishDataColumnSidecar(
|
|
106
|
+
publishDataColumnSidecar(
|
|
107
|
+
dataColumnSideCar: DataColumnSidecar
|
|
108
|
+
): Promise<{sentPeers: number; alreadyPublished: boolean}>;
|
|
107
109
|
publishVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): Promise<number>;
|
|
108
110
|
publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number>;
|
|
109
111
|
publishProposerSlashing(proposerSlashing: phase0.ProposerSlashing): Promise<number>;
|
package/src/network/network.ts
CHANGED
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
} from "./reqresp/utils/collect.js";
|
|
66
66
|
import {collectSequentialBlocksInRange} from "./reqresp/utils/collectSequentialBlocksInRange.js";
|
|
67
67
|
import {CommitteeSubscription} from "./subnets/index.js";
|
|
68
|
-
import {isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
|
|
68
|
+
import {isPublishDuplicateError, isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
|
|
69
69
|
|
|
70
70
|
type NetworkModules = {
|
|
71
71
|
opts: NetworkOptions;
|
|
@@ -366,7 +366,9 @@ export class Network implements INetwork {
|
|
|
366
366
|
});
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
async publishDataColumnSidecar(
|
|
369
|
+
async publishDataColumnSidecar(
|
|
370
|
+
dataColumnSidecar: DataColumnSidecar
|
|
371
|
+
): Promise<{sentPeers: number; alreadyPublished: boolean}> {
|
|
370
372
|
const slot = isGloasDataColumnSidecar(dataColumnSidecar)
|
|
371
373
|
? dataColumnSidecar.slot
|
|
372
374
|
: dataColumnSidecar.signedBlockHeader.message.slot;
|
|
@@ -374,18 +376,25 @@ export class Network implements INetwork {
|
|
|
374
376
|
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
375
377
|
|
|
376
378
|
const subnet = computeSubnetForDataColumnSidecar(this.config, dataColumnSidecar);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
try {
|
|
380
|
+
const sentPeers = await this.publishGossip<GossipType.data_column_sidecar>(
|
|
381
|
+
{type: GossipType.data_column_sidecar, boundary, subnet},
|
|
382
|
+
dataColumnSidecar,
|
|
383
|
+
{
|
|
384
|
+
// we ensure having all topic peers via prioritizePeers() function
|
|
385
|
+
// in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
|
|
386
|
+
// because supernode will rebuild and publish missing data column sidecars for us
|
|
387
|
+
// hence we want to track sent peers as 0 instead of an error
|
|
388
|
+
allowPublishToZeroTopicPeers: true,
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
return {sentPeers, alreadyPublished: false};
|
|
392
|
+
} catch (e) {
|
|
393
|
+
if (isPublishDuplicateError(e as Error)) {
|
|
394
|
+
return {sentPeers: 0, alreadyPublished: true};
|
|
387
395
|
}
|
|
388
|
-
|
|
396
|
+
throw e;
|
|
397
|
+
}
|
|
389
398
|
}
|
|
390
399
|
|
|
391
400
|
async publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise<number> {
|
|
@@ -860,8 +869,8 @@ export class Network implements INetwork {
|
|
|
860
869
|
this.core.setTargetGroupCount(count);
|
|
861
870
|
};
|
|
862
871
|
|
|
863
|
-
private onPublishDataColumns = (sidecars: DataColumnSidecar[]): Promise<
|
|
864
|
-
|
|
872
|
+
private onPublishDataColumns = async (sidecars: DataColumnSidecar[]): Promise<void> => {
|
|
873
|
+
await promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishDataColumnSidecar(sidecar)));
|
|
865
874
|
};
|
|
866
875
|
|
|
867
876
|
private onPublishBlobSidecars = (sidecars: deneb.BlobSidecar[]): Promise<number[]> => {
|
|
@@ -176,7 +176,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
176
176
|
|
|
177
177
|
// optimistically add gossip block to the seen cache
|
|
178
178
|
// if validation fails, we will NOT forward this gossip block to peers
|
|
179
|
-
// - if
|
|
179
|
+
// - if PARENT_BLOCK_UNKNOWN error, blockInput will then be queued inside BlockInputSync. If the gossip block is really invalid, it will be pruned there
|
|
180
180
|
// - if other validator errors, blockInput will stay in the seen cache and will be pruned on finalization
|
|
181
181
|
const blockInput = chain.seenBlockInputCache.getByBlock({
|
|
182
182
|
block: signedBlock,
|
|
@@ -187,7 +187,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
// Optimistically seed the payload-envelope cache too, mirroring seenBlockInputCache above.
|
|
190
|
-
// This ensures we have PayloadEnvelopeInput, even through "
|
|
190
|
+
// This ensures we have PayloadEnvelopeInput, even through "PARENT_BLOCK_UNKNOWN" error
|
|
191
191
|
// see https://github.com/ChainSafe/lodestar/issues/9475
|
|
192
192
|
if (isForkPostGloas(fork)) {
|
|
193
193
|
chain.seenPayloadEnvelopeInputCache.add({
|
|
@@ -227,7 +227,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
227
227
|
if (e instanceof BlockGossipError) {
|
|
228
228
|
logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
|
|
229
229
|
if (
|
|
230
|
-
(e.type.code === BlockErrorCode.
|
|
230
|
+
(e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
|
|
231
|
+
e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
|
|
231
232
|
blockInput
|
|
232
233
|
) {
|
|
233
234
|
chain.emitter.emit(ChainEvent.blockUnknownParent, {
|
|
@@ -661,14 +662,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
661
662
|
break;
|
|
662
663
|
}
|
|
663
664
|
// ALREADY_KNOWN should not happen with ignoreIfKnown=true above
|
|
664
|
-
//
|
|
665
|
+
// PARENT_BLOCK_UNKNOWN should not happen, we handled this in validateBeaconBlock() function above
|
|
665
666
|
case BlockErrorCode.ALREADY_KNOWN:
|
|
666
|
-
case BlockErrorCode.
|
|
667
|
+
case BlockErrorCode.PARENT_BLOCK_UNKNOWN:
|
|
667
668
|
case BlockErrorCode.PRESTATE_MISSING:
|
|
668
669
|
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
|
|
669
|
-
// Errors might indicate an issue with our node or the connected EL client
|
|
670
|
+
// Errors might indicate an issue with our node or the connected EL client.
|
|
670
671
|
logLevel = LogLevel.error;
|
|
671
672
|
break;
|
|
673
|
+
case BlockErrorCode.EXECUTION_ENGINE_INVALID:
|
|
674
|
+
// the peer served a bad block
|
|
675
|
+
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "ExecutionEngineInvalid");
|
|
676
|
+
logLevel = LogLevel.warn;
|
|
677
|
+
break;
|
|
672
678
|
default:
|
|
673
679
|
// TODO: Should it use PeerId or string?
|
|
674
680
|
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipBlock");
|
|
@@ -107,8 +107,10 @@ export function validateExecutionPayloadEnvelopesByRangeRequest(
|
|
|
107
107
|
// The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
|
|
108
108
|
// Archival nodes may still serve older retained payloads to allow genesis sync.
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// Spec: EnvelopesByRange response is bounded by MAX_REQUEST_PAYLOADS (consensus-specs #5383),
|
|
111
|
+
// distinct from the MAX_REQUEST_BLOCKS_DENEB cap used for block-by-range.
|
|
112
|
+
if (count > config.MAX_REQUEST_PAYLOADS) {
|
|
113
|
+
count = config.MAX_REQUEST_PAYLOADS;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
return {startSlot, count};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {Type} from "@chainsafe/ssz";
|
|
2
|
+
import {ProtocolHandler, RespStatus, ResponseError} from "@lodestar/reqresp";
|
|
2
3
|
import {ssz} from "@lodestar/types";
|
|
3
4
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
4
5
|
import {IBeaconDb} from "../../../db/index.js";
|
|
@@ -29,6 +30,14 @@ function notImplemented(method: ReqRespMethod): ProtocolHandler {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function deserializeRequestBody<T>(type: Type<T>, data: Uint8Array): T {
|
|
34
|
+
try {
|
|
35
|
+
return type.deserialize(data);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
throw new ResponseError(RespStatus.INVALID_REQUEST, e instanceof Error ? e.message : String(e));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
/**
|
|
33
42
|
* The ReqRespHandler module handles app-level requests / responses from other peers,
|
|
34
43
|
* fetching state from the chain and database as needed.
|
|
@@ -40,51 +49,51 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
|
|
|
40
49
|
[ReqRespMethod.Ping]: notImplemented(ReqRespMethod.Ping),
|
|
41
50
|
[ReqRespMethod.Metadata]: notImplemented(ReqRespMethod.Metadata),
|
|
42
51
|
[ReqRespMethod.BeaconBlocksByRange]: (req, peerId, peerClient) => {
|
|
43
|
-
const body = ssz.phase0.BeaconBlocksByRangeRequest
|
|
52
|
+
const body = deserializeRequestBody(ssz.phase0.BeaconBlocksByRangeRequest, req.data);
|
|
44
53
|
return onBeaconBlocksByRange(body, chain, db, peerId, peerClient);
|
|
45
54
|
},
|
|
46
55
|
[ReqRespMethod.BeaconBlocksByRoot]: (req) => {
|
|
47
56
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
48
|
-
const body = BeaconBlocksByRootRequestType(fork, chain.config)
|
|
57
|
+
const body = deserializeRequestBody(BeaconBlocksByRootRequestType(fork, chain.config), req.data);
|
|
49
58
|
return onBeaconBlocksByRoot(body, chain);
|
|
50
59
|
},
|
|
51
60
|
[ReqRespMethod.BeaconBlocksByHead]: (req, peerId, peerClient) => {
|
|
52
|
-
const body = ssz.fulu.BeaconBlocksByHeadRequest
|
|
61
|
+
const body = deserializeRequestBody(ssz.fulu.BeaconBlocksByHeadRequest, req.data);
|
|
53
62
|
return onBeaconBlocksByHead(body, chain, peerId, peerClient);
|
|
54
63
|
},
|
|
55
64
|
[ReqRespMethod.BlobSidecarsByRoot]: (req) => {
|
|
56
65
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
57
|
-
const body = BlobSidecarsByRootRequestType(fork, chain.config)
|
|
66
|
+
const body = deserializeRequestBody(BlobSidecarsByRootRequestType(fork, chain.config), req.data);
|
|
58
67
|
return onBlobSidecarsByRoot(body, chain);
|
|
59
68
|
},
|
|
60
69
|
[ReqRespMethod.BlobSidecarsByRange]: (req) => {
|
|
61
|
-
const body = ssz.deneb.BlobSidecarsByRangeRequest
|
|
70
|
+
const body = deserializeRequestBody(ssz.deneb.BlobSidecarsByRangeRequest, req.data);
|
|
62
71
|
return onBlobSidecarsByRange(body, chain, db);
|
|
63
72
|
},
|
|
64
73
|
[ReqRespMethod.DataColumnSidecarsByRange]: (req, peerId, peerClient) => {
|
|
65
|
-
const body = ssz.fulu.DataColumnSidecarsByRangeRequest
|
|
74
|
+
const body = deserializeRequestBody(ssz.fulu.DataColumnSidecarsByRangeRequest, req.data);
|
|
66
75
|
return onDataColumnSidecarsByRange(body, chain, db, peerId, peerClient);
|
|
67
76
|
},
|
|
68
77
|
[ReqRespMethod.DataColumnSidecarsByRoot]: (req, peerId, peerClient) => {
|
|
69
|
-
const body = DataColumnSidecarsByRootRequestType(chain.config)
|
|
78
|
+
const body = deserializeRequestBody(DataColumnSidecarsByRootRequestType(chain.config), req.data);
|
|
70
79
|
return onDataColumnSidecarsByRoot(body, chain, db, peerId, peerClient);
|
|
71
80
|
},
|
|
72
81
|
|
|
73
82
|
[ReqRespMethod.ExecutionPayloadEnvelopesByRoot]: (req, peerId, peerClient) => {
|
|
74
|
-
const body = ExecutionPayloadEnvelopesByRootRequestType(chain.config)
|
|
83
|
+
const body = deserializeRequestBody(ExecutionPayloadEnvelopesByRootRequestType(chain.config), req.data);
|
|
75
84
|
return onExecutionPayloadEnvelopesByRoot(body, chain, db, peerId, peerClient);
|
|
76
85
|
},
|
|
77
86
|
[ReqRespMethod.ExecutionPayloadEnvelopesByRange]: (req, peerId, peerClient) => {
|
|
78
|
-
const body = ssz.gloas.ExecutionPayloadEnvelopesByRangeRequest
|
|
87
|
+
const body = deserializeRequestBody(ssz.gloas.ExecutionPayloadEnvelopesByRangeRequest, req.data);
|
|
79
88
|
return onExecutionPayloadEnvelopesByRange(body, chain, db, peerId, peerClient);
|
|
80
89
|
},
|
|
81
90
|
|
|
82
91
|
[ReqRespMethod.LightClientBootstrap]: (req) => {
|
|
83
|
-
const body = ssz.Root
|
|
92
|
+
const body = deserializeRequestBody(ssz.Root, req.data);
|
|
84
93
|
return onLightClientBootstrap(body, chain);
|
|
85
94
|
},
|
|
86
95
|
[ReqRespMethod.LightClientUpdatesByRange]: (req) => {
|
|
87
|
-
const body = ssz.altair.LightClientUpdatesByRange
|
|
96
|
+
const body = deserializeRequestBody(ssz.altair.LightClientUpdatesByRange, req.data);
|
|
88
97
|
return onLightClientUpdatesByRange(body, chain);
|
|
89
98
|
},
|
|
90
99
|
[ReqRespMethod.LightClientFinalityUpdate]: () => onLightClientFinalityUpdate(chain),
|
package/src/network/util.ts
CHANGED
|
@@ -27,3 +27,8 @@ export function getConnection(libp2p: Libp2p, peerIdStr: string): Connection | u
|
|
|
27
27
|
export function isPublishToZeroPeersError(e: Error): boolean {
|
|
28
28
|
return e.message.includes("PublishError.NoPeersSubscribedToTopic");
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
// https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
|
|
32
|
+
export function isPublishDuplicateError(e: Error): boolean {
|
|
33
|
+
return e.message.includes("PublishError.Duplicate");
|
|
34
|
+
}
|
package/src/sync/sync.ts
CHANGED
|
@@ -75,6 +75,11 @@ export class BeaconSync implements IBeaconSync {
|
|
|
75
75
|
if (metrics) {
|
|
76
76
|
metrics.syncStatus.addCollect(() => this.scrapeMetrics(metrics));
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
// A from-scratch or large-gap start boots not synced; updateSyncState resumes once synced
|
|
80
|
+
if (this.state !== SyncState.Synced) {
|
|
81
|
+
this.chain.forkChoice.pauseFastConfirmation();
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
close(): void {
|
|
@@ -228,6 +233,8 @@ export class BeaconSync implements IBeaconSync {
|
|
|
228
233
|
|
|
229
234
|
// We have become synced, subscribe to all the gossip core topics
|
|
230
235
|
if (state === SyncState.Synced && this.chain.clock.currentEpoch >= MIN_EPOCH_TO_START_GOSSIP) {
|
|
236
|
+
this.chain.forkChoice.resumeFastConfirmation();
|
|
237
|
+
|
|
231
238
|
if (!this.network.isSubscribedToGossipCoreTopics()) {
|
|
232
239
|
this.network
|
|
233
240
|
.subscribeGossipCoreTopics()
|
|
@@ -251,6 +258,9 @@ export class BeaconSync implements IBeaconSync {
|
|
|
251
258
|
else if (state !== SyncState.Synced) {
|
|
252
259
|
const syncDiff = this.chain.clock.currentSlot - this.chain.forkChoice.getHead().slot;
|
|
253
260
|
if (syncDiff > this.slotImportTolerance * 2) {
|
|
261
|
+
// Same debounce as gossip: transient blips keep the rule running, only a real gap pauses it
|
|
262
|
+
this.chain.forkChoice.pauseFastConfirmation();
|
|
263
|
+
|
|
254
264
|
if (this.network.isSubscribedToGossipCoreTopics()) {
|
|
255
265
|
this.logger.warn(`Node sync has fallen behind by ${syncDiff} slots`);
|
|
256
266
|
this.network
|