@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
112 lines • 6.38 kB
JavaScript
import { Tree, toGindex } from "@chainsafe/persistent-merkle-tree";
import { GENESIS_EPOCH, GENESIS_SLOT } from "@lodestar/params";
import { applyDeposits, applyEth1BlockHash, applyTimestamp, createCachedBeaconState, createEmptyEpochCacheImmutableData, getActiveValidatorIndices, getGenesisBeaconState, getTemporaryBlockHeader, } from "@lodestar/state-transition";
import { ssz } from "@lodestar/types";
import { getDepositsAndBlockStreamForGenesis, getDepositsStream } from "../../eth1/stream.js";
export class GenesisBuilder {
constructor({ config, eth1Provider, logger, signal, pendingStatus, maxBlocksPerPoll }) {
/** Is null if no block has been processed yet */
this.lastProcessedBlockNumber = null;
this.depositCache = new Set();
this.logEvery = 30 * 1000;
this.lastLog = 0;
// at genesis builder, there is no genesis validator so we don't have a real BeaconConfig
// but we need BeaconConfig to temporarily create CachedBeaconState, the cast here is safe since we don't use any getDomain here
// the use of state as CachedBeaconState is just for convenient, GenesisResult returns TreeView anyway
this.eth1Provider = eth1Provider;
this.logger = logger;
this.signal = signal;
this.eth1Params = {
...config,
maxBlocksPerPoll: maxBlocksPerPoll ?? 10000,
};
let stateView;
if (pendingStatus) {
this.logger.info("Restoring pending genesis state", { block: pendingStatus.lastProcessedBlockNumber });
stateView = pendingStatus.state;
this.depositTree = pendingStatus.depositTree;
this.fromBlock = Math.max(pendingStatus.lastProcessedBlockNumber + 1, this.eth1Provider.deployBlock);
}
else {
stateView = getGenesisBeaconState(config, ssz.phase0.Eth1Data.defaultValue(), getTemporaryBlockHeader(config, config.getForkTypes(GENESIS_SLOT).BeaconBlock.defaultValue()));
this.depositTree = ssz.phase0.DepositDataRootList.defaultViewDU();
this.fromBlock = this.eth1Provider.deployBlock;
}
// TODO - PENDING: Ensure EpochCacheImmutableData is created only once
this.state = createCachedBeaconState(stateView, createEmptyEpochCacheImmutableData(config, stateView));
this.config = this.state.config;
this.activatedValidatorCount = getActiveValidatorIndices(stateView, GENESIS_EPOCH).length;
}
/**
* Get eth1 deposit events and blocks and apply to this.state until we found genesis.
*/
async waitForGenesis() {
await this.eth1Provider.validateContract();
// Load data from data from this.db.depositData, this.db.depositDataRoot
// And start from a more recent fromBlock
const blockNumberValidatorGenesis = await this.waitForGenesisValidators();
const depositsAndBlocksStream = getDepositsAndBlockStreamForGenesis(blockNumberValidatorGenesis, this.eth1Provider, this.eth1Params, this.signal);
for await (const [depositEvents, block] of depositsAndBlocksStream) {
this.applyDeposits(depositEvents);
applyTimestamp(this.config, this.state, block.timestamp);
applyEth1BlockHash(this.state, block.blockHash);
this.lastProcessedBlockNumber = block.blockNumber;
if (this.state.genesisTime >= this.config.MIN_GENESIS_TIME &&
this.activatedValidatorCount >= this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT) {
this.logger.info("Found genesis state", { blockNumber: block.blockNumber });
return {
state: this.state,
depositTree: this.depositTree,
block,
};
}
this.throttledLog(`Waiting for min genesis time ${block.timestamp} / ${this.config.MIN_GENESIS_TIME}`);
}
throw Error("depositsStream stopped without a valid genesis state");
}
/**
* First phase of waiting for genesis.
* Stream deposits events in batches as big as possible without querying block data
* @returns Block number at which there are enough active validators is state for genesis
*/
async waitForGenesisValidators() {
const depositsStream = getDepositsStream(this.fromBlock, this.eth1Provider, this.eth1Params, this.signal);
for await (const { depositEvents, blockNumber } of depositsStream) {
this.applyDeposits(depositEvents);
this.lastProcessedBlockNumber = blockNumber;
if (this.activatedValidatorCount >= this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT) {
this.logger.info("Found enough genesis validators", { blockNumber });
return blockNumber;
}
this.throttledLog(`Found ${this.state.validators.length} / ${this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT} validators to genesis`);
}
throw Error("depositsStream stopped without a valid genesis state");
}
applyDeposits(depositEvents) {
const newDeposits = depositEvents
.filter((depositEvent) => !this.depositCache.has(depositEvent.index))
.map((depositEvent) => {
this.depositCache.add(depositEvent.index);
this.depositTree.push(ssz.phase0.DepositData.hashTreeRoot(depositEvent.depositData));
const gindex = toGindex(this.depositTree.type.depth, BigInt(depositEvent.index));
// Apply changes from the push above
this.depositTree.commit();
const depositTreeNode = this.depositTree.node;
return {
proof: new Tree(depositTreeNode).getSingleProof(gindex),
data: depositEvent.depositData,
};
});
const { activatedValidatorCount } = applyDeposits(this.config, this.state, newDeposits, this.depositTree);
this.activatedValidatorCount += activatedValidatorCount;
// TODO: If necessary persist deposits here to this.db.depositData, this.db.depositDataRoot
}
/** Throttle genesis generation status log to prevent spamming */
throttledLog(message) {
if (Date.now() - this.lastLog > this.logEvery) {
this.lastLog = Date.now();
this.logger.info(message);
}
}
}
//# sourceMappingURL=genesis.js.map