Skip to content

Commit 4e7661e

Browse files
twoethsg11tech
authored andcommitted
fix: get seen AttData key from SignedAggregateAndProof electra (#6802)
* fix: get seen AttData key from SignedAggregateAndProof electra * chore: revert the naming change to COMMITTEE_BITS_SIZE and add comment * fix: add toBase64() util
1 parent b53660e commit 4e7661e

File tree

3 files changed

+120
-17
lines changed

3 files changed

+120
-17
lines changed

packages/beacon-node/src/chain/validation/aggregateAndProof.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
import {IBeaconChain} from "..";
1010
import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js";
1111
import {RegenCaller} from "../regen/index.js";
12-
import {getAttDataBase64FromSignedAggregateAndProofSerialized} from "../../util/sszBytes.js";
12+
import {
13+
getSeenAttDataKeyFromSignedAggregateAndProof,
14+
} from "../../util/sszBytes.js";
1315
import {getSelectionProofSignatureSet, getAggregateAndProofSignatureSet} from "./signatureSets/index.js";
1416
import {
1517
getAttestationDataSigningRoot,
@@ -71,8 +73,10 @@ async function validateAggregateAndProof(
7173
const attData = aggregate.data;
7274
const attSlot = attData.slot;
7375

74-
const attDataBase64 = serializedData ? getAttDataBase64FromSignedAggregateAndProofSerialized(serializedData) : null;
75-
const cachedAttData = attDataBase64 ? chain.seenAttestationDatas.get(attSlot, attDataBase64) : null;
76+
const seenAttDataKey = serializedData
77+
? getSeenAttDataKeyFromSignedAggregateAndProof(ForkSeq[fork], serializedData)
78+
: null;
79+
const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, seenAttDataKey) : null;
7680

7781
let attIndex;
7882
if (ForkSeq[fork] >= ForkSeq.electra) {

packages/beacon-node/src/util/sszBytes.ts

+37-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8;
4242
const ROOT_SIZE = 32;
4343
const SLOT_SIZE = 8;
4444
const ATTESTATION_DATA_SIZE = 128;
45+
// MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte
4546
const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1);
4647
const SIGNATURE_SIZE = 96;
4748

@@ -88,7 +89,7 @@ export function getSeenAttDataKeyElectra(electraAttestationBytes: Uint8Array): A
8889
return null;
8990
}
9091

91-
return Buffer.from(electraAttestationBytes.subarray(startIndex, startIndex + seenKeyLength)).toString("base64");
92+
return toBase64(electraAttestationBytes.subarray(startIndex, startIndex + seenKeyLength));
9293
}
9394

9495
/**
@@ -171,8 +172,9 @@ const SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET = AGGREGATE_OFFSET + VARIABLE_FIELD
171172
const SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + 8 + 8;
172173

173174
/**
174-
* Extract slot from signed aggregate and proof serialized bytes.
175-
* Return null if data is not long enough to extract slot.
175+
* Extract slot from signed aggregate and proof serialized bytes
176+
* Return null if data is not long enough to extract slot
177+
* This works for both phase + electra
176178
*/
177179
export function getSlotFromSignedAggregateAndProofSerialized(data: Uint8Array): Slot | null {
178180
if (data.length < SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + SLOT_SIZE) {
@@ -183,8 +185,9 @@ export function getSlotFromSignedAggregateAndProofSerialized(data: Uint8Array):
183185
}
184186

185187
/**
186-
* Extract block root from signed aggregate and proof serialized bytes.
187-
* Return null if data is not long enough to extract block root.
188+
* Extract block root from signed aggregate and proof serialized bytes
189+
* Return null if data is not long enough to extract block root
190+
* This works for both phase + electra
188191
*/
189192
export function getBlockRootFromSignedAggregateAndProofSerialized(data: Uint8Array): BlockRootHex | null {
190193
if (data.length < SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET + ROOT_SIZE) {
@@ -199,11 +202,39 @@ export function getBlockRootFromSignedAggregateAndProofSerialized(data: Uint8Arr
199202
);
200203
}
201204

205+
/**
206+
* Extract attestation data key from SignedAggregateAndProof Uint8Array to use cached data from SeenAttestationDatas
207+
*/
208+
export function getSeenAttDataKeyFromSignedAggregateAndProof(
209+
forkSeq: ForkSeq,
210+
data: Uint8Array
211+
): SeenAttDataKey | null {
212+
return forkSeq >= ForkSeq.electra
213+
? getSeenAttDataKeyFromSignedAggregateAndProofElectra(data)
214+
: getSeenAttDataKeyFromSignedAggregateAndProofPhase0(data);
215+
}
216+
217+
/**
218+
* Extract AttestationData + CommitteeBits from SignedAggregateAndProof for electra
219+
* Return null if data is not long enough
220+
*/
221+
export function getSeenAttDataKeyFromSignedAggregateAndProofElectra(data: Uint8Array): SeenAttDataKey | null {
222+
const startIndex = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET;
223+
const endIndex = startIndex + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE;
224+
225+
if (data.length < endIndex) {
226+
return null;
227+
}
228+
229+
// base64 is a bit efficient than hex
230+
return toBase64(data.subarray(startIndex, endIndex));
231+
}
232+
202233
/**
203234
* Extract attestation data base64 from signed aggregate and proof serialized bytes.
204235
* Return null if data is not long enough to extract attestation data.
205236
*/
206-
export function getAttDataBase64FromSignedAggregateAndProofSerialized(data: Uint8Array): AttDataBase64 | null {
237+
export function getSeenAttDataKeyFromSignedAggregateAndProofPhase0(data: Uint8Array): AttDataBase64 | null {
207238
if (data.length < SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE) {
208239
return null;
209240
}

packages/beacon-node/test/unit/util/sszBytes.test.ts

+76-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {describe, it, expect} from "vitest";
22
import {BitArray} from "@chainsafe/ssz";
3-
import {allForks, deneb, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types";
3+
import {allForks, deneb, electra, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types";
44
import {fromHex, toHex} from "@lodestar/utils";
55
import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params";
66
import {
77
getSeenAttDataKeyPhase0,
8-
getAttDataBase64FromSignedAggregateAndProofSerialized,
8+
getSeenAttDataKeyFromSignedAggregateAndProofPhase0,
99
getAggregationBitsFromAttestationSerialized as getAggregationBitsFromAttestationSerialized,
1010
getBlockRootFromAttestationSerialized,
1111
getBlockRootFromSignedAggregateAndProofSerialized,
@@ -15,6 +15,7 @@ import {
1515
getSlotFromSignedBeaconBlockSerialized,
1616
getSlotFromBlobSidecarSerialized,
1717
getCommitteeBitsFromAttestationSerialized,
18+
getSeenAttDataKeyFromSignedAggregateAndProofElectra,
1819
} from "../../../src/util/sszBytes.js";
1920

2021
describe("attestation SSZ serialized picking", () => {
@@ -104,16 +105,15 @@ describe("attestation SSZ serialized picking", () => {
104105
});
105106
});
106107

107-
describe("aggregateAndProof SSZ serialized picking", () => {
108+
describe("phase0 SignedAggregateAndProof SSZ serialized picking", () => {
108109
const testCases: phase0.SignedAggregateAndProof[] = [
109110
ssz.phase0.SignedAggregateAndProof.defaultValue(),
110-
signedAggregateAndProofFromValues(
111+
phase0SignedAggregateAndProofFromValues(
111112
4_000_000,
112113
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
113114
200_00,
114115
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
115116
),
116-
ssz.electra.SignedAggregateAndProof.defaultValue(),
117117
];
118118

119119
for (const [i, signedAggregateAndProof] of testCases.entries()) {
@@ -128,7 +128,7 @@ describe("aggregateAndProof SSZ serialized picking", () => {
128128
);
129129

130130
const attDataBase64 = ssz.phase0.AttestationData.serialize(signedAggregateAndProof.message.aggregate.data);
131-
expect(getAttDataBase64FromSignedAggregateAndProofSerialized(bytes)).toBe(
131+
expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(bytes)).toBe(
132132
Buffer.from(attDataBase64).toString("base64")
133133
);
134134
});
@@ -151,7 +151,60 @@ describe("aggregateAndProof SSZ serialized picking", () => {
151151
it("getAttDataBase64FromSignedAggregateAndProofSerialized - invalid data", () => {
152152
const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 339];
153153
for (const size of invalidAttDataBase64DataSizes) {
154-
expect(getAttDataBase64FromSignedAggregateAndProofSerialized(Buffer.alloc(size))).toBeNull();
154+
expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull();
155+
}
156+
});
157+
});
158+
159+
describe("electra SignedAggregateAndProof SSZ serialized picking", () => {
160+
const testCases: electra.SignedAggregateAndProof[] = [
161+
ssz.electra.SignedAggregateAndProof.defaultValue(),
162+
electraSignedAggregateAndProofFromValues(
163+
4_000_000,
164+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
165+
200_00,
166+
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff"
167+
),
168+
];
169+
170+
for (const [i, signedAggregateAndProof] of testCases.entries()) {
171+
it(`signedAggregateAndProof ${i}`, () => {
172+
const bytes = ssz.electra.SignedAggregateAndProof.serialize(signedAggregateAndProof);
173+
174+
expect(getSlotFromSignedAggregateAndProofSerialized(bytes)).toBe(
175+
signedAggregateAndProof.message.aggregate.data.slot
176+
);
177+
expect(getBlockRootFromSignedAggregateAndProofSerialized(bytes)).toBe(
178+
toHex(signedAggregateAndProof.message.aggregate.data.beaconBlockRoot)
179+
);
180+
181+
const attDataBase64 = ssz.phase0.AttestationData.serialize(signedAggregateAndProof.message.aggregate.data);
182+
const committeeBits = ssz.electra.CommitteeBits.serialize(
183+
signedAggregateAndProof.message.aggregate.committeeBits
184+
);
185+
const seenKey = Buffer.concat([attDataBase64, committeeBits]).toString("base64");
186+
expect(getSeenAttDataKeyFromSignedAggregateAndProofElectra(bytes)).toBe(seenKey);
187+
});
188+
}
189+
190+
it("getSlotFromSignedAggregateAndProofSerialized - invalid data", () => {
191+
const invalidSlotDataSizes = [0, 4, 11];
192+
for (const size of invalidSlotDataSizes) {
193+
expect(getSlotFromSignedAggregateAndProofSerialized(Buffer.alloc(size))).toBeNull();
194+
}
195+
});
196+
197+
it("getBlockRootFromSignedAggregateAndProofSerialized - invalid data", () => {
198+
const invalidBlockRootDataSizes = [0, 4, 20, 227];
199+
for (const size of invalidBlockRootDataSizes) {
200+
expect(getBlockRootFromSignedAggregateAndProofSerialized(Buffer.alloc(size))).toBeNull();
201+
}
202+
});
203+
204+
it("getAttDataBase64FromSignedAggregateAndProofSerialized - invalid data", () => {
205+
const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 339];
206+
for (const size of invalidAttDataBase64DataSizes) {
207+
expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull();
155208
}
156209
});
157210
});
@@ -206,7 +259,7 @@ function attestationFromValues(
206259
return attestation;
207260
}
208261

209-
function signedAggregateAndProofFromValues(
262+
function phase0SignedAggregateAndProofFromValues(
210263
slot: Slot,
211264
blockRoot: RootHex,
212265
targetEpoch: Epoch,
@@ -220,6 +273,21 @@ function signedAggregateAndProofFromValues(
220273
return signedAggregateAndProof;
221274
}
222275

276+
function electraSignedAggregateAndProofFromValues(
277+
slot: Slot,
278+
blockRoot: RootHex,
279+
targetEpoch: Epoch,
280+
targetRoot: RootHex
281+
): electra.SignedAggregateAndProof {
282+
const signedAggregateAndProof = ssz.electra.SignedAggregateAndProof.defaultValue();
283+
signedAggregateAndProof.message.aggregate.data.slot = slot;
284+
signedAggregateAndProof.message.aggregate.data.beaconBlockRoot = fromHex(blockRoot);
285+
signedAggregateAndProof.message.aggregate.data.target.epoch = targetEpoch;
286+
signedAggregateAndProof.message.aggregate.data.target.root = fromHex(targetRoot);
287+
signedAggregateAndProof.message.aggregate.committeeBits = BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 1);
288+
return signedAggregateAndProof;
289+
}
290+
223291
function signedBeaconBlockFromValues(slot: Slot): phase0.SignedBeaconBlock {
224292
const signedBeaconBlock = ssz.phase0.SignedBeaconBlock.defaultValue();
225293
signedBeaconBlock.message.slot = slot;

0 commit comments

Comments
 (0)