diff --git a/buildSrc/src/main/kotlin/com.hedera.block.jpms-modules.gradle.kts b/buildSrc/src/main/kotlin/com.hedera.block.jpms-modules.gradle.kts index 568b13afc..964d27a85 100644 --- a/buildSrc/src/main/kotlin/com.hedera.block.jpms-modules.gradle.kts +++ b/buildSrc/src/main/kotlin/com.hedera.block.jpms-modules.gradle.kts @@ -49,6 +49,13 @@ jvmDependencyConflicts.patch { module("io.grpc:grpc-stub") { annotationLibraries.forEach { removeDependency(it) } } module("io.grpc:grpc-testing") { annotationLibraries.forEach { removeDependency(it) } } module("io.grpc:grpc-util") { annotationLibraries.forEach { removeDependency(it) } } + module("com.google.dagger:dagger-compiler") { + annotationLibraries.forEach { removeDependency(it) } + } + module("com.google.dagger:dagger-producers") { + annotationLibraries.forEach { removeDependency(it) } + } + module("com.google.dagger:dagger-spi") { annotationLibraries.forEach { removeDependency(it) } } module("com.google.guava:guava") { (annotationLibraries - "com.google.code.findbugs:jsr305" - @@ -63,6 +70,9 @@ jvmDependencyConflicts.patch { removeDependency("io.prometheus:simpleclient_tracer_otel") removeDependency("io.prometheus:simpleclient_tracer_otel_agent") } + module("org.jetbrains.kotlin:kotlin-stdlib") { + removeDependency("org.jetbrains.kotlin:kotlin-stdlib-common") + } module("junit:junit") { removeDependency("org.hamcrest:hamcrest-core") } module("org.hyperledger.besu:secp256k1") { addApiDependency("net.java.dev.jna:jna") } } @@ -110,7 +120,7 @@ extraJavaModuleInfo { } module("com.google.guava:failureaccess", "com.google.common.util.concurrent.internal") module("com.google.api.grpc:proto-google-common-protos", "com.google.api.grpc.common") - + module("com.google.dagger:dagger", "dagger") module("io.perfmark:perfmark-api", "io.perfmark") module("javax.inject:javax.inject", "javax.inject") @@ -121,6 +131,7 @@ extraJavaModuleInfo { module("org.checkerframework:checker-qual", "org.checkerframework.checker.qual") module("net.i2p.crypto:eddsa", "net.i2p.crypto.eddsa") + module("org.jetbrains:annotations", "org.jetbrains.annotations") module("org.antlr:antlr4-runtime", "org.antlr.antlr4.runtime") // needed for metrics and logging, but also several platform classes @@ -143,9 +154,24 @@ extraJavaModuleInfo { module("com.google.auto.service:auto-service-annotations", "com.google.auto.service") module("com.google.auto.service:auto-service", "com.google.auto.service.processor") module("com.google.auto:auto-common", "com.google.auto.common") + module("com.google.dagger:dagger-compiler", "dagger.compiler") + module("com.google.dagger:dagger-producers", "dagger.producers") + module("com.google.dagger:dagger-spi", "dagger.spi") + module( + "com.google.devtools.ksp:symbol-processing-api", + "com.google.devtools.ksp.symbolprocessingapi" + ) + module("com.google.errorprone:javac-shaded", "com.google.errorprone.javac.shaded") + module("com.google.googlejavaformat:google-java-format", "com.google.googlejavaformat") + module("net.ltgt.gradle.incap:incap", "net.ltgt.gradle.incap") + module("org.jetbrains.kotlinx:kotlinx-metadata-jvm", "kotlinx.metadata.jvm") // Test clients only module("com.google.protobuf:protobuf-java-util", "com.google.protobuf.util") + module("com.squareup:javapoet", "com.squareup.javapoet") { + exportAllPackages() + requires("java.compiler") + } module("junit:junit", "junit") module("org.hamcrest:hamcrest", "org.hamcrest") module("org.json:json", "org.json") diff --git a/gradle/modules.properties b/gradle/modules.properties index 16df503d7..93a164fe9 100644 --- a/gradle/modules.properties +++ b/gradle/modules.properties @@ -32,3 +32,10 @@ org.apache.commons.compress=org.apache.commons:commons-compress java.annotation=javax.annotation:javax.annotation-api org.apache.logging.log4j.slf4j2.impl=org.apache.logging.log4j:log4j-slf4j2-impl + +grpc.protobuf=io.grpc:grpc-protobuf +dagger=com.google.dagger:dagger +dagger.compiler=com.google.dagger:dagger-compiler +com.squareup.javapoet=com.squareup:javapoet +javax.inject=javax.inject:javax.inject +org.checkerframework.checker.qual=org.checkerframework:checker-qual diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 4e1f45d59..75b890ef2 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -25,6 +25,7 @@ application { } mainModuleInfo { + annotationProcessor("dagger.compiler") annotationProcessor("com.google.auto.service.processor") runtimeOnly("com.swirlds.config.impl") runtimeOnly("org.apache.logging.log4j.slf4j2.impl") diff --git a/server/src/main/java/com/hedera/block/server/BlockNodeApp.java b/server/src/main/java/com/hedera/block/server/BlockNodeApp.java new file mode 100644 index 000000000..58b668eda --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/BlockNodeApp.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.block.server; + +import static java.lang.System.Logger; +import static java.lang.System.Logger.Level.INFO; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.data.ObjectEvent; +import com.hedera.block.server.health.HealthService; +import com.hedera.block.server.mediator.LiveStreamMediatorBuilder; +import com.hedera.block.server.mediator.StreamMediator; +import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; +import com.hedera.block.server.persistence.storage.read.BlockAsDirReaderBuilder; +import com.hedera.block.server.persistence.storage.read.BlockReader; +import com.hedera.block.server.persistence.storage.write.BlockAsDirWriterBuilder; +import com.hedera.block.server.persistence.storage.write.BlockWriter; +import com.hedera.hapi.block.SubscribeStreamResponse; +import com.hedera.hapi.block.stream.Block; +import com.hedera.hapi.block.stream.BlockItem; +import edu.umd.cs.findbugs.annotations.NonNull; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.grpc.GrpcRouting; +import io.helidon.webserver.http.HttpRouting; +import java.io.IOException; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * The main class for the Block Node application. This class is responsible for starting the server + * and initializing the context. + */ +@Singleton +public class BlockNodeApp { + + private static final Logger LOGGER = System.getLogger(BlockNodeApp.class.getName()); + + private final ServiceStatus serviceStatus; + private final HealthService healthService; + private final BlockNodeContext blockNodeContext; + + /** + * Has all needed dependencies to start the server and initialize the context. + * + * @param serviceStatus the status of the service + * @param healthService the health service + * @param blockNodeContext the context of the block node + */ + @Inject + public BlockNodeApp( + @NonNull ServiceStatus serviceStatus, + @NonNull HealthService healthService, + @NonNull BlockNodeContext blockNodeContext) { + this.serviceStatus = serviceStatus; + this.healthService = healthService; + this.blockNodeContext = blockNodeContext; + } + + /** + * Starts the server and binds to the specified port. + * + * @throws IOException if the server cannot be started + */ + public void start() throws IOException { + + final BlockWriter blockWriter = + BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); + final StreamMediator> streamMediator = + LiveStreamMediatorBuilder.newBuilder(blockWriter, blockNodeContext, serviceStatus) + .build(); + + final BlockReader blockReader = + BlockAsDirReaderBuilder.newBuilder( + blockNodeContext + .configuration() + .getConfigData(PersistenceStorageConfig.class)) + .build(); + + final BlockStreamService blockStreamService = + buildBlockStreamService( + streamMediator, blockReader, serviceStatus, blockNodeContext); + + final GrpcRouting.Builder grpcRouting = GrpcRouting.builder().service(blockStreamService); + + final HttpRouting.Builder httpRouting = + HttpRouting.builder().register(healthService.getHealthRootPath(), healthService); + + // Build the web server + // TODO: make port server a configurable value. + final WebServer webServer = + WebServer.builder() + .port(8080) + .addRouting(grpcRouting) + .addRouting(httpRouting) + .build(); + + // Update the serviceStatus with the web server + serviceStatus.setWebServer(webServer); + + // Start the web server + webServer.start(); + + // Log the server status + LOGGER.log(INFO, String.format("Block Node Server started at port: %d", webServer.port())); + } + + @NonNull + private static BlockStreamService buildBlockStreamService( + @NonNull + final StreamMediator> + streamMediator, + @NonNull final BlockReader blockReader, + @NonNull final ServiceStatus serviceStatus, + @NonNull final BlockNodeContext blockNodeContext) { + + return new BlockStreamService(streamMediator, blockReader, serviceStatus, blockNodeContext); + } +} diff --git a/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java new file mode 100644 index 000000000..b429b4e85 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.block.server; + +import com.hedera.block.server.health.HealthInjectionModule; +import dagger.Component; +import javax.inject.Singleton; + +/** The infrastructure used to manage the instances and inject them using Dagger */ +@Singleton +@Component( + modules = { + BlockNodeAppInjectionModule.class, + HealthInjectionModule.class, + }) +public interface BlockNodeAppInjectionComponent { + /** + * Get the block node app server. + * + * @return the block node app server + */ + BlockNodeApp getBlockNodeApp(); +} diff --git a/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionModule.java b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionModule.java new file mode 100644 index 000000000..5a5b076e5 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionModule.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.block.server; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.config.BlockNodeContextFactory; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import java.io.IOException; +import javax.inject.Singleton; + +/** + * A Dagger Module for interfaces that are at the BlockNodeApp Level, should be temporary and + * everything should be inside its own modules. + */ +@Module +public interface BlockNodeAppInjectionModule { + + /** + * Binds the service status to the service status implementation. + * + * @param serviceStatus needs a service status implementation + * @return the service status implementation + */ + @Singleton + @Binds + ServiceStatus bindServiceStatus(ServiceStatusImpl serviceStatus); + + /** + * Provides a block node context singleton using the factory. + * + * @return a block node context singleton + */ + @Provides + @Singleton + static BlockNodeContext provideBlockNodeContext() { + try { + return BlockNodeContextFactory.create(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/server/src/main/java/com/hedera/block/server/Server.java b/server/src/main/java/com/hedera/block/server/Server.java index 7fc021bc6..77d5e68cc 100644 --- a/server/src/main/java/com/hedera/block/server/Server.java +++ b/server/src/main/java/com/hedera/block/server/Server.java @@ -19,25 +19,6 @@ import static java.lang.System.Logger; import static java.lang.System.Logger.Level.INFO; -import com.hedera.block.server.config.BlockNodeContext; -import com.hedera.block.server.config.BlockNodeContextFactory; -import com.hedera.block.server.data.ObjectEvent; -import com.hedera.block.server.health.HealthService; -import com.hedera.block.server.health.HealthServiceImpl; -import com.hedera.block.server.mediator.LiveStreamMediatorBuilder; -import com.hedera.block.server.mediator.StreamMediator; -import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; -import com.hedera.block.server.persistence.storage.read.BlockAsDirReaderBuilder; -import com.hedera.block.server.persistence.storage.read.BlockReader; -import com.hedera.block.server.persistence.storage.write.BlockAsDirWriterBuilder; -import com.hedera.block.server.persistence.storage.write.BlockWriter; -import com.hedera.hapi.block.SubscribeStreamResponse; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.helidon.webserver.WebServer; -import io.helidon.webserver.grpc.GrpcRouting; -import io.helidon.webserver.http.HttpRouting; import java.io.IOException; /** Main class for the block node server */ @@ -51,75 +32,13 @@ private Server() {} * Main entrypoint for the block node server * * @param args Command line arguments. Not used at present. + * @throws IOException if there is an error starting the server */ - public static void main(final String[] args) { - + public static void main(final String[] args) throws IOException { LOGGER.log(INFO, "Starting BlockNode Server"); - - try { - // init context, metrics, and configuration. - final BlockNodeContext blockNodeContext = BlockNodeContextFactory.create(); - final ServiceStatus serviceStatus = new ServiceStatusImpl(); - - final BlockWriter blockWriter = - BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); - - final StreamMediator> streamMediator = - LiveStreamMediatorBuilder.newBuilder( - blockWriter, blockNodeContext, serviceStatus) - .build(); - - final BlockReader blockReader = - BlockAsDirReaderBuilder.newBuilder( - blockNodeContext - .configuration() - .getConfigData(PersistenceStorageConfig.class)) - .build(); - - final BlockStreamService blockStreamService = - buildBlockStreamService( - streamMediator, blockReader, serviceStatus, blockNodeContext); - - final GrpcRouting.Builder grpcRouting = - GrpcRouting.builder().service(blockStreamService); - - final HealthService healthService = new HealthServiceImpl(serviceStatus); - - final HttpRouting.Builder httpRouting = - HttpRouting.builder() - .register(healthService.getHealthRootPath(), healthService); - - // Build the web server - // TODO: make port server a configurable value. - final WebServer webServer = - WebServer.builder() - .port(8080) - .addRouting(grpcRouting) - .addRouting(httpRouting) - .build(); - - // Update the serviceStatus with the web server - serviceStatus.setWebServer(webServer); - - // Start the web server - webServer.start(); - - // Log the server status - LOGGER.log(INFO, "Block Node Server started at port: " + webServer.port()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @NonNull - private static BlockStreamService buildBlockStreamService( - @NonNull - final StreamMediator> - streamMediator, - @NonNull final BlockReader blockReader, - @NonNull final ServiceStatus serviceStatus, - @NonNull final BlockNodeContext blockNodeContext) { - - return new BlockStreamService(streamMediator, blockReader, serviceStatus, blockNodeContext); + final BlockNodeAppInjectionComponent daggerComponent = + DaggerBlockNodeAppInjectionComponent.create(); + final BlockNodeApp blockNodeApp = daggerComponent.getBlockNodeApp(); + blockNodeApp.start(); } } diff --git a/server/src/main/java/com/hedera/block/server/ServiceStatusImpl.java b/server/src/main/java/com/hedera/block/server/ServiceStatusImpl.java index ac9b869e2..dd3243a15 100644 --- a/server/src/main/java/com/hedera/block/server/ServiceStatusImpl.java +++ b/server/src/main/java/com/hedera/block/server/ServiceStatusImpl.java @@ -19,17 +19,21 @@ import edu.umd.cs.findbugs.annotations.NonNull; import io.helidon.webserver.WebServer; import java.util.concurrent.atomic.AtomicBoolean; +import javax.inject.Inject; +import javax.inject.Singleton; /** * The ServiceStatusImpl class implements the ServiceStatus interface. It provides the * implementation for checking the status of the service and shutting down the web server. */ +@Singleton public class ServiceStatusImpl implements ServiceStatus { private final AtomicBoolean isRunning = new AtomicBoolean(true); private WebServer webServer; /** Constructor for the ServiceStatusImpl class. */ + @Inject public ServiceStatusImpl() {} /** diff --git a/server/src/main/java/com/hedera/block/server/health/HealthInjectionModule.java b/server/src/main/java/com/hedera/block/server/health/HealthInjectionModule.java new file mode 100644 index 000000000..db882c9bb --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/health/HealthInjectionModule.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.block.server.health; + +import dagger.Binds; +import dagger.Module; +import edu.umd.cs.findbugs.annotations.NonNull; +import javax.inject.Singleton; + +/** + * A Dagger module for providing dependencies for Health Module, should we refactor to have an + * observability module instead?. + */ +@Module +public interface HealthInjectionModule { + + /** + * Binds the health service to the health service implementation. + * + * @param healthService needs a health service implementation + * @return the health service implementation + */ + @Singleton + @Binds + HealthService bindHealthService(@NonNull HealthServiceImpl healthService); +} diff --git a/server/src/main/java/com/hedera/block/server/health/HealthServiceImpl.java b/server/src/main/java/com/hedera/block/server/health/HealthServiceImpl.java index 912d39963..0f622866d 100644 --- a/server/src/main/java/com/hedera/block/server/health/HealthServiceImpl.java +++ b/server/src/main/java/com/hedera/block/server/health/HealthServiceImpl.java @@ -21,8 +21,11 @@ import io.helidon.webserver.http.HttpRules; import io.helidon.webserver.http.ServerRequest; import io.helidon.webserver.http.ServerResponse; +import javax.inject.Inject; +import javax.inject.Singleton; /** Provides implementation for the health endpoints of the server. */ +@Singleton public class HealthServiceImpl implements HealthService { private static final String LIVENESS_PATH = "/liveness"; @@ -35,6 +38,7 @@ public class HealthServiceImpl implements HealthService { * * @param serviceStatus is used to check the status of the service */ + @Inject public HealthServiceImpl(@NonNull ServiceStatus serviceStatus) { this.serviceStatus = serviceStatus; } diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 6fdbf8219..4ad339a6f 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -11,6 +11,7 @@ exports com.hedera.block.server.config; exports com.hedera.block.server.mediator; exports com.hedera.block.server.data; + exports com.hedera.block.server.health; requires com.hedera.block.stream; requires com.google.protobuf; @@ -20,10 +21,12 @@ requires com.swirlds.config.api; requires com.swirlds.config.extensions; requires com.swirlds.metrics.api; + requires dagger; requires io.grpc.stub; requires io.helidon.common; requires io.helidon.webserver.grpc; requires io.helidon.webserver; + requires javax.inject; requires static com.github.spotbugs.annotations; requires static com.google.auto.service; diff --git a/server/src/test/java/com/hedera/block/server/BlockNodeAppInjectionModuleTest.java b/server/src/test/java/com/hedera/block/server/BlockNodeAppInjectionModuleTest.java new file mode 100644 index 000000000..704409c6a --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/BlockNodeAppInjectionModuleTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.block.server; + +import com.hedera.block.server.config.BlockNodeContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BlockNodeAppInjectionModuleTest { + @Test + void testProvideBlockNodeContext() { + BlockNodeContext blockNodeContext = BlockNodeAppInjectionModule.provideBlockNodeContext(); + + Assertions.assertNotNull(blockNodeContext); + Assertions.assertNotNull(blockNodeContext.configuration()); + Assertions.assertNotNull(blockNodeContext.metrics()); + Assertions.assertNotNull(blockNodeContext.metricsService()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 28546250d..7c27adc01 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,6 +41,9 @@ dependencyResolutionManagement { // Platform SDK modules are all released together with matching versions. val swirldsVersion = "0.51.5" + // Define a constant for the Dagger version. + val daggerVersion = "2.42" + // Compile time dependencies version("com.google.protobuf", "3.24.0") version("io.helidon.webserver.http2", "4.0.11") @@ -81,6 +84,11 @@ dependencyResolutionManagement { version("org.apache.commons.compress", "1.26.0") version("org.apache.logging.log4j.slf4j2.impl", "2.21.1") + // needed for dagger + version("dagger", daggerVersion) + version("dagger.compiler", daggerVersion) + version("com.squareup.javapoet", "1.13.0") + // Testing only versions version("org.assertj.core", "3.23.1") version("org.junit.jupiter.api", "5.10.2")