Skip to content

Commit f1ced8b

Browse files
ensi321twoethsg11tech
committed
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 <tuyen@chainsafe.io> * Add isElectraAttestation * Update unit test * Update unit test * chore: add comments for sszBytes.ts --------- Co-authored-by: tuyennhv <tuyen@chainsafe.io> Co-authored-by: Tuyen Nguyen <vutuyen2636@gmail.com> Co-authored-by: Gajinder <develop@g11tech.io>
1 parent b89e71c commit f1ced8b

File tree

13 files changed

+233
-32
lines changed

13 files changed

+233
-32
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
/**
@@ -66,32 +82,51 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att
6682
* Extract aggregation bits from attestation serialized bytes.
6783
* Return null if data is not long enough to extract aggregation bits.
6884
*/
69-
export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
70-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
85+
export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null {
86+
const aggregationBitsStartIndex =
87+
ForkSeq[fork] >= ForkSeq.electra
88+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + SIGNATURE_SIZE
89+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;
90+
91+
if (data.length < aggregationBitsStartIndex) {
7192
return null;
7293
}
7394

74-
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(
75-
data,
76-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE,
77-
data.length
78-
);
95+
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(data, aggregationBitsStartIndex, data.length);
7996
return new BitArray(uint8Array, bitLen);
8097
}
8198

8299
/**
83100
* Extract signature from attestation serialized bytes.
84101
* Return null if data is not long enough to extract signature.
85102
*/
86-
export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null {
87-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
103+
export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null {
104+
const signatureStartIndex =
105+
ForkSeq[fork] >= ForkSeq.electra
106+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE
107+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
108+
109+
if (data.length < signatureStartIndex + SIGNATURE_SIZE) {
88110
return null;
89111
}
90112

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

97132
//

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 = {

0 commit comments

Comments
 (0)