public BesuController build() {
    checkNotNull(genesisConfig, "Missing genesis config file");
    checkNotNull(genesisConfigOptions, "Missing genesis config options");
    checkNotNull(syncConfig, "Missing sync config");
    checkNotNull(ethereumWireProtocolConfiguration, "Missing ethereum protocol configuration");
    checkNotNull(networkId, "Missing network ID");
    checkNotNull(miningConfiguration, "Missing mining parameters");
    checkNotNull(metricsSystem, "Missing metrics system");
    checkNotNull(dataDirectory, "Missing data directory"); // Why do we need this?
    checkNotNull(clock, "Missing clock");
    checkNotNull(transactionPoolConfiguration, "Missing transaction pool configuration");
    checkNotNull(nodeKey, "Missing node key");
    checkNotNull(storageProvider, "Must supply a storage provider");
    checkNotNull(evmConfiguration, "Missing evm config");
    checkNotNull(networkingConfiguration, "Missing network configuration");
    checkNotNull(apiConfiguration, "Missing API configuration");
    checkNotNull(dataStorageConfiguration, "Missing data storage configuration");
    checkNotNull(besuComponent, "Must supply a BesuComponent");
    prepForBuild();

    final ProtocolSchedule protocolSchedule = createProtocolSchedule();

    final VariablesStorage variablesStorage = storageProvider.createVariablesStorage();

    final WorldStateStorageCoordinator worldStateStorageCoordinator =
        storageProvider.createWorldStateStorageCoordinator(dataStorageConfiguration);

    final BlockchainStorage blockchainStorage =
        storageProvider.createBlockchainStorage(
            protocolSchedule, variablesStorage, dataStorageConfiguration);

    final var maybeStoredGenesisBlockHash = blockchainStorage.getBlockHash(0L);

    final var genesisState =
        getGenesisState(
            maybeStoredGenesisBlockHash.flatMap(blockchainStorage::getBlockHeader),
            protocolSchedule);

    final MutableBlockchain blockchain =
        DefaultBlockchain.createMutable(
            genesisState.getBlock(),
            blockchainStorage,
            metricsSystem,
            reorgLoggingThreshold,
            dataDirectory.toString(),
            numberOfBlocksToCache);
    final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader =
        besuComponent
            .map(BesuComponent::getCachedMerkleTrieLoader)
            .orElseGet(() -> new BonsaiCachedMerkleTrieLoader(metricsSystem));

    final var worldStateHealerSupplier = new AtomicReference<WorldStateHealer>();

    final WorldStateArchive worldStateArchive =
        createWorldStateArchive(
            worldStateStorageCoordinator,
            blockchain,
            bonsaiCachedMerkleTrieLoader,
            worldStateHealerSupplier::get);

    if (maybeStoredGenesisBlockHash.isEmpty()) {
      genesisState.writeStateTo(worldStateArchive.getWorldState());
    }

    transactionSimulator =
        new TransactionSimulator(
            blockchain,
            worldStateArchive,
            protocolSchedule,
            miningConfiguration,
            apiConfiguration.getGasCap());

    final var consensusContext =
        createConsensusContext(blockchain, worldStateArchive, protocolSchedule);

    final ProtocolContext protocolContext =
        createProtocolContext(
            blockchain,
            worldStateArchive,
            consensusContext,
            besuComponent
                .map(BesuComponent::getBesuPluginContext)
                .orElse(new BesuPluginContextImpl()));
    validateContext(protocolContext);

    final int maxMessageSize = ethereumWireProtocolConfiguration.getMaxMessageSize();
    final Supplier<ProtocolSpec> currentProtocolSpecSupplier =
        () -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader());
    final ForkIdManager forkIdManager =
        new ForkIdManager(
            blockchain,
            genesisConfigOptions.getForkBlockNumbers(),
            genesisConfigOptions.getForkBlockTimestamps());
    final EthPeers ethPeers =
        new EthPeers(
            currentProtocolSpecSupplier,
            clock,
            metricsSystem,
            maxMessageSize,
            messagePermissioningProviders,
            nodeKey.getPublicKey().getEncodedBytes(),
            maxPeers,
            maxRemotelyInitiatedPeers,
            randomPeerPriority,
            syncConfig.getSyncMode(),
            forkIdManager);

    final EthMessages ethMessages = new EthMessages();
    final EthMessages snapMessages = new EthMessages();

    final EthScheduler scheduler =
        new EthScheduler(
            syncConfig.getDownloaderParallelism(),
            syncConfig.getTransactionsParallelism(),
            syncConfig.getComputationParallelism(),
            metricsSystem);

    Optional<Checkpoint> checkpoint = Optional.empty();
    if (genesisConfigOptions.getCheckpointOptions().isValid()) {
      checkpoint =
          Optional.of(
              ImmutableCheckpoint.builder()
                  .blockHash(
                      Hash.fromHexString(
                          genesisConfigOptions.getCheckpointOptions().getHash().get()))
                  .blockNumber(genesisConfigOptions.getCheckpointOptions().getNumber().getAsLong())
                  .totalDifficulty(
                      Difficulty.fromHexString(
                          genesisConfigOptions.getCheckpointOptions().getTotalDifficulty().get()))
                  .build());
    }

    final PeerTaskExecutor peerTaskExecutor =
        new PeerTaskExecutor(ethPeers, new PeerTaskRequestSender(), metricsSystem);
    final EthContext ethContext =
        new EthContext(ethPeers, ethMessages, snapMessages, scheduler, peerTaskExecutor);
    final boolean fullSyncDisabled = !SyncMode.isFullSync(syncConfig.getSyncMode());
    final SyncState syncState = new SyncState(blockchain, ethPeers, fullSyncDisabled, checkpoint);

    if (chainPrunerConfiguration.chainPruningEnabled()
        || dataStorageConfiguration.getHistoryExpiryPruneEnabled()) {
      LOG.info("Adding ChainDataPruner to observe block added events");
      final AtomicLong chainDataPrunerObserverId = new AtomicLong();
      final ChainDataPruner chainDataPruner =
          createChainPruner(
              blockchainStorage,
              () -> blockchain.removeObserver(chainDataPrunerObserverId.get()),
              syncState);
      chainDataPrunerObserverId.set(blockchain.observeBlockAdded(chainDataPruner));
      if (chainPrunerConfiguration.chainPruningEnabled()) {
        LOG.info(
            "Chain data pruning enabled with recent blocks retained to be: "
                + chainPrunerConfiguration.chainPruningBlocksRetained()
                + " and frequency to be: "
                + chainPrunerConfiguration.blocksFrequency());
      } else if (dataStorageConfiguration.getHistoryExpiryPruneEnabled()) {
        LOG.info(
            "Pre-merge block pruning enabled with frequency: "
                + chainPrunerConfiguration.blocksFrequency()
                + " and quantity: "
                + chainPrunerConfiguration.preMergePruningBlocksQuantity());
      }
    }

    final TransactionPool transactionPool =
        TransactionPoolFactory.createTransactionPool(
            protocolSchedule,
            protocolContext,
            ethContext,
            clock,
            metricsSystem,
            syncState,
            transactionPoolConfiguration,
            besuComponent.map(BesuComponent::getBlobCache).orElse(new BlobCache()),
            miningConfiguration,
            syncConfig.isPeerTaskSystemEnabled());

    final List<PeerValidator> peerValidators =
        createPeerValidators(protocolSchedule, peerTaskExecutor);

    final EthProtocolManager ethProtocolManager =
        createEthProtocolManager(
            protocolContext,
            syncConfig,
            transactionPool,
            ethereumWireProtocolConfiguration,
            ethPeers,
            ethContext,
            ethMessages,
            scheduler,
            peerValidators,
            Optional.empty(),
            forkIdManager);

    final PivotBlockSelector pivotBlockSelector =
        createPivotSelector(
            protocolSchedule, protocolContext, ethContext, syncState, metricsSystem, blockchain);

    final DefaultSynchronizer synchronizer =
        createSynchronizer(
            protocolSchedule,
            worldStateStorageCoordinator,
            protocolContext,
            ethContext,
            peerTaskExecutor,
            syncState,
            ethProtocolManager,
            pivotBlockSelector);

    worldStateHealerSupplier.set(synchronizer::healWorldState);

    ethPeers.setTrailingPeerRequirementsSupplier(synchronizer::calculateTrailingPeerRequirements);

    if (syncConfig.getSyncMode() == SyncMode.SNAP
        || syncConfig.getSyncMode() == SyncMode.CHECKPOINT) {
      synchronizer.subscribeInSync((b) -> ethPeers.snapServerPeersNeeded(!b));
      ethPeers.snapServerPeersNeeded(true);
    } else {
      ethPeers.snapServerPeersNeeded(false);
    }

    final Optional<SnapProtocolManager> maybeSnapProtocolManager =
        createSnapProtocolManager(
            protocolContext, worldStateStorageCoordinator, ethPeers, snapMessages, synchronizer);

    final MiningCoordinator miningCoordinator =
        createMiningCoordinator(
            protocolSchedule,
            protocolContext,
            transactionPool,
            miningConfiguration,
            syncState,
            ethProtocolManager);

    final PluginServiceFactory additionalPluginServices =
        createAdditionalPluginServices(blockchain, protocolContext);

    final SubProtocolConfiguration subProtocolConfiguration =
        createSubProtocolConfiguration(ethProtocolManager, maybeSnapProtocolManager);

    final JsonRpcMethods additionalJsonRpcMethodFactory =
        createAdditionalJsonRpcMethodFactory(
            protocolContext, protocolSchedule, miningConfiguration);

    if (DataStorageFormat.BONSAI.equals(dataStorageConfiguration.getDataStorageFormat())) {
      final PathBasedExtraStorageConfiguration subStorageConfiguration =
          dataStorageConfiguration.getPathBasedExtraStorageConfiguration();
      if (subStorageConfiguration.getLimitTrieLogsEnabled()) {
        final TrieLogManager trieLogManager =
            ((BonsaiWorldStateProvider) worldStateArchive).getTrieLogManager();
        final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage =
            worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class);
        final TrieLogPruner trieLogPruner =
            createTrieLogPruner(worldStateKeyValueStorage, blockchain, scheduler);
        trieLogManager.subscribe(trieLogPruner);
      }
    }

    final List<Closeable> closeables = new ArrayList<>();
    closeables.add(protocolContext.getWorldStateArchive());
    closeables.add(storageProvider);

    return new BesuController(
        protocolSchedule,
        protocolContext,
        ethProtocolManager,
        genesisConfigOptions,
        subProtocolConfiguration,
        synchronizer,
        syncState,
        transactionPool,
        miningCoordinator,
        miningConfiguration,
        additionalJsonRpcMethodFactory,
        nodeKey,
        closeables,
        additionalPluginServices,
        ethPeers,
        storageProvider,
        dataStorageConfiguration,
        transactionSimulator);
  }
Build besu controller.

@return the besu controller
app/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java