Skip to content

Commit b19abdf

Browse files
ensi321twoeths
andauthored
feat: add presets and ssz types for EIP-7549 (#6715)
* Add types * Update unit test * lint * Address comments * Address comments * Lint * Update packages/beacon-node/src/util/sszBytes.ts Co-authored-by: tuyennhv <[email protected]> * Add isElectraAttestation * Update unit test * Update unit test * chore: add comments for sszBytes.ts --------- Co-authored-by: tuyennhv <[email protected]> Co-authored-by: Tuyen Nguyen <[email protected]>
1 parent e5be557 commit b19abdf

File tree

13 files changed

+245
-30
lines changed

13 files changed

+245
-30
lines changed

packages/beacon-node/src/chain/validation/attestation.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ async function validateGossipAttestationNoSignatureCheck(
305305
// > TODO: Do this check **before** getting the target state but don't recompute zipIndexes
306306
const aggregationBits = attestationOrCache.attestation
307307
? attestationOrCache.attestation.aggregationBits
308-
: getAggregationBitsFromAttestationSerialized(attestationOrCache.serializedData);
308+
: getAggregationBitsFromAttestationSerialized(fork, attestationOrCache.serializedData);
309309
if (aggregationBits === null) {
310310
throw new AttestationError(GossipAction.REJECT, {
311311
code: AttestationErrorCode.INVALID_SERIALIZED_BYTES,
@@ -414,7 +414,7 @@ async function validateGossipAttestationNoSignatureCheck(
414414
let attDataRootHex: RootHex;
415415
const signature = attestationOrCache.attestation
416416
? attestationOrCache.attestation.signature
417-
: getSignatureFromAttestationSerialized(attestationOrCache.serializedData);
417+
: getSignatureFromAttestationSerialized(fork, attestationOrCache.serializedData);
418418
if (signature === null) {
419419
throw new AttestationError(GossipAction.REJECT, {
420420
code: AttestationErrorCode.INVALID_SERIALIZED_BYTES,

packages/beacon-node/src/util/sszBytes.ts

+49-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz";
22
import {BLSSignature, RootHex, Slot} from "@lodestar/types";
33
import {toHex} from "@lodestar/utils";
4-
import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params";
4+
import {
5+
BYTES_PER_FIELD_ELEMENT,
6+
FIELD_ELEMENTS_PER_BLOB,
7+
ForkName,
8+
ForkSeq,
9+
MAX_COMMITTEES_PER_SLOT,
10+
} from "@lodestar/params";
511

612
export type BlockRootHex = RootHex;
713
export type AttDataBase64 = string;
814

15+
// pre-electra
916
// class Attestation(Container):
1017
// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4
1118
// data: AttestationData - target data - 128
1219
// signature: BLSSignature - 96
20+
21+
// electra
22+
// class Attestation(Container):
23+
// aggregation_bits: BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - offset 4
24+
// data: AttestationData - target data - 128
25+
// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT]
26+
// signature: BLSSignature - 96
1327
//
28+
// for all forks
1429
// class AttestationData(Container): 128 bytes fixed size
1530
// slot: Slot - data 8
1631
// index: CommitteeIndex - data 8
@@ -23,6 +38,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8;
2338
const ROOT_SIZE = 32;
2439
const SLOT_SIZE = 8;
2540
const ATTESTATION_DATA_SIZE = 128;
41+
const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1);
2642
const SIGNATURE_SIZE = 96;
2743

2844
/**
@@ -68,32 +84,51 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att
6884
* Extract aggregation bits from attestation serialized bytes.
6985
* Return null if data is not long enough to extract aggregation bits.
7086
*/
71-
export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
72-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
87+
export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null {
88+
const aggregationBitsStartIndex =
89+
ForkSeq[fork] >= ForkSeq.electra
90+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + SIGNATURE_SIZE
91+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;
92+
93+
if (data.length < aggregationBitsStartIndex) {
7394
return null;
7495
}
7596

76-
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(
77-
data,
78-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE,
79-
data.length
80-
);
97+
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(data, aggregationBitsStartIndex, data.length);
8198
return new BitArray(uint8Array, bitLen);
8299
}
83100

84101
/**
85102
* Extract signature from attestation serialized bytes.
86103
* Return null if data is not long enough to extract signature.
87104
*/
88-
export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null {
89-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
105+
export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null {
106+
const signatureStartIndex =
107+
ForkSeq[fork] >= ForkSeq.electra
108+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE
109+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
110+
111+
if (data.length < signatureStartIndex + SIGNATURE_SIZE) {
90112
return null;
91113
}
92114

93-
return data.subarray(
94-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE,
95-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE
96-
);
115+
return data.subarray(signatureStartIndex, signatureStartIndex + SIGNATURE_SIZE);
116+
}
117+
118+
/**
119+
* Extract committee bits from Electra attestation serialized bytes.
120+
* Return null if data is not long enough to extract committee bits.
121+
*/
122+
export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
123+
const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
124+
125+
if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) {
126+
return null;
127+
}
128+
129+
const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE);
130+
131+
return new BitArray(uint8Array, MAX_COMMITTEES_PER_SLOT);
97132
}
98133

99134
//

packages/beacon-node/test/unit/util/sszBytes.test.ts

+37-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {describe, it, expect} from "vitest";
2-
import {deneb, Epoch, phase0, RootHex, Slot, ssz} from "@lodestar/types";
2+
import {BitArray} from "@chainsafe/ssz";
3+
import {allForks, deneb, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types";
34
import {fromHex, toHex} from "@lodestar/utils";
5+
import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params";
46
import {
57
getAttDataBase64FromAttestationSerialized,
68
getAttDataBase64FromSignedAggregateAndProofSerialized,
@@ -12,29 +14,52 @@ import {
1214
getSignatureFromAttestationSerialized,
1315
getSlotFromSignedBeaconBlockSerialized,
1416
getSlotFromBlobSidecarSerialized,
17+
getCommitteeBitsFromAttestationSerialized,
1518
} from "../../../src/util/sszBytes.js";
1619

1720
describe("attestation SSZ serialized picking", () => {
18-
const testCases: phase0.Attestation[] = [
21+
const testCases: allForks.Attestation[] = [
1922
ssz.phase0.Attestation.defaultValue(),
2023
attestationFromValues(
2124
4_000_000,
2225
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
2326
200_00,
2427
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
2528
),
29+
ssz.electra.Attestation.defaultValue(),
30+
{
31+
...attestationFromValues(
32+
4_000_000,
33+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
34+
200_00,
35+
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
36+
),
37+
committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 3),
38+
},
2639
];
2740

2841
for (const [i, attestation] of testCases.entries()) {
2942
it(`attestation ${i}`, () => {
30-
const bytes = ssz.phase0.Attestation.serialize(attestation);
43+
const isElectra = isElectraAttestation(attestation);
44+
const bytes = isElectra
45+
? ssz.electra.Attestation.serialize(attestation)
46+
: ssz.phase0.Attestation.serialize(attestation);
3147

3248
expect(getSlotFromAttestationSerialized(bytes)).toBe(attestation.data.slot);
3349
expect(getBlockRootFromAttestationSerialized(bytes)).toBe(toHex(attestation.data.beaconBlockRoot));
34-
expect(getAggregationBitsFromAttestationSerialized(bytes)?.toBoolArray()).toEqual(
35-
attestation.aggregationBits.toBoolArray()
36-
);
37-
expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature);
50+
51+
if (isElectra) {
52+
expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual(
53+
attestation.aggregationBits.toBoolArray()
54+
);
55+
expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual(attestation.committeeBits);
56+
expect(getSignatureFromAttestationSerialized(ForkName.electra, bytes)).toEqual(attestation.signature);
57+
} else {
58+
expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual(
59+
attestation.aggregationBits.toBoolArray()
60+
);
61+
expect(getSignatureFromAttestationSerialized(ForkName.phase0, bytes)).toEqual(attestation.signature);
62+
}
3863

3964
const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data);
4065
expect(getAttDataBase64FromAttestationSerialized(bytes)).toBe(Buffer.from(attDataBase64).toString("base64"));
@@ -65,14 +90,16 @@ describe("attestation SSZ serialized picking", () => {
6590
it("getAggregateionBitsFromAttestationSerialized - invalid data", () => {
6691
const invalidAggregationBitsDataSizes = [0, 4, 100, 128, 227];
6792
for (const size of invalidAggregationBitsDataSizes) {
68-
expect(getAggregationBitsFromAttestationSerialized(Buffer.alloc(size))).toBeNull();
93+
expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull();
94+
expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull();
6995
}
7096
});
7197

7298
it("getSignatureFromAttestationSerialized - invalid data", () => {
7399
const invalidSignatureDataSizes = [0, 4, 100, 128, 227];
74100
for (const size of invalidSignatureDataSizes) {
75-
expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull();
101+
expect(getSignatureFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull();
102+
expect(getSignatureFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull();
76103
}
77104
});
78105
});
@@ -86,6 +113,7 @@ describe("aggregateAndProof SSZ serialized picking", () => {
86113
200_00,
87114
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
88115
),
116+
ssz.electra.SignedAggregateAndProof.defaultValue(),
89117
];
90118

91119
for (const [i, signedAggregateAndProof] of testCases.entries()) {

packages/params/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export const {
9696

9797
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD,
9898
MAX_EXECUTION_LAYER_EXITS,
99+
MAX_ATTESTER_SLASHINGS_ELECTRA,
100+
MAX_ATTESTATIONS_ELECTRA,
99101
} = activePreset;
100102

101103
////////////

packages/params/src/presets/mainnet.ts

+2
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,6 @@ export const mainnetPreset: BeaconPreset = {
122122
// ELECTRA
123123
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192,
124124
MAX_EXECUTION_LAYER_EXITS: 16,
125+
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
126+
MAX_ATTESTATIONS_ELECTRA: 8,
125127
};

packages/params/src/presets/minimal.ts

+2
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,6 @@ export const minimalPreset: BeaconPreset = {
123123
// ELECTRA
124124
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4,
125125
MAX_EXECUTION_LAYER_EXITS: 16,
126+
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
127+
MAX_ATTESTATIONS_ELECTRA: 8,
126128
};

packages/params/src/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export type BeaconPreset = {
8686
// ELECTRA
8787
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number;
8888
MAX_EXECUTION_LAYER_EXITS: number;
89+
MAX_ATTESTER_SLASHINGS_ELECTRA: number;
90+
MAX_ATTESTATIONS_ELECTRA: number;
8991
};
9092

9193
/**
@@ -175,6 +177,8 @@ export const beaconPresetTypes: BeaconPresetTypes = {
175177
// ELECTRA
176178
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: "number",
177179
MAX_EXECUTION_LAYER_EXITS: "number",
180+
MAX_ATTESTER_SLASHINGS_ELECTRA: "number",
181+
MAX_ATTESTATIONS_ELECTRA: "number",
178182
};
179183

180184
type BeaconPresetTypes = {

packages/types/src/allForks/sszTypes.ts

+18
Original file line numberDiff line numberDiff line change
@@ -16,41 +16,59 @@ export const allForks = {
1616
SignedBeaconBlock: phase0.SignedBeaconBlock,
1717
BeaconState: phase0.BeaconState,
1818
Metadata: phase0.Metadata,
19+
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
20+
Attestation: phase0.Attestation,
21+
AttesterSlashing: phase0.AttesterSlashing,
1922
},
2023
altair: {
2124
BeaconBlockBody: altair.BeaconBlockBody,
2225
BeaconBlock: altair.BeaconBlock,
2326
SignedBeaconBlock: altair.SignedBeaconBlock,
2427
BeaconState: altair.BeaconState,
2528
Metadata: altair.Metadata,
29+
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
30+
Attestation: phase0.Attestation,
31+
AttesterSlashing: phase0.AttesterSlashing,
2632
},
2733
bellatrix: {
2834
BeaconBlockBody: bellatrix.BeaconBlockBody,
2935
BeaconBlock: bellatrix.BeaconBlock,
3036
SignedBeaconBlock: bellatrix.SignedBeaconBlock,
3137
BeaconState: bellatrix.BeaconState,
3238
Metadata: altair.Metadata,
39+
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
40+
Attestation: phase0.Attestation,
41+
AttesterSlashing: phase0.AttesterSlashing,
3342
},
3443
capella: {
3544
BeaconBlockBody: capella.BeaconBlockBody,
3645
BeaconBlock: capella.BeaconBlock,
3746
SignedBeaconBlock: capella.SignedBeaconBlock,
3847
BeaconState: capella.BeaconState,
3948
Metadata: altair.Metadata,
49+
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
50+
Attestation: phase0.Attestation,
51+
AttesterSlashing: phase0.AttesterSlashing,
4052
},
4153
deneb: {
4254
BeaconBlockBody: deneb.BeaconBlockBody,
4355
BeaconBlock: deneb.BeaconBlock,
4456
SignedBeaconBlock: deneb.SignedBeaconBlock,
4557
BeaconState: deneb.BeaconState,
4658
Metadata: altair.Metadata,
59+
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
60+
Attestation: phase0.Attestation,
61+
AttesterSlashing: phase0.AttesterSlashing,
4762
},
4863
electra: {
4964
BeaconBlockBody: electra.BeaconBlockBody,
5065
BeaconBlock: electra.BeaconBlock,
5166
SignedBeaconBlock: electra.SignedBeaconBlock,
5267
BeaconState: electra.BeaconState,
5368
Metadata: altair.Metadata,
69+
SignedAggregateAndProof: electra.SignedAggregateAndProof,
70+
Attestation: electra.Attestation,
71+
AttesterSlashing: electra.AttesterSlashing,
5472
},
5573
};
5674

packages/types/src/allForks/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export type BeaconState =
4343
| capella.BeaconState
4444
| deneb.BeaconState
4545
| electra.BeaconState;
46+
export type Attestation = phase0.Attestation | electra.Attestation;
47+
export type AggregateAndProof = phase0.AggregateAndProof | electra.AggregateAndProof;
48+
export type SignedAggregateAndProof = phase0.SignedAggregateAndProof | electra.SignedAggregateAndProof;
49+
export type IndexedAttestation = phase0.IndexedAttestation | electra.IndexedAttestation;
50+
export type IndexedAttestationBigint = phase0.IndexedAttestationBigint | electra.IndexedAttestationBigint;
51+
export type AttesterSlashing = phase0.AttesterSlashing | electra.AttesterSlashing;
4652
export type Metadata = phase0.Metadata | altair.Metadata;
4753

4854
// For easy reference in the assemble block for building payloads

0 commit comments

Comments
 (0)