UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

112 lines 6.38 kB
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