From 98b2eebb01bdd824c2e87dd1864f2f89825d38ac Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 8 Nov 2024 13:55:25 -0700 Subject: [PATCH] feat: Replace `grpc.io` with PBJ Helidon Plugin (#237) Signed-off-by: Matt Peterson --- codecov.yml | 5 +- gradle/modules.properties | 7 +- server/build.gradle.kts | 1 + server/docker/logging.properties | 6 +- .../metrics/dashboards/block-node-server.json | 643 +++++++++++++----- server/docker/update-env.sh | 5 +- .../com/hedera/block/server/BlockNodeApp.java | 46 +- .../BlockNodeAppInjectionComponent.java | 2 + .../com/hedera/block/server/Constants.java | 15 +- .../java/com/hedera/block/server/Server.java | 2 + .../com/hedera/block/server/Translator.java | 190 ------ .../config/BlockNodeConfigExtension.java | 2 + .../server/config/ConfigInjectionModule.java | 13 + .../ConsumerStreamResponseObserver.java | 112 +-- .../block/server/grpc/BlockAccessService.java | 176 ----- .../block/server/grpc/BlockStreamService.java | 189 ----- .../mediator/LiveStreamMediatorImpl.java | 4 + .../block/server/mediator/MediatorConfig.java | 8 +- .../mediator/MediatorInjectionModule.java | 11 +- .../mediator/NoOpLiveStreamMediator.java | 89 +++ .../server/metrics/BlockNodeMetricTypes.java | 22 +- .../block/server/notifier/NotifierConfig.java | 2 +- .../block/server/notifier/NotifierImpl.java | 31 +- .../server/pbj/PbjBlockAccessService.java | 73 ++ .../pbj/PbjBlockAccessServiceProxy.java | 157 +++++ .../server/pbj/PbjBlockStreamService.java | 79 +++ .../pbj/PbjBlockStreamServiceProxy.java | 203 ++++++ .../PbjInjectionModule.java} | 14 +- .../PersistenceInjectionModule.java | 8 + .../storage/PersistenceStorageConfig.java | 6 +- .../storage/write/NoOpBlockWriter.java | 60 ++ .../server/producer/NoOpProducerObserver.java | 92 +++ .../producer/ProducerBlockItemObserver.java | 122 ++-- .../block/server/producer/ProducerConfig.java | 42 ++ server/src/main/java/module-info.java | 8 +- server/src/main/resources/app.properties | 8 +- server/src/main/resources/helidon.properties | 2 + .../hedera/block/server/BlockNodeAppTest.java | 64 +- .../ConsumerStreamResponseObserverTest.java | 105 +-- .../server/grpc/BlockAccessServiceTest.java | 400 +++++------ .../server/grpc/BlockStreamServiceTest.java | 181 +---- .../mediator/LiveStreamMediatorImplTest.java | 328 ++++----- .../server/mediator/MediatorConfigTest.java | 10 +- .../server/notifier/NotifierImplTest.java | 274 +++----- .../pbj/PbjBlockAccessServiceProxyTest.java | 163 +++++ ...PbjBlockStreamServiceIntegrationTest.java} | 261 ++++--- .../storage/PersistenceStorageConfigTest.java | 13 +- .../ProducerBlockItemObserverTest.java | 169 +---- server/src/test/resources/block_service.proto | 22 +- server/src/test/resources/producer.sh | 18 +- .../templates/block_proof_template.json | 6 +- .../resources/templates/event_template.json | 11 +- .../resources/templates/header_template.json | 32 +- settings.gradle.kts | 23 +- simulator/build.gradle.kts | 1 + .../simulator/BlockStreamSimulatorApp.java | 4 +- .../hedera/block/simulator/Translator.java | 209 ------ .../config/data/BlockGeneratorConfig.java | 45 +- .../BlockAsDirBlockStreamManager.java | 14 +- .../BlockAsFileBlockStreamManager.java | 12 +- .../generator/BlockAsFileLargeDataSets.java | 69 +- .../generator/BlockStreamManager.java | 4 +- .../generator/GeneratorInjectionModule.java | 5 +- .../grpc/PublishStreamGrpcClient.java | 4 +- .../grpc/PublishStreamGrpcClientImpl.java | 80 ++- .../simulator/grpc/PublishStreamObserver.java | 5 +- .../metrics/SimulatorMetricTypes.java | 4 +- .../simulator/mode/PublisherModeHandler.java | 28 +- simulator/src/main/java/module-info.java | 1 - simulator/src/main/resources/app.properties | 11 + .../simulator/BlockStreamSimulatorTest.java | 28 +- .../BlockAsDirBlockStreamManagerTest.java | 18 +- .../BlockAsFileLargeDataSetsTest.java | 87 ++- .../grpc/PublishStreamGrpcClientImplTest.java | 17 +- .../grpc/PublishStreamObserverTest.java | 6 +- .../mode/PublisherModeHandlerTest.java | 51 +- stream/src/main/java/module-info.java | 5 +- 77 files changed, 2732 insertions(+), 2511 deletions(-) delete mode 100644 server/src/main/java/com/hedera/block/server/Translator.java delete mode 100644 server/src/main/java/com/hedera/block/server/grpc/BlockAccessService.java delete mode 100644 server/src/main/java/com/hedera/block/server/grpc/BlockStreamService.java create mode 100644 server/src/main/java/com/hedera/block/server/mediator/NoOpLiveStreamMediator.java create mode 100644 server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessService.java create mode 100644 server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java create mode 100644 server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamService.java create mode 100644 server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java rename server/src/main/java/com/hedera/block/server/{grpc/GrpcServiceInjectionModule.java => pbj/PbjInjectionModule.java} (66%) create mode 100644 server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java create mode 100644 server/src/main/java/com/hedera/block/server/producer/NoOpProducerObserver.java create mode 100644 server/src/main/java/com/hedera/block/server/producer/ProducerConfig.java create mode 100644 server/src/main/resources/helidon.properties create mode 100644 server/src/test/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxyTest.java rename server/src/test/java/com/hedera/block/server/{grpc/BlockStreamServiceIntegrationTest.java => pbj/PbjBlockStreamServiceIntegrationTest.java} (68%) delete mode 100644 simulator/src/main/java/com/hedera/block/simulator/Translator.java diff --git a/codecov.yml b/codecov.yml index ec4798fb7..7b9965a9c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -20,6 +20,7 @@ coverage: ignore: - "server/src/main/java/com/hedera/block/server/Server.java" - - "server/src/main/java/com/hedera/block/server/Translator.java" + - "server/src/main/java/com/hedera/block/server/mediator/NoOpLiveStreamMediator.java" + - "server/src/main/java/com/hedera/block/server/producer/NoOpProducerObserver.java" + - "server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java" - "simulator/src/main/java/com/hedera/block/simulator/BlockStreamSimulator.java" - - "simulator/src/main/java/com/hedera/block/simulator/Translator.java" diff --git a/gradle/modules.properties b/gradle/modules.properties index b63ad263a..2dd01cb42 100644 --- a/gradle/modules.properties +++ b/gradle/modules.properties @@ -10,21 +10,22 @@ com.google.auto.common=com.google.auto:auto-common com.github.spotbugs.annotations=com.github.spotbugs:spotbugs-annotations com.lmax.disruptor=com.lmax:disruptor io.helidon.webserver=io.helidon.webserver:helidon-webserver -io.helidon.webserver.grpc=io.helidon.webserver:helidon-webserver-grpc io.helidon.webserver.testing.junit5=io.helidon.webserver.testing.junit5:helidon-webserver-testing-junit5 - io.helidon.logging=io.helidon.logging:helidon-logging-jul org.antlr.antlr4.runtime=org.antlr:antlr4-runtime com.google.common=com.google.guava:guava +com.hedera.pbj.runtime=com.hedera.pbj:pbj-runtime +com.hedera.pbj.grpc.helidon=com.hedera.pbj:pbj-grpc-helidon +com.hedera.pbj.grpc.helidon.config=com.hedera.pbj:pbj-grpc-helidon-config + google.proto=com.google.protobuf:protoc io.grpc=io.grpc:grpc-api io.grpc.protobuf=io.grpc:grpc-protobuf io.grpc.stub=io.grpc:grpc-stub io.grpc.netty.shaded=io.grpc:grpc-netty-shaded -com.hedera.pbj.runtime=com.hedera.pbj:pbj-runtime com.google.protobuf=com.google.protobuf:protobuf-java com.google.protobuf.util=com.google.protobuf:protobuf-java-util com.apache.commons.codec=commons-codec:commons-codec diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 1696527a0..ca5357d30 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -30,6 +30,7 @@ mainModuleInfo { runtimeOnly("com.swirlds.config.impl") runtimeOnly("org.apache.logging.log4j.slf4j2.impl") runtimeOnly("io.helidon.logging") + runtimeOnly("com.hedera.pbj.grpc.helidon.config") } testModuleInfo { diff --git a/server/docker/logging.properties b/server/docker/logging.properties index 91d8f4419..1ed70c240 100644 --- a/server/docker/logging.properties +++ b/server/docker/logging.properties @@ -12,7 +12,7 @@ .level=INFO # Helidon loggers -io.helidon.webserver.level=SEVERE +io.helidon.webserver.level=INFO io.helidon.config.level=SEVERE io.helidon.security.level=INFO io.helidon.common.level=INFO @@ -22,11 +22,13 @@ io.helidon.common.level=INFO #com.hedera.block.server.level=FINE # Configure specific loggers +#com.hedera.block.server.producer.ProducerBlockItemObserver.level=FINE #com.hedera.block.server.mediator.LiveStreamMediatorImpl.level=FINE #com.hedera.block.server.persistence.storage.write.BlockAsDirWriter.level=FINE -#com.hedera.block.server.producer.ProducerBlockItemObserver.level=FINE +#com.hedera.block.server.consumer.ConsumerStreamResponseObserver.level=FINE # Console handler configuration handlers = java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level = FINE java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + diff --git a/server/docker/metrics/dashboards/block-node-server.json b/server/docker/metrics/dashboards/block-node-server.json index 831b956f0..b2686108e 100644 --- a/server/docker/metrics/dashboards/block-node-server.json +++ b/server/docker/metrics/dashboards/block-node-server.json @@ -21,7 +21,7 @@ "links": [], "panels": [ { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, @@ -29,10 +29,348 @@ "y": 0 }, "id": 16, - "panels": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 33 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_stream_mediator_error_total", + "instant": false, + "legendFormat": "Block Item Errors", + "range": true, + "refId": "A" + } + ], + "title": "Mediator Errors", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 33 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_stream_mediator_error_total [$__rate_interval])", + "instant": false, + "legendFormat": "Mediator Errors", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Mediator Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 39 + }, + "id": 28, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "hedera_block_node_stream_persistence_handler_error_total", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Stream Persistence Errors", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 39 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_stream_persistence_handler_error_total [$__rate_interval])", + "instant": false, + "legendFormat": "Stream Persistence Errors", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Stream Persistence Errors", + "type": "timeseries" + } + ], "title": "Errors", "type": "row" }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 9, + "panels": [], + "title": "Live Stream", + "type": "row" + }, { "datasource": { "type": "prometheus", @@ -50,10 +388,6 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 1 } ] }, @@ -62,12 +396,12 @@ "overrides": [] }, "gridPos": { - "h": 6, + "h": 5, "w": 6, "x": 0, - "y": 1 + "y": 2 }, - "id": 15, + "id": 13, "options": { "colorMode": "value", "graphMode": "none", @@ -93,14 +427,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "hedera_block_node_live_block_stream_mediator_error_total", + "expr": "hedera_block_node_live_block_items_received_total", "instant": false, - "legendFormat": "Block Item Errors", + "legendFormat": "BlockItems", "range": true, "refId": "A" } ], - "title": "Mediator Errors", + "title": "Live Block Items Received from Producer", "type": "stat" }, { @@ -108,6 +442,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -150,12 +485,16 @@ "mode": "absolute", "steps": [ { - "color": "green", + "color": "red", "value": null }, { - "color": "red", - "value": 1 + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 } ] }, @@ -164,12 +503,12 @@ "overrides": [] }, "gridPos": { - "h": 6, + "h": 5, "w": 18, "x": 6, - "y": 1 + "y": 2 }, - "id": 14, + "id": 12, "options": { "legend": { "calcs": [], @@ -189,14 +528,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_stream_mediator_error_total [$__rate_interval])", + "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", "instant": false, - "legendFormat": "Mediator Errors", + "legendFormat": "BlockItems", "range": true, "refId": "A" } ], - "title": "Rate of Mediator Errors", + "title": "Rate of Live Block Items Received from Producer", "type": "timeseries" }, { @@ -216,10 +555,6 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 1 } ] }, @@ -228,15 +563,15 @@ "overrides": [] }, "gridPos": { - "h": 7, + "h": 5, "w": 6, "x": 0, "y": 7 }, - "id": 28, + "id": 3, "options": { "colorMode": "value", - "graphMode": "area", + "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", @@ -259,14 +594,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "hedera_block_node_stream_persistence_handler_error_total", + "expr": "hedera_block_node_live_block_items_total", "instant": false, - "legendFormat": "__auto", + "legendFormat": "BlockItems", "range": true, "refId": "A" } ], - "title": "Stream Persistence Errors", + "title": "Live Block Item Counter", "type": "stat" }, { @@ -316,8 +651,16 @@ "mode": "absolute", "steps": [ { - "color": "green", + "color": "dark-red", "value": null + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 } ] }, @@ -326,12 +669,12 @@ "overrides": [] }, "gridPos": { - "h": 7, + "h": 5, "w": 18, "x": 6, "y": 7 }, - "id": 29, + "id": 7, "options": { "legend": { "calcs": [], @@ -344,6 +687,7 @@ "sort": "none" } }, + "pluginVersion": "11.1.3", "targets": [ { "datasource": { @@ -351,29 +695,16 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "rate(hedera_block_node_stream_persistence_handler_error_total [$__rate_interval])", + "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", "instant": false, - "legendFormat": "Stream Persistence Errors", + "legendFormat": "BlockItems", "range": true, "refId": "A" } ], - "title": "Rate of Stream Persistence Errors", + "title": "Rate of Live Block Items Received by Mediator", "type": "timeseries" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 9, - "panels": [], - "title": "Live Stream", - "type": "row" - }, { "datasource": { "type": "prometheus", @@ -402,9 +733,9 @@ "h": 5, "w": 6, "x": 0, - "y": 15 + "y": 12 }, - "id": 13, + "id": 11, "options": { "colorMode": "value", "graphMode": "none", @@ -430,14 +761,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "hedera_block_node_live_block_items_received_total", + "expr": "hedera_block_node_live_block_items_consumed_total", "instant": false, - "legendFormat": "BlockItems", + "legendFormat": "BlockItems Consumed", "range": true, "refId": "A" } ], - "title": "Live Block Items Received from Producer", + "title": "Live Block Items Consumed Counter", "type": "stat" }, { @@ -445,7 +776,6 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -497,7 +827,7 @@ }, { "color": "green", - "value": 30 + "value": 80 } ] }, @@ -509,9 +839,9 @@ "h": 5, "w": 18, "x": 6, - "y": 15 + "y": 12 }, - "id": 12, + "id": 10, "options": { "legend": { "calcs": [], @@ -531,14 +861,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", + "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", "instant": false, "legendFormat": "BlockItems", "range": true, "refId": "A" } ], - "title": "Rate of Live Block Items Received from Producer", + "title": "Rate of Block Items Sent to Consumer(s)", "type": "timeseries" }, { @@ -551,33 +881,43 @@ "color": { "mode": "thresholds" }, + "displayName": "Mediator Remaining Capacity", "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", + "color": "red", "value": null + }, + { + "color": "red", + "value": 1000 + }, + { + "color": "yellow", + "value": 2000 + }, + { + "color": "green", + "value": 4096 } ] - }, - "unit": "short" + } }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 7, "w": 6, "x": 0, - "y": 20 + "y": 17 }, - "id": 3, + "id": 30, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", + "minVizHeight": 75, + "minVizWidth": 75, "orientation": "auto", - "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -585,9 +925,9 @@ "fields": "", "values": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" }, "pluginVersion": "11.1.4", "targets": [ @@ -597,15 +937,15 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "hedera_block_node_live_block_items_total", + "expr": "hedera_block_node_mediator_ring_buffer_remaining_capacity", "instant": false, - "legendFormat": "BlockItems", + "legendFormat": "Mediator Remaining Capacity", "range": true, "refId": "A" } ], - "title": "Live Block Item Counter", - "type": "stat" + "title": "Mediator Remaining Capacity", + "type": "gauge" }, { "datasource": { @@ -646,7 +986,7 @@ "mode": "none" }, "thresholdsStyle": { - "mode": "area" + "mode": "off" } }, "mappings": [], @@ -654,30 +994,25 @@ "mode": "absolute", "steps": [ { - "color": "dark-red", + "color": "green", "value": null }, { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 30 + "color": "red", + "value": 80 } ] - }, - "unit": "reqps" + } }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 7, "w": 18, "x": 6, - "y": 20 + "y": 17 }, - "id": 7, + "id": 32, "options": { "legend": { "calcs": [], @@ -690,22 +1025,25 @@ "sort": "none" } }, - "pluginVersion": "11.1.3", "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "disableTextWrap": false, "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", + "expr": "min(hedera_block_node_mediator_ring_buffer_remaining_capacity)", + "fullMetaSearch": false, + "includeNullMetadata": true, "instant": false, - "legendFormat": "BlockItems", + "legendFormat": "__auto", "range": true, - "refId": "A" + "refId": "A", + "useBackend": false } ], - "title": "Rate of Live Block Items Received by Mediator", + "title": "Mediator Ring Buffer Capacity", "type": "timeseries" }, { @@ -718,33 +1056,43 @@ "color": { "mode": "thresholds" }, + "displayName": "Notifier Remaining Capacity", "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", + "color": "red", "value": null + }, + { + "color": "red", + "value": 200 + }, + { + "color": "#EAB839", + "value": 201 + }, + { + "color": "green", + "value": 501 } ] - }, - "unit": "short" + } }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 7, "w": 6, "x": 0, - "y": 25 + "y": 24 }, - "id": 11, + "id": 31, "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", + "minVizHeight": 75, + "minVizWidth": 75, "orientation": "auto", - "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -752,9 +1100,9 @@ "fields": "", "values": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" }, "pluginVersion": "11.1.4", "targets": [ @@ -764,15 +1112,15 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "hedera_block_node_live_block_items_consumed_total", + "expr": "hedera_block_node_notifier_ring_buffer_remaining_capacity", "instant": false, - "legendFormat": "BlockItems Consumed", + "legendFormat": "Notifier Remaining Capacity", "range": true, "refId": "A" } ], - "title": "Live Block Items Consumed Counter", - "type": "stat" + "title": "Notifier Remaining Capacity", + "type": "gauge" }, { "datasource": { @@ -821,30 +1169,25 @@ "mode": "absolute", "steps": [ { - "color": "red", + "color": "green", "value": null }, { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", + "color": "red", "value": 80 } ] - }, - "unit": "reqps" + } }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 7, "w": 18, "x": 6, - "y": 25 + "y": 24 }, - "id": 10, + "id": 33, "options": { "legend": { "calcs": [], @@ -864,14 +1207,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", + "expr": "min(hedera_block_node_notifier_ring_buffer_remaining_capacity)", "instant": false, - "legendFormat": "BlockItems", + "legendFormat": "__auto", "range": true, "refId": "A" } ], - "title": "Rate of Block Items Sent to Consumer(s)", + "title": "Notifier Ring Buffer Capacity", "type": "timeseries" }, { @@ -911,7 +1254,7 @@ "h": 7, "w": 6, "x": 0, - "y": 30 + "y": 31 }, "id": 25, "options": { @@ -985,7 +1328,7 @@ "h": 7, "w": 6, "x": 6, - "y": 30 + "y": 31 }, "id": 4, "options": { @@ -1027,7 +1370,7 @@ "h": 1, "w": 24, "x": 0, - "y": 37 + "y": 38 }, "id": 18, "panels": [], @@ -1062,7 +1405,7 @@ "h": 6, "w": 6, "x": 0, - "y": 38 + "y": 39 }, "id": 6, "options": { @@ -1139,7 +1482,7 @@ "mode": "none" }, "thresholdsStyle": { - "mode": "area" + "mode": "off" } }, "mappings": [], @@ -1168,7 +1511,7 @@ "h": 6, "w": 18, "x": 6, - "y": 38 + "y": 39 }, "id": 8, "options": { @@ -1207,7 +1550,7 @@ "h": 1, "w": 24, "x": 0, - "y": 44 + "y": 45 }, "id": 23, "panels": [], @@ -1242,7 +1585,7 @@ "h": 6, "w": 6, "x": 0, - "y": 45 + "y": 46 }, "id": 24, "options": { @@ -1344,7 +1687,7 @@ "h": 6, "w": 18, "x": 6, - "y": 45 + "y": 46 }, "id": 22, "options": { @@ -1391,8 +1734,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1403,7 +1745,7 @@ "h": 6, "w": 6, "x": 0, - "y": 51 + "y": 52 }, "id": 27, "options": { @@ -1488,8 +1830,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1501,7 +1842,7 @@ "h": 6, "w": 18, "x": 6, - "y": 51 + "y": 52 }, "id": 26, "options": { @@ -1539,7 +1880,7 @@ "h": 1, "w": 24, "x": 0, - "y": 57 + "y": 58 }, "id": 17, "panels": [], @@ -1561,8 +1902,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1578,7 +1918,7 @@ "h": 6, "w": 6, "x": 0, - "y": 58 + "y": 59 }, "id": 19, "options": { @@ -1663,8 +2003,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1680,7 +2019,7 @@ "h": 6, "w": 18, "x": 6, - "y": 58 + "y": 59 }, "id": 5, "options": { @@ -1743,7 +2082,7 @@ "h": 6, "w": 6, "x": 0, - "y": 64 + "y": 65 }, "id": 21, "options": { @@ -1844,7 +2183,7 @@ "h": 6, "w": 18, "x": 6, - "y": 64 + "y": 65 }, "id": 20, "options": { @@ -1893,4 +2232,4 @@ "uid": "edu86nutnxts0c", "version": 1, "weekStart": "" -} \ No newline at end of file +} diff --git a/server/docker/update-env.sh b/server/docker/update-env.sh index 1c3375ec0..98e282d63 100755 --- a/server/docker/update-env.sh +++ b/server/docker/update-env.sh @@ -18,7 +18,6 @@ echo "VERSION=$project_version" > .env echo "REGISTRY_PREFIX=" >> .env # Storage root path, this is temporary until we have a proper .properties file for all configs echo "BLOCKNODE_STORAGE_ROOT_PATH=/app/storage" >> .env -echo "JAVA_OPTS='-Xms8G -Xmx16G'" >> .env if [ true = "$is_debug" ]; then # wait for debugger to attach @@ -29,6 +28,10 @@ if [ true = "$is_smoke_test" ]; then # add smoke test variables echo "MEDIATOR_RING_BUFFER_SIZE=1024" >> .env echo "NOTIFIER_RING_BUFFER_SIZE=1024" >> .env + echo "JAVA_OPTS='-Xms4G -Xmx4G'" >> .env +else + # Set the production default values + echo "JAVA_OPTS='-Xms16G -Xmx16G'" >> .env fi # Output the values diff --git a/server/src/main/java/com/hedera/block/server/BlockNodeApp.java b/server/src/main/java/com/hedera/block/server/BlockNodeApp.java index f8d9393ab..e5d7723c4 100644 --- a/server/src/main/java/com/hedera/block/server/BlockNodeApp.java +++ b/server/src/main/java/com/hedera/block/server/BlockNodeApp.java @@ -16,17 +16,19 @@ package com.hedera.block.server; +import static com.hedera.block.server.Constants.PBJ_PROTOCOL_PROVIDER_CONFIG_NAME; import static java.lang.System.Logger; import static java.lang.System.Logger.Level.INFO; -import com.hedera.block.server.grpc.BlockAccessService; -import com.hedera.block.server.grpc.BlockStreamService; import com.hedera.block.server.health.HealthService; +import com.hedera.block.server.pbj.PbjBlockAccessService; +import com.hedera.block.server.pbj.PbjBlockStreamService; import com.hedera.block.server.service.ServiceStatus; +import com.hedera.pbj.grpc.helidon.PbjRouting; +import com.hedera.pbj.grpc.helidon.config.PbjConfig; import edu.umd.cs.findbugs.annotations.NonNull; import io.helidon.webserver.WebServer; import io.helidon.webserver.WebServerConfig; -import io.helidon.webserver.grpc.GrpcRouting; import io.helidon.webserver.http.HttpRouting; import java.io.IOException; import javax.inject.Inject; @@ -43,31 +45,31 @@ public class BlockNodeApp { private final ServiceStatus serviceStatus; private final HealthService healthService; - private final BlockStreamService blockStreamService; - private final BlockAccessService blockAccessService; private final WebServerConfig.Builder webServerBuilder; + private final PbjBlockStreamService pbjBlockStreamService; + private final PbjBlockAccessService pbjBlockAccessService; /** * Constructs a new BlockNodeApp with the specified dependencies. * * @param serviceStatus has the status of the service * @param healthService handles the health API requests - * @param blockStreamService handles the block stream requests + * @param pbjBlockStreamService defines the Block Stream services + * @param pbjBlockAccessService defines the Block Access services * @param webServerBuilder used to build the web server and start it - * @param blockAccessService grpc service for block access */ @Inject public BlockNodeApp( @NonNull ServiceStatus serviceStatus, @NonNull HealthService healthService, - @NonNull BlockStreamService blockStreamService, - @NonNull WebServerConfig.Builder webServerBuilder, - @NonNull BlockAccessService blockAccessService) { + @NonNull PbjBlockStreamService pbjBlockStreamService, + @NonNull PbjBlockAccessService pbjBlockAccessService, + @NonNull WebServerConfig.Builder webServerBuilder) { this.serviceStatus = serviceStatus; this.healthService = healthService; - this.blockStreamService = blockStreamService; + this.pbjBlockStreamService = pbjBlockStreamService; + this.pbjBlockAccessService = pbjBlockAccessService; this.webServerBuilder = webServerBuilder; - this.blockAccessService = blockAccessService; } /** @@ -77,16 +79,26 @@ public BlockNodeApp( */ public void start() throws IOException { - final GrpcRouting.Builder grpcRouting = - GrpcRouting.builder().service(blockStreamService).service(blockAccessService); - final HttpRouting.Builder httpRouting = HttpRouting.builder().register(healthService.getHealthRootPath(), healthService); + final PbjRouting.Builder pbjRouting = + PbjRouting.builder().service(pbjBlockStreamService).service(pbjBlockAccessService); + + // Override the default message size + final PbjConfig pbjConfig = PbjConfig.builder() + .name(PBJ_PROTOCOL_PROVIDER_CONFIG_NAME) + .maxMessageSizeBytes(1024 * 4096) + .build(); + // Build the web server // TODO: make port server a configurable value. - final WebServer webServer = - webServerBuilder.port(8080).addRouting(grpcRouting).addRouting(httpRouting).build(); + final WebServer webServer = webServerBuilder + .port(8080) + .addProtocol(pbjConfig) + .addRouting(pbjRouting) + .addRouting(httpRouting) + .build(); // Update the serviceStatus with the web server serviceStatus.setWebServer(webServer); diff --git a/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java index 8d469746c..ca109812e 100644 --- a/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java +++ b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java @@ -21,6 +21,7 @@ import com.hedera.block.server.mediator.MediatorInjectionModule; import com.hedera.block.server.metrics.MetricsInjectionModule; import com.hedera.block.server.notifier.NotifierInjectionModule; +import com.hedera.block.server.pbj.PbjInjectionModule; import com.hedera.block.server.persistence.PersistenceInjectionModule; import com.hedera.block.server.service.ServiceInjectionModule; import com.swirlds.config.api.Configuration; @@ -40,6 +41,7 @@ MediatorInjectionModule.class, ConfigInjectionModule.class, MetricsInjectionModule.class, + PbjInjectionModule.class, }) public interface BlockNodeAppInjectionComponent { /** diff --git a/server/src/main/java/com/hedera/block/server/Constants.java b/server/src/main/java/com/hedera/block/server/Constants.java index 72f085fda..047243a4d 100644 --- a/server/src/main/java/com/hedera/block/server/Constants.java +++ b/server/src/main/java/com/hedera/block/server/Constants.java @@ -21,20 +21,23 @@ public final class Constants { /** Constant mapped to the semantic name of the Block Node root directory */ public static final String BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME = "Block Node Root Directory"; + /** Constant mapped to PbjProtocolProvider.CONFIG_NAME in the PBJ Helidon Plugin */ + public static final String PBJ_PROTOCOL_PROVIDER_CONFIG_NAME = "pbj"; + /** Constant mapped to the name of the BlockStream service in the .proto file */ public static final String SERVICE_NAME_BLOCK_STREAM = "BlockStreamService"; /** Constant mapped to the name of the BlockAccess service in the .proto file */ public static final String SERVICE_NAME_BLOCK_ACCESS = "BlockAccessService"; - /** Constant mapped to the publishBlockStream service method name in the .proto file */ - public static final String CLIENT_STREAMING_METHOD_NAME = "publishBlockStream"; + /** Constant representing the service domain */ + public static final String SERVICE_DOMAIN = "com.hedera.hapi.block."; - /** Constant mapped to the subscribeBlockStream service method name in the .proto file */ - public static final String SERVER_STREAMING_METHOD_NAME = "subscribeBlockStream"; + /** Constant mapped to the full name of the BlockStream service */ + public static final String FULL_SERVICE_NAME_BLOCK_STREAM = SERVICE_DOMAIN + SERVICE_NAME_BLOCK_STREAM; - /** Constant mapped to the singleBlock service method name in the .proto file */ - public static final String SINGLE_BLOCK_METHOD_NAME = "singleBlock"; + /** Constant mapped to the full name of the BlockAccess service */ + public static final String FULL_SERVICE_NAME_BLOCK_ACCESS = SERVICE_DOMAIN + SERVICE_NAME_BLOCK_ACCESS; /** Constant defining the block file extension */ public static final String BLOCK_FILE_EXTENSION = ".blk"; 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 fa9a215d7..89c7dc580 100644 --- a/server/src/main/java/com/hedera/block/server/Server.java +++ b/server/src/main/java/com/hedera/block/server/Server.java @@ -18,6 +18,7 @@ import static com.hedera.block.common.constants.StringsConstants.APPLICATION_PROPERTIES; import static com.hedera.block.common.constants.StringsConstants.LOGGING_PROPERTIES; +import static io.helidon.config.ConfigSources.classpath; import static io.helidon.config.ConfigSources.file; import static java.lang.System.Logger; import static java.lang.System.Logger.Level.INFO; @@ -51,6 +52,7 @@ public static void main(final String[] args) throws IOException { // Set the global configuration final Config config = Config.builder() .sources(file(Paths.get("/app", LOGGING_PROPERTIES)).optional()) + .sources(classpath("helidon.properties")) .build(); Config.global(config); diff --git a/server/src/main/java/com/hedera/block/server/Translator.java b/server/src/main/java/com/hedera/block/server/Translator.java deleted file mode 100644 index ec98baeef..000000000 --- a/server/src/main/java/com/hedera/block/server/Translator.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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.ERROR; -import static java.util.Objects.requireNonNull; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.hapi.block.PublishStreamRequest; -import com.hedera.hapi.block.PublishStreamResponse; -import com.hedera.hapi.block.SingleBlockResponse; -import com.hedera.hapi.block.SubscribeStreamRequest; -import com.hedera.hapi.block.SubscribeStreamResponse; -import com.hedera.pbj.runtime.Codec; -import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.pbj.runtime.io.stream.WritableStreamingData; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Translator class to convert between PBJ and google protoc objects. - * - *

TODO: Remove this class once the Helidon PBJ gRPC work is integrated. - */ -public final class Translator { - private static final Logger LOGGER = System.getLogger(Translator.class.getName()); - - private static final String INVALID_BUFFER_MESSAGE = - "Invalid protocol buffer converting %s from PBJ to protoc for %s"; - - private Translator() {} - - /** - * Converts a {@link SingleBlockResponse} to a {@link - * com.hedera.hapi.block.protoc.SingleBlockResponse}. - * - * @param singleBlockResponse the {@link SingleBlockResponse} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.SingleBlockResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SingleBlockResponse fromPbj( - @NonNull final SingleBlockResponse singleBlockResponse) { - try { - final byte[] pbjBytes = asBytes(SingleBlockResponse.PROTOBUF, singleBlockResponse); - return com.hedera.hapi.block.protoc.SingleBlockResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted("SingleBlockResponse", singleBlockResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link com.hedera.hapi.block.PublishStreamResponse} to a {@link - * com.hedera.hapi.block.protoc.PublishStreamResponse}. - * - * @param publishStreamResponse the {@link com.hedera.hapi.block.PublishStreamResponse} to - * convert - * @return the converted {@link com.hedera.hapi.block.protoc.PublishStreamResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.PublishStreamResponse fromPbj( - @NonNull final com.hedera.hapi.block.PublishStreamResponse publishStreamResponse) { - try { - final byte[] pbjBytes = asBytes(PublishStreamResponse.PROTOBUF, publishStreamResponse); - return com.hedera.hapi.block.protoc.PublishStreamResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "PublishStreamResponse", publishStreamResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link com.hedera.hapi.block.PublishStreamRequest} to a {@link - * com.hedera.hapi.block.protoc.PublishStreamRequest}. - * - * @param publishStreamRequest the {@link com.hedera.hapi.block.PublishStreamRequest} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.PublishStreamRequest} - */ - @NonNull - public static com.hedera.hapi.block.protoc.PublishStreamRequest fromPbj( - @NonNull final com.hedera.hapi.block.PublishStreamRequest publishStreamRequest) { - try { - final byte[] pbjBytes = asBytes(PublishStreamRequest.PROTOBUF, publishStreamRequest); - return com.hedera.hapi.block.protoc.PublishStreamRequest.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted("PublishStreamRequest", publishStreamRequest); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link com.hedera.hapi.block.SubscribeStreamResponse} to a {@link - * com.hedera.hapi.block.protoc.SubscribeStreamResponse}. - * - * @param subscribeStreamResponse the {@link com.hedera.hapi.block.SubscribeStreamResponse} to - * convert - * @return the converted {@link com.hedera.hapi.block.protoc.SubscribeStreamResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SubscribeStreamResponse fromPbj( - @NonNull final com.hedera.hapi.block.SubscribeStreamResponse subscribeStreamResponse) { - try { - final byte[] pbjBytes = - asBytes(SubscribeStreamResponse.PROTOBUF, subscribeStreamResponse); - return com.hedera.hapi.block.protoc.SubscribeStreamResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "SubscribeStreamResponse", subscribeStreamResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link com.hedera.hapi.block.SubscribeStreamRequest} to a {@link - * com.hedera.hapi.block.protoc.SubscribeStreamRequest}. - * - * @param subscribeStreamRequest the {@link com.hedera.hapi.block.SubscribeStreamRequest} to - * convert - * @return the converted {@link com.hedera.hapi.block.protoc.SubscribeStreamRequest} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SubscribeStreamRequest fromPbj( - @NonNull final com.hedera.hapi.block.SubscribeStreamRequest subscribeStreamRequest) { - try { - final byte[] pbjBytes = - asBytes(SubscribeStreamRequest.PROTOBUF, subscribeStreamRequest); - return com.hedera.hapi.block.protoc.SubscribeStreamRequest.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "SubscribeStreamRequest", subscribeStreamRequest); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts protoc bytes to a PBJ record of the same type. - * - * @param the type of PBJ record to convert to - * @param codec the record codec to convert the bytes to a PBJ record - * @param bytes the protoc bytes to convert to a PBJ record - * @return the converted PBJ record - * @throws ParseException if the conversion between the protoc bytes and PBJ objects fails - */ - @NonNull - public static T toPbj( - @NonNull final Codec codec, @NonNull final byte[] bytes) throws ParseException { - return codec.parse(Bytes.wrap(bytes)); - } - - @NonNull - private static byte[] asBytes(@NonNull Codec codec, @NonNull T tx) { - requireNonNull(codec); - requireNonNull(tx); - try { - final var bytes = new ByteArrayOutputStream(); - codec.write(tx, new WritableStreamingData(bytes)); - return bytes.toByteArray(); - } catch (IOException e) { - throw new RuntimeException("Unable to convert from PBJ to bytes", e); - } - } -} diff --git a/server/src/main/java/com/hedera/block/server/config/BlockNodeConfigExtension.java b/server/src/main/java/com/hedera/block/server/config/BlockNodeConfigExtension.java index 870dea54c..198a9ec0b 100644 --- a/server/src/main/java/com/hedera/block/server/config/BlockNodeConfigExtension.java +++ b/server/src/main/java/com/hedera/block/server/config/BlockNodeConfigExtension.java @@ -21,6 +21,7 @@ import com.hedera.block.server.mediator.MediatorConfig; import com.hedera.block.server.notifier.NotifierConfig; import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; +import com.hedera.block.server.producer.ProducerConfig; import com.hedera.block.server.service.ServiceConfig; import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.platform.prometheus.PrometheusConfig; @@ -51,6 +52,7 @@ public Set> getConfigDataTypes() { NotifierConfig.class, MetricsConfig.class, PrometheusConfig.class, + ProducerConfig.class, ConsumerConfig.class, PersistenceStorageConfig.class); } diff --git a/server/src/main/java/com/hedera/block/server/config/ConfigInjectionModule.java b/server/src/main/java/com/hedera/block/server/config/ConfigInjectionModule.java index 2de4a6521..342790d80 100644 --- a/server/src/main/java/com/hedera/block/server/config/ConfigInjectionModule.java +++ b/server/src/main/java/com/hedera/block/server/config/ConfigInjectionModule.java @@ -20,6 +20,7 @@ import com.hedera.block.server.mediator.MediatorConfig; import com.hedera.block.server.notifier.NotifierConfig; import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; +import com.hedera.block.server.producer.ProducerConfig; import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.platform.prometheus.PrometheusConfig; import com.swirlds.config.api.Configuration; @@ -105,4 +106,16 @@ static MediatorConfig provideMediatorConfig(Configuration configuration) { static NotifierConfig provideNotifierConfig(Configuration configuration) { return configuration.getConfigData(NotifierConfig.class); } + + /** + * Provides a producer configuration singleton using the configuration. + * + * @param configuration is the configuration singleton + * @return a producer configuration singleton + */ + @Singleton + @Provides + static ProducerConfig provideProducerConfig(Configuration configuration) { + return configuration.getConfigData(ProducerConfig.class); + } } diff --git a/server/src/main/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserver.java b/server/src/main/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserver.java index 4e36dff26..f0c388694 100644 --- a/server/src/main/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserver.java +++ b/server/src/main/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserver.java @@ -16,7 +16,6 @@ package com.hedera.block.server.consumer; -import static com.hedera.block.server.Translator.fromPbj; import static java.lang.System.Logger; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; @@ -32,27 +31,24 @@ import com.hedera.hapi.block.stream.BlockItem; import com.hedera.pbj.runtime.OneOf; import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; import java.time.InstantSource; import java.util.List; import java.util.Objects; +import java.util.concurrent.Flow; import java.util.concurrent.atomic.AtomicBoolean; /** - * The ConsumerBlockItemObserver class is the primary integration point between the LMAX Disruptor + * The ConsumerStreamResponseObserver class is the primary integration point between the LMAX Disruptor * and an instance of a downstream consumer (represented by subscribeStreamResponseObserver provided * by Helidon). The ConsumerBlockItemObserver implements the BlockNodeEventHandler interface so the * Disruptor can invoke the onEvent() method when a new SubscribeStreamResponse is available. */ -public class ConsumerStreamResponseObserver - implements BlockNodeEventHandler> { +public class ConsumerStreamResponseObserver implements BlockNodeEventHandler> { private final Logger LOGGER = System.getLogger(getClass().getName()); private final MetricsService metricsService; - private final StreamObserver - subscribeStreamResponseObserver; + private final Flow.Subscriber subscribeStreamResponseObserver; private final SubscriptionHandler subscriptionHandler; private final AtomicBoolean isResponsePermitted = new AtomicBoolean(true); @@ -64,18 +60,6 @@ public class ConsumerStreamResponseObserver private final LivenessCalculator livenessCalculator; - /** - * The onCancel handler to execute when the consumer cancels the stream. This handler is - * protected to facilitate testing. - */ - protected Runnable onCancel; - - /** - * The onClose handler to execute when the consumer closes the stream. This handler is protected - * to facilitate testing. - */ - protected Runnable onClose; - /** * Constructor for the ConsumerBlockItemObserver class. It is responsible for observing the * SubscribeStreamResponse events from the Disruptor and passing them to the downstream consumer @@ -91,51 +75,18 @@ public class ConsumerStreamResponseObserver public ConsumerStreamResponseObserver( @NonNull final InstantSource producerLivenessClock, @NonNull final SubscriptionHandler subscriptionHandler, - @NonNull - final StreamObserver - subscribeStreamResponseObserver, + @NonNull final Flow.Subscriber subscribeStreamResponseObserver, @NonNull final BlockNodeContext blockNodeContext) { - this.livenessCalculator = - new LivenessCalculator( - producerLivenessClock, - blockNodeContext - .configuration() - .getConfigData(ConsumerConfig.class) - .timeoutThresholdMillis()); + this.livenessCalculator = new LivenessCalculator( + producerLivenessClock, + blockNodeContext + .configuration() + .getConfigData(ConsumerConfig.class) + .timeoutThresholdMillis()); this.subscriptionHandler = subscriptionHandler; this.metricsService = blockNodeContext.metricsService(); - - // The ServerCallStreamObserver can be configured with Runnable handlers to - // be executed when a downstream consumer closes the connection. The handlers - // unsubscribe this observer. - if (subscribeStreamResponseObserver - instanceof - ServerCallStreamObserver - serverCallStreamObserver) { - - onCancel = - () -> { - // The consumer has cancelled the stream. - // Do not allow additional responses to be sent. - isResponsePermitted.set(false); - subscriptionHandler.unsubscribe(this); - LOGGER.log(DEBUG, "Consumer cancelled the stream. Observer unsubscribed."); - }; - serverCallStreamObserver.setOnCancelHandler(onCancel); - - onClose = - () -> { - // The consumer has closed the stream. - // Do not allow additional responses to be sent. - isResponsePermitted.set(false); - subscriptionHandler.unsubscribe(this); - LOGGER.log(DEBUG, "Consumer completed stream. Observer unsubscribed."); - }; - serverCallStreamObserver.setOnCloseHandler(onClose); - } - this.subscribeStreamResponseObserver = subscribeStreamResponseObserver; } @@ -151,19 +102,14 @@ public ConsumerStreamResponseObserver( * @param b true if the event is the last in the sequence */ @Override - public void onEvent( - @NonNull final ObjectEvent event, - final long l, - final boolean b) { + public void onEvent(@NonNull final ObjectEvent event, final long l, final boolean b) { // Only send the response if the consumer has not cancelled // or closed the stream. if (isResponsePermitted.get()) { if (isTimeoutExpired()) { subscriptionHandler.unsubscribe(this); - LOGGER.log( - DEBUG, - "Producer liveness timeout. Unsubscribed ConsumerBlockItemObserver."); + LOGGER.log(DEBUG, "Producer liveness timeout. Unsubscribed ConsumerBlockItemObserver."); } else { // Refresh the producer liveness and pass the BlockItem to the downstream observer. livenessCalculator.refresh(); @@ -181,16 +127,17 @@ public boolean isTimeoutExpired() { } @NonNull - private ResponseSender getResponseSender( - @NonNull final SubscribeStreamResponse subscribeStreamResponse) { + private ResponseSender getResponseSender(@NonNull final SubscribeStreamResponse subscribeStreamResponse) { - final OneOf responseType = - subscribeStreamResponse.response(); + final OneOf responseType = subscribeStreamResponse.response(); return switch (responseType.kind()) { - case STATUS -> statusResponseSender; + case STATUS -> { + isResponsePermitted.set(false); + subscriptionHandler.unsubscribe(this); + yield statusResponseSender; + } case BLOCK_ITEMS -> blockItemsResponseSender; - default -> throw new IllegalArgumentException( - "Unknown response type: " + responseType.kind()); + default -> throw new IllegalArgumentException("Unknown response type: " + responseType.kind()); }; } @@ -204,12 +151,8 @@ private final class BlockItemsResponseSender implements ResponseSender { public void send(@NonNull final SubscribeStreamResponse subscribeStreamResponse) { if (subscribeStreamResponse.blockItems() == null) { - final String message = - PROTOCOL_VIOLATION_MESSAGE.formatted( - "SubscribeStreamResponse", - "BLOCK_ITEMS", - "block_items", - subscribeStreamResponse); + final String message = PROTOCOL_VIOLATION_MESSAGE.formatted( + "SubscribeStreamResponse", "BLOCK_ITEMS", "block_items", subscribeStreamResponse); LOGGER.log(ERROR, message); throw new IllegalArgumentException(message); } @@ -230,7 +173,7 @@ public void send(@NonNull final SubscribeStreamResponse subscribeStreamResponse) metricsService .get(BlockNodeMetricTypes.Counter.LiveBlockItemsReceived) .add(blockItems.size()); - subscribeStreamResponseObserver.onNext(fromPbj(subscribeStreamResponse)); + subscribeStreamResponseObserver.onNext(subscribeStreamResponse); } } } @@ -239,10 +182,9 @@ public void send(@NonNull final SubscribeStreamResponse subscribeStreamResponse) // status code is fixed. private final class StatusResponseSender implements ResponseSender { public void send(@NonNull final SubscribeStreamResponse subscribeStreamResponse) { - LOGGER.log( - DEBUG, - "Sending SubscribeStreamResponse downstream: " + subscribeStreamResponse); - subscribeStreamResponseObserver.onNext(fromPbj(subscribeStreamResponse)); + LOGGER.log(DEBUG, "Sending SubscribeStreamResponse downstream: " + subscribeStreamResponse); + subscribeStreamResponseObserver.onNext(subscribeStreamResponse); + subscribeStreamResponseObserver.onComplete(); } } } diff --git a/server/src/main/java/com/hedera/block/server/grpc/BlockAccessService.java b/server/src/main/java/com/hedera/block/server/grpc/BlockAccessService.java deleted file mode 100644 index 23652b9b4..000000000 --- a/server/src/main/java/com/hedera/block/server/grpc/BlockAccessService.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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.grpc; - -import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_ACCESS; -import static com.hedera.block.server.Constants.SINGLE_BLOCK_METHOD_NAME; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.Translator.toPbj; -import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SingleBlocksNotFound; -import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SingleBlocksRetrieved; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.ERROR; - -import com.google.protobuf.Descriptors; -import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.block.server.metrics.MetricsService; -import com.hedera.block.server.persistence.storage.read.BlockReader; -import com.hedera.block.server.service.ServiceStatus; -import com.hedera.hapi.block.SingleBlockRequest; -import com.hedera.hapi.block.SingleBlockResponseCode; -import com.hedera.hapi.block.protoc.BlockService; -import com.hedera.hapi.block.protoc.SingleBlockResponse; -import com.hedera.hapi.block.stream.Block; -import com.hedera.pbj.runtime.ParseException; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.StreamObserver; -import io.helidon.webserver.grpc.GrpcService; -import java.io.IOException; -import java.util.Optional; -import javax.inject.Inject; - -/** - * The BlockAccessService class provides a gRPC service to access blocks. - * - *

This service provides a unary gRPC method to retrieve a single block by block number. - */ -public class BlockAccessService implements GrpcService { - private final System.Logger LOGGER = System.getLogger(getClass().getName()); - - private final ServiceStatus serviceStatus; - private final BlockReader blockReader; - private final MetricsService metricsService; - - /** - * Constructs a new BlockAccessService instance with the given dependencies. - * - * @param serviceStatus used to query the service status - * @param blockReader used to retrieve blocks - * @param metricsService used to observe metrics - */ - @Inject - public BlockAccessService( - @NonNull ServiceStatus serviceStatus, - @NonNull BlockReader blockReader, - @NonNull MetricsService metricsService) { - this.serviceStatus = serviceStatus; - this.blockReader = blockReader; - this.metricsService = metricsService; - } - - @Override - public Descriptors.FileDescriptor proto() { - return BlockService.getDescriptor(); - } - - @Override - public String serviceName() { - return SERVICE_NAME_BLOCK_ACCESS; - } - - @Override - public void update(Routing routing) { - routing.unary(SINGLE_BLOCK_METHOD_NAME, this::protocSingleBlock); - } - - void protocSingleBlock( - @NonNull final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest, - @NonNull final StreamObserver singleBlockResponseStreamObserver) { - LOGGER.log(DEBUG, "Executing Unary singleBlock gRPC method"); - - try { - final SingleBlockRequest pbjSingleBlockRequest = - toPbj(SingleBlockRequest.PROTOBUF, singleBlockRequest.toByteArray()); - singleBlock(pbjSingleBlockRequest, singleBlockResponseStreamObserver); - } catch (ParseException e) { - LOGGER.log(ERROR, "Error parsing protoc SingleBlockRequest: {0}", singleBlockRequest); - singleBlockResponseStreamObserver.onNext(buildSingleBlockNotAvailableResponse()); - } - } - - private void singleBlock( - @NonNull final SingleBlockRequest singleBlockRequest, - @NonNull - final StreamObserver - singleBlockResponseStreamObserver) { - - LOGGER.log(DEBUG, "Executing Unary singleBlock gRPC method"); - - if (serviceStatus.isRunning()) { - final long blockNumber = singleBlockRequest.blockNumber(); - try { - final Optional blockOpt = blockReader.read(blockNumber); - if (blockOpt.isPresent()) { - LOGGER.log(DEBUG, "Successfully returning block number: {0}", blockNumber); - singleBlockResponseStreamObserver.onNext( - fromPbjSingleBlockSuccessResponse(blockOpt.get())); - - metricsService.get(SingleBlocksRetrieved).increment(); - } else { - LOGGER.log(DEBUG, "Block number {0} not found", blockNumber); - singleBlockResponseStreamObserver.onNext(buildSingleBlockNotFoundResponse()); - metricsService.get(SingleBlocksNotFound).increment(); - } - } catch (IOException e) { - LOGGER.log(ERROR, "Error reading block number: {0}", blockNumber); - singleBlockResponseStreamObserver.onNext(buildSingleBlockNotAvailableResponse()); - } catch (ParseException e) { - LOGGER.log(ERROR, "Error parsing block number: {0}", blockNumber); - singleBlockResponseStreamObserver.onNext(buildSingleBlockNotAvailableResponse()); - } - } else { - LOGGER.log(ERROR, "Unary singleBlock gRPC method is not currently running"); - singleBlockResponseStreamObserver.onNext(buildSingleBlockNotAvailableResponse()); - } - - // Send the response - singleBlockResponseStreamObserver.onCompleted(); - } - - @NonNull - static com.hedera.hapi.block.protoc.SingleBlockResponse buildSingleBlockNotAvailableResponse() { - final com.hedera.hapi.block.SingleBlockResponse response = - com.hedera.hapi.block.SingleBlockResponse.newBuilder() - .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) - .build(); - - return fromPbj(response); - } - - @NonNull - static com.hedera.hapi.block.protoc.SingleBlockResponse buildSingleBlockNotFoundResponse() - throws InvalidProtocolBufferException { - final com.hedera.hapi.block.SingleBlockResponse response = - com.hedera.hapi.block.SingleBlockResponse.newBuilder() - .status(SingleBlockResponseCode.READ_BLOCK_NOT_FOUND) - .build(); - - return fromPbj(response); - } - - @NonNull - static com.hedera.hapi.block.protoc.SingleBlockResponse fromPbjSingleBlockSuccessResponse( - @NonNull final Block block) { - final com.hedera.hapi.block.SingleBlockResponse singleBlockResponse = - com.hedera.hapi.block.SingleBlockResponse.newBuilder() - .status(SingleBlockResponseCode.READ_BLOCK_SUCCESS) - .block(block) - .build(); - - return fromPbj(singleBlockResponse); - } -} diff --git a/server/src/main/java/com/hedera/block/server/grpc/BlockStreamService.java b/server/src/main/java/com/hedera/block/server/grpc/BlockStreamService.java deleted file mode 100644 index 929fd3e60..000000000 --- a/server/src/main/java/com/hedera/block/server/grpc/BlockStreamService.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.grpc; - -import static com.hedera.block.server.Constants.CLIENT_STREAMING_METHOD_NAME; -import static com.hedera.block.server.Constants.SERVER_STREAMING_METHOD_NAME; -import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_STREAM; -import static com.hedera.block.server.Translator.fromPbj; -import static java.lang.System.Logger; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.ERROR; - -import com.google.protobuf.Descriptors; -import com.hedera.block.server.config.BlockNodeContext; -import com.hedera.block.server.consumer.ConsumerStreamResponseObserver; -import com.hedera.block.server.events.BlockNodeEventHandler; -import com.hedera.block.server.events.ObjectEvent; -import com.hedera.block.server.mediator.LiveStreamMediator; -import com.hedera.block.server.notifier.Notifier; -import com.hedera.block.server.producer.ProducerBlockItemObserver; -import com.hedera.block.server.service.ServiceStatus; -import com.hedera.hapi.block.SubscribeStreamResponse; -import com.hedera.hapi.block.SubscribeStreamResponseCode; -import com.hedera.hapi.block.protoc.BlockService; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.StreamObserver; -import io.helidon.webserver.grpc.GrpcService; -import java.time.Clock; -import javax.inject.Inject; - -/** - * The BlockStreamService class defines the gRPC service for the block stream service. It provides - * the implementation for the bidirectional streaming, server streaming as defined in the proto file. - */ -public class BlockStreamService implements GrpcService { - - private final Logger LOGGER = System.getLogger(getClass().getName()); - - private final LiveStreamMediator streamMediator; - private final ServiceStatus serviceStatus; - private final BlockNodeContext blockNodeContext; - - private final Notifier notifier; - - /** - * Constructor for the BlockStreamService class. It initializes the BlockStreamService with the - * given parameters. - * - * @param streamMediator the stream mediator to proxy block items from the producer to the - * subscribers and manage the subscription lifecycle for subscribers - * @param serviceStatus the service status provides methods to check service availability and to - * stop the service and web server in the event of an unrecoverable exception - */ - @Inject - BlockStreamService( - @NonNull final LiveStreamMediator streamMediator, - @NonNull final ServiceStatus serviceStatus, - @NonNull - final BlockNodeEventHandler> - streamPersistenceHandler, - @NonNull final Notifier notifier, - @NonNull final BlockNodeContext blockNodeContext) { - this.serviceStatus = serviceStatus; - this.notifier = notifier; - this.blockNodeContext = blockNodeContext; - - streamMediator.subscribe(streamPersistenceHandler); - this.streamMediator = streamMediator; - } - - /** - * Returns the proto descriptor for the BlockStreamService. This descriptor corresponds to the - * proto file for the BlockStreamService. - * - * @return the proto descriptor for the BlockStreamService - */ - @NonNull - @Override - public Descriptors.FileDescriptor proto() { - return BlockService.getDescriptor(); - } - - /** - * Returns the service name for the BlockStreamService. This service name corresponds to the - * service name in the proto file. - * - * @return the service name corresponding to the service name in the proto file - */ - @NonNull - @Override - public String serviceName() { - return SERVICE_NAME_BLOCK_STREAM; - } - - /** - * Updates the routing definitions for the BlockStreamService. It establishes the bidirectional - * streaming method for publishBlockStream, server streaming method for subscribeBlockStream and - * a unary method for singleBlock. - * - * @param routing the routing for the BlockStreamService - */ - @Override - public void update(@NonNull final Routing routing) { - routing.bidi(CLIENT_STREAMING_METHOD_NAME, this::protocPublishBlockStream); - routing.serverStream(SERVER_STREAMING_METHOD_NAME, this::protocSubscribeBlockStream); - } - - StreamObserver protocPublishBlockStream( - @NonNull - final StreamObserver - publishStreamResponseObserver) { - LOGGER.log(DEBUG, "Executing bidirectional publishBlockStream gRPC method"); - - // Unsubscribe any expired notifiers - notifier.unsubscribeAllExpired(); - - final var producerBlockItemObserver = - new ProducerBlockItemObserver( - Clock.systemDefaultZone(), - streamMediator, - notifier, - publishStreamResponseObserver, - blockNodeContext, - serviceStatus); - - // Register the producer observer with the notifier to publish responses back to the - // producer - notifier.subscribe(producerBlockItemObserver); - - return producerBlockItemObserver; - } - - void protocSubscribeBlockStream( - @NonNull - final com.hedera.hapi.block.protoc.SubscribeStreamRequest - subscribeStreamRequest, - @NonNull - final StreamObserver - subscribeStreamResponseObserver) { - LOGGER.log(DEBUG, "Executing Server Streaming subscribeBlockStream gRPC method"); - - if (serviceStatus.isRunning()) { - // Unsubscribe any expired notifiers - streamMediator.unsubscribeAllExpired(); - - final var consumerStreamResponseObserver = - new ConsumerStreamResponseObserver( - Clock.systemDefaultZone(), - streamMediator, - subscribeStreamResponseObserver, - blockNodeContext); - - streamMediator.subscribe(consumerStreamResponseObserver); - } else { - LOGGER.log( - ERROR, - "Server Streaming subscribeBlockStream gRPC Service is not currently running"); - - subscribeStreamResponseObserver.onNext(buildSubscribeStreamNotAvailableResponse()); - } - } - - // TODO: Fix this error type once it's been standardized in `hedera-protobufs` - // this should not be success - @NonNull - static com.hedera.hapi.block.protoc.SubscribeStreamResponse - buildSubscribeStreamNotAvailableResponse() { - final SubscribeStreamResponse response = - SubscribeStreamResponse.newBuilder() - .status(SubscribeStreamResponseCode.READ_STREAM_SUCCESS) - .build(); - - return fromPbj(response); - } -} diff --git a/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java b/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java index c296556fe..a9e6551e9 100644 --- a/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java +++ b/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java @@ -19,6 +19,7 @@ import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItems; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockStreamMediatorError; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Gauge.Consumers; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Gauge.MediatorRingBufferRemainingCapacity; import static java.lang.System.Logger; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; @@ -108,6 +109,9 @@ public void publish(@NonNull final List blockItems) { .build(); ringBuffer.publishEvent((event, sequence) -> event.set(subscribeStreamResponse)); + long remainingCapacity = ringBuffer.remainingCapacity(); + metricsService.get(MediatorRingBufferRemainingCapacity).set(remainingCapacity); + // Increment the block item counter by all block items published metricsService.get(LiveBlockItems).add(blockItems.size()); diff --git a/server/src/main/java/com/hedera/block/server/mediator/MediatorConfig.java b/server/src/main/java/com/hedera/block/server/mediator/MediatorConfig.java index 4b7f5e539..b0dc7dc28 100644 --- a/server/src/main/java/com/hedera/block/server/mediator/MediatorConfig.java +++ b/server/src/main/java/com/hedera/block/server/mediator/MediatorConfig.java @@ -27,9 +27,14 @@ *

MediatorConfig will set the ring buffer size for the mediator. * * @param ringBufferSize the size of the ring buffer used by the mediator + * @param type use a predefined type string to replace the mediator component implementation. + * Non-PRODUCTION values should only be used for troubleshooting and development purposes. */ +// 131072 works but not with persistence @ConfigData("mediator") -public record MediatorConfig(@ConfigProperty(defaultValue = "67108864") int ringBufferSize) { +public record MediatorConfig( + @ConfigProperty(defaultValue = "4194304") int ringBufferSize, + @ConfigProperty(defaultValue = "PRODUCTION") String type) { private static final System.Logger LOGGER = System.getLogger(MediatorConfig.class.getName()); /** @@ -47,5 +52,6 @@ public record MediatorConfig(@ConfigProperty(defaultValue = "67108864") int ring } LOGGER.log(INFO, "Mediator configuration mediator.ringBufferSize: " + ringBufferSize); + LOGGER.log(INFO, "Mediator configuration mediator.type: " + type); } } diff --git a/server/src/main/java/com/hedera/block/server/mediator/MediatorInjectionModule.java b/server/src/main/java/com/hedera/block/server/mediator/MediatorInjectionModule.java index 7a8c22900..95644ce5e 100644 --- a/server/src/main/java/com/hedera/block/server/mediator/MediatorInjectionModule.java +++ b/server/src/main/java/com/hedera/block/server/mediator/MediatorInjectionModule.java @@ -41,7 +41,16 @@ public interface MediatorInjectionModule { @Singleton static LiveStreamMediator providesLiveStreamMediator( @NonNull BlockNodeContext blockNodeContext, @NonNull ServiceStatus serviceStatus) { - return LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus).build(); + final String mediatorType = blockNodeContext + .configuration() + .getConfigData(MediatorConfig.class) + .type(); + if ("NOOP".equals(mediatorType)) { + return new NoOpLiveStreamMediator(blockNodeContext); + } + + return LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus) + .build(); } /** diff --git a/server/src/main/java/com/hedera/block/server/mediator/NoOpLiveStreamMediator.java b/server/src/main/java/com/hedera/block/server/mediator/NoOpLiveStreamMediator.java new file mode 100644 index 000000000..2cf443d59 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/mediator/NoOpLiveStreamMediator.java @@ -0,0 +1,89 @@ +/* + * 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.mediator; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItems; +import static java.lang.System.Logger.Level.INFO; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.events.BlockNodeEventHandler; +import com.hedera.block.server.events.ObjectEvent; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.hapi.block.SubscribeStreamResponse; +import com.hedera.hapi.block.stream.BlockItem; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * The NoOpLiveStreamMediator class is a stub implementation of the live stream mediator intended for testing + * purposes only. It is designed to isolate the Producer component from downstream components subscribed to + * the ring buffer during testing while still providing metrics and logging for troubleshooting. + */ +public class NoOpLiveStreamMediator implements LiveStreamMediator { + + private final MetricsService metricsService; + + /** + * Creates a new NoOpLiveStreamMediator instance for testing and troubleshooting only. + * + * @param blockNodeContext the block node context + */ + public NoOpLiveStreamMediator(@NonNull final BlockNodeContext blockNodeContext) { + System.getLogger(getClass().getName()).log(INFO, "Using " + getClass().getSimpleName()); + this.metricsService = blockNodeContext.metricsService(); + } + + /** + * {@inheritDoc} + */ + @Override + public void publish(@NonNull List blockItems) { + metricsService.get(LiveBlockItems).add(blockItems.size()); + } + + /** + * {@inheritDoc} + */ + @Override + public void subscribe(@NonNull BlockNodeEventHandler> handler) {} + + /** + * {@inheritDoc} + */ + @Override + public void unsubscribe(@NonNull BlockNodeEventHandler> handler) {} + + /** + * {@inheritDoc} + */ + @Override + public boolean isSubscribed(@NonNull BlockNodeEventHandler> handler) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void unsubscribeAllExpired() {} + + /** + * {@inheritDoc} + */ + @Override + public void notifyUnrecoverableError() {} +} diff --git a/server/src/main/java/com/hedera/block/server/metrics/BlockNodeMetricTypes.java b/server/src/main/java/com/hedera/block/server/metrics/BlockNodeMetricTypes.java index a7aafc0a4..50c582269 100644 --- a/server/src/main/java/com/hedera/block/server/metrics/BlockNodeMetricTypes.java +++ b/server/src/main/java/com/hedera/block/server/metrics/BlockNodeMetricTypes.java @@ -42,12 +42,10 @@ public enum Counter implements MetricMetadata { LiveBlockItems("live_block_items", "Live BlockItems"), /** The number of PublishStreamResponses generated and published to the subscribers. */ - SuccessfulPubStreamResp( - "successful_pub_stream_resp", "Successful Publish Stream Responses"), + SuccessfulPubStreamResp("successful_pub_stream_resp", "Successful Publish Stream Responses"), /** The number of PublishStreamResponses sent to the producers. */ - SuccessfulPubStreamRespSent( - "successful_pub_stream_resp_sent", "Successful Publish Stream Responses Sent"), + SuccessfulPubStreamRespSent("successful_pub_stream_resp_sent", "Successful Publish Stream Responses Sent"), /** The number of blocks persisted to storage. */ BlocksPersisted("blocks_persisted", "Blocks Persisted"), @@ -64,12 +62,10 @@ public enum Counter implements MetricMetadata { // Error counters /** The number of errors encountered by the live block stream mediator. */ - LiveBlockStreamMediatorError( - "live_block_stream_mediator_error", "Live Block Stream Mediator Error"), + LiveBlockStreamMediatorError("live_block_stream_mediator_error", "Live Block Stream Mediator Error"), /** The number of errors encountered by the stream persistence handler. */ - StreamPersistenceHandlerError( - "stream_persistence_handler_error", "Stream Persistence Handler Error"); + StreamPersistenceHandlerError("stream_persistence_handler_error", "Stream Persistence Handler Error"); private final String grafanaLabel; private final String description; @@ -104,7 +100,15 @@ public enum Gauge implements MetricMetadata { Consumers("consumers", "Consumers"), /** The number of producers publishing block items. */ - Producers("producers", "Producers"); + Producers("producers", "Producers"), + + /** The amount of capacity remaining in the mediator ring buffer. */ + MediatorRingBufferRemainingCapacity( + "mediator_ring_buffer_remaining_capacity", "Mediator Ring Buffer Remaining Capacity"), + + /** The amount of capacity remaining in the notifier ring buffer. */ + NotifierRingBufferRemainingCapacity( + "notifier_ring_buffer_remaining_capacity", "Notifier Ring Buffer Remaining Capacity"); private final String grafanaLabel; private final String description; diff --git a/server/src/main/java/com/hedera/block/server/notifier/NotifierConfig.java b/server/src/main/java/com/hedera/block/server/notifier/NotifierConfig.java index 1100aa505..a55ac0081 100644 --- a/server/src/main/java/com/hedera/block/server/notifier/NotifierConfig.java +++ b/server/src/main/java/com/hedera/block/server/notifier/NotifierConfig.java @@ -29,7 +29,7 @@ * @param ringBufferSize the size of the ring buffer used by the notifier */ @ConfigData("notifier") -public record NotifierConfig(@ConfigProperty(defaultValue = "2048") int ringBufferSize) { +public record NotifierConfig(@ConfigProperty(defaultValue = "1024") int ringBufferSize) { private static final System.Logger LOGGER = System.getLogger(NotifierConfig.class.getName()); /** diff --git a/server/src/main/java/com/hedera/block/server/notifier/NotifierImpl.java b/server/src/main/java/com/hedera/block/server/notifier/NotifierImpl.java index 4023cf9e4..3537913e3 100644 --- a/server/src/main/java/com/hedera/block/server/notifier/NotifierImpl.java +++ b/server/src/main/java/com/hedera/block/server/notifier/NotifierImpl.java @@ -17,6 +17,7 @@ package com.hedera.block.server.notifier; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SuccessfulPubStreamResp; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Gauge.NotifierRingBufferRemainingCapacity; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Gauge.Producers; import static com.hedera.block.server.producer.Util.getFakeHash; import static java.lang.System.Logger.Level.ERROR; @@ -49,8 +50,7 @@ * will stop the server in the event of an unrecoverable error. */ @Singleton -public class NotifierImpl extends SubscriptionHandlerBase - implements Notifier { +public class NotifierImpl extends SubscriptionHandlerBase implements Notifier { private final System.Logger LOGGER = System.getLogger(getClass().getName()); @@ -114,12 +114,12 @@ public void publish(@NonNull List blockItems) { try { if (serviceStatus.isRunning()) { // Publish the block item to the subscribers - final var publishStreamResponse = - PublishStreamResponse.newBuilder() - .acknowledgement(buildAck(blockItems)) - .build(); + final var publishStreamResponse = PublishStreamResponse.newBuilder() + .acknowledgement(buildAck(blockItems)) + .build(); ringBuffer.publishEvent((event, sequence) -> event.set(publishStreamResponse)); + metricsService.get(NotifierRingBufferRemainingCapacity).set(ringBuffer.remainingCapacity()); metricsService.get(SuccessfulPubStreamResp).increment(); } else { LOGGER.log(ERROR, "Notifier is not running."); @@ -141,10 +141,9 @@ public void publish(@NonNull List blockItems) { @NonNull static PublishStreamResponse buildErrorStreamResponse() { // TODO: Replace this with a real error enum. - final EndOfStream endOfStream = - EndOfStream.newBuilder() - .status(PublishStreamResponseCode.STREAM_ITEMS_UNKNOWN) - .build(); + final EndOfStream endOfStream = EndOfStream.newBuilder() + .status(PublishStreamResponseCode.STREAM_ITEMS_UNKNOWN) + .build(); return PublishStreamResponse.newBuilder().status(endOfStream).build(); } @@ -156,13 +155,11 @@ static PublishStreamResponse buildErrorStreamResponse() { * @throws NoSuchAlgorithmException if the hash algorithm is not supported */ @NonNull - Acknowledgement buildAck(@NonNull final List blockItems) - throws NoSuchAlgorithmException { - final ItemAcknowledgement itemAck = - ItemAcknowledgement.newBuilder() - // TODO: Replace this with a real hash generator - .itemsHash(Bytes.wrap(getFakeHash(blockItems))) - .build(); + Acknowledgement buildAck(@NonNull final List blockItems) throws NoSuchAlgorithmException { + final ItemAcknowledgement itemAck = ItemAcknowledgement.newBuilder() + // TODO: Replace this with a real hash generator + .itemsHash(Bytes.wrap(getFakeHash(blockItems))) + .build(); return Acknowledgement.newBuilder().itemAck(itemAck).build(); } diff --git a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessService.java b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessService.java new file mode 100644 index 000000000..83dce954f --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessService.java @@ -0,0 +1,73 @@ +/* + * 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.pbj; + +import static com.hedera.block.server.Constants.FULL_SERVICE_NAME_BLOCK_ACCESS; +import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_ACCESS; + +import com.hedera.pbj.runtime.grpc.ServiceInterface; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Arrays; +import java.util.List; + +/** + * The PbjBlockAccessService interface provides type definitions and default method + * implementations for PBJ to route gRPC requests to the block access services. + */ +public interface PbjBlockAccessService extends ServiceInterface { + + /** + * BlockAccessMethod types define the gRPC methods available on the BlockAccessService. + */ + enum BlockAccessMethod implements Method { + /** + * The singleBlock method represents the unary gRPC method + * consumers should use to get specific Blocks from the Block Node. + */ + singleBlock + } + + /** + * Streams the block item. + * + * @return the block item + */ + @NonNull + default String serviceName() { + return SERVICE_NAME_BLOCK_ACCESS; + } + + /** + * Provides the full name of the BlockStreamService. + * + * @return the full name of the BlockStreamService. + */ + @NonNull + default String fullName() { + return FULL_SERVICE_NAME_BLOCK_ACCESS; + } + + /** + * Provides the methods of the methods on the BlockStreamService. + * + * @return the methods of the BlockStreamService. + */ + @NonNull + default List methods() { + return Arrays.asList(PbjBlockAccessService.BlockAccessMethod.values()); + } +} diff --git a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java new file mode 100644 index 000000000..2f9aecb00 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java @@ -0,0 +1,157 @@ +/* + * 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.pbj; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SingleBlocksNotFound; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SingleBlocksRetrieved; +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.persistence.storage.read.BlockReader; +import com.hedera.block.server.service.ServiceStatus; +import com.hedera.hapi.block.SingleBlockRequest; +import com.hedera.hapi.block.SingleBlockResponse; +import com.hedera.hapi.block.SingleBlockResponseCode; +import com.hedera.hapi.block.stream.Block; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.grpc.Pipeline; +import com.hedera.pbj.runtime.grpc.Pipelines; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.Flow; +import javax.inject.Inject; + +/** + * PbjBlockAccessServiceProxy is the runtime binding between the PBJ Helidon Plugin and the + * Block Node. The Helidon Plugin routes inbound requests to this class based on the methods + * and service names in PbjBlockAccessService. Service implementations are instantiated via + * the open method thereby bridging the client requests into the Block Node application. + */ +public class PbjBlockAccessServiceProxy implements PbjBlockAccessService { + + private final System.Logger LOGGER = System.getLogger(getClass().getName()); + + private final ServiceStatus serviceStatus; + private final BlockReader blockReader; + private final MetricsService metricsService; + + /** + * Creates a new PbjBlockAccessServiceProxy instance. + * + * @param serviceStatus the service status + * @param blockReader the block reader + * @param blockNodeContext the block node context + */ + @Inject + public PbjBlockAccessServiceProxy( + @NonNull final ServiceStatus serviceStatus, + @NonNull final BlockReader blockReader, + @NonNull final BlockNodeContext blockNodeContext) { + this.serviceStatus = serviceStatus; + this.blockReader = blockReader; + this.metricsService = blockNodeContext.metricsService(); + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public Pipeline open( + final @NonNull Method method, + final @NonNull RequestOptions options, + final @NonNull Flow.Subscriber replies) { + + try { + final var m = (BlockAccessMethod) method; + return switch (m) { + case singleBlock -> Pipelines.unary() + .mapRequest(bytes -> parseSingleBlockRequest(bytes, options)) + .method(this::singleBlock) + .mapResponse(reply -> createSingleBlockResponse(reply, options)) + .respondTo(replies) + .build(); + }; + } catch (Exception e) { + replies.onError(e); + return Pipelines.noop(); + } + } + + SingleBlockResponse singleBlock(SingleBlockRequest singleBlockRequest) { + + LOGGER.log(DEBUG, "Executing Unary singleBlock gRPC method"); + + if (serviceStatus.isRunning()) { + final long blockNumber = singleBlockRequest.blockNumber(); + try { + final Optional blockOpt = blockReader.read(blockNumber); + if (blockOpt.isPresent()) { + LOGGER.log(DEBUG, "Successfully returning block number: {0}", blockNumber); + metricsService.get(SingleBlocksRetrieved).increment(); + + return SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_SUCCESS) + .block(blockOpt.get()) + .build(); + } else { + LOGGER.log(DEBUG, "Block number {0} not found", blockNumber); + metricsService.get(SingleBlocksNotFound).increment(); + + return SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_FOUND) + .build(); + } + } catch (IOException e) { + LOGGER.log(ERROR, "Error reading block number: {0}", blockNumber); + + return SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) + .build(); + } catch (ParseException e) { + LOGGER.log(ERROR, "Error parsing block number: {0}", blockNumber); + + return SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) + .build(); + } + } else { + LOGGER.log(ERROR, "Unary singleBlock gRPC method is not currently running"); + + return SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) + .build(); + } + } + + @NonNull + private SingleBlockRequest parseSingleBlockRequest( + @NonNull final Bytes message, @NonNull final RequestOptions options) throws ParseException { + // TODO: Copying bytes to avoid using references passed from Helidon. Investigate if this is necessary. + return SingleBlockRequest.PROTOBUF.parse(Bytes.wrap(message.toByteArray())); + } + + @NonNull + private Bytes createSingleBlockResponse( + @NonNull final SingleBlockResponse reply, @NonNull final RequestOptions options) { + return SingleBlockResponse.PROTOBUF.toBytes(reply); + } +} diff --git a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamService.java b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamService.java new file mode 100644 index 000000000..fe2979168 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamService.java @@ -0,0 +1,79 @@ +/* + * 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.pbj; + +import static com.hedera.block.server.Constants.FULL_SERVICE_NAME_BLOCK_STREAM; +import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_STREAM; + +import com.hedera.pbj.runtime.grpc.ServiceInterface; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Arrays; +import java.util.List; + +/** + * The PbjBlockStreamService interface provides type definitions and default method + * implementations for PBJ to route gRPC requests to the block stream services. + */ +public interface PbjBlockStreamService extends ServiceInterface { + + /** + * BlockStreamMethod types define the gRPC methods available on the BlockStreamService. + */ + enum BlockStreamMethod implements Method { + /** + * The publishBlockStream method represents the bidirectional gRPC streaming method + * Consensus Nodes should use to publish the BlockStream to the Block Node. + */ + publishBlockStream, + + /** + * The subscribeBlockStream method represents the server-streaming gRPC method + * consumers should use to subscribe to the BlockStream from the Block Node. + */ + subscribeBlockStream + } + + /** + * Streams the block item. + * + * @return the block item + */ + @NonNull + default String serviceName() { + return SERVICE_NAME_BLOCK_STREAM; + } + + /** + * Provides the full name of the BlockStreamService. + * + * @return the full name of the BlockStreamService. + */ + @NonNull + default String fullName() { + return FULL_SERVICE_NAME_BLOCK_STREAM; + } + + /** + * Provides the methods of the methods on the BlockStreamService. + * + * @return the methods of the BlockStreamService. + */ + @NonNull + default List methods() { + return Arrays.asList(BlockStreamMethod.values()); + } +} diff --git a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java new file mode 100644 index 000000000..ddae0f5eb --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java @@ -0,0 +1,203 @@ +/* + * 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.pbj; + +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.consumer.ConsumerStreamResponseObserver; +import com.hedera.block.server.events.BlockNodeEventHandler; +import com.hedera.block.server.events.ObjectEvent; +import com.hedera.block.server.mediator.LiveStreamMediator; +import com.hedera.block.server.notifier.Notifier; +import com.hedera.block.server.producer.NoOpProducerObserver; +import com.hedera.block.server.producer.ProducerBlockItemObserver; +import com.hedera.block.server.producer.ProducerConfig; +import com.hedera.block.server.service.ServiceStatus; +import com.hedera.hapi.block.PublishStreamRequest; +import com.hedera.hapi.block.PublishStreamResponse; +import com.hedera.hapi.block.SubscribeStreamRequest; +import com.hedera.hapi.block.SubscribeStreamResponse; +import com.hedera.hapi.block.SubscribeStreamResponseCode; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.grpc.Pipeline; +import com.hedera.pbj.runtime.grpc.Pipelines; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Clock; +import java.util.concurrent.Flow; +import javax.inject.Inject; + +/** + * PbjBlockStreamServiceProxy is the runtime binding between the PBJ Helidon Plugin and the + * Block Node. The Helidon Plugin routes inbound requests to this class based on the methods + * and service names in PbjBlockStreamService. Service implementations are instantiated via + * the open method thereby bridging the client requests into the Block Node application. + */ +public class PbjBlockStreamServiceProxy implements PbjBlockStreamService { + + private final System.Logger LOGGER = System.getLogger(getClass().getName()); + + private final LiveStreamMediator streamMediator; + private final ServiceStatus serviceStatus; + private final BlockNodeContext blockNodeContext; + private final Notifier notifier; + + /** + * Creates a new PbjBlockStreamServiceProxy instance. + * + * @param streamMediator the live stream mediator + * @param serviceStatus the service status + * @param streamPersistenceHandler the stream persistence handler + * @param notifier the notifier + * @param blockNodeContext the block node context + */ + @Inject + public PbjBlockStreamServiceProxy( + @NonNull final LiveStreamMediator streamMediator, + @NonNull final ServiceStatus serviceStatus, + @NonNull final BlockNodeEventHandler> streamPersistenceHandler, + @NonNull final Notifier notifier, + @NonNull final BlockNodeContext blockNodeContext) { + this.serviceStatus = serviceStatus; + this.notifier = notifier; + this.blockNodeContext = blockNodeContext; + + streamMediator.subscribe(streamPersistenceHandler); + this.streamMediator = streamMediator; + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public Pipeline open( + final @NonNull Method method, + final @NonNull RequestOptions options, + final @NonNull Flow.Subscriber replies) { + + final var m = (BlockStreamMethod) method; + try { + return switch (m) { + case publishBlockStream -> { + notifier.unsubscribeAllExpired(); + yield Pipelines.bidiStreaming() + .mapRequest(bytes -> parsePublishStreamRequest(bytes, options)) + .method(this::publishBlockStream) + .mapResponse(bytes -> createPublishStreamResponse(bytes, options)) + .respondTo(replies) + .build(); + } + case subscribeBlockStream -> Pipelines + .serverStreaming() + .mapRequest(bytes -> parseSubscribeStreamRequest(bytes, options)) + .method(this::subscribeBlockStream) + .mapResponse(reply -> createSubscribeStreamResponse(reply, options)) + .respondTo(replies) + .build(); + }; + } catch (Exception e) { + replies.onError(e); + return Pipelines.noop(); + } + } + + Flow.Subscriber publishBlockStream( + Flow.Subscriber helidonProducerObserver) { + LOGGER.log(DEBUG, "Executing bidirectional publishBlockStream gRPC method"); + + // Unsubscribe any expired notifiers + notifier.unsubscribeAllExpired(); + + final String observerClassType = blockNodeContext + .configuration() + .getConfigData(ProducerConfig.class) + .type(); + + if ("NOOP".equalsIgnoreCase(observerClassType)) { + // No need to register with the notifier for NOOP + return new NoOpProducerObserver(helidonProducerObserver, blockNodeContext); + } else { + final var producerBlockItemObserver = new ProducerBlockItemObserver( + Clock.systemDefaultZone(), + streamMediator, + notifier, + helidonProducerObserver, + blockNodeContext, + serviceStatus); + + if (serviceStatus.isRunning()) { + // Register the producer observer with the notifier to publish responses back to the + // producer + notifier.subscribe(producerBlockItemObserver); + } + + return producerBlockItemObserver; + } + } + + void subscribeBlockStream( + SubscribeStreamRequest subscribeStreamRequest, + Flow.Subscriber subscribeStreamResponseObserver) { + + LOGGER.log(DEBUG, "Executing Server Streaming subscribeBlockStream gRPC method"); + + if (serviceStatus.isRunning()) { + // Unsubscribe any expired notifiers + streamMediator.unsubscribeAllExpired(); + + final var consumerStreamResponseObserver = new ConsumerStreamResponseObserver( + Clock.systemDefaultZone(), streamMediator, subscribeStreamResponseObserver, blockNodeContext); + + streamMediator.subscribe(consumerStreamResponseObserver); + } else { + LOGGER.log(ERROR, "Server Streaming subscribeBlockStream gRPC Service is not currently running"); + + subscribeStreamResponseObserver.onNext(SubscribeStreamResponse.newBuilder() + .status(SubscribeStreamResponseCode.READ_STREAM_SUCCESS) + .build()); + } + } + + @NonNull + private SubscribeStreamRequest parseSubscribeStreamRequest( + @NonNull final Bytes message, @NonNull final RequestOptions options) throws ParseException { + // TODO: Copying bytes to avoid using references passed from Helidon. Investigate if this is necessary. + return SubscribeStreamRequest.PROTOBUF.parse(Bytes.wrap(message.toByteArray())); + } + + @NonNull + private Bytes createSubscribeStreamResponse( + @NonNull final SubscribeStreamResponse subscribeStreamResponse, @NonNull final RequestOptions options) { + return SubscribeStreamResponse.PROTOBUF.toBytes(subscribeStreamResponse); + } + + @NonNull + private PublishStreamRequest parsePublishStreamRequest( + @NonNull final Bytes message, @NonNull final RequestOptions options) throws ParseException { + // TODO: Copying bytes to avoid using references passed from Helidon. Investigate if this is necessary. + return PublishStreamRequest.PROTOBUF.parse(Bytes.wrap(message.toByteArray())); + } + + @NonNull + private Bytes createPublishStreamResponse( + @NonNull final PublishStreamResponse publishStreamResponse, @NonNull final RequestOptions options) { + return PublishStreamResponse.PROTOBUF.toBytes(publishStreamResponse); + } +} diff --git a/server/src/main/java/com/hedera/block/server/grpc/GrpcServiceInjectionModule.java b/server/src/main/java/com/hedera/block/server/pbj/PbjInjectionModule.java similarity index 66% rename from server/src/main/java/com/hedera/block/server/grpc/GrpcServiceInjectionModule.java rename to server/src/main/java/com/hedera/block/server/pbj/PbjInjectionModule.java index f2463cf24..0020ec28f 100644 --- a/server/src/main/java/com/hedera/block/server/grpc/GrpcServiceInjectionModule.java +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjInjectionModule.java @@ -14,35 +14,35 @@ * limitations under the License. */ -package com.hedera.block.server.grpc; +package com.hedera.block.server.pbj; import dagger.Binds; import dagger.Module; import javax.inject.Singleton; /** - * A Dagger Module for GRPC services that are at the BlockNode Services. + * A Dagger module for providing PBJ dependencies, any specific PBJ should be part of this module. */ @Module -public interface GrpcServiceInjectionModule { +public interface PbjInjectionModule { /** * Provides a block stream service singleton using DI. * - * @param blockStreamService should come from DI + * @param pbjBlockStreamServiceProxy should come from DI * @return a block stream service singleton */ @Singleton @Binds - BlockStreamService bindBlockStreamService(BlockStreamService blockStreamService); + PbjBlockStreamService bindPbjBlockStreamService(PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy); /** * Provides a block access service singleton using DI. * - * @param blockAccessService should come from DI + * @param pbjBlockAccessServiceProxy should come from DI * @return a block access service singleton */ @Singleton @Binds - BlockAccessService bindBlockAccessService(BlockAccessService blockAccessService); + PbjBlockAccessService bindPbjBlockAccessService(PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy); } diff --git a/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java b/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java index 08ebc22b2..28c0f7426 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java +++ b/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java @@ -24,6 +24,7 @@ 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.block.server.persistence.storage.write.NoOpBlockWriter; import com.hedera.hapi.block.SubscribeStreamResponse; import com.hedera.hapi.block.stream.Block; import com.hedera.hapi.block.stream.BlockItem; @@ -47,6 +48,13 @@ public interface PersistenceInjectionModule { @Provides @Singleton static BlockWriter> providesBlockWriter(BlockNodeContext blockNodeContext) { + final String persistenceType = blockNodeContext + .configuration() + .getConfigData(PersistenceStorageConfig.class) + .type(); + if ("NOOP".equalsIgnoreCase(persistenceType)) { + return new NoOpBlockWriter(blockNodeContext); + } try { return BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); } catch (IOException e) { diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java b/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java index 0e626f5de..2ae5711a9 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java @@ -32,9 +32,12 @@ * * @param rootPath provides the root path for saving block data, if you want to override it need to * set it as persistence.storage.rootPath + * @param type use a predefined type string to replace the persistence component implementation. + * Non-PRODUCTION values should only be used for troubleshooting and development purposes. */ @ConfigData("persistence.storage") -public record PersistenceStorageConfig(@ConfigProperty(defaultValue = "") String rootPath) { +public record PersistenceStorageConfig( + @ConfigProperty(defaultValue = "") String rootPath, @ConfigProperty(defaultValue = "PRODUCTION") String type) { private static final System.Logger LOGGER = System.getLogger(PersistenceStorageConfig.class.getName()); /** @@ -63,5 +66,6 @@ public record PersistenceStorageConfig(@ConfigProperty(defaultValue = "") String LOGGER.log(INFO, "Persistence Storage configuration persistence.storage.rootPath: " + path); rootPath = path.toString(); + LOGGER.log(INFO, "Persistence configuration persistence.storage.type: " + type); } } diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java new file mode 100644 index 000000000..bacf8f323 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java @@ -0,0 +1,60 @@ +/* + * 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.persistence.storage.write; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted; +import static java.lang.System.Logger.Level.INFO; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.hapi.block.stream.BlockItem; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +/** + * The NoOpBlockWriter class is a stub implementation of the block writer intended for testing purposes only. It is + * designed to isolate the Producer and Mediator components from storage implementation during testing while still + * providing metrics and logging for troubleshooting. + */ +public class NoOpBlockWriter implements BlockWriter> { + + private final MetricsService metricsService; + + /** + * Creates a new NoOpBlockWriter instance for testing and troubleshooting only. + * + * @param blockNodeContext the block node context + */ + public NoOpBlockWriter(BlockNodeContext blockNodeContext) { + this.metricsService = blockNodeContext.metricsService(); + System.getLogger(getClass().getName()).log(INFO, "Using " + getClass().getSimpleName()); + } + + /** + * {@inheritDoc} + */ + @Override + public Optional> write(@NonNull List blockItems) throws IOException { + if (blockItems.getLast().hasBlockProof()) { + metricsService.get(BlocksPersisted).increment(); + } + + return Optional.empty(); + } +} diff --git a/server/src/main/java/com/hedera/block/server/producer/NoOpProducerObserver.java b/server/src/main/java/com/hedera/block/server/producer/NoOpProducerObserver.java new file mode 100644 index 000000000..dcdd61b1d --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/producer/NoOpProducerObserver.java @@ -0,0 +1,92 @@ +/* + * 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.producer; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItemsReceived; +import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.INFO; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.events.BlockNodeEventHandler; +import com.hedera.block.server.events.ObjectEvent; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.hapi.block.PublishStreamRequest; +import com.hedera.hapi.block.PublishStreamResponse; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.Flow; + +/** + * The NoOpProducerObserver class is a stub implementation of the producer observer intended for testing + * purposes only. It is designed to isolate the Block Node from the Helidon layers during testing while + * still providing metrics and logging for troubleshooting. + */ +public class NoOpProducerObserver + implements Flow.Subscriber, BlockNodeEventHandler> { + + private final System.Logger LOGGER = System.getLogger(getClass().getName()); + private final MetricsService metricsService; + + /** + * Creates a new NoOpProducerObserver instance for testing and troubleshooting only. + * + * @param publishStreamResponseObserver the stream response observer provided by Helidon + * @param blockNodeContext the block node context + */ + public NoOpProducerObserver( + @NonNull final Flow.Subscriber publishStreamResponseObserver, + @NonNull final BlockNodeContext blockNodeContext) { + LOGGER.log(INFO, "Using " + getClass().getName()); + this.metricsService = blockNodeContext.metricsService(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onNext(PublishStreamRequest publishStreamRequest) { + metricsService + .get(LiveBlockItemsReceived) + .add(publishStreamRequest.blockItems().blockItems().size()); + } + + /** + * {@inheritDoc} + */ + @Override + public void onEvent(ObjectEvent publishStreamResponseObjectEvent, long l, boolean b) + throws Exception {} + + /** + * {@inheritDoc} + */ + @Override + public void onSubscribe(Flow.Subscription subscription) {} + + /** + * {@inheritDoc} + */ + @Override + public void onError(Throwable throwable) { + LOGGER.log(ERROR, "onError method invoked with an exception: ", throwable); + } + + /** + * {@inheritDoc} + */ + @Override + public void onComplete() {} +} diff --git a/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java b/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java index d651bf162..922e9be4f 100644 --- a/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java +++ b/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java @@ -16,8 +16,6 @@ package com.hedera.block.server.producer; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.Translator.toPbj; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItemsReceived; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.SuccessfulPubStreamRespSent; import static java.lang.System.Logger; @@ -33,18 +31,17 @@ import com.hedera.block.server.mediator.SubscriptionHandler; import com.hedera.block.server.metrics.MetricsService; import com.hedera.block.server.service.ServiceStatus; +import com.hedera.hapi.block.BlockItemSet; import com.hedera.hapi.block.EndOfStream; +import com.hedera.hapi.block.PublishStreamRequest; import com.hedera.hapi.block.PublishStreamResponse; import com.hedera.hapi.block.PublishStreamResponseCode; import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.pbj.runtime.ParseException; -import com.swirlds.metrics.api.Counter; +import com.hedera.pbj.runtime.grpc.Pipeline; import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; import java.time.InstantSource; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Flow; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -54,33 +51,20 @@ * server). */ public class ProducerBlockItemObserver - implements StreamObserver, - BlockNodeEventHandler> { + implements Pipeline, BlockNodeEventHandler> { private final Logger LOGGER = System.getLogger(getClass().getName()); - private final StreamObserver publishStreamResponseObserver; private final SubscriptionHandler subscriptionHandler; private final Publisher> publisher; private final ServiceStatus serviceStatus; private final MetricsService metricsService; + private final Flow.Subscriber publishStreamResponseObserver; private final AtomicBoolean isResponsePermitted = new AtomicBoolean(true); private final LivenessCalculator livenessCalculator; - /** - * The onCancel handler to execute when the producer cancels the stream. This handler is - * protected to facilitate testing. - */ - protected Runnable onCancel; - - /** - * The onClose handler to execute when the producer closes the stream. This handler is protected - * to facilitate testing. - */ - protected Runnable onClose; - /** * Constructor for the ProducerBlockStreamObserver class. It is responsible for calling the * mediator with blocks as they arrive from the upstream producer. It also sends responses back @@ -101,9 +85,7 @@ public ProducerBlockItemObserver( @NonNull final InstantSource producerLivenessClock, @NonNull final Publisher> publisher, @NonNull final SubscriptionHandler subscriptionHandler, - @NonNull - final StreamObserver - publishStreamResponseObserver, + @NonNull final Flow.Subscriber publishStreamResponseObserver, @NonNull final BlockNodeContext blockNodeContext, @NonNull final ServiceStatus serviceStatus) { @@ -119,30 +101,11 @@ public ProducerBlockItemObserver( this.subscriptionHandler = subscriptionHandler; this.metricsService = blockNodeContext.metricsService(); this.serviceStatus = serviceStatus; + } - if (publishStreamResponseObserver - instanceof - ServerCallStreamObserver - serverCallStreamObserver) { - - onCancel = () -> { - // The producer has cancelled the stream. - // Do not allow additional responses to be sent. - isResponsePermitted.set(false); - subscriptionHandler.unsubscribe(this); - LOGGER.log(DEBUG, "Producer cancelled the stream. Observer unsubscribed."); - }; - serverCallStreamObserver.setOnCancelHandler(onCancel); - - onClose = () -> { - // The producer has closed the stream. - // Do not allow additional responses to be sent. - isResponsePermitted.set(false); - subscriptionHandler.unsubscribe(this); - LOGGER.log(DEBUG, "Producer completed the stream. Observer unsubscribed."); - }; - serverCallStreamObserver.setOnCloseHandler(onClose); - } + @Override + public void onSubscribe(Flow.Subscription subscription) { + LOGGER.log(DEBUG, "onSubscribe called"); } /** @@ -153,29 +116,14 @@ public ProducerBlockItemObserver( * @param publishStreamRequest the PublishStreamRequest received from the upstream producer */ @Override - public void onNext(@NonNull final com.hedera.hapi.block.protoc.PublishStreamRequest publishStreamRequest) { - - LOGGER.log(DEBUG, "Received PublishStreamRequest from producer"); - final List blockItemsPbj = new ArrayList<>(); - - final Counter liveBlockItemsReceived = metricsService.get(LiveBlockItemsReceived); - for (final com.hedera.hapi.block.stream.protoc.BlockItem blockItemProtoc : - publishStreamRequest.getBlockItems().getBlockItemsList()) { - try { - final BlockItem blockItem = toPbj(BlockItem.PROTOBUF, blockItemProtoc.toByteArray()); - blockItemsPbj.add(blockItem); - } catch (ParseException e) { - final var errorResponse = buildErrorStreamResponse(); - publishStreamResponseObserver.onNext(errorResponse); - LOGGER.log(ERROR, "Error parsing inbound block item from a producer: " + blockItemProtoc, e); - - // Stop the server - serviceStatus.stopWebServer(getClass().getName()); - } - liveBlockItemsReceived.increment(); - } + public void onNext(@NonNull final PublishStreamRequest publishStreamRequest) { - LOGGER.log(DEBUG, "Received block item batch with {} items.", blockItemsPbj.size()); + final BlockItemSet blockItemSet = publishStreamRequest.blockItems(); + LOGGER.log( + DEBUG, + "Received PublishStreamRequest from producer with " + + blockItemSet.blockItems().size() + " BlockItems."); + metricsService.get(LiveBlockItemsReceived).add(blockItemSet.blockItems().size()); // Publish the block to all the subscribers unless // there's an issue with the StreamMediator. @@ -184,13 +132,16 @@ public void onNext(@NonNull final com.hedera.hapi.block.protoc.PublishStreamRequ livenessCalculator.refresh(); // Publish the block to the mediator - publisher.publish(blockItemsPbj); + publisher.publish(blockItemSet.blockItems()); } else { LOGGER.log(ERROR, getClass().getName() + " is not accepting BlockItems"); // Close the upstream connection to the producer(s) final var errorResponse = buildErrorStreamResponse(); + + isResponsePermitted.set(false); + subscriptionHandler.unsubscribe(this); publishStreamResponseObserver.onNext(errorResponse); LOGGER.log(ERROR, "Error PublishStreamResponse sent to upstream producer"); } @@ -204,20 +155,20 @@ public void onEvent(ObjectEvent event, long sequence, boo subscriptionHandler.unsubscribe(this); LOGGER.log(DEBUG, "Producer liveness timeout. Unsubscribed ProducerBlockItemObserver."); } else { - LOGGER.log(DEBUG, "Publishing response to upstream producer: " + this); - publishStreamResponseObserver.onNext(fromPbj(event.get())); + LOGGER.log(DEBUG, "Publishing response to upstream producer: " + publishStreamResponseObserver); + publishStreamResponseObserver.onNext(event.get()); metricsService.get(SuccessfulPubStreamRespSent).increment(); } } } @NonNull - private static com.hedera.hapi.block.protoc.PublishStreamResponse buildErrorStreamResponse() { + private static PublishStreamResponse buildErrorStreamResponse() { // TODO: Replace this with a real error enum. final EndOfStream endOfStream = EndOfStream.newBuilder() .status(PublishStreamResponseCode.STREAM_ITEMS_UNKNOWN) .build(); - return fromPbj(PublishStreamResponse.newBuilder().status(endOfStream).build()); + return PublishStreamResponse.newBuilder().status(endOfStream).build(); } /** @@ -229,7 +180,10 @@ private static com.hedera.hapi.block.protoc.PublishStreamResponse buildErrorStre @Override public void onError(@NonNull final Throwable t) { LOGGER.log(ERROR, "onError method invoked with an exception: ", t); - publishStreamResponseObserver.onError(t); + + isResponsePermitted.set(false); + subscriptionHandler.unsubscribe(this); + LOGGER.log(ERROR, "Producer cancelled the stream. Observer unsubscribed."); } /** @@ -237,13 +191,23 @@ public void onError(@NonNull final Throwable t) { * completed. Unsubscribe all the observers from the mediator. */ @Override - public void onCompleted() { - LOGGER.log(DEBUG, "ProducerBlockStreamObserver completed"); - publishStreamResponseObserver.onCompleted(); + public void onComplete() { + isResponsePermitted.set(false); + subscriptionHandler.unsubscribe(this); + LOGGER.log(DEBUG, "Producer completed the stream. Observer unsubscribed."); + + publishStreamResponseObserver.onComplete(); } @Override public boolean isTimeoutExpired() { return livenessCalculator.isTimeoutExpired(); } + + @Override + public void clientEndStreamReceived() { + isResponsePermitted.set(false); + subscriptionHandler.unsubscribe(this); + LOGGER.log(DEBUG, "Producer cancelled the stream. Observer unsubscribed."); + } } diff --git a/server/src/main/java/com/hedera/block/server/producer/ProducerConfig.java b/server/src/main/java/com/hedera/block/server/producer/ProducerConfig.java new file mode 100644 index 000000000..d6f53211d --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/producer/ProducerConfig.java @@ -0,0 +1,42 @@ +/* + * 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.producer; + +import static java.lang.System.Logger.Level.INFO; + +import com.swirlds.config.api.ConfigData; +import com.swirlds.config.api.ConfigProperty; + +/** + * Use this configuration across the producer package + * + * @param type use a predefined type string to replace the producer component implementation. + * Non-PRODUCTION values should only be used for troubleshooting and development purposes. + */ +@ConfigData("producer") +public record ProducerConfig(@ConfigProperty(defaultValue = "PRODUCTION") String type) { + private static final System.Logger LOGGER = System.getLogger(ProducerConfig.class.getName()); + + /** + * Creates a new ProducerConfig instance. + * + * @param type the producer type + */ + public ProducerConfig { + LOGGER.log(INFO, "Producer configuration producer.type: " + type); + } +} diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 60f924102..642467866 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -17,11 +17,13 @@ exports com.hedera.block.server.persistence; exports com.hedera.block.server.notifier; exports com.hedera.block.server.service; - exports com.hedera.block.server.grpc; + exports com.hedera.block.server.pbj; + exports com.hedera.block.server.producer; requires com.hedera.block.common; requires com.hedera.block.stream; - requires com.google.protobuf; + requires com.hedera.pbj.grpc.helidon.config; + requires com.hedera.pbj.grpc.helidon; requires com.hedera.pbj.runtime; requires com.lmax.disruptor; requires com.swirlds.common; @@ -29,10 +31,8 @@ requires com.swirlds.config.extensions; requires com.swirlds.metrics.api; requires dagger; - requires io.grpc.stub; requires io.helidon.common; requires io.helidon.config; - requires io.helidon.webserver.grpc; requires io.helidon.webserver; requires javax.inject; requires static com.github.spotbugs.annotations; diff --git a/server/src/main/resources/app.properties b/server/src/main/resources/app.properties index 73dd9fe85..ce2fe915a 100644 --- a/server/src/main/resources/app.properties +++ b/server/src/main/resources/app.properties @@ -1,8 +1,10 @@ prometheus.endpointPortNumber=9999 # Ring buffer sizes for the mediator and notifier -#mediator.ringBufferSize= -#notifier.ringBufferSize= +# with default values. +#mediator.ringBufferSize=4194304 +#notifier.ringBufferSize=1024 -# Timeout for consumers to wait for block item before timing out. Default is 1500 milliseconds. +# Timeout for consumers to wait for block item before timing out. +# Default is 1500 milliseconds. #consumer.timeoutThresholdMillis=1500 diff --git a/server/src/main/resources/helidon.properties b/server/src/main/resources/helidon.properties new file mode 100644 index 000000000..54dcb102b --- /dev/null +++ b/server/src/main/resources/helidon.properties @@ -0,0 +1,2 @@ +#server.receive-buffer-size=16384 +#server.send-buffer-size=16384 diff --git a/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java b/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java index 9a78ada39..81ea98e50 100644 --- a/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java +++ b/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java @@ -20,46 +20,77 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.hedera.block.server.grpc.BlockAccessService; -import com.hedera.block.server.grpc.BlockStreamService; +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.events.BlockNodeEventHandler; +import com.hedera.block.server.events.ObjectEvent; import com.hedera.block.server.health.HealthService; +import com.hedera.block.server.mediator.LiveStreamMediator; +import com.hedera.block.server.notifier.Notifier; +import com.hedera.block.server.pbj.PbjBlockAccessServiceProxy; +import com.hedera.block.server.pbj.PbjBlockStreamServiceProxy; +import com.hedera.block.server.persistence.storage.read.BlockReader; import com.hedera.block.server.service.ServiceStatus; +import com.hedera.hapi.block.SubscribeStreamResponse; +import com.hedera.hapi.block.stream.Block; +import com.hedera.pbj.grpc.helidon.PbjRouting; +import com.hedera.pbj.grpc.helidon.config.PbjConfig; import io.helidon.webserver.WebServer; import io.helidon.webserver.WebServerConfig; -import io.helidon.webserver.grpc.GrpcRouting; import io.helidon.webserver.http.HttpRouting; import java.io.IOException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class BlockNodeAppTest { - @Mock private ServiceStatus serviceStatus; + @Mock + private ServiceStatus serviceStatus; - @Mock private HealthService healthService; + @Mock + private HealthService healthService; - @Mock private BlockStreamService blockStreamService; + @Mock + private WebServerConfig.Builder webServerBuilder; - @Mock private BlockAccessService blockAccessService; + @Mock + private WebServer webServer; - @Mock private WebServerConfig.Builder webServerBuilder; + @Mock + private LiveStreamMediator liveStreamMediator; - @Mock private WebServer webServer; + @Mock + private BlockReader blockReader; - @InjectMocks private BlockNodeApp blockNodeApp; + @Mock + private BlockNodeEventHandler> blockNodeEventHandler; + + @Mock + private Notifier notifier; + + @Mock + private BlockNodeContext blockNodeContext; + + private BlockNodeApp blockNodeApp; @BeforeEach void setup() { + + blockNodeApp = new BlockNodeApp( + serviceStatus, + healthService, + new PbjBlockStreamServiceProxy( + liveStreamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext), + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext), + webServerBuilder); + when(webServerBuilder.port(8080)).thenReturn(webServerBuilder); - when(webServerBuilder.addRouting(any(GrpcRouting.Builder.class))) - .thenReturn(webServerBuilder); - when(webServerBuilder.addRouting(any(HttpRouting.Builder.class))) - .thenReturn(webServerBuilder); + when(webServerBuilder.addProtocol(any(PbjConfig.class))).thenReturn(webServerBuilder); + when(webServerBuilder.addRouting(any(PbjRouting.Builder.class))).thenReturn(webServerBuilder); + when(webServerBuilder.addRouting(any(HttpRouting.Builder.class))).thenReturn(webServerBuilder); when(webServerBuilder.build()).thenReturn(webServer); when(healthService.getHealthRootPath()).thenReturn("/health"); } @@ -74,8 +105,9 @@ void testStartServer() throws IOException { verify(webServer).start(); verify(healthService).getHealthRootPath(); verify(webServerBuilder).port(8080); - verify(webServerBuilder).addRouting(any(GrpcRouting.Builder.class)); + verify(webServerBuilder).addRouting(any(PbjRouting.Builder.class)); verify(webServerBuilder).addRouting(any(HttpRouting.Builder.class)); + verify(webServerBuilder).addProtocol(any(PbjConfig.class)); verify(webServerBuilder).build(); } } diff --git a/server/src/test/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserverTest.java b/server/src/test/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserverTest.java index 184903c60..d70c18cf8 100644 --- a/server/src/test/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserverTest.java +++ b/server/src/test/java/com/hedera/block/server/consumer/ConsumerStreamResponseObserverTest.java @@ -16,10 +16,7 @@ package com.hedera.block.server.consumer; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -36,12 +33,9 @@ import com.hedera.hapi.block.stream.input.EventHeader; import com.hedera.hapi.block.stream.output.BlockHeader; import com.hedera.hapi.platform.event.EventCore; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; +import com.hedera.pbj.runtime.grpc.Pipeline; import java.io.IOException; import java.time.InstantSource; -import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -60,14 +54,11 @@ public class ConsumerStreamResponseObserverTest { private StreamMediator streamMediator; @Mock - private StreamObserver responseStreamObserver; + private Pipeline responseStreamObserver; @Mock private ObjectEvent objectEvent; - @Mock - private ServerCallStreamObserver serverCallStreamObserver; - @Mock private InstantSource testClock; @@ -99,7 +90,7 @@ public void testProducerTimeoutWithinWindow() { consumerBlockItemObserver.onEvent(objectEvent, 0, true); // verify the observer is called with the next BlockItem - verify(responseStreamObserver).onNext(fromPbj(subscribeStreamResponse)); + verify(responseStreamObserver).onNext(subscribeStreamResponse); // verify the mediator is NOT called to unsubscribe the observer verify(streamMediator, timeout(testTimeout).times(0)).unsubscribe(consumerBlockItemObserver); @@ -119,73 +110,6 @@ public void testProducerTimeoutOutsideWindow() throws InterruptedException { verify(streamMediator).unsubscribe(consumerBlockItemObserver); } - @Test - public void testHandlersSetOnObserver() throws InterruptedException { - - // Mock a clock with 2 different return values in response to anticipated - // millis() calls. Here the second call will always be inside the timeout window. - when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS); - - new ConsumerStreamResponseObserver(testClock, streamMediator, serverCallStreamObserver, testContext); - - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).setOnCloseHandler(any()); - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).setOnCancelHandler(any()); - } - - @Test - public void testResponseNotPermittedAfterCancel() { - - final TestConsumerStreamResponseObserver consumerStreamResponseObserver = - new TestConsumerStreamResponseObserver( - testClock, streamMediator, serverCallStreamObserver, testContext); - - final List blockItems = generateBlockItems(1); - final BlockItemSet blockItemSet = - BlockItemSet.newBuilder().blockItems(blockItems).build(); - final SubscribeStreamResponse subscribeStreamResponse = - SubscribeStreamResponse.newBuilder().blockItems(blockItemSet).build(); - when(objectEvent.get()).thenReturn(subscribeStreamResponse); - - // Confirm that the observer is called with the first BlockItem - consumerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Cancel the observer - consumerStreamResponseObserver.cancel(); - - // Attempt to send another BlockItem - consumerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Confirm that canceling the observer allowed only 1 response to be sent. - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - } - - @Test - public void testResponseNotPermittedAfterClose() { - - final TestConsumerStreamResponseObserver consumerStreamResponseObserver = - new TestConsumerStreamResponseObserver( - testClock, streamMediator, serverCallStreamObserver, testContext); - - final List blockItems = generateBlockItems(1); - final BlockItemSet blockItemSet = - BlockItemSet.newBuilder().blockItems(blockItems).build(); - final SubscribeStreamResponse subscribeStreamResponse = - SubscribeStreamResponse.newBuilder().blockItems(blockItemSet).build(); - when(objectEvent.get()).thenReturn(subscribeStreamResponse); - - // Confirm that the observer is called with the first BlockItem - consumerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Close the observer - consumerStreamResponseObserver.close(); - - // Attempt to send another BlockItem - consumerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Confirm that closing the observer allowed only 1 response to be sent. - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - } - @Test public void testConsumerNotToSendBeforeBlockHeader() { @@ -234,7 +158,7 @@ public void testConsumerNotToSendBeforeBlockHeader() { // Confirm that the observer was called with the next BlockItem // since we never send a BlockItem with a Header to start the stream. - verify(responseStreamObserver, timeout(testTimeout).times(0)).onNext(fromPbj(subscribeStreamResponse)); + verify(responseStreamObserver, timeout(testTimeout).times(0)).onNext(subscribeStreamResponse); } @Test @@ -269,25 +193,4 @@ public void testSubscribeStreamResponseTypeNotSupported() { assertThrows(IllegalArgumentException.class, () -> consumerBlockItemObserver.onEvent(objectEvent, 0, true)); } - - private static class TestConsumerStreamResponseObserver extends ConsumerStreamResponseObserver { - - public TestConsumerStreamResponseObserver( - @NonNull final InstantSource producerLivenessClock, - @NonNull final StreamMediator subscriptionHandler, - @NonNull - final StreamObserver - subscribeStreamResponseObserver, - @NonNull final BlockNodeContext blockNodeContext) { - super(producerLivenessClock, subscriptionHandler, subscribeStreamResponseObserver, blockNodeContext); - } - - public void cancel() { - onCancel.run(); - } - - public void close() { - onClose.run(); - } - } } diff --git a/server/src/test/java/com/hedera/block/server/grpc/BlockAccessServiceTest.java b/server/src/test/java/com/hedera/block/server/grpc/BlockAccessServiceTest.java index 12c824963..d038ce71f 100644 --- a/server/src/test/java/com/hedera/block/server/grpc/BlockAccessServiceTest.java +++ b/server/src/test/java/com/hedera/block/server/grpc/BlockAccessServiceTest.java @@ -16,45 +16,26 @@ package com.hedera.block.server.grpc; -import static com.hedera.block.server.Constants.SINGLE_BLOCK_METHOD_NAME; -import static com.hedera.block.server.grpc.BlockAccessService.buildSingleBlockNotAvailableResponse; -import static com.hedera.block.server.grpc.BlockAccessService.buildSingleBlockNotFoundResponse; -import static com.hedera.block.server.grpc.BlockAccessService.fromPbjSingleBlockSuccessResponse; -import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; +import static com.hedera.block.server.Constants.FULL_SERVICE_NAME_BLOCK_ACCESS; +import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_ACCESS; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.protobuf.Descriptors; -import com.hedera.block.server.Constants; import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.pbj.PbjBlockAccessService; +import com.hedera.block.server.pbj.PbjBlockAccessServiceProxy; 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.block.server.service.ServiceStatus; import com.hedera.block.server.util.TestConfigUtil; import com.hedera.hapi.block.protoc.SingleBlockResponse; import com.hedera.hapi.block.stream.Block; import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.pbj.runtime.ParseException; -import io.grpc.stub.ServerCalls; -import io.grpc.stub.StreamObserver; -import io.helidon.webserver.grpc.GrpcService; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Map; -import java.util.Optional; -import org.junit.jupiter.api.Assertions; +import java.util.concurrent.Flow; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -65,204 +46,227 @@ @ExtendWith(MockitoExtension.class) class BlockAccessServiceTest { - @Mock private StreamObserver responseObserver; + @Mock + private Flow.Subscriber responseObserver; - @Mock private BlockReader blockReader; + @Mock + private BlockReader blockReader; - @Mock private BlockWriter> blockWriter; + @Mock + private BlockWriter> blockWriter; - @Mock private ServiceStatus serviceStatus; + @Mock + private ServiceStatus serviceStatus; private static final int testTimeout = 1000; - @TempDir private Path testPath; + @TempDir + private Path testPath; + private BlockNodeContext blockNodeContext; private PersistenceStorageConfig config; + private PbjBlockAccessService blockAccessService; @BeforeEach public void setUp() throws IOException { blockNodeContext = - TestConfigUtil.getTestBlockNodeContext( - Map.of("persistence.storage.rootPath", testPath.toString())); + TestConfigUtil.getTestBlockNodeContext(Map.of("persistence.storage.rootPath", testPath.toString())); config = blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class); - } - - @Test - void testProto() { - BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - Descriptors.FileDescriptor fileDescriptor = blockAccessService.proto(); - // Verify the current rpc methods on - Descriptors.ServiceDescriptor blockAccessServiceDescriptor = - fileDescriptor.getServices().stream() - .filter( - service -> - service.getName() - .equals(Constants.SERVICE_NAME_BLOCK_ACCESS)) - .findFirst() - .orElse(null); - - Assertions.assertNotNull( - blockAccessServiceDescriptor, - "Service descriptor not found for: " + Constants.SERVICE_NAME_BLOCK_ACCESS); - assertEquals(1, blockAccessServiceDescriptor.getMethods().size()); - - assertNotNull(blockAccessServiceDescriptor.getName(), blockAccessService.serviceName()); - - // Verify the current rpc methods on the service - Descriptors.MethodDescriptor singleBlockMethod = - blockAccessServiceDescriptor.getMethods().stream() - .filter(method -> method.getName().equals(SINGLE_BLOCK_METHOD_NAME)) - .findFirst() - .orElse(null); - - assertEquals(SINGLE_BLOCK_METHOD_NAME, singleBlockMethod.getName()); - } - - @Test - void testSingleBlockHappyPath() throws IOException, ParseException { - - final BlockReader blockReader = BlockAsDirReaderBuilder.newBuilder(config).build(); - - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - // Enable the serviceStatus - when(serviceStatus.isRunning()).thenReturn(true); - - // Generate and persist a block - final BlockWriter> blockWriter = - BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); - final List blockItems = generateBlockItems(1); - blockWriter.write(blockItems); - - // Get the block so we can verify the response payload - final Optional blockOpt = blockReader.read(1); - if (blockOpt.isEmpty()) { - fail("Block 1 should be present"); - return; - } - - // Build a response to verify what's passed to the response observer - final com.hedera.hapi.block.protoc.SingleBlockResponse expectedSingleBlockResponse = - fromPbjSingleBlockSuccessResponse(blockOpt.get()); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); - - // Call the service - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(expectedSingleBlockResponse); - } - - @Test - void testSingleBlockNotFoundPath() throws IOException, ParseException { - - // Get the block so we can verify the response payload - when(blockReader.read(1)).thenReturn(Optional.empty()); - - // Build a response to verify what's passed to the response observer - final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotFound = - buildSingleBlockNotFoundResponse(); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); - - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - // Enable the serviceStatus - when(serviceStatus.isRunning()).thenReturn(true); - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(expectedNotFound); + blockAccessService = new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); } @Test - void testSingleBlockServiceNotAvailable() { - - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - // Set the service status to not running - when(serviceStatus.isRunning()).thenReturn(false); - - final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = - buildSingleBlockNotAvailableResponse(); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(expectedNotAvailable); + public void testServiceName() { + assertEquals(SERVICE_NAME_BLOCK_ACCESS, blockAccessService.serviceName()); } @Test - public void testSingleBlockIOExceptionPath() throws IOException, ParseException { - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - when(serviceStatus.isRunning()).thenReturn(true); - when(blockReader.read(1)).thenThrow(new IOException("Test exception")); - - final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = - buildSingleBlockNotAvailableResponse(); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(expectedNotAvailable); + public void testFullName() { + assertEquals(FULL_SERVICE_NAME_BLOCK_ACCESS, blockAccessService.fullName()); } @Test - public void testSingleBlockParseExceptionPath() throws IOException, ParseException { - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - when(serviceStatus.isRunning()).thenReturn(true); - when(blockReader.read(1)).thenThrow(new ParseException("Test exception")); - - final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = - buildSingleBlockNotAvailableResponse(); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(expectedNotAvailable); + public void testMethods() { + assertEquals(1, blockAccessService.methods().size()); } - @Test - public void testUpdateInvokesRoutingWithLambdas() { - - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - GrpcService.Routing routing = mock(GrpcService.Routing.class); - blockAccessService.update(routing); - - verify(routing, timeout(testTimeout).times(1)) - .unary(eq(SINGLE_BLOCK_METHOD_NAME), any(ServerCalls.UnaryMethod.class)); - } + // @Test + // void testProto() { + // BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // Descriptors.FileDescriptor fileDescriptor = blockAccessService.proto(); + // // Verify the current rpc methods on + // Descriptors.ServiceDescriptor blockAccessServiceDescriptor = + // fileDescriptor.getServices().stream() + // .filter( + // service -> + // service.getName() + // .equals(Constants.SERVICE_NAME_BLOCK_ACCESS)) + // .findFirst() + // .orElse(null); + // + // Assertions.assertNotNull( + // blockAccessServiceDescriptor, + // "Service descriptor not found for: " + Constants.SERVICE_NAME_BLOCK_ACCESS); + // assertEquals(1, blockAccessServiceDescriptor.getMethods().size()); + // + // assertNotNull(blockAccessServiceDescriptor.getName(), blockAccessService.serviceName()); + // + // // Verify the current rpc methods on the service + // Descriptors.MethodDescriptor singleBlockMethod = + // blockAccessServiceDescriptor.getMethods().stream() + // .filter(method -> method.getName().equals(SINGLE_BLOCK_METHOD_NAME)) + // .findFirst() + // .orElse(null); + // + // assertEquals(SINGLE_BLOCK_METHOD_NAME, singleBlockMethod.getName()); + // } + // + // @Test + // void testSingleBlockHappyPath() throws IOException, ParseException { + // + // final BlockReader blockReader = BlockAsDirReaderBuilder.newBuilder(config).build(); + // + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // // Enable the serviceStatus + // when(serviceStatus.isRunning()).thenReturn(true); + // + // // Generate and persist a block + // final BlockWriter> blockWriter = + // BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); + // final List blockItems = generateBlockItems(1); + // blockWriter.write(blockItems); + // + // // Get the block so we can verify the response payload + // final Optional blockOpt = blockReader.read(1); + // if (blockOpt.isEmpty()) { + // fail("Block 1 should be present"); + // return; + // } + // + // // Build a response to verify what's passed to the response observer + // final com.hedera.hapi.block.protoc.SingleBlockResponse expectedSingleBlockResponse = + // fromPbjSingleBlockSuccessResponse(blockOpt.get()); + // + // // Build a request to invoke the service + // final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = + // com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() + // .setBlockNumber(1) + // .build(); + // + // // Call the service + // blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); + // verify(responseObserver, times(1)).onNext(expectedSingleBlockResponse); + // } + // + // @Test + // void testSingleBlockNotFoundPath() throws IOException, ParseException { + // + // // Get the block so we can verify the response payload + // when(blockReader.read(1)).thenReturn(Optional.empty()); + // + // // Build a response to verify what's passed to the response observer + // final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotFound = + // buildSingleBlockNotFoundResponse(); + // + // // Build a request to invoke the service + // final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = + // com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() + // .setBlockNumber(1) + // .build(); + // + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // // Enable the serviceStatus + // when(serviceStatus.isRunning()).thenReturn(true); + // + // blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); + // verify(responseObserver, times(1)).onNext(expectedNotFound); + // } + // + // @Test + // void testSingleBlockServiceNotAvailable() { + // + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // // Set the service status to not running + // when(serviceStatus.isRunning()).thenReturn(false); + // + // final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = + // buildSingleBlockNotAvailableResponse(); + // + // // Build a request to invoke the service + // final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = + // com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() + // .setBlockNumber(1) + // .build(); + // blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); + // verify(responseObserver, times(1)).onNext(expectedNotAvailable); + // } + // + // @Test + // public void testSingleBlockIOExceptionPath() throws IOException, ParseException { + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // when(serviceStatus.isRunning()).thenReturn(true); + // when(blockReader.read(1)).thenThrow(new IOException("Test exception")); + // + // final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = + // buildSingleBlockNotAvailableResponse(); + // + // // Build a request to invoke the service + // final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = + // com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() + // .setBlockNumber(1) + // .build(); + // blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); + // verify(responseObserver, times(1)).onNext(expectedNotAvailable); + // } + // + // @Test + // public void testSingleBlockParseExceptionPath() throws IOException, ParseException { + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // when(serviceStatus.isRunning()).thenReturn(true); + // when(blockReader.read(1)).thenThrow(new ParseException("Test exception")); + // + // final com.hedera.hapi.block.protoc.SingleBlockResponse expectedNotAvailable = + // buildSingleBlockNotAvailableResponse(); + // + // // Build a request to invoke the service + // final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = + // com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() + // .setBlockNumber(1) + // .build(); + // blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); + // verify(responseObserver, times(1)).onNext(expectedNotAvailable); + // } + // + // @Test + // public void testUpdateInvokesRoutingWithLambdas() { + // + // final BlockAccessService blockAccessService = + // new BlockAccessService( + // serviceStatus, blockReader, blockNodeContext.metricsService()); + // + // GrpcService.Routing routing = mock(GrpcService.Routing.class); + // blockAccessService.update(routing); + // + // verify(routing, timeout(testTimeout).times(1)) + // .unary(eq(SINGLE_BLOCK_METHOD_NAME), any(ServerCalls.UnaryMethod.class)); + // } } diff --git a/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceTest.java b/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceTest.java index 56a35c8af..8f722f7eb 100644 --- a/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceTest.java +++ b/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceTest.java @@ -16,26 +16,16 @@ package com.hedera.block.server.grpc; -import static com.hedera.block.server.Constants.CLIENT_STREAMING_METHOD_NAME; -import static com.hedera.block.server.Constants.SERVER_STREAMING_METHOD_NAME; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.util.PersistTestUtils.reverseByteArray; +import static com.hedera.block.server.Constants.FULL_SERVICE_NAME_BLOCK_STREAM; +import static com.hedera.block.server.Constants.SERVICE_NAME_BLOCK_STREAM; import static java.lang.System.Logger; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.google.protobuf.Descriptors; -import com.hedera.block.server.Constants; + import com.hedera.block.server.config.BlockNodeContext; import com.hedera.block.server.mediator.LiveStreamMediator; import com.hedera.block.server.notifier.Notifier; +import com.hedera.block.server.pbj.PbjBlockStreamService; +import com.hedera.block.server.pbj.PbjBlockStreamServiceProxy; import com.hedera.block.server.persistence.StreamPersistenceHandlerImpl; import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; import com.hedera.block.server.persistence.storage.read.BlockReader; @@ -43,180 +33,77 @@ import com.hedera.block.server.service.ServiceStatus; import com.hedera.block.server.util.TestConfigUtil; import com.hedera.hapi.block.SingleBlockResponse; -import com.hedera.hapi.block.SingleBlockResponseCode; import com.hedera.hapi.block.stream.Block; import com.hedera.hapi.block.stream.BlockItem; -import io.grpc.stub.ServerCalls; -import io.grpc.stub.StreamObserver; -import io.helidon.webserver.grpc.GrpcService; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Map; -import org.junit.jupiter.api.Assertions; +import java.util.concurrent.Flow; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class BlockStreamServiceTest { - @Mock private Notifier notifier; + @Mock + private Notifier notifier; - @Mock private StreamObserver responseObserver; + @Mock + private Flow.Subscriber responseObserver; - @Mock private LiveStreamMediator streamMediator; + @Mock + private LiveStreamMediator streamMediator; - @Mock private BlockReader blockReader; + @Mock + private BlockReader blockReader; - @Mock private BlockWriter> blockWriter; + @Mock + private BlockWriter> blockWriter; - @Mock private ServiceStatus serviceStatus; + @Mock + private ServiceStatus serviceStatus; private final Logger LOGGER = System.getLogger(getClass().getName()); private static final int testTimeout = 1000; - @TempDir private Path testPath; + private PbjBlockStreamService blockStreamService; + + @TempDir + private Path testPath; + private BlockNodeContext blockNodeContext; private PersistenceStorageConfig config; @BeforeEach public void setUp() throws IOException { blockNodeContext = - TestConfigUtil.getTestBlockNodeContext( - Map.of("persistence.storage.rootPath", testPath.toString())); + TestConfigUtil.getTestBlockNodeContext(Map.of("persistence.storage.rootPath", testPath.toString())); config = blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class); - } - - @Test - public void testServiceName() { - final var blockNodeEventHandler = - new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final BlockStreamService blockStreamService = - new BlockStreamService( - streamMediator, - serviceStatus, - blockNodeEventHandler, - notifier, - blockNodeContext); - - // Verify the service name - Assertions.assertEquals( - Constants.SERVICE_NAME_BLOCK_STREAM, blockStreamService.serviceName()); - - // Verify other methods not invoked - verify(streamMediator, timeout(testTimeout).times(0)).publish(any()); + final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( + streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + blockStreamService = new PbjBlockStreamServiceProxy( + streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); } @Test - public void testProto() { - - final var blockNodeEventHandler = - new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final BlockStreamService blockStreamService = - new BlockStreamService( - streamMediator, - serviceStatus, - blockNodeEventHandler, - notifier, - blockNodeContext); - Descriptors.FileDescriptor fileDescriptor = blockStreamService.proto(); - - // Verify the current rpc methods on - Descriptors.ServiceDescriptor blockStreamServiceDescriptor = - fileDescriptor.getServices().stream() - .filter( - service -> - service.getName() - .equals(Constants.SERVICE_NAME_BLOCK_STREAM)) - .findFirst() - .orElse(null); - - Assertions.assertNotNull( - blockStreamServiceDescriptor, - "Service descriptor not found for: " + Constants.SERVICE_NAME_BLOCK_STREAM); - assertEquals(2, blockStreamServiceDescriptor.getMethods().size()); - - Descriptors.ServiceDescriptor blockAccessServiceDescriptor = - fileDescriptor.getServices().stream() - .filter( - service -> - service.getName() - .equals(Constants.SERVICE_NAME_BLOCK_ACCESS)) - .findFirst() - .orElse(null); - Assertions.assertNotNull( - blockAccessServiceDescriptor, - "Service descriptor not found for: " + Constants.SERVICE_NAME_BLOCK_ACCESS); - assertEquals(1, blockAccessServiceDescriptor.getMethods().size()); - - // Verify other methods not invoked - verify(streamMediator, timeout(testTimeout).times(0)) - .publish(Mockito.>any()); + public void testServiceName() { + assertEquals(SERVICE_NAME_BLOCK_STREAM, blockStreamService.serviceName()); } @Test - public void testUpdateInvokesRoutingWithLambdas() { - - final BlockStreamService blockStreamService = getBlockStreamService(); - - GrpcService.Routing routing = mock(GrpcService.Routing.class); - blockStreamService.update(routing); - - verify(routing, timeout(testTimeout).times(1)) - .bidi(eq(CLIENT_STREAMING_METHOD_NAME), any(ServerCalls.BidiStreamingMethod.class)); - verify(routing, timeout(testTimeout).times(1)) - .serverStream( - eq(SERVER_STREAMING_METHOD_NAME), - any(ServerCalls.ServerStreamingMethod.class)); - } - - private BlockStreamService getBlockStreamService() { - final var blockNodeEventHandler = - new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final BlockStreamService blockStreamService = - new BlockStreamService( - streamMediator, - serviceStatus, - blockNodeEventHandler, - notifier, - blockNodeContext); - return blockStreamService; + public void testFullName() { + assertEquals(FULL_SERVICE_NAME_BLOCK_STREAM, blockStreamService.fullName()); } @Test - public void testProtocParseExceptionHandling() { - // TODO: We might be able to remove this test once we can remove the Translator class - final BlockAccessService blockAccessService = - new BlockAccessService( - serviceStatus, blockReader, blockNodeContext.metricsService()); - - // Build a request to invoke the service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - spy( - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build()); - - // Create a corrupted set of bytes to provoke a parse exception - byte[] okBytes = singleBlockRequest.toByteArray(); - when(singleBlockRequest.toByteArray()).thenReturn(reverseByteArray(okBytes)); - - final SingleBlockResponse expectedSingleBlockErrorResponse = - SingleBlockResponse.newBuilder() - .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) - .build(); - // Call the service - blockAccessService.protocSingleBlock(singleBlockRequest, responseObserver); - verify(responseObserver, times(1)).onNext(fromPbj(expectedSingleBlockErrorResponse)); + public void testMethods() { + assertEquals(2, blockStreamService.methods().size()); } } diff --git a/server/src/test/java/com/hedera/block/server/mediator/LiveStreamMediatorImplTest.java b/server/src/test/java/com/hedera/block/server/mediator/LiveStreamMediatorImplTest.java index f079bd5a1..a2f86653a 100644 --- a/server/src/test/java/com/hedera/block/server/mediator/LiveStreamMediatorImplTest.java +++ b/server/src/test/java/com/hedera/block/server/mediator/LiveStreamMediatorImplTest.java @@ -16,14 +16,12 @@ package com.hedera.block.server.mediator; -import static com.hedera.block.server.Translator.fromPbj; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItems; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockStreamMediatorError; import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -46,10 +44,8 @@ import com.hedera.hapi.block.SubscribeStreamResponseCode; import com.hedera.hapi.block.stream.BlockItem; import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.grpc.Pipeline; import com.swirlds.metrics.api.LongGauge; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; import java.io.IOException; import java.time.InstantSource; import java.util.HashMap; @@ -80,16 +76,13 @@ public class LiveStreamMediatorImplTest { private Notifier notifier; @Mock - private StreamObserver streamObserver1; + private Pipeline streamObserver1; @Mock - private StreamObserver streamObserver2; + private Pipeline streamObserver2; @Mock - private StreamObserver streamObserver3; - - @Mock - private ServerCallStreamObserver serverCallStreamObserver; + private Pipeline streamObserver3; @Mock private InstantSource testClock; @@ -218,9 +211,9 @@ public void testMediatorPublishEventToSubscribers() throws IOException { assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); // Confirm each subscriber was notified of the new block - verify(streamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(streamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(streamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); + verify(streamObserver1, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(streamObserver2, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(streamObserver3, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); // Confirm the BlockStorage write method was called verify(blockWriter, timeout(testTimeout).times(1)).write(List.of(blockItem)); @@ -286,100 +279,109 @@ public void testSubscribeWhenHandlerAlreadySubscribed() throws IOException { assertEquals(0L, consumersGauge.get()); } - @Test - public void testOnCancelSubscriptionHandling() throws IOException { - - final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); - final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); - final var streamMediator = LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus) - .build(); - - when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS); - - final List blockItems = generateBlockItems(1); - - // register the stream validator - when(blockWriter.write(List.of(blockItems.getFirst()))).thenReturn(Optional.empty()); - final var streamValidator = new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - streamMediator.subscribe(streamValidator); - - // register the test observer - final var testConsumerBlockItemObserver = new TestConsumerStreamResponseObserver( - testClock, streamMediator, serverCallStreamObserver, testContext); - - streamMediator.subscribe(testConsumerBlockItemObserver); - assertTrue(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Simulate the producer notifying the mediator of a new block - streamMediator.publish(List.of(blockItems.getFirst())); - - // Simulate the consumer cancelling the stream - testConsumerBlockItemObserver.getOnCancel().run(); - - // Verify the block item incremented the counter - assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); - - // Verify the event made it to the consumer - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).setOnCancelHandler(any()); - - // Confirm the mediator unsubscribed the consumer - assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Confirm the BlockStorage write method was called - verify(blockWriter, timeout(testTimeout).times(1)).write(List.of(blockItems.getFirst())); - - // Confirm the stream validator is still subscribed - assertTrue(streamMediator.isSubscribed(streamValidator)); - } - - @Test - public void testOnCloseSubscriptionHandling() throws IOException { - - final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); - final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); - final var streamMediator = LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus) - .build(); - - // testClock configured to be outside the timeout window - when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS + 1); - - final List blockItems = generateBlockItems(1); - - // register the stream validator - when(blockWriter.write(List.of(blockItems.getFirst()))).thenReturn(Optional.empty()); - final var streamValidator = new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - streamMediator.subscribe(streamValidator); - - final var testConsumerBlockItemObserver = new TestConsumerStreamResponseObserver( - testClock, streamMediator, serverCallStreamObserver, testContext); - - streamMediator.subscribe(testConsumerBlockItemObserver); - assertTrue(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Simulate the producer notifying the mediator of a new block - streamMediator.publish(List.of(blockItems.getFirst())); - - // Simulate the consumer completing the stream - testConsumerBlockItemObserver.getOnClose().run(); - - // Verify the block item incremented the counter - assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); - - // Verify the event made it to the consumer - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).setOnCancelHandler(any()); - - // Confirm the mediator unsubscribed the consumer - assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Confirm the BlockStorage write method was called - verify(blockWriter, timeout(testTimeout).times(1)).write(List.of(blockItems.getFirst())); - - // Confirm the stream validator is still subscribed - assertTrue(streamMediator.isSubscribed(streamValidator)); - } - + // @Test + // public void testOnCancelSubscriptionHandling() throws IOException { + // + // final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); + // final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); + // final var streamMediator = + // LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus).build(); + // + // when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS); + // + // final List blockItems = generateBlockItems(1); + // + // // register the stream validator + // when(blockWriter.write(blockItems.getFirst())).thenReturn(Optional.empty()); + // final var streamValidator = + // new StreamPersistenceHandlerImpl( + // streamMediator, notifier, blockWriter, blockNodeContext, + // serviceStatus); + // streamMediator.subscribe(streamValidator); + // + // // register the test observer + // final var testConsumerBlockItemObserver = + // new TestConsumerStreamResponseObserver( + // testClock, streamMediator, serverCallStreamObserver, testContext); + // + // streamMediator.subscribe(testConsumerBlockItemObserver); + // assertTrue(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Simulate the producer notifying the mediator of a new block + // streamMediator.publish(blockItems.getFirst()); + // + // // Simulate the consumer cancelling the stream + // testConsumerBlockItemObserver.getOnCancel().run(); + // + // // Verify the block item incremented the counter + // assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); + // + // // Verify the event made it to the consumer + // verify(serverCallStreamObserver, + // timeout(testTimeout).times(1)).setOnCancelHandler(any()); + // + // // Confirm the mediator unsubscribed the consumer + // assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Confirm the BlockStorage write method was called + // verify(blockWriter, timeout(testTimeout).times(1)).write(blockItems.getFirst()); + // + // // Confirm the stream validator is still subscribed + // assertTrue(streamMediator.isSubscribed(streamValidator)); + // } + // + // @Test + // public void testOnCloseSubscriptionHandling() throws IOException { + // + // final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); + // final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); + // final var streamMediator = + // LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus).build(); + // + // // testClock configured to be outside the timeout window + // when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS + + // 1); + // + // final List blockItems = generateBlockItems(1); + // + // // register the stream validator + // when(blockWriter.write(blockItems.getFirst())).thenReturn(Optional.empty()); + // final var streamValidator = + // new StreamPersistenceHandlerImpl( + // streamMediator, notifier, blockWriter, blockNodeContext, + // serviceStatus); + // streamMediator.subscribe(streamValidator); + // + // final var testConsumerBlockItemObserver = + // new TestConsumerStreamResponseObserver( + // testClock, streamMediator, serverCallStreamObserver, testContext); + // + // streamMediator.subscribe(testConsumerBlockItemObserver); + // assertTrue(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Simulate the producer notifying the mediator of a new block + // streamMediator.publish(blockItems.getFirst()); + // + // // Simulate the consumer completing the stream + // testConsumerBlockItemObserver.getOnClose().run(); + // + // // Verify the block item incremented the counter + // assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); + // + // // Verify the event made it to the consumer + // verify(serverCallStreamObserver, + // timeout(testTimeout).times(1)).setOnCancelHandler(any()); + // + // // Confirm the mediator unsubscribed the consumer + // assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Confirm the BlockStorage write method was called + // verify(blockWriter, timeout(testTimeout).times(1)).write(blockItems.getFirst()); + // + // // Confirm the stream validator is still subscribed + // assertTrue(streamMediator.isSubscribed(streamValidator)); + // } + // @Test public void testMediatorBlocksPublishAfterException() throws IOException, InterruptedException { @@ -440,70 +442,68 @@ public void testMediatorBlocksPublishAfterException() throws IOException, Interr BlockItemSet.newBuilder().blockItems(firstBlockItem).build(); final var subscribeStreamResponse = SubscribeStreamResponse.newBuilder().blockItems(blockItemSet).build(); - verify(streamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(streamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(streamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); + verify(streamObserver1, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(streamObserver2, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(streamObserver3, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); // TODO: Replace READ_STREAM_SUCCESS (2) with a generic error code? final SubscribeStreamResponse endOfStreamResponse = SubscribeStreamResponse.newBuilder() .status(SubscribeStreamResponseCode.READ_STREAM_SUCCESS) .build(); - verify(streamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(endOfStreamResponse)); - verify(streamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(endOfStreamResponse)); - verify(streamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(endOfStreamResponse)); + verify(streamObserver1, timeout(testTimeout).times(1)).onNext(endOfStreamResponse); + verify(streamObserver2, timeout(testTimeout).times(1)).onNext(endOfStreamResponse); + verify(streamObserver3, timeout(testTimeout).times(1)).onNext(endOfStreamResponse); // verify write method only called once despite the second block being published. verify(blockWriter, timeout(testTimeout).times(1)).write(List.of(firstBlockItem)); } - @Test - public void testUnsubscribeWhenNotSubscribed() throws IOException { - - final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); - final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); - final var streamMediator = LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus) - .build(); - - // register the stream validator - final var streamValidator = new StreamPersistenceHandlerImpl( - streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - streamMediator.subscribe(streamValidator); - - final var testConsumerBlockItemObserver = new TestConsumerStreamResponseObserver( - testClock, streamMediator, serverCallStreamObserver, testContext); - - // Confirm the observer is not subscribed - assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Attempt to unsubscribe the observer - streamMediator.unsubscribe(testConsumerBlockItemObserver); - - // Confirm the observer is still not subscribed - assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); - - // Confirm the stream validator is still subscribed - assertTrue(streamMediator.isSubscribed(streamValidator)); - } - - private static class TestConsumerStreamResponseObserver extends ConsumerStreamResponseObserver { - public TestConsumerStreamResponseObserver( - @NonNull final InstantSource producerLivenessClock, - @NonNull final StreamMediator, SubscribeStreamResponse> streamMediator, - @NonNull - final StreamObserver - responseStreamObserver, - @NonNull final BlockNodeContext blockNodeContext) { - super(producerLivenessClock, streamMediator, responseStreamObserver, blockNodeContext); - } - - @NonNull - public Runnable getOnCancel() { - return onCancel; - } - - @NonNull - public Runnable getOnClose() { - return onClose; - } - } + // @Test + // public void testUnsubscribeWhenNotSubscribed() throws IOException { + // + // final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); + // final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); + // final var streamMediator = LiveStreamMediatorBuilder.newBuilder(blockNodeContext, serviceStatus) + // .build(); + // + // // register the stream validator + // final var streamValidator = new StreamPersistenceHandlerImpl( + // streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + // streamMediator.subscribe(streamValidator); + // + // final var testConsumerBlockItemObserver = + // new TestConsumerStreamResponseObserver(testClock, streamMediator, streamObserver1, testContext); + // + // // Confirm the observer is not subscribed + // assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Attempt to unsubscribe the observer + // streamMediator.unsubscribe(testConsumerBlockItemObserver); + // + // // Confirm the observer is still not subscribed + // assertFalse(streamMediator.isSubscribed(testConsumerBlockItemObserver)); + // + // // Confirm the stream validator is still subscribed + // assertTrue(streamMediator.isSubscribed(streamValidator)); + // } + + // private static class TestConsumerStreamResponseObserver extends ConsumerStreamResponseObserver { + // public TestConsumerStreamResponseObserver( + // @NonNull final InstantSource producerLivenessClock, + // @NonNull final StreamMediator, SubscribeStreamResponse> streamMediator, + // @NonNull final Pipeline responseStreamObserver, + // @NonNull final BlockNodeContext blockNodeContext) { + // super(producerLivenessClock, streamMediator, responseStreamObserver, blockNodeContext); + // } + // + // @NonNull + // public Runnable getOnCancel() { + // return onCancel; + // } + // + // @NonNull + // public Runnable getOnClose() { + // return onClose; + // } + // } } diff --git a/server/src/test/java/com/hedera/block/server/mediator/MediatorConfigTest.java b/server/src/test/java/com/hedera/block/server/mediator/MediatorConfigTest.java index 8586c6d5d..582e7037b 100644 --- a/server/src/test/java/com/hedera/block/server/mediator/MediatorConfigTest.java +++ b/server/src/test/java/com/hedera/block/server/mediator/MediatorConfigTest.java @@ -26,14 +26,14 @@ public class MediatorConfigTest { @Test public void testMediatorConfig_happyPath() { - MediatorConfig mediatorConfig = new MediatorConfig(2048); + MediatorConfig mediatorConfig = new MediatorConfig(2048, ""); assertEquals(2048, mediatorConfig.ringBufferSize()); } @Test public void testMediatorConfig_negativeRingBufferSize() { IllegalArgumentException exception = - assertThrows(IllegalArgumentException.class, () -> new MediatorConfig(-1)); + assertThrows(IllegalArgumentException.class, () -> new MediatorConfig(-1, "")); assertEquals("Ring buffer size must be greater than 0", exception.getMessage()); } @@ -44,16 +44,14 @@ public void testMediatorConfig_powerOf2Values() { // Test the power of 2 values for (int powerOf2Value : powerOf2Values) { - MediatorConfig mediatorConfig = new MediatorConfig(powerOf2Value); + MediatorConfig mediatorConfig = new MediatorConfig(powerOf2Value, ""); assertEquals(powerOf2Value, mediatorConfig.ringBufferSize()); } // Test the non-power of 2 values for (int powerOf2Value : powerOf2Values) { IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> new MediatorConfig(powerOf2Value + 1)); + assertThrows(IllegalArgumentException.class, () -> new MediatorConfig(powerOf2Value + 1, "")); assertEquals("Ring buffer size must be a power of 2", exception.getMessage()); } } diff --git a/server/src/test/java/com/hedera/block/server/notifier/NotifierImplTest.java b/server/src/test/java/com/hedera/block/server/notifier/NotifierImplTest.java index a4c926180..f57bf01d2 100644 --- a/server/src/test/java/com/hedera/block/server/notifier/NotifierImplTest.java +++ b/server/src/test/java/com/hedera/block/server/notifier/NotifierImplTest.java @@ -16,9 +16,8 @@ package com.hedera.block.server.notifier; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.grpc.BlockStreamServiceIntegrationTest.buildAck; import static com.hedera.block.server.notifier.NotifierImpl.buildErrorStreamResponse; +import static com.hedera.block.server.pbj.PbjBlockStreamServiceIntegrationTest.buildAck; import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -37,8 +36,8 @@ import com.hedera.hapi.block.Acknowledgement; import com.hedera.hapi.block.PublishStreamResponse; import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.pbj.runtime.grpc.Pipeline; import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.StreamObserver; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.time.InstantSource; @@ -53,21 +52,29 @@ @ExtendWith(MockitoExtension.class) public class NotifierImplTest { - @Mock private Notifiable mediator; - @Mock private Publisher> publisher; - @Mock private ServiceStatus serviceStatus; - @Mock private SubscriptionHandler subscriptionHandler; + @Mock + private Notifiable mediator; + + @Mock + private Publisher> publisher; + + @Mock + private ServiceStatus serviceStatus; + + @Mock + private SubscriptionHandler subscriptionHandler; @Mock - private StreamObserver streamObserver1; + private Pipeline streamObserver1; @Mock - private StreamObserver streamObserver2; + private Pipeline streamObserver2; @Mock - private StreamObserver streamObserver3; + private Pipeline streamObserver3; - @Mock private InstantSource testClock; + @Mock + private InstantSource testClock; private final long TIMEOUT_THRESHOLD_MILLIS = 100L; private final long TEST_TIME = 1_719_427_664_950L; @@ -90,75 +97,43 @@ public void testRegistration() throws NoSuchAlgorithmException { when(testClock.millis()).thenReturn(TEST_TIME, TEST_TIME + TIMEOUT_THRESHOLD_MILLIS); - final var concreteObserver1 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver1, - testContext, - serviceStatus); - - final var concreteObserver2 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver2, - testContext, - serviceStatus); - - final var concreteObserver3 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver3, - testContext, - serviceStatus); + final var concreteObserver1 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver1, testContext, serviceStatus); + + final var concreteObserver2 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver2, testContext, serviceStatus); + + final var concreteObserver3 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver3, testContext, serviceStatus); notifier.subscribe(concreteObserver1); notifier.subscribe(concreteObserver2); notifier.subscribe(concreteObserver3); - assertTrue( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have observer1 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have observer2 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have observer3 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have observer1 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have observer2 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have observer3 subscribed"); List blockItems = generateBlockItems(1); notifier.publish(blockItems); // Verify the response was received by all observers - final var publishStreamResponse = - PublishStreamResponse.newBuilder().acknowledgement(buildAck(blockItems)).build(); - verify(streamObserver1, timeout(testTimeout).times(1)) - .onNext(fromPbj(publishStreamResponse)); - verify(streamObserver2, timeout(testTimeout).times(1)) - .onNext(fromPbj(publishStreamResponse)); - verify(streamObserver3, timeout(testTimeout).times(1)) - .onNext(fromPbj(publishStreamResponse)); + final var publishStreamResponse = PublishStreamResponse.newBuilder() + .acknowledgement(buildAck(blockItems)) + .build(); + verify(streamObserver1, timeout(testTimeout).times(1)).onNext(publishStreamResponse); + verify(streamObserver2, timeout(testTimeout).times(1)).onNext(publishStreamResponse); + verify(streamObserver3, timeout(testTimeout).times(1)).onNext(publishStreamResponse); // Unsubscribe the observers notifier.unsubscribe(concreteObserver1); - assertFalse( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have unsubscribed observer1"); + assertFalse(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have unsubscribed observer1"); notifier.unsubscribe(concreteObserver2); - assertFalse( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have unsubscribed observer2"); + assertFalse(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have unsubscribed observer2"); notifier.unsubscribe(concreteObserver3); - assertFalse( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have unsubscribed observer3"); + assertFalse(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have unsubscribed observer3"); } @Test @@ -178,61 +153,31 @@ public void testTimeoutExpiredHandling() throws InterruptedException { final InstantSource testClock3 = mock(InstantSource.class); when(testClock3.millis()).thenReturn(TEST_TIME, TEST_TIME + 1501L); - final var concreteObserver1 = - new ProducerBlockItemObserver( - testClock1, - publisher, - notifier, - streamObserver1, - testContext, - serviceStatus); - - final var concreteObserver2 = - new ProducerBlockItemObserver( - testClock2, - publisher, - notifier, - streamObserver2, - testContext, - serviceStatus); - - final var concreteObserver3 = - new ProducerBlockItemObserver( - testClock3, - publisher, - notifier, - streamObserver3, - testContext, - serviceStatus); + final var concreteObserver1 = new ProducerBlockItemObserver( + testClock1, publisher, notifier, streamObserver1, testContext, serviceStatus); + + final var concreteObserver2 = new ProducerBlockItemObserver( + testClock2, publisher, notifier, streamObserver2, testContext, serviceStatus); + + final var concreteObserver3 = new ProducerBlockItemObserver( + testClock3, publisher, notifier, streamObserver3, testContext, serviceStatus); notifier.subscribe(concreteObserver1); notifier.subscribe(concreteObserver2); notifier.subscribe(concreteObserver3); - assertTrue( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have observer1 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have observer2 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have observer3 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have observer1 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have observer2 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have observer3 subscribed"); List blockItems = generateBlockItems(1); notifier.publish(blockItems); Thread.sleep(testTimeout); - assertFalse( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have observer1 unsubscribed"); - assertFalse( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have observer2 unsubscribed"); - assertFalse( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have observer3 unsubscribed"); + assertFalse(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have observer1 unsubscribed"); + assertFalse(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have observer2 unsubscribed"); + assertFalse(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have observer3 unsubscribed"); } @Test @@ -240,54 +185,30 @@ public void testPublishThrowsNoSuchAlgorithmException() { when(serviceStatus.isRunning()).thenReturn(true); final var notifier = new TestNotifier(mediator, testContext, serviceStatus); - final var concreteObserver1 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver1, - testContext, - serviceStatus); - - final var concreteObserver2 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver2, - testContext, - serviceStatus); - - final var concreteObserver3 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver3, - testContext, - serviceStatus); + final var concreteObserver1 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver1, testContext, serviceStatus); + + final var concreteObserver2 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver2, testContext, serviceStatus); + + final var concreteObserver3 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver3, testContext, serviceStatus); notifier.subscribe(concreteObserver1); notifier.subscribe(concreteObserver2); notifier.subscribe(concreteObserver3); - assertTrue( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have observer1 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have observer2 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have observer3 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have observer1 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have observer2 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have observer3 subscribed"); List blockItems = generateBlockItems(1); notifier.publish(blockItems); final PublishStreamResponse errorResponse = buildErrorStreamResponse(); - verify(streamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(errorResponse)); - verify(streamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(errorResponse)); - verify(streamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(errorResponse)); + verify(streamObserver1, timeout(testTimeout).times(1)).onNext(errorResponse); + verify(streamObserver2, timeout(testTimeout).times(1)).onNext(errorResponse); + verify(streamObserver3, timeout(testTimeout).times(1)).onNext(errorResponse); } @Test @@ -296,59 +217,33 @@ public void testServiceStatusNotRunning() throws NoSuchAlgorithmException { // Set the serviceStatus to not running when(serviceStatus.isRunning()).thenReturn(false); final var notifier = new TestNotifier(mediator, testContext, serviceStatus); - final var concreteObserver1 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver1, - testContext, - serviceStatus); - - final var concreteObserver2 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver2, - testContext, - serviceStatus); - - final var concreteObserver3 = - new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - streamObserver3, - testContext, - serviceStatus); + final var concreteObserver1 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver1, testContext, serviceStatus); + + final var concreteObserver2 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver2, testContext, serviceStatus); + + final var concreteObserver3 = new ProducerBlockItemObserver( + testClock, publisher, subscriptionHandler, streamObserver3, testContext, serviceStatus); notifier.subscribe(concreteObserver1); notifier.subscribe(concreteObserver2); notifier.subscribe(concreteObserver3); - assertTrue( - notifier.isSubscribed(concreteObserver1), - "Expected the notifier to have observer1 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver2), - "Expected the notifier to have observer2 subscribed"); - assertTrue( - notifier.isSubscribed(concreteObserver3), - "Expected the notifier to have observer3 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver1), "Expected the notifier to have observer1 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver2), "Expected the notifier to have observer2 subscribed"); + assertTrue(notifier.isSubscribed(concreteObserver3), "Expected the notifier to have observer3 subscribed"); final List blockItems = generateBlockItems(1); notifier.publish(blockItems); // Verify once the serviceStatus is not running that we do not publish the responses - final var publishStreamResponse = - PublishStreamResponse.newBuilder().acknowledgement(buildAck(blockItems)).build(); - verify(streamObserver1, timeout(testTimeout).times(0)) - .onNext(fromPbj(publishStreamResponse)); - verify(streamObserver2, timeout(testTimeout).times(0)) - .onNext(fromPbj(publishStreamResponse)); - verify(streamObserver3, timeout(testTimeout).times(0)) - .onNext(fromPbj(publishStreamResponse)); + final var publishStreamResponse = PublishStreamResponse.newBuilder() + .acknowledgement(buildAck(blockItems)) + .build(); + verify(streamObserver1, timeout(testTimeout).times(0)).onNext(publishStreamResponse); + verify(streamObserver2, timeout(testTimeout).times(0)).onNext(publishStreamResponse); + verify(streamObserver3, timeout(testTimeout).times(0)).onNext(publishStreamResponse); } private static final class TestNotifier extends NotifierImpl { @@ -361,8 +256,7 @@ public TestNotifier( @Override @NonNull - Acknowledgement buildAck(@NonNull final List blockItems) - throws NoSuchAlgorithmException { + Acknowledgement buildAck(@NonNull final List blockItems) throws NoSuchAlgorithmException { throw new NoSuchAlgorithmException("Test exception"); } } diff --git a/server/src/test/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxyTest.java b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxyTest.java new file mode 100644 index 000000000..9ede231a2 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxyTest.java @@ -0,0 +1,163 @@ +/* + * 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.pbj; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.persistence.storage.read.BlockReader; +import com.hedera.block.server.service.ServiceStatus; +import com.hedera.block.server.util.TestConfigUtil; +import com.hedera.hapi.block.SingleBlockRequest; +import com.hedera.hapi.block.SingleBlockResponse; +import com.hedera.hapi.block.SingleBlockResponseCode; +import com.hedera.hapi.block.stream.Block; +import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.grpc.Pipeline; +import com.hedera.pbj.runtime.grpc.ServiceInterface; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Flow; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class PbjBlockAccessServiceProxyTest { + + @Mock + private ServiceStatus serviceStatus; + + @Mock + private BlockReader blockReader; + + @Mock + private ServiceInterface.RequestOptions options; + + @Mock + private Flow.Subscriber replies; + + private BlockNodeContext blockNodeContext; + + private static final int testTimeout = 100; + + @BeforeEach + public void setUp() throws IOException { + Map properties = new HashMap<>(); + blockNodeContext = TestConfigUtil.getTestBlockNodeContext(properties); + } + + @Test + public void testOpenWithIncorrectMethod() { + + final PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy = + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); + Pipeline pipeline = pbjBlockAccessServiceProxy.open( + PbjBlockStreamService.BlockStreamMethod.publishBlockStream, options, replies); + + verify(replies, timeout(testTimeout).times(1)).onError(any()); + assertNotNull(pipeline); + } + + @Test + public void testSingleBlock() throws IOException, ParseException { + final PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy = + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); + final Pipeline pipeline = + pbjBlockAccessServiceProxy.open(PbjBlockAccessService.BlockAccessMethod.singleBlock, options, replies); + assertNotNull(pipeline); + + when(serviceStatus.isRunning()).thenReturn(true); + + final Block block = Block.newBuilder() + .items(BlockItem.newBuilder() + .blockHeader(BlockHeader.newBuilder().number(1).build()) + .build()) + .build(); + when(blockReader.read(1)).thenReturn(Optional.of(block)); + + final SingleBlockRequest singleBlockRequest = + SingleBlockRequest.newBuilder().blockNumber(1).build(); + pipeline.onNext(SingleBlockRequest.PROTOBUF.toBytes(singleBlockRequest)); + + final var readSuccessResponse = SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_SUCCESS) + .block(block) + .build(); + verify(replies, timeout(testTimeout).times(1)).onSubscribe(any()); + verify(replies, timeout(testTimeout).times(1)) + .onNext(SingleBlockResponse.PROTOBUF.toBytes(readSuccessResponse)); + verify(replies, timeout(testTimeout).times(1)).onComplete(); + } + + @Test + public void testSingleBlockNotFound() throws IOException, ParseException { + final PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy = + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); + final Pipeline pipeline = + pbjBlockAccessServiceProxy.open(PbjBlockAccessService.BlockAccessMethod.singleBlock, options, replies); + assertNotNull(pipeline); + + when(serviceStatus.isRunning()).thenReturn(true); + when(blockReader.read(1)).thenReturn(Optional.empty()); + + final SingleBlockRequest singleBlockRequest = + SingleBlockRequest.newBuilder().blockNumber(1).build(); + pipeline.onNext(SingleBlockRequest.PROTOBUF.toBytes(singleBlockRequest)); + + final var blockNotFound = SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_FOUND) + .build(); + verify(replies, timeout(testTimeout).times(1)).onSubscribe(any()); + verify(replies, timeout(testTimeout).times(1)).onNext(SingleBlockResponse.PROTOBUF.toBytes(blockNotFound)); + verify(replies, timeout(testTimeout).times(1)).onComplete(); + } + + @Test + public void testSingleBlockIOException() throws IOException, ParseException { + final PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy = + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); + final Pipeline pipeline = + pbjBlockAccessServiceProxy.open(PbjBlockAccessService.BlockAccessMethod.singleBlock, options, replies); + assertNotNull(pipeline); + + when(serviceStatus.isRunning()).thenReturn(true); + when(blockReader.read(1)).thenThrow(new IOException("Test IOException")); + + final SingleBlockRequest singleBlockRequest = + SingleBlockRequest.newBuilder().blockNumber(1).build(); + pipeline.onNext(SingleBlockRequest.PROTOBUF.toBytes(singleBlockRequest)); + + final var blockNotAvailable = SingleBlockResponse.newBuilder() + .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) + .build(); + verify(replies, timeout(testTimeout).times(1)).onSubscribe(any()); + verify(replies, timeout(testTimeout).times(1)).onNext(SingleBlockResponse.PROTOBUF.toBytes(blockNotAvailable)); + verify(replies, timeout(testTimeout).times(1)).onComplete(); + } +} diff --git a/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceIntegrationTest.java b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java similarity index 68% rename from server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceIntegrationTest.java rename to server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java index 81622bc53..56c4eb2b5 100644 --- a/server/src/test/java/com/hedera/block/server/grpc/BlockStreamServiceIntegrationTest.java +++ b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.hedera.block.server.grpc; +package com.hedera.block.server.pbj; -import static com.hedera.block.server.Translator.fromPbj; import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.LiveBlockItems; import static com.hedera.block.server.producer.Util.getFakeHash; import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; @@ -53,6 +52,7 @@ import com.hedera.hapi.block.PublishStreamRequest; import com.hedera.hapi.block.PublishStreamResponse; import com.hedera.hapi.block.PublishStreamResponseCode; +import com.hedera.hapi.block.SingleBlockRequest; import com.hedera.hapi.block.SingleBlockResponse; import com.hedera.hapi.block.SingleBlockResponseCode; import com.hedera.hapi.block.SubscribeStreamRequest; @@ -60,10 +60,10 @@ import com.hedera.hapi.block.SubscribeStreamResponseCode; import com.hedera.hapi.block.stream.Block; import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.pbj.runtime.grpc.Pipeline; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.lmax.disruptor.BatchEventProcessor; import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.StreamObserver; import io.helidon.webserver.WebServer; import java.io.IOException; import java.nio.file.Files; @@ -75,6 +75,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Flow; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -83,7 +84,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class BlockStreamServiceIntegrationTest { +public class PbjBlockStreamServiceIntegrationTest { private final Logger LOGGER = System.getLogger(getClass().getName()); @@ -91,46 +92,43 @@ public class BlockStreamServiceIntegrationTest { private Notifier notifier; @Mock - private StreamObserver publishStreamResponseObserver1; + private Pipeline helidonPublishStreamObserver1; @Mock - private StreamObserver publishStreamResponseObserver2; + private Pipeline helidonPublishStreamObserver2; @Mock - private StreamObserver publishStreamResponseObserver3; + private Pipeline helidonPublishStreamObserver3; @Mock - private StreamObserver singleBlockResponseStreamObserver; + private SubscribeStreamRequest subscribeStreamRequest; @Mock - private com.hedera.hapi.block.protoc.SubscribeStreamRequest subscribeStreamRequest; + private Pipeline subscribeStreamObserver1; @Mock - private StreamObserver subscribeStreamObserver1; + private Pipeline subscribeStreamObserver2; @Mock - private StreamObserver subscribeStreamObserver2; + private Pipeline subscribeStreamObserver3; @Mock - private StreamObserver subscribeStreamObserver3; + private Pipeline subscribeStreamObserver4; @Mock - private StreamObserver subscribeStreamObserver4; + private Pipeline subscribeStreamObserver5; @Mock - private StreamObserver subscribeStreamObserver5; - - @Mock - private StreamObserver subscribeStreamObserver6; + private Pipeline subscribeStreamObserver6; @Mock private WebServer webServer; @Mock - private BlockReader blockReader; + private BlockWriter> blockWriter; @Mock - private BlockWriter> blockWriter; + private BlockReader blockReader; private static final String TEMP_DIR = "block-node-unit-test-dir"; @@ -164,19 +162,21 @@ public void testPublishBlockStreamRegistrationAndExecution() throws IOException, final var notifier = new NotifierImpl(streamMediator, blockNodeContext, serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final BlockStreamService blockStreamService = new BlockStreamService( + + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); // Register 3 producers - final StreamObserver publishStreamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver2); - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver3); + final Flow.Subscriber publishStreamObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); + + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver2); + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver3); // Register 3 consumers - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); List blockItems = generateBlockItems(1); for (int i = 0; i < blockItems.size(); i++) { @@ -194,30 +194,30 @@ public void testPublishBlockStreamRegistrationAndExecution() throws IOException, // Calling onNext() as Helidon does with each block item for // the first producer. - publishStreamObserver.onNext(fromPbj(publishStreamRequest)); + publishStreamObserver.onNext(publishStreamRequest); } // Verify all 10 BlockItems were sent to each of the 3 consumers verify(subscribeStreamObserver1, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.getFirst()))); + .onNext(buildSubscribeStreamResponse(blockItems.getFirst())); verify(subscribeStreamObserver1, timeout(testTimeout).times(8)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(1)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(1))); verify(subscribeStreamObserver1, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(9)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(9))); verify(subscribeStreamObserver2, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.getFirst()))); + .onNext(buildSubscribeStreamResponse(blockItems.getFirst())); verify(subscribeStreamObserver2, timeout(testTimeout).times(8)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(1)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(1))); verify(subscribeStreamObserver2, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(9)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(9))); verify(subscribeStreamObserver3, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.getFirst()))); + .onNext(buildSubscribeStreamResponse(blockItems.getFirst())); verify(subscribeStreamObserver3, timeout(testTimeout).times(8)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(1)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(1))); verify(subscribeStreamObserver3, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildSubscribeStreamResponse(blockItems.get(9)))); + .onNext(buildSubscribeStreamResponse(blockItems.get(9))); // Only 1 response is expected per block sent final Acknowledgement itemAck = buildAck(List.of(blockItems.get(9))); @@ -225,17 +225,17 @@ public void testPublishBlockStreamRegistrationAndExecution() throws IOException, PublishStreamResponse.newBuilder().acknowledgement(itemAck).build(); // Verify all 3 producers received the response - verify(publishStreamResponseObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(publishStreamResponse)); + verify(helidonPublishStreamObserver1, timeout(testTimeout).times(1)).onNext(publishStreamResponse); - verify(publishStreamResponseObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(publishStreamResponse)); + verify(helidonPublishStreamObserver2, timeout(testTimeout).times(1)).onNext(publishStreamResponse); - verify(publishStreamResponseObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(publishStreamResponse)); + verify(helidonPublishStreamObserver3, timeout(testTimeout).times(1)).onNext(publishStreamResponse); // Close the stream as Helidon does - publishStreamObserver.onCompleted(); + publishStreamObserver.onComplete(); // verify the onCompleted() method is invoked on the wrapped StreamObserver - verify(publishStreamResponseObserver1, timeout(testTimeout).times(1)).onCompleted(); + verify(helidonPublishStreamObserver1, timeout(testTimeout).times(1)).onComplete(); } @Test @@ -252,17 +252,18 @@ public void testSubscribeBlockStream() throws IOException { // Build the BlockStreamService final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final BlockStreamService blockStreamService = new BlockStreamService( + + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); // Subscribe the consumers - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); // Subscribe the producer - final StreamObserver streamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); + final Flow.Subscriber producerBlockItemsObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); // Build the BlockItem final List blockItems = generateBlockItems(1); @@ -271,7 +272,7 @@ public void testSubscribeBlockStream() throws IOException { .build(); // Calling onNext() with a BlockItem - streamObserver.onNext(fromPbj(publishStreamRequest)); + producerBlockItemsObserver.onNext(publishStreamRequest); // Verify the counter was incremented assertEquals(1, blockNodeContext.metricsService().get(LiveBlockItems).get()); @@ -284,42 +285,40 @@ public void testSubscribeBlockStream() throws IOException { final SubscribeStreamResponse subscribeStreamResponse = SubscribeStreamResponse.newBuilder().blockItems(blockItemSet).build(); - verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); + verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); } @Test - public void testFullHappyPath() throws IOException { + public void testFullProducerConsumerHappyPath() throws IOException { int numberOfBlocks = 100; final BlockWriter> blockWriter = BlockAsDirWriterBuilder.newBuilder(blockNodeContext).build(); - final BlockStreamService blockStreamService = buildBlockStreamService(blockWriter); + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = buildBlockStreamService(blockWriter); - // Pass a StreamObserver to the producer as Helidon does - final StreamObserver streamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); + final Flow.Subscriber producerBlockItemObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + // Subscribe the consumers + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); final List blockItems = generateBlockItems(numberOfBlocks); for (BlockItem blockItem : blockItems) { final PublishStreamRequest publishStreamRequest = PublishStreamRequest.newBuilder() .blockItems(new BlockItemSet(List.of(blockItem))) .build(); - streamObserver.onNext(fromPbj(publishStreamRequest)); + producerBlockItemObserver.onNext(publishStreamRequest); } verifySubscribeStreamResponse(numberOfBlocks, 0, numberOfBlocks, subscribeStreamObserver1, blockItems); verifySubscribeStreamResponse(numberOfBlocks, 0, numberOfBlocks, subscribeStreamObserver2, blockItems); verifySubscribeStreamResponse(numberOfBlocks, 0, numberOfBlocks, subscribeStreamObserver3, blockItems); - streamObserver.onCompleted(); - - verify(publishStreamResponseObserver1, timeout(testTimeout).times(100)).onNext(any()); + verify(helidonPublishStreamObserver1, timeout(testTimeout).times(100)).onNext(any()); } @Test @@ -327,18 +326,16 @@ public void testFullWithSubscribersAddedDynamically() { int numberOfBlocks = 100; - final BlockStreamService blockStreamService = buildBlockStreamService(blockWriter); - - // Pass a StreamObserver to the producer as Helidon does - final StreamObserver streamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); + final PbjBlockStreamServiceProxy blockStreamServiceProxy = buildBlockStreamService(blockWriter); + final Flow.Subscriber streamObserver = + blockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); final List blockItems = generateBlockItems(numberOfBlocks); // Subscribe the initial consumers - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); for (int i = 0; i < blockItems.size(); i++) { final PublishStreamRequest publishStreamRequest = PublishStreamRequest.newBuilder() @@ -347,20 +344,20 @@ public void testFullWithSubscribersAddedDynamically() { // Add a new subscriber if (i == 51) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver4); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver4); } // Transmit the BlockItem - streamObserver.onNext(fromPbj(publishStreamRequest)); + streamObserver.onNext(publishStreamRequest); // Add a new subscriber if (i == 76) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver5); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver5); } // Add a new subscriber if (i == 88) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver6); + blockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver6); } } @@ -377,8 +374,6 @@ public void testFullWithSubscribersAddedDynamically() { verifySubscribeStreamResponse(numberOfBlocks, 59, numberOfBlocks, subscribeStreamObserver4, blockItems); verifySubscribeStreamResponse(numberOfBlocks, 79, numberOfBlocks, subscribeStreamObserver5, blockItems); verifySubscribeStreamResponse(numberOfBlocks, 89, numberOfBlocks, subscribeStreamObserver6, blockItems); - - streamObserver.onCompleted(); } @Test @@ -395,25 +390,25 @@ public void testSubAndUnsubWhileStreaming() throws InterruptedException { final var streamMediator = buildStreamMediator(consumers, serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final var blockStreamService = new BlockStreamService( + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); // Pass a StreamObserver to the producer as Helidon does - final StreamObserver streamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); + final Flow.Subscriber streamObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); final List blockItems = generateBlockItems(numberOfBlocks); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); for (int i = 0; i < blockItems.size(); i++) { // Transmit the BlockItem - streamObserver.onNext(fromPbj(PublishStreamRequest.newBuilder() + streamObserver.onNext(PublishStreamRequest.newBuilder() .blockItems(new BlockItemSet(List.of(blockItems.get(i)))) - .build())); + .build()); // Remove 1st subscriber if (i == 10) { @@ -435,7 +430,7 @@ public void testSubAndUnsubWhileStreaming() throws InterruptedException { // Add a new subscriber if (i == 51) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver4); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver4); } // Remove 3rd subscriber @@ -449,12 +444,12 @@ public void testSubAndUnsubWhileStreaming() throws InterruptedException { // Add a new subscriber if (i == 76) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver5); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver5); } // Add a new subscriber if (i == 88) { - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver6); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver6); } } @@ -472,7 +467,7 @@ public void testSubAndUnsubWhileStreaming() throws InterruptedException { verifySubscribeStreamResponse(numberOfBlocks, 79, numberOfBlocks, subscribeStreamObserver5, blockItems); verifySubscribeStreamResponse(numberOfBlocks, 89, numberOfBlocks, subscribeStreamObserver6, blockItems); - streamObserver.onCompleted(); + streamObserver.onComplete(); } @Test @@ -500,26 +495,26 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure() throws IOExcep final var notifier = new NotifierImpl(streamMediator, blockNodeContext, serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); - final var blockStreamService = new BlockStreamService( + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); - final BlockAccessService blockAccessService = - new BlockAccessService(serviceStatus, blockReader, blockNodeContext.metricsService()); - // Subscribe the consumers - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); - blockStreamService.protocSubscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver1); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver2); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver3); + + // 3 subscribers + 1 streamPersistenceHandler + assertEquals(4, consumers.size()); // Initialize the producer - final StreamObserver streamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver1); + final Flow.Subscriber producerBlockItemObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver1); // Transmit a BlockItem final PublishStreamRequest publishStreamRequest = PublishStreamRequest.newBuilder() .blockItems(new BlockItemSet(blockItems)) .build(); - streamObserver.onNext(fromPbj(publishStreamRequest)); + producerBlockItemObserver.onNext(publishStreamRequest); // Use verify to make sure the serviceStatus.stopRunning() method is called // before the next block is transmitted. @@ -527,27 +522,27 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure() throws IOExcep // Simulate another producer attempting to connect to the Block Node after the exception. // Later, verify they received a response indicating the stream is closed. - final StreamObserver expectedNoOpStreamObserver = - blockStreamService.protocPublishBlockStream(publishStreamResponseObserver2); - expectedNoOpStreamObserver.onNext(fromPbj(publishStreamRequest)); + final Flow.Subscriber expectedNoOpStreamObserver = + pbjBlockStreamServiceProxy.publishBlockStream(helidonPublishStreamObserver2); + expectedNoOpStreamObserver.onNext(publishStreamRequest); - verify(publishStreamResponseObserver2, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildEndOfStreamResponse())); + verify(helidonPublishStreamObserver2, timeout(testTimeout).times(1)).onNext(buildEndOfStreamResponse()); // Build a request to invoke the singleBlock service - final com.hedera.hapi.block.protoc.SingleBlockRequest singleBlockRequest = - com.hedera.hapi.block.protoc.SingleBlockRequest.newBuilder() - .setBlockNumber(1) - .build(); + final SingleBlockRequest singleBlockRequest = + SingleBlockRequest.newBuilder().blockNumber(1).build(); + + final PbjBlockAccessServiceProxy pbjBlockAccessServiceProxy = + new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext); // Simulate a consumer attempting to connect to the Block Node after the exception. - blockAccessService.protocSingleBlock(singleBlockRequest, singleBlockResponseStreamObserver); + final SingleBlockResponse singleBlockResponse = pbjBlockAccessServiceProxy.singleBlock(singleBlockRequest); // Build a request to invoke the subscribeBlockStream service final SubscribeStreamRequest subscribeStreamRequest = SubscribeStreamRequest.newBuilder().startBlockNumber(1).build(); // Simulate a consumer attempting to connect to the Block Node after the exception. - blockStreamService.protocSubscribeBlockStream(fromPbj(subscribeStreamRequest), subscribeStreamObserver4); + pbjBlockStreamServiceProxy.subscribeBlockStream(subscribeStreamRequest, subscribeStreamObserver4); // The BlockItem expected to pass through since it was published // before the IOException was thrown. @@ -556,52 +551,41 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure() throws IOExcep final SubscribeStreamResponse subscribeStreamResponse = SubscribeStreamResponse.newBuilder().blockItems(blockItemSet).build(); - verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); - verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(subscribeStreamResponse)); + verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); + verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(subscribeStreamResponse); // Verify all the consumers received the end of stream response // TODO: Fix the response code when it's available final SubscribeStreamResponse endStreamResponse = SubscribeStreamResponse.newBuilder() .status(SubscribeStreamResponseCode.READ_STREAM_SUCCESS) .build(); - verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(fromPbj(endStreamResponse)); - verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(fromPbj(endStreamResponse)); - verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(fromPbj(endStreamResponse)); - - assertEquals(3, consumers.size()); + verify(subscribeStreamObserver1, timeout(testTimeout).times(1)).onNext(endStreamResponse); + verify(subscribeStreamObserver2, timeout(testTimeout).times(1)).onNext(endStreamResponse); + verify(subscribeStreamObserver3, timeout(testTimeout).times(1)).onNext(endStreamResponse); // Verify the publishBlockStream service returned the expected // error code indicating the service is not available. - verify(publishStreamResponseObserver1, timeout(testTimeout).times(1)) - .onNext(fromPbj(buildEndOfStreamResponse())); + verify(helidonPublishStreamObserver1, timeout(testTimeout).times(1)).onNext(buildEndOfStreamResponse()); // Adding extra time to allow the service to stop given // the built-in delay. - verify(webServer, timeout(2000).times(1)).stop(); + verify(webServer, timeout(testTimeout).times(1)).stop(); - // Verify the singleBlock service returned the expected - // error code indicating the service is not available. - final SingleBlockResponse expectedSingleBlockNotAvailable = SingleBlockResponse.newBuilder() - .status(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE) - .build(); - - verify(singleBlockResponseStreamObserver, timeout(testTimeout).times(1)) - .onNext(fromPbj(expectedSingleBlockNotAvailable)); + assertEquals(SingleBlockResponseCode.READ_BLOCK_NOT_AVAILABLE, singleBlockResponse.status()); // TODO: Fix the response code when it's available final SubscribeStreamResponse expectedSubscriberStreamNotAvailable = SubscribeStreamResponse.newBuilder() .status(SubscribeStreamResponseCode.READ_STREAM_SUCCESS) .build(); - verify(subscribeStreamObserver4, timeout(testTimeout).times(1)) - .onNext(fromPbj(expectedSubscriberStreamNotAvailable)); + verify(subscribeStreamObserver4, timeout(testTimeout).times(1)).onNext(expectedSubscriberStreamNotAvailable); } private static void verifySubscribeStreamResponse( int numberOfBlocks, int blockItemsToWait, int blockItemsToSkip, - StreamObserver streamObserver, + Pipeline streamObserver, List blockItems) { // Each block has 10 BlockItems. Verify all the BlockItems @@ -621,9 +605,9 @@ private static void verifySubscribeStreamResponse( final BlockItem stateProofBlockItem = blockItems.get(block + 9); final SubscribeStreamResponse stateProofStreamResponse = buildSubscribeStreamResponse(stateProofBlockItem); - verify(streamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(headerSubStreamResponse)); - verify(streamObserver, timeout(testTimeout).times(8)).onNext(fromPbj(bodySubStreamResponse)); - verify(streamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(stateProofStreamResponse)); + verify(streamObserver, timeout(testTimeout).times(1)).onNext(headerSubStreamResponse); + verify(streamObserver, timeout(testTimeout).times(8)).onNext(bodySubStreamResponse); + verify(streamObserver, timeout(testTimeout).times(1)).onNext(stateProofStreamResponse); } } @@ -642,7 +626,7 @@ private static PublishStreamResponse buildEndOfStreamResponse() { return PublishStreamResponse.newBuilder().status(endOfStream).build(); } - private BlockStreamService buildBlockStreamService(final BlockWriter> blockWriter) { + private PbjBlockStreamServiceProxy buildBlockStreamService(final BlockWriter> blockWriter) { final ServiceStatus serviceStatus = new ServiceStatusImpl(blockNodeContext); final var streamMediator = buildStreamMediator(new ConcurrentHashMap<>(32), serviceStatus); @@ -650,7 +634,8 @@ private BlockStreamService buildBlockStreamService(final BlockWriter new PersistenceStorageConfig("relative/path")); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, () -> new PersistenceStorageConfig("relative/path", "PRODUCTION")); assertEquals("relative/path Root path must be absolute", exception.getMessage()); } @@ -62,8 +63,8 @@ void persistenceStorageConfig_throwsExceptionForRelativePath() { void persistenceStorageConfig_throwsRuntimeExceptionOnIOException() { Path invalidPath = Paths.get("/invalid/path"); - RuntimeException exception = - assertThrows(RuntimeException.class, () -> new PersistenceStorageConfig(invalidPath.toString())); + RuntimeException exception = assertThrows( + RuntimeException.class, () -> new PersistenceStorageConfig(invalidPath.toString(), "PRODUCTION")); assertInstanceOf(IOException.class, exception.getCause()); } diff --git a/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java b/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java index e12d6e2db..a0ce5428a 100644 --- a/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java +++ b/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java @@ -16,15 +16,10 @@ package com.hedera.block.server.producer; -import static com.hedera.block.server.Translator.fromPbj; -import static com.hedera.block.server.producer.Util.getFakeHash; import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems; -import static com.hedera.block.server.util.PersistTestUtils.reverseByteArray; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import com.hedera.block.server.config.BlockNodeContext; import com.hedera.block.server.events.ObjectEvent; @@ -33,24 +28,17 @@ import com.hedera.block.server.service.ServiceStatus; import com.hedera.block.server.service.ServiceStatusImpl; import com.hedera.block.server.util.TestConfigUtil; -import com.hedera.hapi.block.Acknowledgement; -import com.hedera.hapi.block.EndOfStream; -import com.hedera.hapi.block.ItemAcknowledgement; +import com.hedera.hapi.block.BlockItemSet; import com.hedera.hapi.block.PublishStreamRequest; import com.hedera.hapi.block.PublishStreamResponse; -import com.hedera.hapi.block.PublishStreamResponseCode; -import com.hedera.hapi.block.protoc.BlockItemSet; import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import edu.umd.cs.findbugs.annotations.NonNull; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; +import com.hedera.pbj.runtime.grpc.Pipeline; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.time.InstantSource; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -69,10 +57,7 @@ public class ProducerBlockItemObserverTest { private SubscriptionHandler subscriptionHandler; @Mock - private StreamObserver publishStreamResponseObserver; - - @Mock - private ServerCallStreamObserver serverCallStreamObserver; + private Pipeline publishStreamResponseObserver; @Mock private ServiceStatus serviceStatus; @@ -92,6 +77,7 @@ public void setUp() throws IOException { } @Test + @Disabled public void testOnError() throws IOException { final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); @@ -108,164 +94,29 @@ public void testOnError() throws IOException { verify(publishStreamResponseObserver).onError(t); } - @Test - public void testBlockItemThrowsParseException() throws IOException { - - final BlockNodeContext blockNodeContext = TestConfigUtil.getTestBlockNodeContext(); - final ProducerBlockItemObserver producerBlockItemObserver = new ProducerBlockItemObserver( - testClock, - publisher, - subscriptionHandler, - publishStreamResponseObserver, - blockNodeContext, - serviceStatus); - - // Create a pbj block item - final List blockItems = generateBlockItems(1); - final BlockItem blockHeader = blockItems.getFirst(); - - // Convert the block item to a protoc and add a spy to reverse the bytes to - // provoke a ParseException - final byte[] pbjBytes = BlockItem.PROTOBUF.toBytes(blockHeader).toByteArray(); - final com.hedera.hapi.block.stream.protoc.BlockItem protocBlockItem = - spy(com.hedera.hapi.block.stream.protoc.BlockItem.parseFrom(pbjBytes)); - - // set up the spy to pass the reversed bytes when called - final byte[] reversedBytes = reverseByteArray(protocBlockItem.toByteArray()); - when(protocBlockItem.toByteArray()).thenReturn(reversedBytes); - - // create the PublishStreamRequest with the spy block item - final com.hedera.hapi.block.protoc.PublishStreamRequest protocPublishStreamRequest = - com.hedera.hapi.block.protoc.PublishStreamRequest.newBuilder() - .setBlockItems(BlockItemSet.newBuilder().addBlockItems(protocBlockItem)) - .build(); - - // call the producerBlockItemObserver - producerBlockItemObserver.onNext(protocPublishStreamRequest); - - // TODO: Replace this with a real error enum. - final EndOfStream endOfStream = EndOfStream.newBuilder() - .status(PublishStreamResponseCode.STREAM_ITEMS_UNKNOWN) - .build(); - fromPbj(PublishStreamResponse.newBuilder().status(endOfStream).build()); - - // verify the ProducerBlockItemObserver has sent an error response - verify( - publishStreamResponseObserver, - timeout(testTimeout).atLeast(1)) // TODO: it calls more than 1 usually 2, but why? - .onNext(fromPbj( - PublishStreamResponse.newBuilder().status(endOfStream).build())); - - verify(serviceStatus, timeout(testTimeout).times(1)).stopWebServer(any()); - } - - @Test - public void testResponseNotPermittedAfterCancel() throws NoSuchAlgorithmException { - - final TestProducerBlockItemObserver producerStreamResponseObserver = new TestProducerBlockItemObserver( - testClock, publisher, subscriptionHandler, serverCallStreamObserver, testContext, serviceStatus); - - final List blockItems = generateBlockItems(1); - final ItemAcknowledgement itemAck = ItemAcknowledgement.newBuilder() - .itemsHash(Bytes.wrap(getFakeHash(blockItems))) - .build(); - final PublishStreamResponse publishStreamResponse = PublishStreamResponse.newBuilder() - .acknowledgement(Acknowledgement.newBuilder().itemAck(itemAck).build()) - .build(); - when(objectEvent.get()).thenReturn(publishStreamResponse); - - // Confirm that the observer is called with the first BlockItem - producerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Cancel the observer - producerStreamResponseObserver.cancel(); - - // Attempt to send another BlockItem - producerStreamResponseObserver.onEvent(objectEvent, 0, true); - - // Confirm that canceling the observer allowed only 1 response to be sent. - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(publishStreamResponse)); - } - - @Test - public void testResponseNotPermittedAfterClose() throws NoSuchAlgorithmException { - - final TestProducerBlockItemObserver producerBlockItemObserver = new TestProducerBlockItemObserver( - testClock, publisher, subscriptionHandler, serverCallStreamObserver, testContext, serviceStatus); - - final List blockItems = generateBlockItems(1); - final ItemAcknowledgement itemAck = ItemAcknowledgement.newBuilder() - .itemsHash(Bytes.wrap(getFakeHash(blockItems))) - .build(); - final PublishStreamResponse publishStreamResponse = PublishStreamResponse.newBuilder() - .acknowledgement(Acknowledgement.newBuilder().itemAck(itemAck).build()) - .build(); - when(objectEvent.get()).thenReturn(publishStreamResponse); - - // Confirm that the observer is called with the first BlockItem - producerBlockItemObserver.onEvent(objectEvent, 0, true); - - // Cancel the observer - producerBlockItemObserver.close(); - - // Attempt to send another BlockItem - producerBlockItemObserver.onEvent(objectEvent, 0, true); - - // Confirm that closing the observer allowed only 1 response to be sent. - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).onNext(fromPbj(publishStreamResponse)); - } - @Test public void testOnlyErrorStreamResponseAllowedAfterStatusChange() { final ServiceStatus serviceStatus = new ServiceStatusImpl(testContext); final ProducerBlockItemObserver producerBlockItemObserver = new ProducerBlockItemObserver( - testClock, publisher, subscriptionHandler, serverCallStreamObserver, testContext, serviceStatus); + testClock, publisher, subscriptionHandler, publishStreamResponseObserver, testContext, serviceStatus); final List blockItems = generateBlockItems(1); final PublishStreamRequest publishStreamRequest = PublishStreamRequest.newBuilder() - .blockItems(new com.hedera.hapi.block.BlockItemSet(blockItems)) + .blockItems(new BlockItemSet(blockItems)) .build(); // Confirm that the observer is called with the first BlockItem - producerBlockItemObserver.onNext(fromPbj(publishStreamRequest)); + producerBlockItemObserver.onNext(publishStreamRequest); // Change the status of the service serviceStatus.stopRunning(getClass().getName()); // Confirm that the observer is called with the first BlockItem - producerBlockItemObserver.onNext(fromPbj(publishStreamRequest)); + producerBlockItemObserver.onNext(publishStreamRequest); // Confirm that closing the observer allowed only 1 response to be sent. - verify(serverCallStreamObserver, timeout(testTimeout).times(1)).onNext(any()); - } - - private static class TestProducerBlockItemObserver extends ProducerBlockItemObserver { - public TestProducerBlockItemObserver( - @NonNull final InstantSource clock, - @NonNull final Publisher> publisher, - @NonNull final SubscriptionHandler subscriptionHandler, - @NonNull - final StreamObserver - publishStreamResponseObserver, - @NonNull final BlockNodeContext blockNodeContext, - @NonNull final ServiceStatus serviceStatus) { - super( - clock, - publisher, - subscriptionHandler, - publishStreamResponseObserver, - blockNodeContext, - serviceStatus); - } - - public void cancel() { - onCancel.run(); - } - - public void close() { - onClose.run(); - } + verify(publishStreamResponseObserver, timeout(testTimeout).times(1)).onNext(any()); } } diff --git a/server/src/test/resources/block_service.proto b/server/src/test/resources/block_service.proto index dca29e47c..bee28216a 100644 --- a/server/src/test/resources/block_service.proto +++ b/server/src/test/resources/block_service.proto @@ -10,7 +10,13 @@ option java_multiple_files = true; //import "stream/block_item.proto"; message PublishStreamRequest { - BlockItem block_item = 1; + oneof request { + BlockItemSet block_items = 1; + } +} + +message BlockItemSet { + repeated BlockItem block_items = 1; } message PublishStreamResponse { @@ -81,7 +87,19 @@ message SubscribeStreamRequest { message SubscribeStreamResponse { oneof response { SubscribeStreamResponseCode status = 1; - BlockItem block_item = 2; + SubscribeStreamResponseSet block_items = 2; + } + + message SubscribeStreamResponseSet { + /** + * An ordered list of `BlockItem`s.
+ * This list supports sending block items to subscribers in batches + * for greater channel efficiency. + *

+ * The full response SHALL consist of many of these collections + * followed by a single `status` message. + */ + repeated BlockItem block_items = 1; } } diff --git a/server/src/test/resources/producer.sh b/server/src/test/resources/producer.sh index abc0ae47b..f1cfce6af 100755 --- a/server/src/test/resources/producer.sh +++ b/server/src/test/resources/producer.sh @@ -28,7 +28,7 @@ generate_header() { # Interpolate the integer parameter into the JSON template local result - result=$(echo "$header_template" | jq --argjson number "$block_header_number" ".block_item.block_header.number = $block_header_number") + result=$(echo "$header_template" | jq --argjson number "$block_header_number" ".block_header.number = $block_header_number") echo "$result" } @@ -38,7 +38,7 @@ generate_event() { # Interpolate the integer parameter into the JSON template local result - result=$(echo "$event_template" | jq --argjson creator_id "$creator_node_id" ".block_item.event_header.event_core.creator_node_id = $creator_node_id") + result=$(echo "$event_template" | jq --argjson creator_id "$creator_node_id" ".event_header.event_core.creator_node_id = $creator_node_id") echo "$result" } @@ -48,7 +48,7 @@ generate_block_proof() { # Interpolate the integer parameter into the JSON template local result - result=$(echo "$block_proof_template" | jq --argjson block "$block_number" ".block_item.block_proof.block = $block_number") + result=$(echo "$block_proof_template" | jq --argjson block "$block_number" ".block_proof.block = $block_number") echo "$result" } @@ -85,23 +85,31 @@ event_template=$(cat "templates/event_template.json") block_items=10 while true; do + # Start the BlockItems array + echo "{" + echo "\"block_items\": {" + echo "\"block_items\": [" # Generate 10 BlockItems per Block for ((i=1; i<=$block_items; i++)) do if [[ $i -eq 1 ]]; then result=$(generate_header $iter) - echo "$result" + echo "$result," elif [[ $i -eq $block_items ]]; then result=$(generate_block_proof $iter) echo "$result" else result=$(generate_event $i) - echo "$result" + echo "$result," fi sleep 0.01 done + echo "]" + echo "}" + echo "}" + if [ "$iter" -eq "$2" ]; then exit 0 fi diff --git a/server/src/test/resources/templates/block_proof_template.json b/server/src/test/resources/templates/block_proof_template.json index ca1f17e1d..37f3f2fe3 100644 --- a/server/src/test/resources/templates/block_proof_template.json +++ b/server/src/test/resources/templates/block_proof_template.json @@ -1,7 +1,5 @@ { - "block_item": { - "block_proof": { - "block": -1 - } + "block_proof": { + "block": -1 } } diff --git a/server/src/test/resources/templates/event_template.json b/server/src/test/resources/templates/event_template.json index 2efbcc741..69a4d7be5 100644 --- a/server/src/test/resources/templates/event_template.json +++ b/server/src/test/resources/templates/event_template.json @@ -1,12 +1,7 @@ { - "block_item": - { - "event_header": - { - "event_core": - { - "creator_node_id": 0 - } + "event_header": { + "event_core": { + "creator_node_id": 0 } } } diff --git a/server/src/test/resources/templates/header_template.json b/server/src/test/resources/templates/header_template.json index 2484e0f85..20fcf13c5 100644 --- a/server/src/test/resources/templates/header_template.json +++ b/server/src/test/resources/templates/header_template.json @@ -1,21 +1,19 @@ { - "block_item": { - "block_header": { - "hapi_proto_version": { - "build": "dolor occaecat", - "major": 1, - "minor": 0, - "patch": 0, - "pre": "qui ut quis adipisicing" - }, - "number": -1, - "software_version": { - "build": "sunt sint dolor", - "major": 2, - "minor": 0, - "patch": 0, - "pre": "est" - } + "block_header": { + "hapi_proto_version": { + "build": "dolor occaecat", + "major": 1, + "minor": 0, + "patch": 0, + "pre": "qui ut quis adipisicing" + }, + "number": -1, + "software_version": { + "build": "sunt sint dolor", + "major": 2, + "minor": 0, + "patch": 0, + "pre": "est" } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index fb15cd766..6d46a758c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,7 +40,7 @@ dependencyResolutionManagement { create("libs") { // Define a constant for the platform SDK version. // Platform SDK modules are all released together with matching versions. - val swirldsVersion = "0.51.5" + val swirldsVersion = "0.54.1" // Define a constant for the Dagger version. val daggerVersion = "2.42" @@ -48,10 +48,13 @@ dependencyResolutionManagement { // Define a constant for protobuf version. val protobufVersion = "4.28.2" + val helidonVersion = "4.1.1" + // Compile time dependencies - version("io.helidon.webserver.http2", "4.1.0") - version("io.helidon.webserver.grpc", "4.1.0") - version("io.helidon.logging", "4.1.0") + version("io.helidon.webserver.http2", helidonVersion) + version("io.helidon.webserver", helidonVersion) + version("io.helidon.logging", helidonVersion) + version("com.lmax.disruptor", "4.0.0") version("com.github.spotbugs.annotations", "4.7.3") version("com.swirlds.metrics.api", swirldsVersion) @@ -65,10 +68,12 @@ dependencyResolutionManagement { version("org.hyperledger.besu.nativelib.secp256k1", "0.8.2") version("info.picocli", "4.7.6") - // gRPC dependencies + // gRPC dependencies for the stream subproject version("io.grpc", "1.65.1") version("io.grpc.protobuf", "1.65.1") version("io.grpc.stub", "1.65.1") + + // netty dependency for the simulator subproject version("io.grpc.netty.shaded", "1.65.1") // Reference from the protobuf plugin @@ -79,9 +84,13 @@ dependencyResolutionManagement { version("com.google.protobuf", protobufVersion) version("com.google.protobuf.util", protobufVersion) + var pbjVersion = "0.9.10" + // PBJ dependencies - plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.9.8") - version("com.hedera.pbj.runtime", "0.9.8") + plugin("pbj", "com.hedera.pbj.pbj-compiler").version(pbjVersion) + version("com.hedera.pbj.runtime", pbjVersion) + version("com.hedera.pbj.grpc.helidon", pbjVersion) + version("com.hedera.pbj.grpc.helidon.config", pbjVersion) version("org.antlr.antlr4.runtime", "4.13.1") version("java.annotation", "1.3.2") diff --git a/simulator/build.gradle.kts b/simulator/build.gradle.kts index 18eaf43cc..e7bbf9b6c 100644 --- a/simulator/build.gradle.kts +++ b/simulator/build.gradle.kts @@ -41,6 +41,7 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") + requires("com.google.protobuf") } tasks.register("untarTestBlockStream") { diff --git a/simulator/src/main/java/com/hedera/block/simulator/BlockStreamSimulatorApp.java b/simulator/src/main/java/com/hedera/block/simulator/BlockStreamSimulatorApp.java index 5533736b4..f40431bdc 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/BlockStreamSimulatorApp.java +++ b/simulator/src/main/java/com/hedera/block/simulator/BlockStreamSimulatorApp.java @@ -69,8 +69,8 @@ public BlockStreamSimulatorApp( final SimulatorMode simulatorMode = blockStreamConfig.simulatorMode(); switch (simulatorMode) { - case PUBLISHER -> simulatorModeHandler = - new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + case PUBLISHER -> simulatorModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); case CONSUMER -> simulatorModeHandler = new ConsumerModeHandler(blockStreamConfig); case BOTH -> simulatorModeHandler = new CombinedModeHandler(blockStreamConfig); default -> throw new IllegalArgumentException("Unknown SimulatorMode: " + simulatorMode); diff --git a/simulator/src/main/java/com/hedera/block/simulator/Translator.java b/simulator/src/main/java/com/hedera/block/simulator/Translator.java deleted file mode 100644 index 79c5412b2..000000000 --- a/simulator/src/main/java/com/hedera/block/simulator/Translator.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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.simulator; - -import static java.lang.System.Logger; -import static java.lang.System.Logger.Level.ERROR; -import static java.util.Objects.requireNonNull; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.hedera.hapi.block.PublishStreamRequest; -import com.hedera.hapi.block.PublishStreamResponse; -import com.hedera.hapi.block.SingleBlockResponse; -import com.hedera.hapi.block.SubscribeStreamRequest; -import com.hedera.hapi.block.SubscribeStreamResponse; -import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.pbj.runtime.Codec; -import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.pbj.runtime.io.stream.WritableStreamingData; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Translator class to convert between PBJ and google protoc objects. - * - *

TODO: Remove this class once the Helidon PBJ gRPC work is integrated. - */ -public final class Translator { - private static final Logger LOGGER = System.getLogger(Translator.class.getName()); - - private static final String INVALID_BUFFER_MESSAGE = - "Invalid protocol buffer converting %s from PBJ to protoc for %s"; - - private Translator() {} - - /** - * Converts a {@link BlockItem} to a {@link com.hedera.hapi.block.stream.protoc.BlockItem}. - * - * @param blockItem the {@link BlockItem} to convert - * @return the converted {@link com.hedera.hapi.block.stream.protoc.BlockItem} - */ - @NonNull - public static com.hedera.hapi.block.stream.protoc.BlockItem fromPbj( - @NonNull final BlockItem blockItem) { - try { - final byte[] pbjBytes = - asBytes(com.hedera.hapi.block.stream.BlockItem.PROTOBUF, blockItem); - return com.hedera.hapi.block.stream.protoc.BlockItem.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted("SingleBlockResponse", blockItem); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link SingleBlockResponse} to a {@link - * com.hedera.hapi.block.protoc.SingleBlockResponse}. - * - * @param singleBlockResponse the {@link SingleBlockResponse} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.SingleBlockResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SingleBlockResponse fromPbj( - @NonNull final SingleBlockResponse singleBlockResponse) { - try { - final byte[] pbjBytes = asBytes(SingleBlockResponse.PROTOBUF, singleBlockResponse); - return com.hedera.hapi.block.protoc.SingleBlockResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted("SingleBlockResponse", singleBlockResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link PublishStreamResponse} to a {@link - * com.hedera.hapi.block.protoc.PublishStreamResponse}. - * - * @param publishStreamResponse the {@link PublishStreamResponse} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.PublishStreamResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.PublishStreamResponse fromPbj( - @NonNull final PublishStreamResponse publishStreamResponse) { - try { - final byte[] pbjBytes = asBytes(PublishStreamResponse.PROTOBUF, publishStreamResponse); - return com.hedera.hapi.block.protoc.PublishStreamResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "PublishStreamResponse", publishStreamResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link PublishStreamRequest} to a {@link - * com.hedera.hapi.block.protoc.PublishStreamRequest}. - * - * @param publishStreamRequest the {@link PublishStreamRequest} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.PublishStreamRequest} - */ - @NonNull - public static com.hedera.hapi.block.protoc.PublishStreamRequest fromPbj( - @NonNull final PublishStreamRequest publishStreamRequest) { - try { - final byte[] pbjBytes = asBytes(PublishStreamRequest.PROTOBUF, publishStreamRequest); - return com.hedera.hapi.block.protoc.PublishStreamRequest.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted("PublishStreamRequest", publishStreamRequest); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link SubscribeStreamResponse} to a {@link - * com.hedera.hapi.block.protoc.SubscribeStreamResponse}. - * - * @param subscribeStreamResponse the {@link SubscribeStreamResponse} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.SubscribeStreamResponse} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SubscribeStreamResponse fromPbj( - @NonNull final SubscribeStreamResponse subscribeStreamResponse) { - try { - final byte[] pbjBytes = - asBytes(SubscribeStreamResponse.PROTOBUF, subscribeStreamResponse); - return com.hedera.hapi.block.protoc.SubscribeStreamResponse.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "SubscribeStreamResponse", subscribeStreamResponse); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts a {@link SubscribeStreamRequest} to a {@link - * com.hedera.hapi.block.protoc.SubscribeStreamRequest}. - * - * @param subscribeStreamRequest the {@link SubscribeStreamRequest} to convert - * @return the converted {@link com.hedera.hapi.block.protoc.SubscribeStreamRequest} - */ - @NonNull - public static com.hedera.hapi.block.protoc.SubscribeStreamRequest fromPbj( - @NonNull final SubscribeStreamRequest subscribeStreamRequest) { - try { - final byte[] pbjBytes = - asBytes(SubscribeStreamRequest.PROTOBUF, subscribeStreamRequest); - return com.hedera.hapi.block.protoc.SubscribeStreamRequest.parseFrom(pbjBytes); - } catch (InvalidProtocolBufferException e) { - final String message = - INVALID_BUFFER_MESSAGE.formatted( - "SubscribeStreamRequest", subscribeStreamRequest); - LOGGER.log(ERROR, message); - throw new RuntimeException(message, e); - } - } - - /** - * Converts protoc bytes to a PBJ record of the same type. - * - * @param the type of PBJ record to convert to - * @param codec the record codec to convert the bytes to a PBJ record - * @param bytes the protoc bytes to convert to a PBJ record - * @return the converted PBJ record - * @throws ParseException if the conversion between the protoc bytes and PBJ objects fails - */ - @NonNull - public static T toPbj( - @NonNull final Codec codec, @NonNull final byte[] bytes) throws ParseException { - return codec.parse(Bytes.wrap(bytes)); - } - - @NonNull - private static byte[] asBytes(@NonNull Codec codec, @NonNull T tx) { - requireNonNull(codec); - requireNonNull(tx); - try { - final var bytes = new ByteArrayOutputStream(); - codec.write(tx, new WritableStreamingData(bytes)); - return bytes.toByteArray(); - } catch (IOException e) { - throw new RuntimeException("Unable to convert from PBJ to bytes", e); - } - } -} diff --git a/simulator/src/main/java/com/hedera/block/simulator/config/data/BlockGeneratorConfig.java b/simulator/src/main/java/com/hedera/block/simulator/config/data/BlockGeneratorConfig.java index 23378eeed..097897597 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/config/data/BlockGeneratorConfig.java +++ b/simulator/src/main/java/com/hedera/block/simulator/config/data/BlockGeneratorConfig.java @@ -19,6 +19,7 @@ import com.hedera.block.simulator.config.types.GenerationMode; import com.swirlds.config.api.ConfigData; import com.swirlds.config.api.ConfigProperty; +import com.swirlds.config.api.validation.annotation.Min; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -31,15 +32,19 @@ * @param managerImplementation the implementation class name of the block stream manager * @param paddedLength the length to which block identifiers are padded * @param fileExtension the file extension used for block files + * @param startBlockNumber the optional start block number for the BlockAsFileLargeDataSets manager + * @param endBlockNumber the optional end block number for the BlockAsFileLargeDataSets manager */ @ConfigData("generator") public record BlockGeneratorConfig( @ConfigProperty(defaultValue = "DIR") GenerationMode generationMode, @ConfigProperty(defaultValue = "") String folderRootPath, - @ConfigProperty(defaultValue = "BlockAsFileBlockStreamManager") - String managerImplementation, + @ConfigProperty(defaultValue = "BlockAsFileBlockStreamManager") String managerImplementation, @ConfigProperty(defaultValue = "36") int paddedLength, - @ConfigProperty(defaultValue = ".blk.gz") String fileExtension) { + @ConfigProperty(defaultValue = ".blk.gz") String fileExtension, + // Optional block number range for the BlockAsFileLargeDataSets manager + @ConfigProperty(defaultValue = "1") @Min(1) int startBlockNumber, + @ConfigProperty(defaultValue = "-1") int endBlockNumber) { /** * Constructs a new {@code BlockGeneratorConfig} instance with validation. @@ -64,6 +69,10 @@ public record BlockGeneratorConfig( } folderRootPath = path.toString(); + + if (endBlockNumber > -1 && endBlockNumber < startBlockNumber) { + throw new IllegalArgumentException("endBlockNumber must be greater than or equal to startBlockNumber"); + } } /** @@ -84,6 +93,8 @@ public static class Builder { private String managerImplementation = "BlockAsFileBlockStreamManager"; private int paddedLength = 36; private String fileExtension = ".blk.gz"; + private int startBlockNumber; + private int endBlockNumber; /** * Creates a new instance of the {@code Builder} class with default configuration values. @@ -147,6 +158,30 @@ public Builder fileExtension(String fileExtension) { return this; } + /** + * Sets the start block number for the BlockAsFileLargeDataSets manager to + * begin reading blocks. + * + * @param startBlockNumber the start block number + * @return this {@code Builder} instance + */ + public Builder startBlockNumber(int startBlockNumber) { + this.startBlockNumber = startBlockNumber; + return this; + } + + /** + * Sets the end block number for the BlockAsFileLargeDataSets manager to + * stop reading blocks. + * + * @param endBlockNumber the end block number + * @return this {@code Builder} instance + */ + public Builder endBlockNumber(int endBlockNumber) { + this.endBlockNumber = endBlockNumber; + return this; + } + /** * Builds a new {@link BlockGeneratorConfig} instance with the configured values. * @@ -158,7 +193,9 @@ public BlockGeneratorConfig build() { folderRootPath, managerImplementation, paddedLength, - fileExtension); + fileExtension, + startBlockNumber, + endBlockNumber); } } } diff --git a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManager.java b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManager.java index a1f884f27..42f4aa625 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManager.java +++ b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManager.java @@ -25,10 +25,9 @@ import com.hedera.block.common.utils.FileUtilities; import com.hedera.block.simulator.config.data.BlockGeneratorConfig; import com.hedera.block.simulator.config.types.GenerationMode; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -83,9 +82,10 @@ public GenerationMode getGenerationMode() { /** gets the next block item from the manager */ @Override public BlockItem getNextBlockItem() { - BlockItem nextBlockItem = blocks.get(currentBlockIndex).items().get(currentBlockItemIndex); + BlockItem nextBlockItem = blocks.get(currentBlockIndex).getItemsList().get(currentBlockItemIndex); currentBlockItemIndex++; - if (currentBlockItemIndex >= blocks.get(currentBlockIndex).items().size()) { + if (currentBlockItemIndex + >= blocks.get(currentBlockIndex).getItemsList().size()) { currentBlockItemIndex = 0; currentBlockIndex++; if (currentBlockIndex >= blocks.size()) { @@ -130,12 +130,12 @@ private void loadBlocks() throws IOException, ParseException { if (blockItemBytes == null) { continue; } - final BlockItem blockItem = BlockItem.PROTOBUF.parse(Bytes.wrap(blockItemBytes)); + final BlockItem blockItem = BlockItem.parseFrom(blockItemBytes); parsedBlockItems.add(blockItem); } } - blocks.add(Block.newBuilder().items(parsedBlockItems).build()); + blocks.add(Block.newBuilder().addAllItems(parsedBlockItems).build()); LOGGER.log(DEBUG, "Loaded block: " + blockDirPath); } } diff --git a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileBlockStreamManager.java b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileBlockStreamManager.java index ee31a594a..a9339c16e 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileBlockStreamManager.java +++ b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileBlockStreamManager.java @@ -25,10 +25,9 @@ import com.hedera.block.common.utils.FileUtilities; import com.hedera.block.simulator.config.data.BlockGeneratorConfig; import com.hedera.block.simulator.config.types.GenerationMode; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -77,9 +76,10 @@ public GenerationMode getGenerationMode() { @Override public BlockItem getNextBlockItem() { - BlockItem nextBlockItem = blocks.get(currentBlockIndex).items().get(currentBlockItemIndex); + BlockItem nextBlockItem = blocks.get(currentBlockIndex).getItemsList().get(currentBlockItemIndex); currentBlockItemIndex++; - if (currentBlockItemIndex >= blocks.get(currentBlockIndex).items().size()) { + if (currentBlockItemIndex + >= blocks.get(currentBlockIndex).getItemsList().size()) { currentBlockItemIndex = 0; currentBlockIndex++; if (currentBlockIndex >= blocks.size()) { @@ -117,7 +117,7 @@ private void loadBlocks() throws IOException, ParseException { continue; } - final Block block = Block.PROTOBUF.parse(Bytes.wrap(blockBytes)); + final Block block = Block.parseFrom(blockBytes); blocks.add(block); LOGGER.log(DEBUG, "Loaded block: " + blockPath); } diff --git a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSets.java b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSets.java index 9ccad224a..6fd8ef4d3 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSets.java +++ b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSets.java @@ -24,10 +24,8 @@ import com.hedera.block.simulator.config.data.BlockGeneratorConfig; import com.hedera.block.simulator.config.types.GenerationMode; import com.hedera.block.simulator.exception.BlockSimulatorParsingException; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -40,11 +38,14 @@ public class BlockAsFileLargeDataSets implements BlockStreamManager { private final System.Logger LOGGER = System.getLogger(getClass().getName()); - private final String blockstreamPath; - private int currentBlockIndex = 0; - private int currentBlockItemIndex = 0; + // State for getNextBlock() + private final String blockStreamPath; + private int currentBlockIndex; + private final int endBlockNumber; - private Block currentBlock = null; + // State for getNextBlockItem() + private int currentBlockItemIndex; + private Block currentBlock; private final String formatString; /** @@ -54,7 +55,13 @@ public class BlockAsFileLargeDataSets implements BlockStreamManager { */ @Inject public BlockAsFileLargeDataSets(@NonNull BlockGeneratorConfig config) { - this.blockstreamPath = config.folderRootPath(); + + this.blockStreamPath = config.folderRootPath(); + this.endBlockNumber = config.endBlockNumber(); + + // Override if startBlockNumber is set + this.currentBlockIndex = (config.startBlockNumber() > 1) ? config.startBlockNumber() : 1; + this.formatString = "%0" + config.paddedLength() + "d" + config.fileExtension(); } @@ -65,8 +72,8 @@ public GenerationMode getGenerationMode() { @Override public BlockItem getNextBlockItem() throws IOException, BlockSimulatorParsingException { - if (currentBlock != null && currentBlock.items().size() > currentBlockItemIndex) { - return currentBlock.items().get(currentBlockItemIndex++); + if (currentBlock != null && currentBlock.getItemsList().size() > currentBlockItemIndex) { + return currentBlock.getItemsList().get(currentBlockItemIndex++); } else { currentBlock = getNextBlock(); if (currentBlock != null) { @@ -80,30 +87,34 @@ public BlockItem getNextBlockItem() throws IOException, BlockSimulatorParsingExc @Override public Block getNextBlock() throws IOException, BlockSimulatorParsingException { - currentBlockIndex++; + + // If endBlockNumber is set, evaluate if we've exceeded the + // range. If so, then return null. + if (endBlockNumber > 0 && currentBlockIndex > endBlockNumber) { + return null; + } final String nextBlockFileName = String.format(formatString, currentBlockIndex); - final Path localBlockStreamPath = Path.of(blockstreamPath).resolve(nextBlockFileName); + final Path localBlockStreamPath = Path.of(blockStreamPath).resolve(nextBlockFileName); if (!Files.exists(localBlockStreamPath)) { return null; } - try { - final byte[] blockBytes = - FileUtilities.readFileBytesUnsafe(localBlockStreamPath, RECORD_EXTENSION, GZ_EXTENSION); - - if (Objects.isNull(blockBytes)) { - throw new NullPointerException( - "Unable to read block file [%s]! Most likely not found with the extensions '%s' or '%s'" - .formatted(localBlockStreamPath, RECORD_EXTENSION, GZ_EXTENSION)); - } - - LOGGER.log(INFO, "Loading block: " + localBlockStreamPath.getFileName()); + final byte[] blockBytes = + FileUtilities.readFileBytesUnsafe(localBlockStreamPath, RECORD_EXTENSION, GZ_EXTENSION); - final Block block = Block.PROTOBUF.parse(Bytes.wrap(blockBytes)); - LOGGER.log(INFO, "block loaded with items size= " + block.items().size()); - return block; - } catch (final ParseException e) { - throw new BlockSimulatorParsingException(e); + if (Objects.isNull(blockBytes)) { + throw new NullPointerException( + "Unable to read block file [%s]! Most likely not found with the extensions '%s' or '%s'" + .formatted(localBlockStreamPath, RECORD_EXTENSION, GZ_EXTENSION)); } + + LOGGER.log(INFO, "Loading block: " + localBlockStreamPath.getFileName()); + + final Block block = Block.parseFrom(blockBytes); + LOGGER.log(INFO, "block loaded with items size= " + block.getItemsList().size()); + + currentBlockIndex++; + + return block; } } diff --git a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockStreamManager.java b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockStreamManager.java index 550f83421..b6012f068 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/generator/BlockStreamManager.java +++ b/simulator/src/main/java/com/hedera/block/simulator/generator/BlockStreamManager.java @@ -18,8 +18,8 @@ import com.hedera.block.simulator.config.types.GenerationMode; import com.hedera.block.simulator.exception.BlockSimulatorParsingException; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import java.io.IOException; /** The block stream manager interface. */ diff --git a/simulator/src/main/java/com/hedera/block/simulator/generator/GeneratorInjectionModule.java b/simulator/src/main/java/com/hedera/block/simulator/generator/GeneratorInjectionModule.java index 1b23a1ccb..68fcdd897 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/generator/GeneratorInjectionModule.java +++ b/simulator/src/main/java/com/hedera/block/simulator/generator/GeneratorInjectionModule.java @@ -37,9 +37,10 @@ public interface GeneratorInjectionModule { @Provides static BlockStreamManager providesBlockStreamManager(BlockGeneratorConfig config) { - if ("BlockAsDirBlockStreamManager".equalsIgnoreCase(config.managerImplementation())) { + final String managerImpl = config.managerImplementation(); + if ("BlockAsDirBlockStreamManager".equalsIgnoreCase(managerImpl)) { return new BlockAsDirBlockStreamManager(config); - } else if ("BlockAsFileLargeDataSets".equalsIgnoreCase(config.managerImplementation())) { + } else if ("BlockAsFileLargeDataSets".equalsIgnoreCase(managerImpl)) { return new BlockAsFileLargeDataSets(config); } diff --git a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClient.java b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClient.java index 6b42f180d..27901561d 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClient.java +++ b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClient.java @@ -16,8 +16,8 @@ package com.hedera.block.simulator.grpc; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import java.util.List; /** diff --git a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImpl.java b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImpl.java index b3dd94b36..afc85ae61 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImpl.java +++ b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImpl.java @@ -17,24 +17,24 @@ package com.hedera.block.simulator.grpc; import static com.hedera.block.simulator.metrics.SimulatorMetricTypes.Counter.LiveBlockItemsSent; +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; import static java.util.Objects.requireNonNull; import com.hedera.block.common.utils.ChunkUtils; -import com.hedera.block.simulator.Translator; import com.hedera.block.simulator.config.data.BlockStreamConfig; import com.hedera.block.simulator.config.data.GrpcConfig; import com.hedera.block.simulator.metrics.MetricsService; import com.hedera.hapi.block.protoc.BlockItemSet; import com.hedera.hapi.block.protoc.BlockStreamServiceGrpc; import com.hedera.hapi.block.protoc.PublishStreamRequest; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import edu.umd.cs.findbugs.annotations.NonNull; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -44,7 +44,7 @@ */ public class PublishStreamGrpcClientImpl implements PublishStreamGrpcClient { - private System.Logger LOGGER = System.getLogger(getClass().getName()); + private final System.Logger LOGGER = System.getLogger(getClass().getName()); private StreamObserver requestStreamObserver; private final BlockStreamConfig blockStreamConfig; @@ -93,23 +93,23 @@ public void init() { @Override public boolean streamBlockItem(List blockItems) { - List blockItemsProtoc = new ArrayList<>(); - for (BlockItem blockItem : blockItems) { - blockItemsProtoc.add(Translator.fromPbj(blockItem)); + if (streamEnabled.get()) { + requestStreamObserver.onNext(PublishStreamRequest.newBuilder() + .setBlockItems(BlockItemSet.newBuilder() + .addAllBlockItems(blockItems) + .build()) + .build()); + + metricsService.get(LiveBlockItemsSent).add(blockItems.size()); + LOGGER.log( + INFO, + "Number of block items sent: " + + metricsService.get(LiveBlockItemsSent).get()); + } else { + LOGGER.log(ERROR, "Not allowed to send next batch of block items"); } - requestStreamObserver.onNext(PublishStreamRequest.newBuilder() - .setBlockItems(BlockItemSet.newBuilder() - .addAllBlockItems(blockItemsProtoc) - .build()) - .build()); - metricsService.get(LiveBlockItemsSent).add(blockItemsProtoc.size()); - LOGGER.log( - INFO, - "Total Block items sent: {0}", - metricsService.get(LiveBlockItemsSent).get()); - - return true; + return streamEnabled.get(); } /** @@ -118,30 +118,28 @@ public boolean streamBlockItem(List blockItems) { */ @Override public boolean streamBlock(Block block) { - if (!streamEnabled.get()) { - return false; - } - List blockItemsProtoc = new ArrayList<>(); - for (BlockItem blockItem : block.items()) { - blockItemsProtoc.add(Translator.fromPbj(blockItem)); - } - List> streamingBatches = - ChunkUtils.chunkify(blockItemsProtoc, blockStreamConfig.blockItemsBatchSize()); - for (List streamingBatch : streamingBatches) { - requestStreamObserver.onNext(PublishStreamRequest.newBuilder() - .setBlockItems(BlockItemSet.newBuilder() - .addAllBlockItems(streamingBatch) - .build()) - .build()); - metricsService.get(LiveBlockItemsSent).add(streamingBatch.size()); - LOGGER.log( - INFO, - "Total Block items sent: {0}", - metricsService.get(LiveBlockItemsSent).get()); + List> streamingBatches = + ChunkUtils.chunkify(block.getItemsList(), blockStreamConfig.blockItemsBatchSize()); + for (List streamingBatch : streamingBatches) { + if (streamEnabled.get()) { + requestStreamObserver.onNext(PublishStreamRequest.newBuilder() + .setBlockItems(BlockItemSet.newBuilder() + .addAllBlockItems(streamingBatch) + .build()) + .build()); + metricsService.get(LiveBlockItemsSent).add(streamingBatch.size()); + LOGGER.log( + DEBUG, + "Number of block items sent: " + + metricsService.get(LiveBlockItemsSent).get()); + } else { + LOGGER.log(ERROR, "Not allowed to send next batch of block items"); + break; + } } - return true; + return streamEnabled.get(); } @Override diff --git a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamObserver.java b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamObserver.java index 75bd210c2..8daddfae1 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamObserver.java +++ b/simulator/src/main/java/com/hedera/block/simulator/grpc/PublishStreamObserver.java @@ -16,6 +16,7 @@ package com.hedera.block.simulator.grpc; +import static java.lang.System.Logger.Level.INFO; import static java.util.Objects.requireNonNull; import com.hedera.hapi.block.protoc.PublishStreamResponse; @@ -45,7 +46,7 @@ public PublishStreamObserver(@NonNull final AtomicBoolean streamEnabled) { /** what will the stream observer do with the response from the server */ @Override public void onNext(PublishStreamResponse publishStreamResponse) { - logger.log(Logger.Level.INFO, "Received Response: " + publishStreamResponse.toString()); + logger.log(INFO, "Received Response: " + publishStreamResponse.toString()); } /** Responsible for stream observer behaviour, in case of error. For now, we will stop the stream for every error. In the future we'd want to have a retry mechanism depending on the error. */ @@ -59,6 +60,6 @@ public void onError(@NonNull final Throwable streamError) { /** what will the stream observer do when the stream is completed */ @Override public void onCompleted() { - logger.log(Logger.Level.DEBUG, "Completed"); + logger.log(INFO, "Completed"); } } diff --git a/simulator/src/main/java/com/hedera/block/simulator/metrics/SimulatorMetricTypes.java b/simulator/src/main/java/com/hedera/block/simulator/metrics/SimulatorMetricTypes.java index 57e88a52c..3a0c5c1e8 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/metrics/SimulatorMetricTypes.java +++ b/simulator/src/main/java/com/hedera/block/simulator/metrics/SimulatorMetricTypes.java @@ -33,7 +33,7 @@ private SimulatorMetricTypes() {} *

Each enum value should have a unique grafana label and meaningful description. These * counters can capture data on standard operations or errors. */ - public enum Counter implements MetricMetadata { + public enum Counter implements SimulatorMetricMetadata { // Standard counters /** The number of live block items sent by the simulator . */ LiveBlockItemsSent("live_block_items_sent", "Live Block Items Sent"); @@ -59,7 +59,7 @@ public String description() { } } - private interface MetricMetadata { + private interface SimulatorMetricMetadata { String grafanaLabel(); String description(); diff --git a/simulator/src/main/java/com/hedera/block/simulator/mode/PublisherModeHandler.java b/simulator/src/main/java/com/hedera/block/simulator/mode/PublisherModeHandler.java index 3a1295361..83cbb1d29 100644 --- a/simulator/src/main/java/com/hedera/block/simulator/mode/PublisherModeHandler.java +++ b/simulator/src/main/java/com/hedera/block/simulator/mode/PublisherModeHandler.java @@ -17,6 +17,8 @@ package com.hedera.block.simulator.mode; import static com.hedera.block.simulator.Constants.NANOS_PER_MILLI; +import static com.hedera.block.simulator.metrics.SimulatorMetricTypes.Counter.LiveBlockItemsSent; +import static java.lang.System.Logger.Level.INFO; import static java.util.Objects.requireNonNull; import com.hedera.block.simulator.config.data.BlockStreamConfig; @@ -24,7 +26,8 @@ import com.hedera.block.simulator.exception.BlockSimulatorParsingException; import com.hedera.block.simulator.generator.BlockStreamManager; import com.hedera.block.simulator.grpc.PublishStreamGrpcClient; -import com.hedera.hapi.block.stream.Block; +import com.hedera.block.simulator.metrics.MetricsService; +import com.hedera.hapi.block.stream.protoc.Block; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -45,6 +48,7 @@ public class PublisherModeHandler implements SimulatorModeHandler { private final StreamingMode streamingMode; private final int delayBetweenBlockItems; private final int millisecondsPerBlock; + private final MetricsService metricsService; /** * Constructs a new {@code PublisherModeHandler} with the specified block stream configuration and publisher client. @@ -52,14 +56,17 @@ public class PublisherModeHandler implements SimulatorModeHandler { * @param blockStreamConfig the configuration data for managing block streams * @param publishStreamGrpcClient the grpc client used for streaming blocks * @param blockStreamManager the block stream manager, responsible for generating blocks + * @param metricsService the metrics service to record and report usage statistics */ public PublisherModeHandler( @NonNull final BlockStreamConfig blockStreamConfig, @NonNull final PublishStreamGrpcClient publishStreamGrpcClient, - @NonNull final BlockStreamManager blockStreamManager) { + @NonNull final BlockStreamManager blockStreamManager, + @NonNull final MetricsService metricsService) { this.blockStreamConfig = requireNonNull(blockStreamConfig); this.publishStreamGrpcClient = requireNonNull(publishStreamGrpcClient); this.blockStreamManager = requireNonNull(blockStreamManager); + this.metricsService = requireNonNull(metricsService); streamingMode = blockStreamConfig.streamingMode(); delayBetweenBlockItems = blockStreamConfig.delayBetweenBlockItems(); @@ -76,7 +83,7 @@ public void start() throws BlockSimulatorParsingException, IOException, Interrup } else { constantRateStreaming(); } - LOGGER.log(System.Logger.Level.INFO, "Block Stream Simulator has stopped streaming."); + LOGGER.log(INFO, "Block Stream Simulator has stopped streaming."); } private void millisPerBlockStreaming() throws IOException, InterruptedException, BlockSimulatorParsingException { @@ -105,6 +112,11 @@ private void millisPerBlockStreaming() throws IOException, InterruptedException, } nextBlock = blockStreamManager.getNextBlock(); } + LOGGER.log(INFO, "Block Stream Simulator has stopped"); + LOGGER.log( + INFO, + "Number of BlockItems sent by the Block Stream Simulator: " + + metricsService.get(LiveBlockItemsSent).get()); } private void constantRateStreaming() throws InterruptedException, IOException, BlockSimulatorParsingException { @@ -118,22 +130,20 @@ private void constantRateStreaming() throws InterruptedException, IOException, B Block block = blockStreamManager.getNextBlock(); if (block == null) { - LOGGER.log(System.Logger.Level.INFO, "Block Stream Simulator has reached the end of the block items"); + LOGGER.log(INFO, "Block Stream Simulator has reached the end of the block items"); break; } if (!publishStreamGrpcClient.streamBlock(block)) { - LOGGER.log(System.Logger.Level.INFO, "Block Stream Simulator stopped streaming due to errors."); + LOGGER.log(INFO, "Block Stream Simulator stopped streaming due to errors."); break; } - blockItemsStreamed += block.items().size(); + blockItemsStreamed += block.getItemsList().size(); Thread.sleep(delayMSBetweenBlockItems, delayNSBetweenBlockItems); if (blockItemsStreamed >= blockStreamConfig.maxBlockItemsToStream()) { - LOGGER.log( - System.Logger.Level.INFO, - "Block Stream Simulator has reached the maximum number of block items to" + " stream"); + LOGGER.log(INFO, "Block Stream Simulator has reached the maximum number of block items to" + " stream"); streamBlockItem = false; } } diff --git a/simulator/src/main/java/module-info.java b/simulator/src/main/java/module-info.java index fb2ab01d5..e7c4075eb 100644 --- a/simulator/src/main/java/module-info.java +++ b/simulator/src/main/java/module-info.java @@ -15,7 +15,6 @@ requires static com.google.auto.service; requires com.hedera.block.common; requires com.hedera.block.stream; - requires com.google.protobuf; requires com.hedera.pbj.runtime; requires com.swirlds.common; requires com.swirlds.config.api; diff --git a/simulator/src/main/resources/app.properties b/simulator/src/main/resources/app.properties index 3539c8e5c..9d61c220f 100644 --- a/simulator/src/main/resources/app.properties +++ b/simulator/src/main/resources/app.properties @@ -7,10 +7,21 @@ # dashboard. prometheus.endpointEnabled=false +# ---------------------------------------------- +# BlockAsFileLargeDataSets config +# ---------------------------------------------- #generator.folderRootPath=/Users/user/Downloads/block-0.0.3-perf #generator.managerImplementation=BlockAsFileLargeDataSets + +# Optional range configuration +#generator.startBlockNumber=1 +#generator.endBlockNumber=2000 + #blockStream.maxBlockItemsToStream=100_000_000 #blockStream.streamingMode=MILLIS_PER_BLOCK #blockStream.millisecondsPerBlock=500 #blockStream.blockItemsBatchSize=1_000 #blockStream.delayBetweenBlockItems=3_000_000 + +# ---------------------------------------------- + diff --git a/simulator/src/test/java/com/hedera/block/simulator/BlockStreamSimulatorTest.java b/simulator/src/test/java/com/hedera/block/simulator/BlockStreamSimulatorTest.java index 51b32f72c..67eff4690 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/BlockStreamSimulatorTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/BlockStreamSimulatorTest.java @@ -32,9 +32,9 @@ import com.hedera.block.simulator.metrics.MetricsService; import com.hedera.block.simulator.metrics.MetricsServiceImpl; import com.hedera.block.simulator.mode.PublisherModeHandler; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; -import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.hapi.block.stream.output.protoc.BlockHeader; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import com.swirlds.config.api.Configuration; import java.io.IOException; import java.nio.file.Paths; @@ -69,9 +69,7 @@ void setUp() throws IOException { Configuration configuration = TestUtils.getTestConfiguration( Map.of("blockStream.maxBlockItemsToStream", "100", "blockStream.streamingMode", "CONSTANT_RATE")); - Configuration config = TestUtils.getTestConfiguration(); - metricsService = new MetricsServiceImpl(getTestMetrics(config)); - + metricsService = new MetricsServiceImpl(getTestMetrics(configuration)); blockStreamSimulator = new BlockStreamSimulatorApp(configuration, blockStreamManager, publishStreamGrpcClient, metricsService); } @@ -91,11 +89,15 @@ void start_logsStartedMessage() throws InterruptedException, BlockSimulatorParsi void start_constantRateStreaming() throws InterruptedException, BlockSimulatorParsingException, IOException { BlockItem blockItem = BlockItem.newBuilder() - .blockHeader(BlockHeader.newBuilder().number(1L).build()) + .setBlockHeader(BlockHeader.newBuilder().setNumber(1L).build()) .build(); - Block block1 = Block.newBuilder().items(blockItem).build(); - Block block2 = Block.newBuilder().items(blockItem, blockItem, blockItem).build(); + Block block1 = Block.newBuilder().addItems(blockItem).build(); + Block block2 = Block.newBuilder() + .addItems(blockItem) + .addItems(blockItem) + .addItems(blockItem) + .build(); BlockStreamManager blockStreamManager = mock(BlockStreamManager.class); when(blockStreamManager.getNextBlock()).thenReturn(block1, block2, null); @@ -133,9 +135,9 @@ void stop_doesNotThrowException() { void start_millisPerBlockStreaming() throws InterruptedException, IOException, BlockSimulatorParsingException { BlockStreamManager blockStreamManager = mock(BlockStreamManager.class); BlockItem blockItem = BlockItem.newBuilder() - .blockHeader(BlockHeader.newBuilder().number(1L).build()) + .setBlockHeader(BlockHeader.newBuilder().setNumber(1L).build()) .build(); - Block block = Block.newBuilder().items(blockItem).build(); + Block block = Block.newBuilder().addItems(blockItem).build(); when(blockStreamManager.getNextBlock()).thenReturn(block, block, null); Configuration configuration = TestUtils.getTestConfiguration(Map.of( @@ -162,9 +164,9 @@ void start_millisPerSecond_streamingLagVerifyWarnLog() BlockStreamManager blockStreamManager = mock(BlockStreamManager.class); BlockItem blockItem = BlockItem.newBuilder() - .blockHeader(BlockHeader.newBuilder().number(1L).build()) + .setBlockHeader(BlockHeader.newBuilder().setNumber(1L).build()) .build(); - Block block = Block.newBuilder().items(blockItem).build(); + Block block = Block.newBuilder().addItems(blockItem).build(); when(blockStreamManager.getNextBlock()).thenReturn(block, block, null); PublishStreamGrpcClient publishStreamGrpcClient = mock(PublishStreamGrpcClient.class); diff --git a/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManagerTest.java b/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManagerTest.java index 09f418c4f..54236fd7c 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManagerTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsDirBlockStreamManagerTest.java @@ -37,8 +37,7 @@ private String getAbsoluteFolder(String relativePath) { @Test void getGenerationMode() { - BlockStreamManager blockStreamManager = - getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); + BlockStreamManager blockStreamManager = getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); assertEquals(GenerationMode.DIR, blockStreamManager.getGenerationMode()); assertEquals(GenerationMode.DIR, blockStreamManager.getGenerationMode()); @@ -46,8 +45,7 @@ void getGenerationMode() { @Test void getNextBlockItem() throws IOException, BlockSimulatorParsingException { - BlockStreamManager blockStreamManager = - getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); + BlockStreamManager blockStreamManager = getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); for (int i = 0; i < 1000; i++) { assertNotNull(blockStreamManager.getNextBlockItem()); @@ -56,8 +54,7 @@ void getNextBlockItem() throws IOException, BlockSimulatorParsingException { @Test void getNextBlock() throws IOException, BlockSimulatorParsingException { - BlockStreamManager blockStreamManager = - getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); + BlockStreamManager blockStreamManager = getBlockAsDirBlockStreamManager(getAbsoluteFolder(rootFolder)); for (int i = 0; i < 3000; i++) { assertNotNull(blockStreamManager.getNextBlock()); @@ -68,15 +65,12 @@ void getNextBlock() throws IOException, BlockSimulatorParsingException { void BlockAsFileBlockStreamManagerInvalidRootPath() { assertThrows( RuntimeException.class, - () -> - getBlockAsDirBlockStreamManager( - getAbsoluteFolder("src/test/resources/BlockAsDirException/"))); + () -> getBlockAsDirBlockStreamManager(getAbsoluteFolder("src/test/resources/BlockAsDirException/"))); } private BlockStreamManager getBlockAsDirBlockStreamManager(String rootFolder) { - final BlockGeneratorConfig blockGeneratorConfig = - new BlockGeneratorConfig( - GenerationMode.DIR, rootFolder, "BlockAsDirBlockStreamManager", 36, ".blk"); + final BlockGeneratorConfig blockGeneratorConfig = new BlockGeneratorConfig( + GenerationMode.DIR, rootFolder, "BlockAsDirBlockStreamManager", 36, ".blk", 0, 0); return new BlockAsDirBlockStreamManager(blockGeneratorConfig); } diff --git a/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSetsTest.java b/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSetsTest.java index e03ad357f..f7083cbd2 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSetsTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/generator/BlockAsFileLargeDataSetsTest.java @@ -16,12 +16,15 @@ package com.hedera.block.simulator.generator; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.hedera.block.simulator.config.data.BlockGeneratorConfig; import com.hedera.block.simulator.config.types.GenerationMode; import com.hedera.block.simulator.exception.BlockSimulatorParsingException; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.BlockItem; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -64,6 +67,33 @@ void getNextBlock() throws IOException, BlockSimulatorParsingException { assertNull(blockStreamManager.getNextBlock()); } + @Test + void getNextBlockInRange() throws IOException, BlockSimulatorParsingException { + + final BlockGeneratorConfig blockGeneratorConfig = BlockGeneratorConfig.builder() + .generationMode(GenerationMode.DIR) + .folderRootPath(getAbsoluteFolder(rootFolder)) + .managerImplementation("BlockAsFileBlockStreamManager") + .paddedLength(36) + .fileExtension(".blk") + .startBlockNumber(2) + .endBlockNumber(4) + .build(); + + final BlockStreamManager blockStreamManager = + getBlockAsFileLargeDatasetsBlockStreamManager(blockGeneratorConfig); + + // The startBlockNumber and endBlockNumber signal to the manager + // that it should only return blocks within the specified range. + // Here, the first 3 should succeed (blocks 2, 3 and 4) but the 4th should + // return null. + for (int i = 0; i < 3; i++) { + assertNotNull(blockStreamManager.getNextBlock()); + } + + assertNull(blockStreamManager.getNextBlock()); + } + @Test void getNextBlockItem() throws IOException, BlockSimulatorParsingException { BlockStreamManager blockStreamManager = @@ -95,36 +125,37 @@ void gettingNextBlockItemThrowsParsingException(@TempDir Path tempDir) throws IO byte[] invalidData = "invalid block data".getBytes(); Files.write(currentBlockFilePath, invalidData); - final BlockGeneratorConfig blockGeneratorConfig = - BlockGeneratorConfig.builder() - .generationMode(GenerationMode.DIR) - .folderRootPath(blockDirPath.toString()) - .managerImplementation("BlockAsFileBlockStreamManager") - .paddedLength(36) - .fileExtension(".blk") - .build(); + final BlockGeneratorConfig blockGeneratorConfig = BlockGeneratorConfig.builder() + .generationMode(GenerationMode.DIR) + .folderRootPath(blockDirPath.toString()) + .managerImplementation("BlockAsFileBlockStreamManager") + .paddedLength(36) + .fileExtension(".blk") + .build(); - BlockAsFileLargeDataSets blockStreamManager = - new BlockAsFileLargeDataSets(blockGeneratorConfig); + BlockAsFileLargeDataSets blockStreamManager = new BlockAsFileLargeDataSets(blockGeneratorConfig); assertThrows( - BlockSimulatorParsingException.class, + com.google.protobuf.InvalidProtocolBufferException.class, blockStreamManager::getNextBlock, - "Expected getNextBlock() to throw BlockSimulatorParsingException"); + "com.google.protobuf.InvalidProtocolBufferException: Protocol message end-group tag did not match expected tag."); } - private BlockAsFileLargeDataSets getBlockAsFileLargeDatasetsBlockStreamManager( - String rootFolder) { + private BlockAsFileLargeDataSets getBlockAsFileLargeDatasetsBlockStreamManager(String rootFolder) { - final BlockGeneratorConfig blockGeneratorConfig = - BlockGeneratorConfig.builder() - .generationMode(GenerationMode.DIR) - .folderRootPath(rootFolder) - .managerImplementation("BlockAsFileBlockStreamManager") - .paddedLength(36) - .fileExtension(".blk") - .build(); + final BlockGeneratorConfig blockGeneratorConfig = BlockGeneratorConfig.builder() + .generationMode(GenerationMode.DIR) + .folderRootPath(rootFolder) + .managerImplementation("BlockAsFileBlockStreamManager") + .paddedLength(36) + .fileExtension(".blk") + .build(); + return getBlockAsFileLargeDatasetsBlockStreamManager(blockGeneratorConfig); + } + + private BlockAsFileLargeDataSets getBlockAsFileLargeDatasetsBlockStreamManager( + BlockGeneratorConfig blockGeneratorConfig) { return new BlockAsFileLargeDataSets(blockGeneratorConfig); } @@ -134,12 +165,8 @@ private static String getAbsoluteFolder(String relativePath) { private static int getFilesInFolder(String absolutePath) { File folder = new File(absolutePath); - File[] blkFiles = - folder.listFiles( - file -> - file.isFile() - && (file.getName().endsWith(".blk") - || file.getName().endsWith(".blk.gz"))); + File[] blkFiles = folder.listFiles(file -> file.isFile() + && (file.getName().endsWith(".blk") || file.getName().endsWith(".blk.gz"))); return blkFiles.length; } } diff --git a/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImplTest.java b/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImplTest.java index 743553ea1..2d1a196cc 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImplTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamGrpcClientImplTest.java @@ -27,8 +27,8 @@ import com.hedera.block.simulator.config.data.GrpcConfig; import com.hedera.block.simulator.metrics.MetricsService; import com.hedera.block.simulator.metrics.MetricsServiceImpl; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; import com.swirlds.config.api.Configuration; import io.grpc.ManagedChannel; import java.io.IOException; @@ -45,9 +45,10 @@ @ExtendWith(MockitoExtension.class) class PublishStreamGrpcClientImplTest { + private MetricsService metricsService; + GrpcConfig grpcConfig; BlockStreamConfig blockStreamConfig; - MetricsService metricsService; AtomicBoolean streamEnabled; @BeforeEach @@ -78,9 +79,13 @@ void streamBlockItem() { @Test void streamBlock() { BlockItem blockItem = BlockItem.newBuilder().build(); - Block block = Block.newBuilder().items(blockItem).build(); + Block block = Block.newBuilder().addItems(blockItem).build(); - Block block1 = Block.newBuilder().items(blockItem, blockItem, blockItem).build(); + Block block1 = Block.newBuilder() + .addItems(blockItem) + .addItems(blockItem) + .addItems(blockItem) + .build(); PublishStreamGrpcClientImpl publishStreamGrpcClient = new PublishStreamGrpcClientImpl(grpcConfig, blockStreamConfig, metricsService, streamEnabled); @@ -96,7 +101,7 @@ void streamBlock() { @Test void streamBlockReturnsFalse() { BlockItem blockItem = BlockItem.newBuilder().build(); - Block block = Block.newBuilder().items(blockItem).build(); + Block block = Block.newBuilder().addItems(blockItem).build(); streamEnabled.set(false); PublishStreamGrpcClientImpl publishStreamGrpcClient = new PublishStreamGrpcClientImpl(grpcConfig, blockStreamConfig, metricsService, streamEnabled); diff --git a/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamObserverTest.java b/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamObserverTest.java index 52c42be03..178178b4c 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamObserverTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/grpc/PublishStreamObserverTest.java @@ -32,7 +32,7 @@ void onNext() { PublishStreamObserver publishStreamObserver = new PublishStreamObserver(streamEnabled); publishStreamObserver.onNext(response); - assertTrue(streamEnabled.get(), "allowNext should remain true after onCompleted"); + assertTrue(streamEnabled.get(), "streamEnabled should remain true after onCompleted"); } @Test @@ -41,7 +41,7 @@ void onError() { PublishStreamObserver publishStreamObserver = new PublishStreamObserver(streamEnabled); publishStreamObserver.onError(new Throwable()); - assertFalse(streamEnabled.get(), "allowNext should be set to false after onError"); + assertFalse(streamEnabled.get(), "streamEnabled should be set to false after onError"); } @Test @@ -50,6 +50,6 @@ void onCompleted() { PublishStreamObserver publishStreamObserver = new PublishStreamObserver(streamEnabled); publishStreamObserver.onCompleted(); - assertTrue(streamEnabled.get(), "allowNext should remain true after onCompleted"); + assertTrue(streamEnabled.get(), "streamEnabled should remain true after onCompleted"); } } diff --git a/simulator/src/test/java/com/hedera/block/simulator/mode/PublisherModeHandlerTest.java b/simulator/src/test/java/com/hedera/block/simulator/mode/PublisherModeHandlerTest.java index 687c842c9..78ee4b250 100644 --- a/simulator/src/test/java/com/hedera/block/simulator/mode/PublisherModeHandlerTest.java +++ b/simulator/src/test/java/com/hedera/block/simulator/mode/PublisherModeHandlerTest.java @@ -25,14 +25,20 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.hedera.block.simulator.TestUtils; import com.hedera.block.simulator.config.data.BlockStreamConfig; import com.hedera.block.simulator.config.types.StreamingMode; import com.hedera.block.simulator.generator.BlockStreamManager; import com.hedera.block.simulator.grpc.PublishStreamGrpcClient; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.block.simulator.metrics.MetricsService; +import com.hedera.block.simulator.metrics.MetricsServiceImpl; +import com.hedera.hapi.block.stream.protoc.Block; +import com.hedera.hapi.block.stream.protoc.BlockItem; +import com.swirlds.config.api.Configuration; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -49,11 +55,19 @@ public class PublisherModeHandlerTest { @Mock private BlockStreamManager blockStreamManager; + @Mock + private MetricsService metricsService; + private PublisherModeHandler publisherModeHandler; @BeforeEach - void setUp() { + void setUp() throws IOException { MockitoAnnotations.openMocks(this); + + Configuration configuration = TestUtils.getTestConfiguration( + Map.of("blockStream.maxBlockItemsToStream", "100", "blockStream.streamingMode", "CONSTANT_RATE")); + + metricsService = new MetricsServiceImpl(TestUtils.getTestMetrics(configuration)); } @Test @@ -62,7 +76,8 @@ void testStartWithMillisPerBlockStreaming_WithBlocks() throws Exception { when(blockStreamConfig.streamingMode()).thenReturn(StreamingMode.MILLIS_PER_BLOCK); when(blockStreamConfig.millisecondsPerBlock()).thenReturn(0); // No delay for testing - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); Block block1 = mock(Block.class); Block block2 = mock(Block.class); @@ -71,6 +86,7 @@ void testStartWithMillisPerBlockStreaming_WithBlocks() throws Exception { .thenReturn(block1) .thenReturn(block2) .thenReturn(null); + when(publishStreamGrpcClient.streamBlock(any(Block.class))).thenReturn(true); when(publishStreamGrpcClient.streamBlock(block1)).thenReturn(true); when(publishStreamGrpcClient.streamBlock(block2)).thenReturn(true); @@ -87,7 +103,8 @@ void testStartWithMillisPerBlockStreaming_WithBlocks() throws Exception { void testStartWithMillisPerBlockStreaming_NoBlocks() throws Exception { when(blockStreamConfig.streamingMode()).thenReturn(StreamingMode.MILLIS_PER_BLOCK); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); when(blockStreamManager.getNextBlock()).thenReturn(null); @@ -103,7 +120,9 @@ void testStartWithConstantRateStreaming_WithinMaxItems() throws Exception { when(blockStreamConfig.delayBetweenBlockItems()).thenReturn(0); when(blockStreamConfig.maxBlockItemsToStream()).thenReturn(5); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); + when(publishStreamGrpcClient.streamBlock(any(Block.class))).thenReturn(true); Block block1 = mock(Block.class); Block block2 = mock(Block.class); @@ -113,8 +132,8 @@ void testStartWithConstantRateStreaming_WithinMaxItems() throws Exception { BlockItem blockItem3 = mock(BlockItem.class); BlockItem blockItem4 = mock(BlockItem.class); - when(block1.items()).thenReturn(Arrays.asList(blockItem1, blockItem2)); - when(block2.items()).thenReturn(Arrays.asList(blockItem3, blockItem4)); + when(block1.getItemsList()).thenReturn(Arrays.asList(blockItem1, blockItem2)); + when(block2.getItemsList()).thenReturn(Arrays.asList(blockItem3, blockItem4)); when(blockStreamManager.getNextBlock()) .thenReturn(block1) @@ -135,7 +154,8 @@ void testStartWithConstantRateStreaming_WithinMaxItems() throws Exception { @Test void testStartWithConstantRateStreaming_NoBlocks() throws Exception { when(blockStreamConfig.streamingMode()).thenReturn(StreamingMode.CONSTANT_RATE); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); when(blockStreamManager.getNextBlock()).thenReturn(null); @@ -149,7 +169,8 @@ void testStartWithConstantRateStreaming_NoBlocks() throws Exception { void testStartWithExceptionDuringStreaming() throws Exception { when(blockStreamConfig.streamingMode()).thenReturn(StreamingMode.MILLIS_PER_BLOCK); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); when(blockStreamManager.getNextBlock()).thenThrow(new IOException("Test exception")); @@ -166,7 +187,8 @@ void testMillisPerBlockStreaming_streamSuccessBecomesFalse() throws Exception { when(blockStreamConfig.streamingMode()).thenReturn(StreamingMode.MILLIS_PER_BLOCK); when(blockStreamConfig.millisecondsPerBlock()).thenReturn(1000); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); Block block1 = mock(Block.class); Block block2 = mock(Block.class); @@ -193,7 +215,8 @@ void testConstantRateStreaming_streamSuccessBecomesFalse() throws Exception { when(blockStreamConfig.delayBetweenBlockItems()).thenReturn(0); when(blockStreamConfig.maxBlockItemsToStream()).thenReturn(100); - publisherModeHandler = new PublisherModeHandler(blockStreamConfig, publishStreamGrpcClient, blockStreamManager); + publisherModeHandler = new PublisherModeHandler( + blockStreamConfig, publishStreamGrpcClient, blockStreamManager, metricsService); Block block1 = mock(Block.class); Block block2 = mock(Block.class); @@ -201,8 +224,8 @@ void testConstantRateStreaming_streamSuccessBecomesFalse() throws Exception { BlockItem blockItem1 = mock(BlockItem.class); BlockItem blockItem2 = mock(BlockItem.class); - when(block1.items()).thenReturn(Arrays.asList(blockItem1)); - when(block2.items()).thenReturn(Arrays.asList(blockItem2)); + when(block1.getItemsList()).thenReturn(Collections.singletonList(blockItem1)); + when(block2.getItemsList()).thenReturn(Collections.singletonList(blockItem2)); when(blockStreamManager.getNextBlock()) .thenReturn(block1) diff --git a/stream/src/main/java/module-info.java b/stream/src/main/java/module-info.java index 71b3bed38..f0e605cf7 100644 --- a/stream/src/main/java/module-info.java +++ b/stream/src/main/java/module-info.java @@ -2,9 +2,13 @@ exports com.hedera.hapi.block; exports com.hedera.hapi.block.protoc; exports com.hedera.hapi.block.stream.protoc; + exports com.hedera.hapi.block.stream.input.protoc; + exports com.hedera.hapi.block.stream.output.protoc; + exports com.hedera.hapi.platform.event.legacy; exports com.hedera.hapi.block.stream; exports com.hedera.hapi.block.stream.input; exports com.hedera.hapi.block.stream.output; + exports com.hedera.hapi.platform.event; exports com.hedera.hapi.node.base; exports com.hedera.hapi.node.base.codec; exports com.hedera.hapi.node.base.schema; @@ -57,7 +61,6 @@ exports com.hedera.hapi.node.state.primitives; exports com.hedera.hapi.node.state.throttles; exports com.hedera.hapi.node.state.congestion; - exports com.hedera.hapi.platform.event; exports com.hedera.services.stream.proto; exports com.hederahashgraph.api.proto.java; exports com.hederahashgraph.service.proto.java;