diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 802b9a266ab1..f87b899e9591 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -198,11 +198,24 @@ export function getSlotFromBlobSidecarSerialized(data: Uint8Array): Slot | null return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR); } -function getSlotFromOffset(data: Uint8Array, offset: number): Slot { - // TODO: Optimize - const dv = new DataView(data.buffer, data.byteOffset, data.byteLength); - // Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis - return dv.getUint32(offset, true); +/** + * Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis + * + * If the high bytes are not zero, return null + */ +function getSlotFromOffset(data: Uint8Array, offset: number): Slot | null { + return checkSlotHighBytes(data, offset) ? getSlotFromOffsetTrusted(data, offset) : null; +} + +/** + * Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis + */ +function getSlotFromOffsetTrusted(data: Uint8Array, offset: number): Slot { + return (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0; +} + +function checkSlotHighBytes(data: Uint8Array, offset: number): boolean { + return (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; } function toBase64(data: Uint8Array): string { diff --git a/packages/beacon-node/test/perf/util/dataview.test.ts b/packages/beacon-node/test/perf/util/dataview.test.ts new file mode 100644 index 000000000000..e0f28a5079e3 --- /dev/null +++ b/packages/beacon-node/test/perf/util/dataview.test.ts @@ -0,0 +1,29 @@ +import {itBench} from "@dapplion/benchmark"; + +describe("dataview", function () { + const data = Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + + itBench({ + id: "getUint32 - dataview", + beforeEach: () => { + return 0; + }, + fn: (offset) => { + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + view.getUint32(offset, true); + }, + }); + + itBench({ + id: "getUint32 - manual", + beforeEach: () => { + return 0; + }, + fn: (offset) => { + // check high bytes for non-zero values + (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; + // create the uint32 + (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0; + }, + }); +}); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index bb5fc67a7ce6..4285f4ca88b5 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -4,7 +4,7 @@ import {fromHex, toHex} from "@lodestar/utils"; import { getAttDataBase64FromAttestationSerialized, getAttDataBase64FromSignedAggregateAndProofSerialized, - getAggregationBitsFromAttestationSerialized as getAggregationBitsFromAttestationSerialized, + getAggregationBitsFromAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, getSlotFromAttestationSerialized, @@ -126,6 +126,15 @@ describe("aggregateAndProof SSZ serialized picking", () => { expect(getAttDataBase64FromSignedAggregateAndProofSerialized(Buffer.alloc(size))).toBeNull(); } }); + it("getSlotFromSignedAggregateAndProofSerialized - invalid data - large slots", () => { + const serialize = (slot: Slot): Uint8Array => { + const s = ssz.phase0.SignedAggregateAndProof.defaultValue(); + s.message.aggregate.data.slot = slot; + return ssz.phase0.SignedAggregateAndProof.serialize(s); + }; + expect(getSlotFromSignedAggregateAndProofSerialized(serialize(0xffffffff))).toBe(0xffffffff); + expect(getSlotFromSignedAggregateAndProofSerialized(serialize(0x0100000000))).toBeNull(); + }); }); describe("signedBeaconBlock SSZ serialized picking", () => {