From d0319a678674366d2d7523d5e0658b5f1ccfd229 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 13 Jun 2024 16:08:52 +0300 Subject: [PATCH 1/4] Move committee bits up --- packages/beacon-node/src/util/sszBytes.ts | 35 ++++++++++++++--------- packages/types/src/electra/sszTypes.ts | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index c2675670daf2..b3c4a63fc34c 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -25,8 +25,8 @@ export type AttDataCommitteeBitsBase64 = string; // 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] +// data: AttestationData - target data - 128 // signature: BLSSignature - 96 // // for all forks @@ -38,36 +38,45 @@ export type AttDataCommitteeBitsBase64 = string; // target: Checkpoint - data 40 const VARIABLE_FIELD_OFFSET = 4; -const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; +// MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte +const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); const ROOT_SIZE = 32; const SLOT_SIZE = 8; +const COMMITTEE_INDEX_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; -// MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte -const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); const SIGNATURE_SIZE = 96; /** * Extract slot from attestation serialized bytes. * Return null if data is not long enough to extract slot. */ -export function getSlotFromAttestationSerialized(data: Uint8Array): Slot | null { - if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE) { +export function getSlotFromAttestationSerialized(fork: ForkName, data: Uint8Array): Slot | null { + const slotStartIndex = + ForkSeq[fork] >= ForkSeq.electra + ? VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE + : VARIABLE_FIELD_OFFSET; + + if (data.length < slotStartIndex + SLOT_SIZE) { return null; } - return getSlotFromOffset(data, VARIABLE_FIELD_OFFSET); + return getSlotFromOffset(data, slotStartIndex); } /** * Extract block root from attestation serialized bytes. * Return null if data is not long enough to extract block root. */ -export function getBlockRootFromAttestationSerialized(data: Uint8Array): BlockRootHex | null { - if (data.length < ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE) { +export function getBlockRootFromAttestationSerialized(fork: ForkName, data: Uint8Array): BlockRootHex | null { + const blockRootStartIndex = ForkSeq[fork] >= ForkSeq.electra ? + VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE + SLOT_SIZE + COMMITTEE_INDEX_SIZE : + VARIABLE_FIELD_OFFSET + SLOT_SIZE + COMMITTEE_INDEX_SIZE; + + if (data.length < blockRootStartIndex + ROOT_SIZE) { return null; } - return toHex(data.subarray(ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE)); + return toHex(data.subarray(blockRootStartIndex, blockRootStartIndex + ROOT_SIZE)); } /** @@ -112,7 +121,7 @@ export function getSeenAttDataKeyPhase0(data: Uint8Array): AttDataBase64 | null 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 + COMMITTEE_BITS_SIZE + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; if (data.length < aggregationBitsStartIndex) { @@ -130,7 +139,7 @@ export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data 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 + COMMITTEE_BITS_SIZE + ATTESTATION_DATA_SIZE : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE; if (data.length < signatureStartIndex + SIGNATURE_SIZE) { @@ -145,7 +154,7 @@ export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint * 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; + const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET; if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) { return null; diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index b03be1697fd1..aaf5b5268893 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -61,8 +61,8 @@ export const AttestingIndices = new ListBasicType( export const Attestation = new ContainerType( { aggregationBits: AggregationBits, // Modified in ELECTRA - data: phase0Ssz.AttestationData, committeeBits: CommitteeBits, // New in ELECTRA + data: phase0Ssz.AttestationData, signature: BLSSignature, }, {typeName: "Attestation", jsonCase: "eth2"} From 814080a913a4507866d222c2cc65a3b9eb36a014 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 13 Jun 2024 17:49:46 +0300 Subject: [PATCH 2/4] move committee bits down --- packages/beacon-node/src/util/sszBytes.ts | 54 ++++++++++------------- packages/types/src/electra/sszTypes.ts | 2 +- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index b3c4a63fc34c..572e64b9b73b 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -25,9 +25,9 @@ export type AttDataCommitteeBitsBase64 = string; // electra // class Attestation(Container): // aggregation_bits: BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - offset 4 -// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] // data: AttestationData - target data - 128 // signature: BLSSignature - 96 +// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] // // for all forks // class AttestationData(Container): 128 bytes fixed size @@ -38,45 +38,36 @@ export type AttDataCommitteeBitsBase64 = string; // target: Checkpoint - data 40 const VARIABLE_FIELD_OFFSET = 4; -// MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte -const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); +const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; const ROOT_SIZE = 32; const SLOT_SIZE = 8; -const COMMITTEE_INDEX_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; +// MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte +const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); const SIGNATURE_SIZE = 96; /** * Extract slot from attestation serialized bytes. * Return null if data is not long enough to extract slot. */ -export function getSlotFromAttestationSerialized(fork: ForkName, data: Uint8Array): Slot | null { - const slotStartIndex = - ForkSeq[fork] >= ForkSeq.electra - ? VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE - : VARIABLE_FIELD_OFFSET; - - if (data.length < slotStartIndex + SLOT_SIZE) { +export function getSlotFromAttestationSerialized(data: Uint8Array): Slot | null { + if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE) { return null; } - return getSlotFromOffset(data, slotStartIndex); + return getSlotFromOffset(data, VARIABLE_FIELD_OFFSET); } /** * Extract block root from attestation serialized bytes. * Return null if data is not long enough to extract block root. */ -export function getBlockRootFromAttestationSerialized(fork: ForkName, data: Uint8Array): BlockRootHex | null { - const blockRootStartIndex = ForkSeq[fork] >= ForkSeq.electra ? - VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE + SLOT_SIZE + COMMITTEE_INDEX_SIZE : - VARIABLE_FIELD_OFFSET + SLOT_SIZE + COMMITTEE_INDEX_SIZE; - - if (data.length < blockRootStartIndex + ROOT_SIZE) { +export function getBlockRootFromAttestationSerialized(data: Uint8Array): BlockRootHex | null { + if (data.length < ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE) { return null; } - return toHex(data.subarray(blockRootStartIndex, blockRootStartIndex + ROOT_SIZE)); + return toHex(data.subarray(ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE)); } /** @@ -91,14 +82,20 @@ export function getSeenAttDataKey(forkSeq: ForkSeq, data: Uint8Array): SeenAttDa * Return null if data is not long enough to extract attestation data. */ export function getSeenAttDataKeyElectra(electraAttestationBytes: Uint8Array): AttDataCommitteeBitsBase64 | null { - const startIndex = VARIABLE_FIELD_OFFSET; - const seenKeyLength = ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE; + const attestationData = getSeenAttDataKeyPhase0(electraAttestationBytes); + + if (attestationData === null) { + return null; + } - if (electraAttestationBytes.length < startIndex + seenKeyLength) { + const committeeBits = getCommitteeBitsFromAttestationSerialized(electraAttestationBytes); + + if (committeeBits === null) { return null; } - return toBase64(electraAttestationBytes.subarray(startIndex, startIndex + seenKeyLength)); + return attestationData + toBase64(committeeBits.uint8Array); + } /** @@ -121,7 +118,7 @@ export function getSeenAttDataKeyPhase0(data: Uint8Array): AttDataBase64 | null export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null { const aggregationBitsStartIndex = ForkSeq[fork] >= ForkSeq.electra - ? VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE + ? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE + COMMITTEE_BITS_SIZE : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; if (data.length < aggregationBitsStartIndex) { @@ -136,11 +133,8 @@ export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data * Extract signature from attestation serialized bytes. * Return null if data is not long enough to extract signature. */ -export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null { - const signatureStartIndex = - ForkSeq[fork] >= ForkSeq.electra - ? VARIABLE_FIELD_OFFSET + COMMITTEE_BITS_SIZE + ATTESTATION_DATA_SIZE - : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE; +export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null { + const signatureStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE; if (data.length < signatureStartIndex + SIGNATURE_SIZE) { return null; @@ -154,7 +148,7 @@ export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint * Return null if data is not long enough to extract committee bits. */ export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null { - const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET; + const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) { return null; diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index aaf5b5268893..ab9cbbaacd80 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -61,9 +61,9 @@ export const AttestingIndices = new ListBasicType( export const Attestation = new ContainerType( { aggregationBits: AggregationBits, // Modified in ELECTRA - committeeBits: CommitteeBits, // New in ELECTRA data: phase0Ssz.AttestationData, signature: BLSSignature, + committeeBits: CommitteeBits, // New in ELECTRA }, {typeName: "Attestation", jsonCase: "eth2"} ); From fff9b6e0f42d4eac3629307e51c41c0ba33f6139 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 13 Jun 2024 17:55:22 +0300 Subject: [PATCH 3/4] lint --- packages/beacon-node/src/chain/validation/attestation.ts | 2 +- packages/beacon-node/src/util/sszBytes.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index 0141eaafbb06..c44a8b7913c9 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -456,7 +456,7 @@ async function validateGossipAttestationNoSignatureCheck( let attDataRootHex: RootHex; const signature = attestationOrCache.attestation ? attestationOrCache.attestation.signature - : getSignatureFromAttestationSerialized(fork, attestationOrCache.serializedData); + : getSignatureFromAttestationSerialized(attestationOrCache.serializedData); if (signature === null) { throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 572e64b9b73b..1e505e83c977 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -83,7 +83,7 @@ export function getSeenAttDataKey(forkSeq: ForkSeq, data: Uint8Array): SeenAttDa */ export function getSeenAttDataKeyElectra(electraAttestationBytes: Uint8Array): AttDataCommitteeBitsBase64 | null { const attestationData = getSeenAttDataKeyPhase0(electraAttestationBytes); - + if (attestationData === null) { return null; } @@ -95,7 +95,6 @@ export function getSeenAttDataKeyElectra(electraAttestationBytes: Uint8Array): A } return attestationData + toBase64(committeeBits.uint8Array); - } /** From 6cf806a931881af64dcb19a3278865697414c789 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 13 Jun 2024 18:07:58 +0300 Subject: [PATCH 4/4] check-types --- packages/beacon-node/test/unit/util/sszBytes.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 03e4a7f7a71d..5edd9fb96fb1 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -54,12 +54,12 @@ describe("attestation SSZ serialized picking", () => { attestation.aggregationBits.toBoolArray() ); expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual(attestation.committeeBits); - expect(getSignatureFromAttestationSerialized(ForkName.electra, bytes)).toEqual(attestation.signature); + expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); } else { expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual( attestation.aggregationBits.toBoolArray() ); - expect(getSignatureFromAttestationSerialized(ForkName.phase0, bytes)).toEqual(attestation.signature); + expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); } const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data); @@ -99,8 +99,8 @@ describe("attestation SSZ serialized picking", () => { it("getSignatureFromAttestationSerialized - invalid data", () => { const invalidSignatureDataSizes = [0, 4, 100, 128, 227]; for (const size of invalidSignatureDataSizes) { - expect(getSignatureFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull(); - expect(getSignatureFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull(); + expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); + expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); } }); });