Skip to content

Commit

Permalink
feat: add presets and ssz types for EIP-7549 (#6715)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed May 4, 2024
1 parent e5be557 commit b19abdf
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 30 deletions.
4 changes: 2 additions & 2 deletions packages/beacon-node/src/chain/validation/attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ async function validateGossipAttestationNoSignatureCheck(
// > TODO: Do this check **before** getting the target state but don't recompute zipIndexes
const aggregationBits = attestationOrCache.attestation
? attestationOrCache.attestation.aggregationBits
: getAggregationBitsFromAttestationSerialized(attestationOrCache.serializedData);
: getAggregationBitsFromAttestationSerialized(fork, attestationOrCache.serializedData);
if (aggregationBits === null) {
throw new AttestationError(GossipAction.REJECT, {
code: AttestationErrorCode.INVALID_SERIALIZED_BYTES,
Expand Down Expand Up @@ -414,7 +414,7 @@ async function validateGossipAttestationNoSignatureCheck(
let attDataRootHex: RootHex;
const signature = attestationOrCache.attestation
? attestationOrCache.attestation.signature
: getSignatureFromAttestationSerialized(attestationOrCache.serializedData);
: getSignatureFromAttestationSerialized(fork, attestationOrCache.serializedData);
if (signature === null) {
throw new AttestationError(GossipAction.REJECT, {
code: AttestationErrorCode.INVALID_SERIALIZED_BYTES,
Expand Down
63 changes: 49 additions & 14 deletions packages/beacon-node/src/util/sszBytes.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz";
import {BLSSignature, RootHex, Slot} from "@lodestar/types";
import {toHex} from "@lodestar/utils";
import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params";
import {
BYTES_PER_FIELD_ELEMENT,
FIELD_ELEMENTS_PER_BLOB,
ForkName,
ForkSeq,
MAX_COMMITTEES_PER_SLOT,
} from "@lodestar/params";

export type BlockRootHex = RootHex;
export type AttDataBase64 = string;

// pre-electra
// class Attestation(Container):
// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4
// data: AttestationData - target data - 128
// signature: BLSSignature - 96

// electra
// class Attestation(Container):
// aggregation_bits: BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - offset 4
// data: AttestationData - target data - 128
// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT]
// signature: BLSSignature - 96
//
// for all forks
// class AttestationData(Container): 128 bytes fixed size
// slot: Slot - data 8
// index: CommitteeIndex - data 8
Expand All @@ -23,6 +38,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8;
const ROOT_SIZE = 32;
const SLOT_SIZE = 8;
const ATTESTATION_DATA_SIZE = 128;
const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1);
const SIGNATURE_SIZE = 96;

/**
Expand Down Expand Up @@ -68,32 +84,51 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att
* Extract aggregation bits from attestation serialized bytes.
* Return null if data is not long enough to extract aggregation bits.
*/
export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null {
const aggregationBitsStartIndex =
ForkSeq[fork] >= ForkSeq.electra
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + SIGNATURE_SIZE
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;

if (data.length < aggregationBitsStartIndex) {
return null;
}

const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(
data,
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE,
data.length
);
const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(data, aggregationBitsStartIndex, data.length);
return new BitArray(uint8Array, bitLen);
}

/**
* Extract signature from attestation serialized bytes.
* Return null if data is not long enough to extract signature.
*/
export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null {
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) {
export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null {
const signatureStartIndex =
ForkSeq[fork] >= ForkSeq.electra
? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE
: VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;

if (data.length < signatureStartIndex + SIGNATURE_SIZE) {
return null;
}

return data.subarray(
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE,
VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE
);
return data.subarray(signatureStartIndex, signatureStartIndex + SIGNATURE_SIZE);
}

/**
* Extract committee bits from Electra attestation serialized bytes.
* Return null if data is not long enough to extract committee bits.
*/
export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null {
const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;

if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) {
return null;
}

const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE);

return new BitArray(uint8Array, MAX_COMMITTEES_PER_SLOT);
}

//
Expand Down
46 changes: 37 additions & 9 deletions packages/beacon-node/test/unit/util/sszBytes.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {describe, it, expect} from "vitest";
import {deneb, Epoch, phase0, RootHex, Slot, ssz} from "@lodestar/types";
import {BitArray} from "@chainsafe/ssz";
import {allForks, deneb, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types";
import {fromHex, toHex} from "@lodestar/utils";
import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params";
import {
getAttDataBase64FromAttestationSerialized,
getAttDataBase64FromSignedAggregateAndProofSerialized,
Expand All @@ -12,29 +14,52 @@ import {
getSignatureFromAttestationSerialized,
getSlotFromSignedBeaconBlockSerialized,
getSlotFromBlobSidecarSerialized,
getCommitteeBitsFromAttestationSerialized,
} from "../../../src/util/sszBytes.js";

describe("attestation SSZ serialized picking", () => {
const testCases: phase0.Attestation[] = [
const testCases: allForks.Attestation[] = [
ssz.phase0.Attestation.defaultValue(),
attestationFromValues(
4_000_000,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
200_00,
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
),
ssz.electra.Attestation.defaultValue(),
{
...attestationFromValues(
4_000_000,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
200_00,
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
),
committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 3),
},
];

for (const [i, attestation] of testCases.entries()) {
it(`attestation ${i}`, () => {
const bytes = ssz.phase0.Attestation.serialize(attestation);
const isElectra = isElectraAttestation(attestation);
const bytes = isElectra
? ssz.electra.Attestation.serialize(attestation)
: ssz.phase0.Attestation.serialize(attestation);

expect(getSlotFromAttestationSerialized(bytes)).toBe(attestation.data.slot);
expect(getBlockRootFromAttestationSerialized(bytes)).toBe(toHex(attestation.data.beaconBlockRoot));
expect(getAggregationBitsFromAttestationSerialized(bytes)?.toBoolArray()).toEqual(
attestation.aggregationBits.toBoolArray()
);
expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature);

if (isElectra) {
expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual(
attestation.aggregationBits.toBoolArray()
);
expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual(attestation.committeeBits);
expect(getSignatureFromAttestationSerialized(ForkName.electra, bytes)).toEqual(attestation.signature);
} else {
expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual(
attestation.aggregationBits.toBoolArray()
);
expect(getSignatureFromAttestationSerialized(ForkName.phase0, bytes)).toEqual(attestation.signature);
}

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

it("getSignatureFromAttestationSerialized - invalid data", () => {
const invalidSignatureDataSizes = [0, 4, 100, 128, 227];
for (const size of invalidSignatureDataSizes) {
expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull();
expect(getSignatureFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull();
expect(getSignatureFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull();
}
});
});
Expand All @@ -86,6 +113,7 @@ describe("aggregateAndProof SSZ serialized picking", () => {
200_00,
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
),
ssz.electra.SignedAggregateAndProof.defaultValue(),
];

for (const [i, signedAggregateAndProof] of testCases.entries()) {
Expand Down
2 changes: 2 additions & 0 deletions packages/params/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export const {

MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD,
MAX_EXECUTION_LAYER_EXITS,
MAX_ATTESTER_SLASHINGS_ELECTRA,
MAX_ATTESTATIONS_ELECTRA,
} = activePreset;

////////////
Expand Down
2 changes: 2 additions & 0 deletions packages/params/src/presets/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@ export const mainnetPreset: BeaconPreset = {
// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192,
MAX_EXECUTION_LAYER_EXITS: 16,
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
MAX_ATTESTATIONS_ELECTRA: 8,
};
2 changes: 2 additions & 0 deletions packages/params/src/presets/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,6 @@ export const minimalPreset: BeaconPreset = {
// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4,
MAX_EXECUTION_LAYER_EXITS: 16,
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
MAX_ATTESTATIONS_ELECTRA: 8,
};
4 changes: 4 additions & 0 deletions packages/params/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export type BeaconPreset = {
// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number;
MAX_EXECUTION_LAYER_EXITS: number;
MAX_ATTESTER_SLASHINGS_ELECTRA: number;
MAX_ATTESTATIONS_ELECTRA: number;
};

/**
Expand Down Expand Up @@ -175,6 +177,8 @@ export const beaconPresetTypes: BeaconPresetTypes = {
// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: "number",
MAX_EXECUTION_LAYER_EXITS: "number",
MAX_ATTESTER_SLASHINGS_ELECTRA: "number",
MAX_ATTESTATIONS_ELECTRA: "number",
};

type BeaconPresetTypes = {
Expand Down
18 changes: 18 additions & 0 deletions packages/types/src/allForks/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,59 @@ export const allForks = {
SignedBeaconBlock: phase0.SignedBeaconBlock,
BeaconState: phase0.BeaconState,
Metadata: phase0.Metadata,
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
Attestation: phase0.Attestation,
AttesterSlashing: phase0.AttesterSlashing,
},
altair: {
BeaconBlockBody: altair.BeaconBlockBody,
BeaconBlock: altair.BeaconBlock,
SignedBeaconBlock: altair.SignedBeaconBlock,
BeaconState: altair.BeaconState,
Metadata: altair.Metadata,
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
Attestation: phase0.Attestation,
AttesterSlashing: phase0.AttesterSlashing,
},
bellatrix: {
BeaconBlockBody: bellatrix.BeaconBlockBody,
BeaconBlock: bellatrix.BeaconBlock,
SignedBeaconBlock: bellatrix.SignedBeaconBlock,
BeaconState: bellatrix.BeaconState,
Metadata: altair.Metadata,
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
Attestation: phase0.Attestation,
AttesterSlashing: phase0.AttesterSlashing,
},
capella: {
BeaconBlockBody: capella.BeaconBlockBody,
BeaconBlock: capella.BeaconBlock,
SignedBeaconBlock: capella.SignedBeaconBlock,
BeaconState: capella.BeaconState,
Metadata: altair.Metadata,
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
Attestation: phase0.Attestation,
AttesterSlashing: phase0.AttesterSlashing,
},
deneb: {
BeaconBlockBody: deneb.BeaconBlockBody,
BeaconBlock: deneb.BeaconBlock,
SignedBeaconBlock: deneb.SignedBeaconBlock,
BeaconState: deneb.BeaconState,
Metadata: altair.Metadata,
SignedAggregateAndProof: phase0.SignedAggregateAndProof,
Attestation: phase0.Attestation,
AttesterSlashing: phase0.AttesterSlashing,
},
electra: {
BeaconBlockBody: electra.BeaconBlockBody,
BeaconBlock: electra.BeaconBlock,
SignedBeaconBlock: electra.SignedBeaconBlock,
BeaconState: electra.BeaconState,
Metadata: altair.Metadata,
SignedAggregateAndProof: electra.SignedAggregateAndProof,
Attestation: electra.Attestation,
AttesterSlashing: electra.AttesterSlashing,
},
};

Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/allForks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export type BeaconState =
| capella.BeaconState
| deneb.BeaconState
| electra.BeaconState;
export type Attestation = phase0.Attestation | electra.Attestation;
export type AggregateAndProof = phase0.AggregateAndProof | electra.AggregateAndProof;
export type SignedAggregateAndProof = phase0.SignedAggregateAndProof | electra.SignedAggregateAndProof;
export type IndexedAttestation = phase0.IndexedAttestation | electra.IndexedAttestation;
export type IndexedAttestationBigint = phase0.IndexedAttestationBigint | electra.IndexedAttestationBigint;
export type AttesterSlashing = phase0.AttesterSlashing | electra.AttesterSlashing;
export type Metadata = phase0.Metadata | altair.Metadata;

// For easy reference in the assemble block for building payloads
Expand Down

0 comments on commit b19abdf

Please sign in to comment.