@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
170 lines (142 loc) • 5.73 kB
text/typescript
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;
}
}