@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
333 lines • 13.7 kB
JavaScript
import { BitArray, deserializeUint8ArrayBitListFromBytes } from "@chainsafe/ssz";
import { BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB, ForkSeq, MAX_COMMITTEES_PER_SLOT, isForkPostElectra, } from "@lodestar/params";
// 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
// signature: BLSSignature - 96
// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT]
// electra
// class SingleAttestation(Container):
// committeeIndex: CommitteeIndex - data 8
// attesterIndex: ValidatorIndex - data 8
// data: AttestationData - data 128
// signature: BLSSignature - data 96
//
// for all forks
// class AttestationData(Container): 128 bytes fixed size
// slot: Slot - data 8
// index: CommitteeIndex - data 8
// beacon_block_root: Root - data 32
// source: Checkpoint - data 40
// target: Checkpoint - data 40
const VARIABLE_FIELD_OFFSET = 4;
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;
const SINGLE_ATTESTATION_ATTDATA_OFFSET = 8 + 8;
const SINGLE_ATTESTATION_SLOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET;
const SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET = 0;
const SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET = 8;
const SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + 8 + 8;
const SINGLE_ATTESTATION_SIGNATURE_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + ATTESTATION_DATA_SIZE;
const SINGLE_ATTESTATION_SIZE = SINGLE_ATTESTATION_SIGNATURE_OFFSET + SIGNATURE_SIZE;
// shared Buffers to convert bytes to hex/base64
const blockRootBuf = Buffer.alloc(ROOT_SIZE);
const attDataBuf = Buffer.alloc(ATTESTATION_DATA_SIZE);
const committeeBitsDataBuf = Buffer.alloc(COMMITTEE_BITS_SIZE);
/**
* Extract slot from attestation serialized bytes.
* Return null if data is not long enough to extract slot.
*/
export function getSlotFromAttestationSerialized(data) {
if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE) {
return null;
}
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(data) {
if (data.length < ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE) {
return null;
}
blockRootBuf.set(data.subarray(ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE));
return "0x" + blockRootBuf.toString("hex");
}
/**
* Extract attestation data base64 from all forks' attestation serialized bytes.
* Return null if data is not long enough to extract attestation data.
*/
export function getAttDataFromAttestationSerialized(data) {
if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE) {
return null;
}
// base64 is a bit efficient than hex
attDataBuf.set(data.subarray(VARIABLE_FIELD_OFFSET, VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE));
return attDataBuf.toString("base64");
}
/**
* Extract AttDataBase64 from `beacon_attestation` gossip message serialized bytes.
* This is used for GossipQueue.
*/
export function getBeaconAttestationGossipIndex(fork, data) {
return ForkSeq[fork] >= ForkSeq.electra
? getAttDataFromSingleAttestationSerialized(data)
: getAttDataFromAttestationSerialized(data);
}
/**
* Extract slot from `beacon_attestation` gossip message serialized bytes.
*/
export function getSlotFromBeaconAttestationSerialized(fork, data) {
return ForkSeq[fork] >= ForkSeq.electra
? getSlotFromSingleAttestationSerialized(data)
: getSlotFromAttestationSerialized(data);
}
/**
* Extract block root from `beacon_attestation` gossip message serialized bytes.
*/
export function getBlockRootFromBeaconAttestationSerialized(fork, data) {
return ForkSeq[fork] >= ForkSeq.electra
? getBlockRootFromSingleAttestationSerialized(data)
: getBlockRootFromAttestationSerialized(data);
}
/**
* Extract aggregation bits from attestation serialized bytes.
* Return null if data is not long enough to extract aggregation bits.
* Pre-electra attestation only
*/
export function getAggregationBitsFromAttestationSerialized(data) {
const aggregationBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;
if (data.length < aggregationBitsStartIndex) {
return null;
}
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) {
const signatureStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE;
if (data.length < signatureStartIndex + SIGNATURE_SIZE) {
return null;
}
return data.subarray(signatureStartIndex, signatureStartIndex + SIGNATURE_SIZE);
}
/**
* Extract slot from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract slot.
*/
export function getSlotFromSingleAttestationSerialized(data) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
return getSlotFromOffset(data, SINGLE_ATTESTATION_SLOT_OFFSET);
}
/**
* Extract committee index from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract slot.
*/
export function getCommitteeIndexFromSingleAttestationSerialized(fork, data) {
if (isForkPostElectra(fork)) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
return getIndexFromOffset(data, SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET);
}
if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE + COMMITTEE_INDEX_SIZE) {
return null;
}
return getIndexFromOffset(data, VARIABLE_FIELD_OFFSET + SLOT_SIZE);
}
/**
* Extract attester index from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract index.
*/
export function getAttesterIndexFromSingleAttestationSerialized(data) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
return getIndexFromOffset(data, SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET);
}
/**
* Extract block root from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract block root.
*/
export function getBlockRootFromSingleAttestationSerialized(data) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
blockRootBuf.set(data.subarray(SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE));
return `0x${blockRootBuf.toString("hex")}`;
}
/**
* Extract attestation data base64 from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract attestation data.
*/
export function getAttDataFromSingleAttestationSerialized(data) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
// base64 is a bit efficient than hex
attDataBuf.set(data.subarray(SINGLE_ATTESTATION_ATTDATA_OFFSET, SINGLE_ATTESTATION_ATTDATA_OFFSET + ATTESTATION_DATA_SIZE));
return attDataBuf.toString("base64");
}
/**
* Extract signature from SingleAttestation serialized bytes.
* Return null if data is not long enough to extract signature.
*/
export function getSignatureFromSingleAttestationSerialized(data) {
if (data.length !== SINGLE_ATTESTATION_SIZE) {
return null;
}
return data.subarray(SINGLE_ATTESTATION_SIGNATURE_OFFSET, SINGLE_ATTESTATION_SIGNATURE_OFFSET + SIGNATURE_SIZE);
}
//
// class SignedAggregateAndProof(Container):
// message: AggregateAndProof - offset 4
// signature: BLSSignature - data 96
// class AggregateAndProof(Container)
// aggregatorIndex: ValidatorIndex - data 8
// aggregate: Attestation - offset 4
// selectionProof: BLSSignature - data 96
const AGGREGATE_AND_PROOF_OFFSET = 4 + 96;
const AGGREGATE_OFFSET = AGGREGATE_AND_PROOF_OFFSET + 8 + 4 + 96;
const SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET = AGGREGATE_OFFSET + VARIABLE_FIELD_OFFSET;
const SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + 8 + 8;
/**
* Extract slot from signed aggregate and proof serialized bytes
* Return null if data is not long enough to extract slot
* This works for both phase + electra
*/
export function getSlotFromSignedAggregateAndProofSerialized(data) {
if (data.length < SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + SLOT_SIZE) {
return null;
}
return getSlotFromOffset(data, SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET);
}
/**
* Extract block root from signed aggregate and proof serialized bytes
* Return null if data is not long enough to extract block root
* This works for both phase + electra
*/
export function getBlockRootFromSignedAggregateAndProofSerialized(data) {
if (data.length < SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET + ROOT_SIZE) {
return null;
}
blockRootBuf.set(data.subarray(SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET, SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET + ROOT_SIZE));
return "0x" + blockRootBuf.toString("hex");
}
/**
* Extract AttestationData base64 from SignedAggregateAndProof for electra
* Return null if data is not long enough
*/
export function getAttDataFromSignedAggregateAndProofElectra(data) {
const startIndex = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET;
const endIndex = startIndex + ATTESTATION_DATA_SIZE;
if (data.length < endIndex + SIGNATURE_SIZE + COMMITTEE_BITS_SIZE) {
return null;
}
attDataBuf.set(data.subarray(startIndex, endIndex));
return attDataBuf.toString("base64");
}
/**
* Extract CommitteeBits base64 from SignedAggregateAndProof for electra
* Return null if data is not long enough
*/
export function getCommitteeBitsFromSignedAggregateAndProofElectra(data) {
const startIndex = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE;
const endIndex = startIndex + COMMITTEE_BITS_SIZE;
if (data.length < endIndex) {
return null;
}
committeeBitsDataBuf.set(data.subarray(startIndex, endIndex));
return committeeBitsDataBuf.toString("base64");
}
/**
* Extract attestation data base64 from signed aggregate and proof serialized bytes.
* Return null if data is not long enough to extract attestation data.
*/
export function getAttDataFromSignedAggregateAndProofPhase0(data) {
if (data.length < SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE) {
return null;
}
// base64 is a bit efficient than hex
attDataBuf.set(data.subarray(SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET, SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE));
return attDataBuf.toString("base64");
}
/**
* 4 + 96 = 100
* ```
* class SignedBeaconBlock(Container):
* message: BeaconBlock [offset - 4 bytes]
* signature: BLSSignature [fixed - 96 bytes]
*
* class BeaconBlock(Container):
* slot: Slot [fixed - 8 bytes]
* proposer_index: ValidatorIndex
* parent_root: Root
* state_root: Root
* body: BeaconBlockBody
* ```
*/
const SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK = VARIABLE_FIELD_OFFSET + SIGNATURE_SIZE;
export function getSlotFromSignedBeaconBlockSerialized(data) {
if (data.length < SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK + SLOT_SIZE) {
return null;
}
return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BEACON_BLOCK);
}
/**
* class BlobSidecar(Container):
* index: BlobIndex [fixed - 8 bytes ],
* blob: Blob, BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB
* kzgCommitment: Bytes48,
* kzgProof: Bytes48,
* signedBlockHeader:
* slot: 8 bytes
*/
const SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR = 8 + BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB + 48 + 48;
export function getSlotFromBlobSidecarSerialized(data) {
if (data.length < SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR + SLOT_SIZE) {
return null;
}
return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR);
}
/**
* 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, offset) {
return checkSlotHighBytes(data, offset) ? getSlotFromOffsetTrusted(data, offset) : null;
}
/**
* Alias of `getSlotFromOffset` for readability
*/
function getIndexFromOffset(data, offset) {
return getSlotFromOffset(data, offset);
}
/**
* 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, offset) {
return (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0;
}
function checkSlotHighBytes(data, offset) {
return (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0;
}
//# sourceMappingURL=sszBytes.js.map