Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add presets and ssz types for EIP-7549 #6715

Merged
merged 11 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
twoeths marked this conversation as resolved.
Show resolved Hide resolved
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 {
twoeths marked this conversation as resolved.
Show resolved Hide resolved
twoeths marked this conversation as resolved.
Show resolved Hide resolved
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(),
twoeths marked this conversation as resolved.
Show resolved Hide resolved
{
...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
Loading
Loading