UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

170 lines (142 loc) 5.73 kB
import {ChainForkConfig} from "@lodestar/config"; import {Db, FilterOptions, KeyValue, Repository} from "@lodestar/db"; import {Root, SignedBeaconBlock, Slot, ssz} from "@lodestar/types"; import {bytesToInt} from "@lodestar/utils"; import {getSignedBlockTypeFromBytes} from "../../util/multifork.js"; import {Bucket, getBucketNameByValue} from "../buckets.js"; import { deleteParentRootIndex, deleteRootIndex, getParentRootIndex, getRootIndex, storeParentRootIndex, storeRootIndex, } from "./blockArchiveIndex.js"; export interface BlockFilterOptions extends FilterOptions<Slot> { step?: number; } export type BlockArchiveBatchPutBinaryItem = KeyValue<Slot, Uint8Array> & { slot: Slot; blockRoot: Root; parentRoot: Root; }; /** * Stores finalized blocks. Block slot is identifier. */ export class BlockArchiveRepository extends Repository<Slot, SignedBeaconBlock> { constructor(config: ChainForkConfig, db: Db) { const bucket = Bucket.allForks_blockArchive; const type = ssz.phase0.SignedBeaconBlock; // Pick some type but won't be used super(config, db, bucket, type, getBucketNameByValue(bucket)); } // Overrides for multi-fork encodeValue(value: SignedBeaconBlock): Uint8Array { return this.config.getForkTypes(value.message.slot).SignedBeaconBlock.serialize(value); } decodeValue(data: Uint8Array): SignedBeaconBlock { return getSignedBlockTypeFromBytes(this.config, data).deserialize(data); } // Handle key as slot getId(value: SignedBeaconBlock): Slot { return value.message.slot; } decodeKey(data: Uint8Array): number { return bytesToInt(super.decodeKey(data) as unknown as Uint8Array, "be"); } // Overrides to index async put(key: Slot, value: SignedBeaconBlock): Promise<void> { const blockRoot = this.config.getForkTypes(value.message.slot).BeaconBlock.hashTreeRoot(value.message); const slot = value.message.slot; await Promise.all([ super.put(key, value), storeRootIndex(this.db, slot, blockRoot), storeParentRootIndex(this.db, slot, value.message.parentRoot), ]); } async batchPut(items: KeyValue<Slot, SignedBeaconBlock>[]): Promise<void> { await Promise.all([ super.batchPut(items), Array.from(items).map((item) => { const slot = item.value.message.slot; const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(item.value.message); return storeRootIndex(this.db, slot, blockRoot); }), Array.from(items).map((item) => { const slot = item.value.message.slot; const parentRoot = item.value.message.parentRoot; return storeParentRootIndex(this.db, slot, parentRoot); }), ]); } async batchPutBinary(items: BlockArchiveBatchPutBinaryItem[]): Promise<void> { await Promise.all([ super.batchPutBinary(items), Array.from(items).map((item) => storeRootIndex(this.db, item.slot, item.blockRoot)), Array.from(items).map((item) => storeParentRootIndex(this.db, item.slot, item.parentRoot)), ]); } async remove(value: SignedBeaconBlock): Promise<void> { await Promise.all([ super.remove(value), deleteRootIndex(this.db, this.config.getForkTypes(value.message.slot).SignedBeaconBlock, value), deleteParentRootIndex(this.db, value), ]); } async batchRemove(values: SignedBeaconBlock[]): Promise<void> { await Promise.all([ super.batchRemove(values), Array.from(values).map((value) => deleteRootIndex(this.db, this.config.getForkTypes(value.message.slot).SignedBeaconBlock, value) ), Array.from(values).map((value) => deleteParentRootIndex(this.db, value)), ]); } async *valuesStream(opts?: BlockFilterOptions): AsyncIterable<SignedBeaconBlock> { const firstSlot = this.getFirstSlot(opts); const valuesStream = super.valuesStream(opts); const step = opts?.step ?? 1; for await (const value of valuesStream) { if ((value.message.slot - firstSlot) % step === 0) { yield value; } } } async values(opts?: BlockFilterOptions): Promise<SignedBeaconBlock[]> { return await Array.fromAsync(this.valuesStream(opts)); } // INDEX async getByRoot(root: Root): Promise<SignedBeaconBlock | null> { const slot = await this.getSlotByRoot(root); return slot !== null ? this.get(slot) : null; } async getBinaryEntryByRoot(root: Root): Promise<KeyValue<Slot, Buffer> | null> { const slot = await this.getSlotByRoot(root); return slot !== null ? ({key: slot, value: await this.getBinary(slot)} as KeyValue<Slot, Buffer>) : null; } async getByParentRoot(root: Root): Promise<SignedBeaconBlock | null> { const slot = await this.getSlotByParentRoot(root); return slot !== null ? this.get(slot) : null; } async getSlotByRoot(root: Root): Promise<Slot | null> { return this.parseSlot(await getRootIndex(this.db, root)); } async getSlotByParentRoot(root: Root): Promise<Slot | null> { return this.parseSlot(await getParentRootIndex(this.db, root)); } private parseSlot(slotBytes: Uint8Array | null): Slot | null { if (!slotBytes) return null; const slot = bytesToInt(slotBytes, "be"); // TODO: Is this necessary? How can bytesToInt return a non-integer? return Number.isInteger(slot) ? slot : null; } private getFirstSlot(opts?: BlockFilterOptions): Slot { const dbFilterOpts = this.dbFilterOptions(opts); const firstSlot = dbFilterOpts.gt ? this.decodeKey(dbFilterOpts.gt) + 1 : dbFilterOpts.gte ? this.decodeKey(dbFilterOpts.gte) : null; if (firstSlot === null) throw Error("specify opts.gt or opts.gte"); return firstSlot; } }