Skip to content

Commit bef3eff

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 a656c69 commit bef3eff

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,15 +1,30 @@
11
import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz";
22
import {BLSSignature, RootHex, Slot} from "@lodestar/types";
3-
import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params";
3+
import {
4+
BYTES_PER_FIELD_ELEMENT,
5+
FIELD_ELEMENTS_PER_BLOB,
6+
ForkName,
7+
ForkSeq,
8+
MAX_COMMITTEES_PER_SLOT,
9+
} from "@lodestar/params";
410

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

14+
// pre-electra
815
// class Attestation(Container):
916
// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4
1017
// data: AttestationData - target data - 128
1118
// signature: BLSSignature - 96
19+
20+
// electra
21+
// class Attestation(Container):
22+
// aggregation_bits: BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - offset 4
23+
// data: AttestationData - target data - 128
24+
// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT]
25+
// signature: BLSSignature - 96
1226
//
27+
// for all forks
1328
// class AttestationData(Container): 128 bytes fixed size
1429
// slot: Slot - data 8
1530
// index: CommitteeIndex - data 8
@@ -22,6 +37,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8;
2237
const ROOT_SIZE = 32;
2338
const SLOT_SIZE = 8;
2439
const ATTESTATION_DATA_SIZE = 128;
40+
const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1);
2541
const SIGNATURE_SIZE = 96;
2642

2743
// shared Buffers to convert bytes to hex/base64
@@ -73,32 +89,51 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att
7389
* Extract aggregation bits from attestation serialized bytes.
7490
* Return null if data is not long enough to extract aggregation bits.
7591
*/
76-
export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
77-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
92+
export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null {
93+
const aggregationBitsStartIndex =
94+
ForkSeq[fork] >= ForkSeq.electra
95+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + SIGNATURE_SIZE
96+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;
97+
98+
if (data.length < aggregationBitsStartIndex) {
7899
return null;
79100
}
80101

81-
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(
82-
data,
83-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE,
84-
data.length
85-
);
102+
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(data, aggregationBitsStartIndex, data.length);
86103
return new BitArray(uint8Array, bitLen);
87104
}
88105

89106
/**
90107
* Extract signature from attestation serialized bytes.
91108
* Return null if data is not long enough to extract signature.
92109
*/
93-
export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null {
94-
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
110+
export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null {
111+
const signatureStartIndex =
112+
ForkSeq[fork] >= ForkSeq.electra
113+
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE
114+
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
115+
116+
if (data.length < signatureStartIndex + SIGNATURE_SIZE) {
95117
return null;
96118
}
97119

98-
return data.subarray(
99-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE,
100-
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE
101-
);
120+
return data.subarray(signatureStartIndex, signatureStartIndex + SIGNATURE_SIZE);
121+
}
122+
123+
/**
124+
* Extract committee bits from Electra attestation serialized bytes.
125+
* Return null if data is not long enough to extract committee bits.
126+
*/
127+
export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
128+
const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
129+
130+
if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) {
131+
return null;
132+
}
133+
134+
const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE);
135+
136+
return new BitArray(uint8Array, MAX_COMMITTEES_PER_SLOT);
102137
}
103138

104139
//

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)