@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
101 lines • 3.8 kB
JavaScript
import { SLOTS_PER_EPOCH } from "@lodestar/params";
import { ssz } from "@lodestar/types";
import { MapDef } from "@lodestar/utils";
import { getLastProcessedSlotFromBeaconStateSerialized, getSlotFromBeaconStateSerialized, } from "../../../util/sszBytes.js";
/**
* Implementation of CPStateDatastore using db.
*/
export class DbCPStateDatastore {
db;
constructor(db) {
this.db = db;
}
async write(cpKey, stateBytes) {
const serializedCheckpoint = checkpointToDatastoreKey(cpKey);
await this.db.checkpointState.putBinary(serializedCheckpoint, stateBytes);
return serializedCheckpoint;
}
async remove(serializedCheckpoint) {
await this.db.checkpointState.delete(serializedCheckpoint);
}
async read(serializedCheckpoint) {
return this.db.checkpointState.getBinary(serializedCheckpoint);
}
async readLatestSafe() {
const allKeys = await this.readKeys();
if (allKeys.length === 0)
return null;
return getLatestSafeDatastoreKey(allKeys, this.read.bind(this));
}
async readKeys() {
return this.db.checkpointState.keys();
}
}
export function datastoreKeyToCheckpoint(key) {
return ssz.phase0.Checkpoint.deserialize(key);
}
export function checkpointToDatastoreKey(cp) {
return ssz.phase0.Checkpoint.serialize(cp);
}
/**
* Get the latest safe checkpoint state the node can use to boot from
* - it should be the checkpoint state that's unique in its epoch
* - its last processed block slot should be at epoch boundary or last slot of previous epoch
* - state slot should be at epoch boundary
* - state slot should be equal to epoch * SLOTS_PER_EPOCH
*
* return the serialized data of Current Root Checkpoint State (CRCS) or Previous Root Checkpoint State (PRCS)
*
*/
export async function getLatestSafeDatastoreKey(allKeys, readFn) {
const checkpointsByEpoch = new MapDef(() => []);
for (const key of allKeys) {
const cp = datastoreKeyToCheckpoint(key);
checkpointsByEpoch.getOrDefault(cp.epoch).push(key);
}
const dataStoreKeyByEpoch = new Map();
for (const [epoch, keys] of checkpointsByEpoch.entries()) {
// only consider epochs with a single checkpoint to avoid ambiguity from forks
if (keys.length === 1) {
dataStoreKeyByEpoch.set(epoch, keys[0]);
}
}
const epochsDesc = Array.from(dataStoreKeyByEpoch.keys()).sort((a, b) => b - a);
for (const epoch of epochsDesc) {
const datastoreKey = dataStoreKeyByEpoch.get(epoch);
if (datastoreKey == null) {
// should not happen
continue;
}
const stateBytes = await readFn(datastoreKey);
if (stateBytes == null) {
// should not happen
continue;
}
const lastProcessedSlot = getLastProcessedSlotFromBeaconStateSerialized(stateBytes);
if (lastProcessedSlot == null) {
// cannot extract last processed slot from serialized state, skip
continue;
}
const stateSlot = getSlotFromBeaconStateSerialized(stateBytes);
if (stateSlot == null) {
// cannot extract slot from serialized state, skip
continue;
}
if (lastProcessedSlot !== stateSlot && lastProcessedSlot !== stateSlot - 1) {
// not CRCS or PRCS, skip
continue;
}
if (stateSlot % SLOTS_PER_EPOCH !== 0) {
// not at epoch boundary, skip
continue;
}
if (stateSlot !== SLOTS_PER_EPOCH * epoch) {
// should not happen after above checks, but just to be safe
continue;
}
return stateBytes;
}
return null;
}
//# sourceMappingURL=db.js.map