Skip to content

Commit 25e78f0

Browse files
authored
feat: add devnet-5 support (#7246)
2 parents 3dcd668 + eb2ee60 commit 25e78f0

File tree

74 files changed

+1287
-469
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1287
-469
lines changed

.env.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
GETH_DOCKER_IMAGE=ethereum/client-go:v1.13.14
55
# Use either image or local binary for the testing
66
GETH_BINARY_DIR=
7-
LIGHTHOUSE_DOCKER_IMAGE=sigp/lighthouse:v5.1.1-amd64-modern-dev
7+
LIGHTHOUSE_DOCKER_IMAGE=sigp/lighthouse:latest-amd64-unstable
88

99
# We can't upgrade nethermind further due to genesis hash mismatch with the geth
1010
# https://github.com/NethermindEth/nethermind/issues/6683

packages/api/src/beacon/routes/beacon/pool.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import {ValueOf} from "@chainsafe/ssz";
22
import {ChainForkConfig} from "@lodestar/config";
3-
import {isForkPostElectra} from "@lodestar/params";
4-
import {AttesterSlashing, CommitteeIndex, Slot, capella, electra, phase0, ssz} from "@lodestar/types";
3+
import {ForkPostElectra, ForkPreElectra, isForkPostElectra} from "@lodestar/params";
4+
import {
5+
AttesterSlashing,
6+
CommitteeIndex,
7+
SingleAttestation,
8+
Slot,
9+
capella,
10+
electra,
11+
phase0,
12+
ssz,
13+
} from "@lodestar/types";
514
import {
615
ArrayOf,
716
EmptyArgs,
@@ -20,6 +29,8 @@ import {MetaHeader, VersionCodec, VersionMeta} from "../../../utils/metadata.js"
2029

2130
// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes
2231

32+
const SingleAttestationListTypePhase0 = ArrayOf(ssz.phase0.Attestation);
33+
const SingleAttestationListTypeElectra = ArrayOf(ssz.electra.SingleAttestation);
2334
const AttestationListTypePhase0 = ArrayOf(ssz.phase0.Attestation);
2435
const AttestationListTypeElectra = ArrayOf(ssz.electra.Attestation);
2536
const AttesterSlashingListTypePhase0 = ArrayOf(ssz.phase0.AttesterSlashing);
@@ -142,7 +153,7 @@ export type Endpoints = {
142153
*/
143154
submitPoolAttestations: Endpoint<
144155
"POST",
145-
{signedAttestations: AttestationListPhase0},
156+
{signedAttestations: SingleAttestation<ForkPreElectra>[]},
146157
{body: unknown},
147158
EmptyResponseData,
148159
EmptyMeta
@@ -158,7 +169,7 @@ export type Endpoints = {
158169
*/
159170
submitPoolAttestationsV2: Endpoint<
160171
"POST",
161-
{signedAttestations: AttestationList},
172+
{signedAttestations: SingleAttestation[]},
162173
{body: unknown; headers: {[MetaHeader.Version]: string}},
163174
EmptyResponseData,
164175
EmptyMeta
@@ -316,10 +327,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
316327
url: "/eth/v1/beacon/pool/attestations",
317328
method: "POST",
318329
req: {
319-
writeReqJson: ({signedAttestations}) => ({body: AttestationListTypePhase0.toJson(signedAttestations)}),
320-
parseReqJson: ({body}) => ({signedAttestations: AttestationListTypePhase0.fromJson(body)}),
321-
writeReqSsz: ({signedAttestations}) => ({body: AttestationListTypePhase0.serialize(signedAttestations)}),
322-
parseReqSsz: ({body}) => ({signedAttestations: AttestationListTypePhase0.deserialize(body)}),
330+
writeReqJson: ({signedAttestations}) => ({body: SingleAttestationListTypePhase0.toJson(signedAttestations)}),
331+
parseReqJson: ({body}) => ({signedAttestations: SingleAttestationListTypePhase0.fromJson(body)}),
332+
writeReqSsz: ({signedAttestations}) => ({body: SingleAttestationListTypePhase0.serialize(signedAttestations)}),
333+
parseReqSsz: ({body}) => ({signedAttestations: SingleAttestationListTypePhase0.deserialize(body)}),
323334
schema: {
324335
body: Schema.ObjectArray,
325336
},
@@ -334,34 +345,34 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
334345
const fork = config.getForkName(signedAttestations[0]?.data.slot ?? 0);
335346
return {
336347
body: isForkPostElectra(fork)
337-
? AttestationListTypeElectra.toJson(signedAttestations as AttestationListElectra)
338-
: AttestationListTypePhase0.toJson(signedAttestations as AttestationListPhase0),
348+
? SingleAttestationListTypeElectra.toJson(signedAttestations as SingleAttestation<ForkPostElectra>[])
349+
: SingleAttestationListTypePhase0.toJson(signedAttestations as SingleAttestation<ForkPreElectra>[]),
339350
headers: {[MetaHeader.Version]: fork},
340351
};
341352
},
342353
parseReqJson: ({body, headers}) => {
343354
const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
344355
return {
345356
signedAttestations: isForkPostElectra(fork)
346-
? AttestationListTypeElectra.fromJson(body)
347-
: AttestationListTypePhase0.fromJson(body),
357+
? SingleAttestationListTypeElectra.fromJson(body)
358+
: SingleAttestationListTypePhase0.fromJson(body),
348359
};
349360
},
350361
writeReqSsz: ({signedAttestations}) => {
351362
const fork = config.getForkName(signedAttestations[0]?.data.slot ?? 0);
352363
return {
353364
body: isForkPostElectra(fork)
354-
? AttestationListTypeElectra.serialize(signedAttestations as AttestationListElectra)
355-
: AttestationListTypePhase0.serialize(signedAttestations as AttestationListPhase0),
365+
? SingleAttestationListTypeElectra.serialize(signedAttestations as SingleAttestation<ForkPostElectra>[])
366+
: SingleAttestationListTypePhase0.serialize(signedAttestations as SingleAttestation<ForkPreElectra>[]),
356367
headers: {[MetaHeader.Version]: fork},
357368
};
358369
},
359370
parseReqSsz: ({body, headers}) => {
360371
const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
361372
return {
362373
signedAttestations: isForkPostElectra(fork)
363-
? AttestationListTypeElectra.deserialize(body)
364-
: AttestationListTypePhase0.deserialize(body),
374+
? SingleAttestationListTypeElectra.deserialize(body)
375+
: SingleAttestationListTypePhase0.deserialize(body),
365376
};
366377
},
367378
schema: {

packages/api/src/beacon/routes/events.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
UintNum64,
1515
altair,
1616
capella,
17+
electra,
1718
phase0,
1819
ssz,
1920
sszTypesFor,
@@ -51,6 +52,8 @@ export enum EventType {
5152
block = "block",
5253
/** The node has received a valid attestation (from P2P or API) */
5354
attestation = "attestation",
55+
/** The node has received a valid SingleAttestation (from P2P or API) */
56+
singleAttestation = "single_attestation",
5457
/** The node has received a valid voluntary exit (from P2P or API) */
5558
voluntaryExit = "voluntary_exit",
5659
/** The node has received a valid proposer slashing (from P2P or API) */
@@ -79,6 +82,7 @@ export const eventTypes: {[K in EventType]: K} = {
7982
[EventType.head]: EventType.head,
8083
[EventType.block]: EventType.block,
8184
[EventType.attestation]: EventType.attestation,
85+
[EventType.singleAttestation]: EventType.singleAttestation,
8286
[EventType.voluntaryExit]: EventType.voluntaryExit,
8387
[EventType.proposerSlashing]: EventType.proposerSlashing,
8488
[EventType.attesterSlashing]: EventType.attesterSlashing,
@@ -108,6 +112,7 @@ export type EventData = {
108112
executionOptimistic: boolean;
109113
};
110114
[EventType.attestation]: Attestation;
115+
[EventType.singleAttestation]: electra.SingleAttestation;
111116
[EventType.voluntaryExit]: phase0.SignedVoluntaryExit;
112117
[EventType.proposerSlashing]: phase0.ProposerSlashing;
113118
[EventType.attesterSlashing]: AttesterSlashing;
@@ -237,6 +242,7 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type
237242
return sszTypesFor(fork).Attestation.fromJson(attestation);
238243
},
239244
},
245+
[EventType.singleAttestation]: ssz.electra.SingleAttestation,
240246
[EventType.voluntaryExit]: ssz.phase0.SignedVoluntaryExit,
241247
[EventType.proposerSlashing]: ssz.phase0.ProposerSlashing,
242248
[EventType.attesterSlashing]: {

packages/api/test/unit/beacon/oapiSpec.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {testData as validatorTestData} from "./testData/validator.js";
2020
// Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules
2121
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2222

23-
const version = "v3.0.0-alpha.6";
23+
const version = "v3.0.0-alpha.9";
2424
const openApiFile: OpenApiFile = {
2525
url: `https://github.com/ethereum/beacon-APIs/releases/download/${version}/beacon-node-oapi.json`,
2626
filepath: path.join(__dirname, "../../../oapi-schemas/beacon-node-oapi.json"),

packages/api/test/unit/beacon/testData/events.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ export const eventTestData: EventData = {
4141
target: {epoch: "1", root: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},
4242
},
4343
}),
44+
[EventType.singleAttestation]: ssz.electra.SingleAttestation.fromJson({
45+
committee_index: "1",
46+
attester_index: "1",
47+
data: {
48+
slot: "1",
49+
index: "1",
50+
beacon_block_root: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
51+
source: {epoch: "1", root: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},
52+
target: {epoch: "1", root: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},
53+
},
54+
signature:
55+
"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
56+
}),
4457
[EventType.voluntaryExit]: ssz.phase0.SignedVoluntaryExit.fromJson({
4558
message: {epoch: "1", validator_index: "1"},
4659
signature:

packages/beacon-node/src/api/impl/beacon/pool/index.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import {routes} from "@lodestar/api";
22
import {ApplicationMethods} from "@lodestar/api/server";
3-
import {ForkName, SYNC_COMMITTEE_SUBNET_SIZE, isForkPostElectra} from "@lodestar/params";
4-
import {Attestation, Epoch, isElectraAttestation, ssz} from "@lodestar/types";
3+
import {
4+
ForkName,
5+
ForkPostElectra,
6+
ForkPreElectra,
7+
SYNC_COMMITTEE_SUBNET_SIZE,
8+
isForkPostElectra,
9+
} from "@lodestar/params";
10+
import {Attestation, Epoch, SingleAttestation, isElectraAttestation, ssz} from "@lodestar/types";
511
import {
612
AttestationError,
713
AttestationErrorCode,
@@ -10,7 +16,7 @@ import {
1016
} from "../../../../chain/errors/index.js";
1117
import {validateApiAttesterSlashing} from "../../../../chain/validation/attesterSlashing.js";
1218
import {validateApiBlsToExecutionChange} from "../../../../chain/validation/blsToExecutionChange.js";
13-
import {validateApiAttestation} from "../../../../chain/validation/index.js";
19+
import {toElectraSingleAttestation, validateApiAttestation} from "../../../../chain/validation/index.js";
1420
import {validateApiProposerSlashing} from "../../../../chain/validation/proposerSlashing.js";
1521
import {validateApiSyncCommittee} from "../../../../chain/validation/syncCommittee.js";
1622
import {validateApiVoluntaryExit} from "../../../../chain/validation/voluntaryExit.js";
@@ -99,20 +105,35 @@ export function getBeaconPoolApi({
99105
// when a validator is configured with multiple beacon node urls, this attestation data may come from another beacon node
100106
// and the block hasn't been in our forkchoice since we haven't seen / processing that block
101107
// see https://github.com/ChainSafe/lodestar/issues/5098
102-
const {indexedAttestation, subnet, attDataRootHex, committeeIndex} = await validateGossipFnRetryUnknownRoot(
103-
validateFn,
104-
network,
105-
chain,
106-
slot,
107-
beaconBlockRoot
108-
);
108+
const {indexedAttestation, subnet, attDataRootHex, committeeIndex, committeeValidatorIndex, committeeSize} =
109+
await validateGossipFnRetryUnknownRoot(validateFn, network, chain, slot, beaconBlockRoot);
109110

110111
if (network.shouldAggregate(subnet, slot)) {
111-
const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex);
112+
const insertOutcome = chain.attestationPool.add(
113+
committeeIndex,
114+
attestation,
115+
attDataRootHex,
116+
committeeValidatorIndex,
117+
committeeSize
118+
);
112119
metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome});
113120
}
114121

115-
chain.emitter.emit(routes.events.EventType.attestation, attestation);
122+
if (isForkPostElectra(fork)) {
123+
chain.emitter.emit(
124+
routes.events.EventType.singleAttestation,
125+
attestation as SingleAttestation<ForkPostElectra>
126+
);
127+
} else {
128+
chain.emitter.emit(routes.events.EventType.attestation, attestation as SingleAttestation<ForkPreElectra>);
129+
chain.emitter.emit(
130+
routes.events.EventType.singleAttestation,
131+
toElectraSingleAttestation(
132+
attestation as SingleAttestation<ForkPreElectra>,
133+
indexedAttestation.attestingIndices[0]
134+
)
135+
);
136+
}
116137

117138
const sentPeers = await network.publishBeaconAttestation(attestation, subnet);
118139
metrics?.onPoolSubmitUnaggregatedAttestation(seenTimestampSec, indexedAttestation, subnet, sentPeers);

packages/beacon-node/src/api/impl/config/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
BLOB_TX_TYPE,
55
BLS_WITHDRAWAL_PREFIX,
66
COMPOUNDING_WITHDRAWAL_PREFIX,
7+
CONSOLIDATION_REQUEST_TYPE,
78
DEPOSIT_CONTRACT_TREE_DEPTH,
9+
DEPOSIT_REQUEST_TYPE,
810
DOMAIN_AGGREGATE_AND_PROOF,
911
DOMAIN_APPLICATION_BUILDER,
1012
DOMAIN_APPLICATION_MASK,
@@ -40,6 +42,7 @@ import {
4042
UNSET_DEPOSIT_REQUESTS_START_INDEX,
4143
VERSIONED_HASH_VERSION_KZG,
4244
WEIGHT_DENOMINATOR,
45+
WITHDRAWAL_REQUEST_TYPE,
4346
} from "@lodestar/params";
4447

4548
/**
@@ -108,4 +111,7 @@ export const specConstants = {
108111
// electra
109112
UNSET_DEPOSIT_REQUESTS_START_INDEX,
110113
FULL_EXIT_REQUEST_AMOUNT,
114+
DEPOSIT_REQUEST_TYPE,
115+
WITHDRAWAL_REQUEST_TYPE,
116+
CONSOLIDATION_REQUEST_TYPE,
111117
};

packages/beacon-node/src/chain/errors/attestationError.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ export enum AttestationErrorCode {
135135
* Electra: Invalid attestationData index: is non-zero
136136
*/
137137
NON_ZERO_ATTESTATION_DATA_INDEX = "ATTESTATION_ERROR_NON_ZERO_ATTESTATION_DATA_INDEX",
138+
/**
139+
* Electra: Attester not in committee
140+
*/
141+
ATTESTER_NOT_IN_COMMITTEE = "ATTESTATION_ERROR_ATTESTER_NOT_IN_COMMITTEE",
138142
}
139143

140144
export type AttestationErrorType =
@@ -170,7 +174,8 @@ export type AttestationErrorType =
170174
| {code: AttestationErrorCode.INVALID_SERIALIZED_BYTES}
171175
| {code: AttestationErrorCode.TOO_MANY_SKIPPED_SLOTS; headBlockSlot: Slot; attestationSlot: Slot}
172176
| {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET}
173-
| {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX};
177+
| {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX}
178+
| {code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE};
174179

175180
export class AttestationError extends GossipActionError<AttestationErrorType> {
176181
getMetadata(): Record<string, string | number | null> {

packages/beacon-node/src/chain/opPools/attestationPool.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {Signature, aggregateSignatures} from "@chainsafe/blst";
22
import {BitArray} from "@chainsafe/ssz";
33
import {ChainForkConfig} from "@lodestar/config";
4-
import {isForkPostElectra} from "@lodestar/params";
5-
import {Attestation, RootHex, Slot, isElectraAttestation} from "@lodestar/types";
4+
import {MAX_COMMITTEES_PER_SLOT, isForkPostElectra} from "@lodestar/params";
5+
import {Attestation, RootHex, SingleAttestation, Slot, isElectraSingleAttestation} from "@lodestar/types";
66
import {assert, MapDef} from "@lodestar/utils";
77
import {IClock} from "../../util/clock.js";
88
import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js";
@@ -105,7 +105,13 @@ export class AttestationPool {
105105
* - Valid committeeIndex
106106
* - Valid data
107107
*/
108-
add(committeeIndex: CommitteeIndex, attestation: Attestation, attDataRootHex: RootHex): InsertOutcome {
108+
add(
109+
committeeIndex: CommitteeIndex,
110+
attestation: SingleAttestation,
111+
attDataRootHex: RootHex,
112+
committeeValidatorIndex: number,
113+
committeeSize: number
114+
): InsertOutcome {
109115
const slot = attestation.data.slot;
110116
const fork = this.config.getForkName(slot);
111117
const lowestPermissibleSlot = this.lowestPermissibleSlot;
@@ -129,9 +135,9 @@ export class AttestationPool {
129135
if (isForkPostElectra(fork)) {
130136
// Electra only: this should not happen because attestation should be validated before reaching this
131137
assert.notNull(committeeIndex, "Committee index should not be null in attestation pool post-electra");
132-
assert.true(isElectraAttestation(attestation), "Attestation should be type electra.Attestation");
138+
assert.true(isElectraSingleAttestation(attestation), "Attestation should be type electra.SingleAttestation");
133139
} else {
134-
assert.true(!isElectraAttestation(attestation), "Attestation should be type phase0.Attestation");
140+
assert.true(!isElectraSingleAttestation(attestation), "Attestation should be type phase0.Attestation");
135141
committeeIndex = null; // For pre-electra, committee index info is encoded in attDataRootIndex
136142
}
137143

@@ -144,10 +150,10 @@ export class AttestationPool {
144150
const aggregate = aggregateByIndex.get(committeeIndex);
145151
if (aggregate) {
146152
// Aggregate mutating
147-
return aggregateAttestationInto(aggregate, attestation);
153+
return aggregateAttestationInto(aggregate, attestation, committeeValidatorIndex);
148154
}
149155
// Create new aggregate
150-
aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation));
156+
aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation, committeeValidatorIndex, committeeSize));
151157
return InsertOutcome.NewData;
152158
}
153159

@@ -216,8 +222,18 @@ export class AttestationPool {
216222
/**
217223
* Aggregate a new attestation into `aggregate` mutating it
218224
*/
219-
function aggregateAttestationInto(aggregate: AggregateFast, attestation: Attestation): InsertOutcome {
220-
const bitIndex = attestation.aggregationBits.getSingleTrueBit();
225+
function aggregateAttestationInto(
226+
aggregate: AggregateFast,
227+
attestation: SingleAttestation,
228+
committeeValidatorIndex: number
229+
): InsertOutcome {
230+
let bitIndex: number | null;
231+
232+
if (isElectraSingleAttestation(attestation)) {
233+
bitIndex = committeeValidatorIndex;
234+
} else {
235+
bitIndex = attestation.aggregationBits.getSingleTrueBit();
236+
}
221237

222238
// Should never happen, attestations are verified against this exact condition before
223239
assert.notNull(bitIndex, "Invalid attestation in pool, not exactly one bit set");
@@ -234,13 +250,16 @@ function aggregateAttestationInto(aggregate: AggregateFast, attestation: Attesta
234250
/**
235251
* Format `contribution` into an efficient `aggregate` to add more contributions in with aggregateContributionInto()
236252
*/
237-
function attestationToAggregate(attestation: Attestation): AggregateFast {
238-
if (isElectraAttestation(attestation)) {
253+
function attestationToAggregate(
254+
attestation: SingleAttestation,
255+
committeeValidatorIndex: number,
256+
committeeSize: number
257+
): AggregateFast {
258+
if (isElectraSingleAttestation(attestation)) {
239259
return {
240260
data: attestation.data,
241-
// clone because it will be mutated
242-
aggregationBits: attestation.aggregationBits.clone(),
243-
committeeBits: attestation.committeeBits,
261+
aggregationBits: BitArray.fromSingleBit(committeeSize, committeeValidatorIndex),
262+
committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, attestation.committeeIndex),
244263
signature: signatureFromBytesNoCheck(attestation.signature),
245264
};
246265
}

0 commit comments

Comments
 (0)