diff --git a/charts/hedera-block-node/dashboards/block-node-server.json b/charts/hedera-block-node/dashboards/block-node-server.json index 8f97037f..9a3febcf 100644 --- a/charts/hedera-block-node/dashboards/block-node-server.json +++ b/charts/hedera-block-node/dashboards/block-node-server.json @@ -18,12 +18,11 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "links": [ - - ], + "id": 1, + "links": [], "panels": [ { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, @@ -32,1991 +31,2957 @@ }, "id": 16, "panels": [ - - ], - "title": "Errors", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 1 - } - ] + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 1 - }, - "id": 15, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 1 + }, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_stream_mediator_error_total", + "instant": false, + "legendFormat": "Block Item Errors", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Mediator Errors", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "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": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 1 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 1 - } - ] - }, - "unit": "reqps" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 1 - }, - "id": 14, - "options": { - "legend": { - "calcs": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_stream_mediator_error_total [$__rate_interval])", + "instant": false, + "legendFormat": "Mediator Errors", + "range": true, + "refId": "A" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Rate of Mediator Errors", + "type": "timeseries" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "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": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 1 - } - ] - }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 7 - }, - "id": 28, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 25 + }, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_stream_persistence_handler_error_total", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Stream Persistence Errors", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "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": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 25 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_stream_persistence_handler_error_total [$__rate_interval])", + "instant": false, + "legendFormat": "Stream Persistence Errors", + "range": true, + "refId": "A" + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "reqps" + "title": "Rate of Stream Persistence Errors", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 7 - }, - "id": 29, - "options": { - "legend": { - "calcs": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "dark-red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 32 + }, + "id": 42, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "last_over_time(hedera_block_node_verification_blocks_failed_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Total Failed Verifications", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate(hedera_block_node_stream_persistence_handler_error_total [$__rate_interval])", - "instant": false, - "legendFormat": "Stream Persistence Errors", - "range": true, - "refId": "A" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 32 + }, + "id": 41, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(hedera_block_node_verification_blocks_failed_total[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Failed Verifications", + "type": "timeseries" } ], - "title": "Rate of Stream Persistence Errors", - "type": "timeseries" + "title": "Errors", + "type": "row" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 14 + "y": 1 }, "id": 9, "panels": [ - - ], - "title": "Live Stream", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 15 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 89 + }, + "id": 13, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Live Block Items Received from Producer", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_received_total", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Items Received from Producer", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "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" + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 89 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 30 - } - ] - }, - "unit": "reqps" + "title": "Rate of Live Block Items Received from Producer", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 15 - }, - "id": 12, - "options": { - "legend": { - "calcs": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 94 + }, + "id": 3, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_total", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Live Block Item Counter", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Live Block Items Received from Producer", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "dark-red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 94 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" + "title": "Rate of Live Block Items Received by Mediator", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 20 - }, - "id": 3, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 99 + }, + "id": 11, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_consumed_total", + "instant": false, + "legendFormat": "BlockItems Consumed", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Live Block Items Consumed Counter", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_total", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Item Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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": "area" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 99 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 30 - } - ] - }, - "unit": "reqps" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 20 - }, - "id": 7, - "options": { - "legend": { - "calcs": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Rate of Block Items Sent to Consumer(s)", + "type": "timeseries" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Live Block Items Received by Mediator", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Mediator Remaining Capacity", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "red", + "value": 1000 + }, + { + "color": "yellow", + "value": 2000 + }, + { + "color": "green", + "value": 4096 + } + ] } - ] - }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 25 - }, - "id": 11, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 130 + }, + "id": 30, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_mediator_ring_buffer_remaining_capacity", + "instant": false, + "legendFormat": "Mediator Remaining Capacity", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Mediator Remaining Capacity", + "type": "gauge" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_consumed_total", - "instant": false, - "legendFormat": "BlockItems Consumed", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Items Consumed Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 130 + }, + "id": 32, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "min(hedera_block_node_mediator_ring_buffer_remaining_capacity)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 80 - } - ] - }, - "unit": "reqps" + "title": "Mediator Ring Buffer Capacity", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 25 - }, - "id": 10, - "options": { - "legend": { - "calcs": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Notifier Remaining Capacity", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "red", + "value": 200 + }, + { + "color": "#EAB839", + "value": 201 + }, + { + "color": "green", + "value": 501 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 143 + }, + "id": 31, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_notifier_ring_buffer_remaining_capacity", + "instant": false, + "legendFormat": "Notifier Remaining Capacity", + "range": true, + "refId": "A" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Notifier Remaining Capacity", + "type": "gauge" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Block Items Sent to Consumer(s)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Producers", - "mappings": [ - - ], - "max": 20, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "red", - "value": 10 + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] } - ] - } - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 30 - }, - "id": 25, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 143 + }, + "id": 33, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "min(hedera_block_node_notifier_ring_buffer_remaining_capacity)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Notifier Ring Buffer Capacity", + "type": "timeseries" }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_producers", - "instant": false, - "legendFormat": "Producers", - "range": true, - "refId": "A" - } - ], - "title": "Producers", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Consumers", - "mappings": [ - - ], - "max": 20, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 10 - }, - { - "color": "red", - "value": 15 + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Producers", + "mappings": [], + "max": 20, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "red", + "value": 10 + } + ] } - ] - } - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 30 - }, - "id": 4, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 150 + }, + "id": 25, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_producers", + "instant": false, + "legendFormat": "Producers", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Producers", + "type": "gauge" }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_consumers", - "instant": false, - "legendFormat": "Consumers", - "range": true, - "refId": "A" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Consumers", + "mappings": [], + "max": 20, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 15 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 150 + }, + "id": 4, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_consumers", + "instant": false, + "legendFormat": "Consumers", + "range": true, + "refId": "A" + } + ], + "title": "Consumers", + "type": "gauge" } ], - "title": "Consumers", - "type": "gauge" + "title": "Live Stream", + "type": "row" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 37 + "y": 2 }, "id": 18, "panels": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 132 + }, + "id": 6, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_blocks_persisted_total", + "instant": false, + "legendFormat": "Block Persistence Counter", + "range": true, + "refId": "A" + } + ], + "title": "Block Persistence Counter", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "green", + "value": 4 + } + ] + }, + "unit": "wps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 132 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_blocks_persisted_total[$__rate_interval])", + "instant": false, + "legendFormat": "Block Persistence Counter", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Blocks Written to the File System", + "type": "timeseries" + } ], "title": "Persistence", "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [ - - ] - }, + "collapsed": true, "gridPos": { - "h": 6, - "w": 6, + "h": 1, + "w": 24, "x": 0, - "y": 38 + "y": 3 }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "id": 23, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 91 + }, + "id": 24, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_successful_pub_stream_resp_total", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "PublishStreamResponses Generated", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_blocks_persisted_total", - "instant": false, - "legendFormat": "Block Persistence Counter", - "range": true, - "refId": "A" - } - ], - "title": "Block Persistence Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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": "area" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 91 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_successful_pub_stream_resp_total [$__rate_interval])", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 2 - }, - { - "color": "green", - "value": 4 - } - ] - }, - "unit": "wps" + "title": "Rate of PublishStreamResponses Generated", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 38 - }, - "id": 8, - "options": { - "legend": { - "calcs": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 97 + }, + "id": 27, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_successful_pub_stream_resp_sent_total", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "PublishStreamResponses Sent", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate(hedera_block_node_blocks_persisted_total[$__rate_interval])", - "instant": false, - "legendFormat": "Block Persistence Counter", - "range": true, - "refId": "A" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 6, + "w": 18, + "x": 6, + "y": 97 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_successful_pub_stream_resp_sent_total[$__rate_interval])", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } + ], + "title": "Rate of PublishStreamResponses Sent to Producers", + "type": "timeseries" } ], - "title": "Rate of Blocks Written to the File System", - "type": "timeseries" + "title": "Live Stream Responses", + "type": "row" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 44 + "y": 4 }, - "id": 23, + "id": 34, "panels": [ - - ], - "title": "Live Stream Responses", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] } - ] - }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 45 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 38, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } ], - "fields": "", - "values": false + "title": "Total Blocks Verified", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_successful_pub_stream_resp_total", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "PublishStreamResponses Generated", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 5 + }, + "id": 36, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "reqps" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 45 - }, - "id": 22, - "options": { - "legend": { - "calcs": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Blocks Verified over time", + "range": true, + "refId": "A", + "useBackend": false + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Blocks Verified", + "type": "timeseries" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate( hedera_block_node_successful_pub_stream_resp_total [$__rate_interval])", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "Rate of PublishStreamResponses Generated", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 51 - }, - "id": 27, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 11 + }, + "id": 37, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "(last_over_time(hedera_block_node_verification_block_time_total[$__interval]) / last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval]) )", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } ], - "fields": "", - "values": false + "title": "Avg. Verification Time per Block", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_successful_pub_stream_resp_sent_total", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "PublishStreamResponses Sent", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 11 + }, + "id": 35, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" } }, - "mappings": [ - + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "(last_over_time(hedera_block_node_verification_block_time_total[$__interval]) / last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval]) )", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Average Block Verification Time in ms", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "reqps" + "title": "Verification Time", + "type": "timeseries" }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 51 - }, - "id": 26, - "options": { - "legend": { - "calcs": [ - + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 17 + }, + "id": 39, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total / hedera_block_node_verification_blocks_verified_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Avg. Items per Block", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate(hedera_block_node_successful_pub_stream_resp_sent_total[$__rate_interval])", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 17 + }, + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total / hedera_block_node_verification_blocks_verified_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Avg. Items per Block", + "type": "timeseries" } ], - "title": "Rate of PublishStreamResponses Sent to Producers", - "type": "timeseries" + "title": "Verification", + "type": "row" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 57 + "y": 5 }, "id": 17, "panels": [ - - ], - "title": "Single Block", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 58 - }, - "id": 19, - "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": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_single_blocks_retrieved_total", - "instant": false, - "legendFormat": "Single Blocks Retrieved", - "range": true, - "refId": "A" - } - ], - "title": "Single Blocks Retrieved", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 92 + }, + "id": 19, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_single_blocks_retrieved_total", + "instant": false, + "legendFormat": "Single Blocks Retrieved", + "range": true, + "refId": "A" } - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "reqps" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 58 - }, - "id": 5, - "options": { - "legend": { - "calcs": [ - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Single Blocks Retrieved", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate(hedera_block_node_single_blocks_retrieved_total[$__rate_interval])", - "instant": false, - "legendFormat": "RPS of Single Blocks Retrieval", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Single Blocks Retrieved", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 92 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "unit": "short" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 64 - }, - "id": 21, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_single_blocks_retrieved_total[$__rate_interval])", + "instant": false, + "legendFormat": "RPS of Single Blocks Retrieval", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Rate of Single Blocks Retrieved", + "type": "timeseries" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_single_blocks_not_found_total", - "instant": false, - "legendFormat": "Single Blocks Not Found", - "range": true, - "refId": "A" - } - ], - "title": "Single Blocks Not Found", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 98 + }, + "id": 21, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_single_blocks_not_found_total", + "instant": false, + "legendFormat": "Single Blocks Not Found", + "range": true, + "refId": "A" } - }, - "mappings": [ - - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "reqps" - }, - "overrides": [ - - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 64 - }, - "id": 20, - "options": { - "legend": { - "calcs": [ - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "title": "Single Blocks Not Found", + "type": "stat" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "rate( hedera_block_node_single_blocks_not_found_total [$__rate_interval])", - "instant": false, - "legendFormat": "Single Blocks Not Found", - "range": true, - "refId": "A" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 98 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_single_blocks_not_found_total [$__rate_interval])", + "instant": false, + "legendFormat": "Single Blocks Not Found", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Single Blocks Not Found", + "type": "timeseries" } ], - "title": "Rate of Single Blocks Not Found", - "type": "timeseries" + "title": "Single Block", + "type": "row" } ], + "preload": false, "refresh": "5s", - "schemaVersion": 39, - "tags": [ - - ], + "schemaVersion": 40, + "tags": [], "templating": { - "list": [ - - ] + "list": [] }, "time": { "from": "now-5m", "to": "now" }, - "timepicker": { - - }, + "timepicker": {}, "timezone": "browser", "title": "Block-Node Server Dashboard", "uid": "edu86nutnxts0c", - "version": 1, + "version": 2, "weekStart": "" } diff --git a/server/docker/metrics/dashboards/block-node-server.json b/server/docker/metrics/dashboards/block-node-server.json index 1444259b..9a3febcf 100644 --- a/server/docker/metrics/dashboards/block-node-server.json +++ b/server/docker/metrics/dashboards/block-node-server.json @@ -18,6 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, + "id": 1, "links": [], "panels": [ { @@ -45,7 +46,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -61,7 +63,7 @@ "h": 6, "w": 6, "x": 0, - "y": 33 + "y": 1 }, "id": 15, "options": { @@ -81,7 +83,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.4", + "pluginVersion": "11.4.0", "targets": [ { "datasource": { @@ -116,6 +118,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -146,7 +149,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -162,7 +166,7 @@ "h": 6, "w": 18, "x": 6, - "y": 33 + "y": 1 }, "id": 14, "options": { @@ -177,6 +181,7 @@ "sort": "none" } }, + "pluginVersion": "11.4.0", "targets": [ { "datasource": { @@ -209,7 +214,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -225,7 +231,7 @@ "h": 7, "w": 6, "x": 0, - "y": 39 + "y": 25 }, "id": 28, "options": { @@ -245,7 +251,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.4", + "pluginVersion": "11.4.0", "targets": [ { "datasource": { @@ -280,6 +286,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -310,7 +317,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null } ] }, @@ -322,7 +330,7 @@ "h": 7, "w": 18, "x": 6, - "y": 39 + "y": 25 }, "id": 29, "options": { @@ -337,6 +345,7 @@ "sort": "none" } }, + "pluginVersion": "11.4.0", "targets": [ { "datasource": { @@ -353,1871 +362,2614 @@ ], "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", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 2 - }, - "id": 13, - "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": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_received_total", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Items Received from Producer", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "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": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 30 + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "dark-red", + "value": 1 + } + ] } - ] + }, + "overrides": [] }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 2 - }, - "id": 12, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 32 }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Live Block Items Received from Producer", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] + "id": 42, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 7 - }, - "id": 3, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "last_over_time(hedera_block_node_verification_blocks_failed_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } ], - "fields": "", - "values": false + "title": "Total Failed Verifications", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_total", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Item Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 30 - } - ] + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 32 }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 7 - }, - "id": 7, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "id": 41, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "rate(hedera_block_node_verification_blocks_failed_total[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Failed Verifications", + "type": "timeseries" } ], - "title": "Rate of Live Block Items Received by Mediator", - "type": "timeseries" + "title": "Errors", + "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, + "collapsed": true, "gridPos": { - "h": 5, - "w": 6, + "h": 1, + "w": 24, "x": 0, - "y": 12 - }, - "id": 11, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "y": 1 }, - "pluginVersion": "11.1.4", - "targets": [ + "id": 9, + "panels": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_live_block_items_consumed_total", - "instant": false, - "legendFormat": "BlockItems Consumed", - "range": true, - "refId": "A" - } - ], - "title": "Live Block Items Consumed Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "green", - "value": 80 - } - ] + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 18, - "x": 6, - "y": 12 - }, - "id": 10, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 89 }, - "editorMode": "code", - "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", - "instant": false, - "legendFormat": "BlockItems", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Block Items Sent to Consumer(s)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Mediator Remaining Capacity", - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "red", - "value": 1000 - }, - { - "color": "yellow", - "value": 2000 - }, - { - "color": "green", - "value": 4096 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 17 - }, - "id": 30, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "id": 13, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Live Block Items Received from Producer", + "type": "stat" }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_mediator_ring_buffer_remaining_capacity", - "instant": false, - "legendFormat": "Mediator Remaining Capacity", - "range": true, - "refId": "A" - } - ], - "title": "Mediator Remaining Capacity", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 17 - }, - "id": 32, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 89 }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "min(hedera_block_node_mediator_ring_buffer_remaining_capacity)", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Mediator Ring Buffer Capacity", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Notifier Remaining Capacity", - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "red", - "value": 200 - }, - { - "color": "#EAB839", - "value": 201 - }, - { - "color": "green", - "value": 501 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 24 - }, - "id": 31, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_received_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Rate of Live Block Items Received from Producer", + "type": "timeseries" }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_notifier_ring_buffer_remaining_capacity", - "instant": false, - "legendFormat": "Notifier Remaining Capacity", - "range": true, - "refId": "A" - } - ], - "title": "Notifier Remaining Capacity", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 24 - }, - "id": 33, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 94 }, - "editorMode": "code", - "expr": "min(hedera_block_node_notifier_ring_buffer_remaining_capacity)", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Notifier Ring Buffer Capacity", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Producers", - "mappings": [], - "max": 20, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 5 - }, - { - "color": "red", - "value": 10 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 31 - }, - "id": 25, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "editorMode": "code", - "expr": "hedera_block_node_producers", - "instant": false, - "legendFormat": "Producers", - "range": true, - "refId": "A" - } - ], - "title": "Producers", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "Consumers", - "mappings": [], - "max": 20, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 10 - }, - { - "color": "red", - "value": 15 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 31 - }, - "id": 4, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_total", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Live Block Item Counter", + "type": "stat" }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_consumers", - "instant": false, - "legendFormat": "Consumers", - "range": true, - "refId": "A" - } - ], - "title": "Consumers", - "type": "gauge" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 38 - }, - "id": 18, - "panels": [], - "title": "Persistence", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "dark-red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 30 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 94 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Live Block Items Received by Mediator", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 99 + }, + "id": 11, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_consumed_total", + "instant": false, + "legendFormat": "BlockItems Consumed", + "range": true, + "refId": "A" + } + ], + "title": "Live Block Items Consumed Counter", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "green", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 99 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_live_block_items_consumed_total [$__rate_interval])", + "instant": false, + "legendFormat": "BlockItems", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Block Items Sent to Consumer(s)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Mediator Remaining Capacity", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "red", + "value": 1000 + }, + { + "color": "yellow", + "value": 2000 + }, + { + "color": "green", + "value": 4096 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 130 + }, + "id": 30, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_mediator_ring_buffer_remaining_capacity", + "instant": false, + "legendFormat": "Mediator Remaining Capacity", + "range": true, + "refId": "A" + } + ], + "title": "Mediator Remaining Capacity", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 130 + }, + "id": 32, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "min(hedera_block_node_mediator_ring_buffer_remaining_capacity)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Mediator Ring Buffer Capacity", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Notifier Remaining Capacity", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "red", + "value": 200 + }, + { + "color": "#EAB839", + "value": 201 + }, + { + "color": "green", + "value": 501 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 143 + }, + "id": 31, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_notifier_ring_buffer_remaining_capacity", + "instant": false, + "legendFormat": "Notifier Remaining Capacity", + "range": true, + "refId": "A" + } + ], + "title": "Notifier Remaining Capacity", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 143 + }, + "id": 33, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "min(hedera_block_node_notifier_ring_buffer_remaining_capacity)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Notifier Ring Buffer Capacity", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Producers", + "mappings": [], + "max": 20, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#EAB839", + "value": 5 + }, + { + "color": "red", + "value": 10 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 150 + }, + "id": 25, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_producers", + "instant": false, + "legendFormat": "Producers", + "range": true, + "refId": "A" + } + ], + "title": "Producers", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Consumers", + "mappings": [], + "max": 20, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 15 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 150 + }, + "id": 4, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_consumers", + "instant": false, + "legendFormat": "Consumers", + "range": true, + "refId": "A" + } + ], + "title": "Consumers", + "type": "gauge" + } + ], + "title": "Live Stream", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 2 + }, + "id": 18, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 132 + }, + "id": 6, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_blocks_persisted_total", + "instant": false, + "legendFormat": "Block Persistence Counter", + "range": true, + "refId": "A" + } + ], + "title": "Block Persistence Counter", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": "red" + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "green", + "value": 4 + } + ] + }, + "unit": "wps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 132 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_blocks_persisted_total[$__rate_interval])", + "instant": false, + "legendFormat": "Block Persistence Counter", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Blocks Written to the File System", + "type": "timeseries" + } + ], + "title": "Persistence", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 23, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 91 + }, + "id": 24, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_successful_pub_stream_resp_total", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } + ], + "title": "PublishStreamResponses Generated", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 91 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_successful_pub_stream_resp_total [$__rate_interval])", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } + ], + "title": "Rate of PublishStreamResponses Generated", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 97 + }, + "id": 27, + "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.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_successful_pub_stream_resp_sent_total", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } + ], + "title": "PublishStreamResponses Sent", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 6, + "w": 18, + "x": 6, + "y": 97 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_successful_pub_stream_resp_sent_total[$__rate_interval])", + "instant": false, + "legendFormat": "PublishStreamResponses", + "range": true, + "refId": "A" + } + ], + "title": "Rate of PublishStreamResponses Sent to Producers", + "type": "timeseries" + } + ], + "title": "Live Stream Responses", "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "id": 34, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] } - ] + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 38, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Blocks Verified", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 5 + }, + "id": 36, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "unit": "short" + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Blocks Verified over time", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Blocks Verified", + "type": "timeseries" }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 39 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 11 + }, + "id": 37, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "(last_over_time(hedera_block_node_verification_block_time_total[$__interval]) / last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval]) )", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Avg. Verification Time per Block", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 11 + }, + "id": 35, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "(last_over_time(hedera_block_node_verification_block_time_total[$__interval]) / last_over_time(hedera_block_node_verification_blocks_verified_total[$__interval]) )", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Average Block Verification Time in ms", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Verification Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 17 + }, + "id": 39, + "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.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total / hedera_block_node_verification_blocks_verified_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } ], - "fields": "", - "values": false + "title": "Avg. Items per Block", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_blocks_persisted_total", - "instant": false, - "legendFormat": "Block Persistence Counter", - "range": true, - "refId": "A" - } - ], - "title": "Block Persistence Counter", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "value": null + }, + { + "color": "#EAB839", + "value": 100000000 + }, + { + "color": "red", + "value": 500000000 + } + ] + }, + "unit": "none" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 2 - }, - { - "color": "green", - "value": 4 - } - ] + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 17 }, - "unit": "wps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 39 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "editorMode": "code", - "expr": "rate(hedera_block_node_blocks_persisted_total[$__rate_interval])", - "instant": false, - "legendFormat": "Block Persistence Counter", - "range": true, - "refId": "A" + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "hedera_block_node_live_block_items_received_total / hedera_block_node_verification_blocks_verified_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Avg. Items per Block", + "type": "timeseries" } ], - "title": "Rate of Blocks Written to the File System", - "type": "timeseries" + "title": "Verification", + "type": "row" }, { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 45 - }, - "id": 23, - "panels": [], - "title": "Live Stream Responses", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 46 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "y": 5 }, - "pluginVersion": "11.1.4", - "targets": [ + "id": 17, + "panels": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_successful_pub_stream_resp_total", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "PublishStreamResponses Generated", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 92 }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 46 - }, - "id": 22, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "id": 19, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "editorMode": "code", - "expr": "rate( hedera_block_node_successful_pub_stream_resp_total [$__rate_interval])", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "Rate of PublishStreamResponses Generated", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 52 - }, - "id": 27, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_single_blocks_retrieved_total", + "instant": false, + "legendFormat": "Single Blocks Retrieved", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Single Blocks Retrieved", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_successful_pub_stream_resp_sent_total", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "PublishStreamResponses Sent", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" - } - ] + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 52 - }, - "id": 26, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 92 }, - "editorMode": "code", - "expr": "rate(hedera_block_node_successful_pub_stream_resp_sent_total[$__rate_interval])", - "instant": false, - "legendFormat": "PublishStreamResponses", - "range": true, - "refId": "A" - } - ], - "title": "Rate of PublishStreamResponses Sent to Producers", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 58 - }, - "id": 17, - "panels": [], - "title": "Single Block", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 59 - }, - "id": 19, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(hedera_block_node_single_blocks_retrieved_total[$__rate_interval])", + "instant": false, + "legendFormat": "RPS of Single Blocks Retrieval", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Rate of Single Blocks Retrieved", + "type": "timeseries" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_single_blocks_retrieved_total", - "instant": false, - "legendFormat": "Single Blocks Retrieved", - "range": true, - "refId": "A" - } - ], - "title": "Single Blocks Retrieved", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, - { - "color": "red", - "value": 80 - } - ] + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 59 - }, - "id": 5, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 98 }, - "editorMode": "code", - "expr": "rate(hedera_block_node_single_blocks_retrieved_total[$__rate_interval])", - "instant": false, - "legendFormat": "RPS of Single Blocks Retrieval", - "range": true, - "refId": "A" - } - ], - "title": "Rate of Single Blocks Retrieved", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 65 - }, - "id": 21, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "hedera_block_node_single_blocks_not_found_total", + "instant": false, + "legendFormat": "Single Blocks Not Found", + "range": true, + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Single Blocks Not Found", + "type": "stat" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.4", - "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, - "editorMode": "code", - "expr": "hedera_block_node_single_blocks_not_found_total", - "instant": false, - "legendFormat": "Single Blocks Not Found", - "range": true, - "refId": "A" - } - ], - "title": "Single Blocks Not Found", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "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" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" }, - { - "color": "red", - "value": 80 - } - ] + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 65 - }, - "id": 20, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 98 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "editorMode": "code", - "expr": "rate( hedera_block_node_single_blocks_not_found_total [$__rate_interval])", - "instant": false, - "legendFormat": "Single Blocks Not Found", - "range": true, - "refId": "A" + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate( hedera_block_node_single_blocks_not_found_total [$__rate_interval])", + "instant": false, + "legendFormat": "Single Blocks Not Found", + "range": true, + "refId": "A" + } + ], + "title": "Rate of Single Blocks Not Found", + "type": "timeseries" } ], - "title": "Rate of Single Blocks Not Found", - "type": "timeseries" + "title": "Single Block", + "type": "row" } ], + "preload": false, "refresh": "5s", - "schemaVersion": 39, + "schemaVersion": 40, "tags": [], "templating": { "list": [] @@ -2230,6 +2982,6 @@ "timezone": "browser", "title": "Block-Node Server Dashboard", "uid": "edu86nutnxts0c", - "version": 1, + "version": 2, "weekStart": "" } diff --git a/server/docs/configuration.md b/server/docs/configuration.md index 69f880d5..92b4b16e 100644 --- a/server/docs/configuration.md +++ b/server/docs/configuration.md @@ -9,16 +9,20 @@ The default configuration allows users to quickly get up and running without hav ease of use at the trade-off of some insecure default configuration. Most configuration settings have appropriate defaults and can be left unchanged. It is recommended to browse the properties below and adjust to your needs. -| Environment Variable | Description | Default Value | -|:---|:---|---:| -| PERSISTENCE_STORAGE_LIVE_ROOT_PATH | The root path for the live storage. | | -| PERSISTENCE_STORAGE_ARCHIVE_ROOT_PATH | The root path for the archive storage. | | -| PERSISTENCE_STORAGE_TYPE | Type of the persistence storage | BLOCK_AS_LOCAL_FILE | -| PERSISTENCE_STORAGE_COMPRESSION | Compression algorithm used during persistence (could be none as well) | ZSTD | -| PERSISTENCE_STORAGE_COMPRESSION_LEVEL | Compression level to be used by the compression algorithm | 3 | -| CONSUMER_TIMEOUT_THRESHOLD_MILLIS | Time to wait for subscribers before disconnecting in milliseconds | 1500 | -| SERVICE_DELAY_MILLIS | Service shutdown delay in milliseconds | 500 | -| MEDIATOR_RING_BUFFER_SIZE | Size of the ring buffer used by the mediator (must be a power of 2) | 67108864 | -| NOTIFIER_RING_BUFFER_SIZE | Size of the ring buffer used by the notifier (must be a power of 2) | 2048 | -| SERVER_PORT | The port the server will listen on | 8080 | -| SERVER_MAX_MESSAGE_SIZE_BYTES | The maximum size of a message frame in bytes | 1048576 | +| Environment Variable | Description | Default Value | +|:---------------------------------------|:-------------------------------------------------------------------------|--------------------:| +| PERSISTENCE_STORAGE_LIVE_ROOT_PATH | The root path for the live storage. | | +| PERSISTENCE_STORAGE_ARCHIVE_ROOT_PATH | The root path for the archive storage. | | +| PERSISTENCE_STORAGE_TYPE | Type of the persistence storage | BLOCK_AS_LOCAL_FILE | +| PERSISTENCE_STORAGE_COMPRESSION | Compression algorithm used during persistence (could be none as well) | ZSTD | +| PERSISTENCE_STORAGE_COMPRESSION_LEVEL | Compression level to be used by the compression algorithm | 3 | +| CONSUMER_TIMEOUT_THRESHOLD_MILLIS | Time to wait for subscribers before disconnecting in milliseconds | 1500 | +| SERVICE_DELAY_MILLIS | Service shutdown delay in milliseconds | 500 | +| MEDIATOR_RING_BUFFER_SIZE | Size of the ring buffer used by the mediator (must be a power of 2) | 67108864 | +| NOTIFIER_RING_BUFFER_SIZE | Size of the ring buffer used by the notifier (must be a power of 2) | 2048 | +| SERVER_PORT | The port the server will listen on | 8080 | +| SERVER_MAX_MESSAGE_SIZE_BYTES | The maximum size of a message frame in bytes | 1048576 | +| VERIFICATION_ENABLED | Enables or disables the block verification process | true | +| VERIFICATION_SESSION_TYPE | The type of BlockVerificationSession to use, either `ASYNC` or `SYNC` | ASYNC | +| VERIFICATION_HASH_COMBINE_BATCH_SIZE | The number of hashes to combine into a single hash during verification | 32 | + 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 ca109812..e9e81271 100644 --- a/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java +++ b/server/src/main/java/com/hedera/block/server/BlockNodeAppInjectionComponent.java @@ -24,6 +24,7 @@ import com.hedera.block.server.pbj.PbjInjectionModule; import com.hedera.block.server.persistence.PersistenceInjectionModule; import com.hedera.block.server.service.ServiceInjectionModule; +import com.hedera.block.server.verification.VerificationInjectionModule; import com.swirlds.config.api.Configuration; import dagger.BindsInstance; import dagger.Component; @@ -42,6 +43,7 @@ ConfigInjectionModule.class, MetricsInjectionModule.class, PbjInjectionModule.class, + VerificationInjectionModule.class, }) public interface BlockNodeAppInjectionComponent { /** 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 48b03278..be7c892f 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 @@ -24,6 +24,7 @@ import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; import com.hedera.block.server.producer.ProducerConfig; import com.hedera.block.server.service.ServiceConfig; +import com.hedera.block.server.verification.VerificationConfig; import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.platform.prometheus.PrometheusConfig; import com.swirlds.config.api.ConfigurationExtension; @@ -56,6 +57,7 @@ public Set> getConfigDataTypes() { ProducerConfig.class, ConsumerConfig.class, PersistenceStorageConfig.class, - ServerConfig.class); + ServerConfig.class, + VerificationConfig.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 e8c2550c..acec9307 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 @@ -22,6 +22,7 @@ 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.verification.VerificationConfig; import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.platform.prometheus.PrometheusConfig; import com.swirlds.config.api.Configuration; @@ -131,4 +132,16 @@ static ProducerConfig provideProducerConfig(Configuration configuration) { static ServerConfig provideServerConfig(Configuration configuration) { return configuration.getConfigData(ServerConfig.class); } + + /** + * Provides a verification configuration singleton using the configuration. + * + * @param configuration is the configuration singleton + * @return a verification configuration singleton + */ + @Singleton + @Provides + static VerificationConfig provideVerificationConfig(Configuration configuration) { + return configuration.getConfigData(VerificationConfig.class); + } } diff --git a/server/src/main/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializer.java b/server/src/main/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializer.java index f347b882..a1264028 100644 --- a/server/src/main/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializer.java +++ b/server/src/main/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializer.java @@ -41,7 +41,10 @@ public final class ServerMappedConfigSourceInitializer { new ConfigMapping("server.maxMessageSizeBytes", "SERVER_MAX_MESSAGE_SIZE_BYTES"), new ConfigMapping("server.port", "SERVER_PORT"), new ConfigMapping("prometheus.endpointEnabled", "PROMETHEUS_ENDPOINT_ENABLED"), - new ConfigMapping("prometheus.endpointPortNumber", "PROMETHEUS_ENDPOINT_PORT_NUMBER")); + new ConfigMapping("prometheus.endpointPortNumber", "PROMETHEUS_ENDPOINT_PORT_NUMBER"), + new ConfigMapping("verification.enabled", "VERIFICATION_ENABLED"), + new ConfigMapping("verification.sessionType", "VERIFICATION_SESSION_TYPE"), + new ConfigMapping("verification.hashCombineBatchSize", "VERIFICATION_HASH_COMBINE_BATCH_SIZE")); private ServerMappedConfigSourceInitializer() {} 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 50c58226..ef435c03 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 @@ -59,6 +59,23 @@ public enum Counter implements MetricMetadata { /** The number of single blocks not found via the singleBlock rpc service. */ SingleBlocksNotFound("single_blocks_not_found", "Single Blocks Not Found"), + // Verification counters + + /** The number of blocks received for verification. */ + VerificationBlocksReceived("verification_blocks_received", "Blocks Received for Verification"), + + /** The number of blocks verified successfully. */ + VerificationBlocksVerified("verification_blocks_verified", "Blocks Verified"), + + /** The number of blocks that failed verification. */ + VerificationBlocksFailed("verification_blocks_failed", "Blocks Failed Verification"), + + /** The number of blocks that failed verification due to an error. */ + VerificationBlocksError("verification_blocks_error", "Blocks Verification Error"), + + /** The time in nanoseconds taken to verify a block */ + VerificationBlockTime("verification_block_time", "Block Verification Time"), + // Error counters /** The number of errors encountered by the live block stream mediator. */ 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 98f49496..f07657e1 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 @@ -138,6 +138,11 @@ public void publish(@NonNull List blockItems) { } } + /** + * Builds an error stream response. + * + * @return the error stream response + */ @NonNull static PublishStreamResponse buildErrorStreamResponse() { // TODO: Replace this with a real error enum. 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 index 26417c5a..9eab48dc 100644 --- a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockAccessServiceProxy.java @@ -95,6 +95,12 @@ public Pipeline open( } } + /** + * Executes the unary singleBlock gRPC method. + * + * @param singleBlockRequest the single block request + * @return the single block response + */ SingleBlockResponseUnparsed singleBlock(SingleBlockRequest singleBlockRequest) { LOGGER.log(DEBUG, "Executing Unary singleBlock gRPC method"); 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 index fa51fdfc..c06c43c8 100644 --- a/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java +++ b/server/src/main/java/com/hedera/block/server/pbj/PbjBlockStreamServiceProxy.java @@ -29,6 +29,7 @@ import com.hedera.block.server.producer.ProducerBlockItemObserver; import com.hedera.block.server.producer.ProducerConfig; import com.hedera.block.server.service.ServiceStatus; +import com.hedera.block.server.verification.StreamVerificationHandlerImpl; import com.hedera.hapi.block.BlockItemUnparsed; import com.hedera.hapi.block.PublishStreamRequestUnparsed; import com.hedera.hapi.block.PublishStreamResponse; @@ -65,6 +66,7 @@ public class PbjBlockStreamServiceProxy implements PbjBlockStreamService { * @param streamMediator the live stream mediator * @param serviceStatus the service status * @param streamPersistenceHandler the stream persistence handler + * @param streamVerificationHandler the stream verification handler * @param notifier the notifier * @param blockNodeContext the block node context */ @@ -73,6 +75,7 @@ public PbjBlockStreamServiceProxy( @NonNull final LiveStreamMediator streamMediator, @NonNull final ServiceStatus serviceStatus, @NonNull final BlockNodeEventHandler> streamPersistenceHandler, + @NonNull final StreamVerificationHandlerImpl streamVerificationHandler, @NonNull final Notifier notifier, @NonNull final BlockNodeContext blockNodeContext) { this.serviceStatus = serviceStatus; @@ -80,6 +83,7 @@ public PbjBlockStreamServiceProxy( this.blockNodeContext = blockNodeContext; streamMediator.subscribe(streamPersistenceHandler); + streamMediator.subscribe(streamVerificationHandler); this.streamMediator = streamMediator; } @@ -119,6 +123,12 @@ public Pipeline open( } } + /** + * Publishes the block stream. + * + * @param helidonProducerObserver the helidon producer observer + * @return the pipeline + */ Pipeline> publishBlockStream( Pipeline helidonProducerObserver) { LOGGER.log(DEBUG, "Executing bidirectional publishBlockStream gRPC method"); @@ -153,6 +163,12 @@ Pipeline> publishBlockStream( } } + /** + * Subscribes to the block stream. + * + * @param subscribeStreamRequest the subscribe stream request + * @param subscribeStreamResponseObserver the subscribe stream response observer + */ void subscribeBlockStream( SubscribeStreamRequest subscribeStreamRequest, Pipeline subscribeStreamResponseObserver) { 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 73311320..403fe66e 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 @@ -173,11 +173,25 @@ public enum CompressionType { private final int minCompressionLevel; private final int maxCompressionLevel; + /** + * Constructs a new instance of {@link CompressionType}. + * + * @param minCompressionLevel the minimum compression level + * @param maxCompressionLevel the maximum compression level + */ CompressionType(final int minCompressionLevel, final int maxCompressionLevel) { this.minCompressionLevel = minCompressionLevel; this.maxCompressionLevel = maxCompressionLevel; } + /** + * This method verifies that the compression level is within the + * acceptable range for the given compression type. + * + * @param levelToCheck the compression level to check + * @throws IllegalArgumentException if the compression level is not within + * the acceptable range + */ public void verifyCompressionLevel(final int levelToCheck) { Preconditions.requireInRange(levelToCheck, minCompressionLevel, maxCompressionLevel); } diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java index d6c6536d..b1e6dd4e 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java @@ -19,5 +19,7 @@ /** * A marker interface that groups all writers that operate on a local file * system. + * + * @param the type of the value to be written */ interface LocalBlockWriter extends BlockWriter {} diff --git a/server/src/main/java/com/hedera/block/server/verification/BlockVerificationStatus.java b/server/src/main/java/com/hedera/block/server/verification/BlockVerificationStatus.java new file mode 100644 index 00000000..3a7ddf58 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/BlockVerificationStatus.java @@ -0,0 +1,31 @@ +/* + * 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.verification; + +/** + * An enum representing the status of block verification. + */ +public enum BlockVerificationStatus { + /** + * The Block has been verified. + */ + VERIFIED, + /** + * The Block failed verification, either due to an invalid signature or an invalid hash. + */ + SIGNATURE_INVALID +} diff --git a/server/src/main/java/com/hedera/block/server/verification/StreamVerificationHandlerImpl.java b/server/src/main/java/com/hedera/block/server/verification/StreamVerificationHandlerImpl.java new file mode 100644 index 00000000..1cee4c64 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/StreamVerificationHandlerImpl.java @@ -0,0 +1,130 @@ +/* + * 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.verification; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlocksError; +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; + +import com.hedera.block.server.events.BlockNodeEventHandler; +import com.hedera.block.server.events.ObjectEvent; +import com.hedera.block.server.exception.BlockStreamProtocolException; +import com.hedera.block.server.mediator.SubscriptionHandler; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.notifier.Notifier; +import com.hedera.block.server.service.ServiceStatus; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.SubscribeStreamResponseUnparsed; +import com.hedera.pbj.runtime.OneOf; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Verification Handler, receives the block items from the ring buffer, validates their type and uses the BlockVerificationService to verify the block items. + */ +@Singleton +public class StreamVerificationHandlerImpl + implements BlockNodeEventHandler> { + + private final System.Logger LOGGER = System.getLogger(getClass().getName()); + private final SubscriptionHandler subscriptionHandler; + private final Notifier notifier; + private final MetricsService metricsService; + private final ServiceStatus serviceStatus; + private final BlockVerificationService blockVerificationService; + + private static final String PROTOCOL_VIOLATION_MESSAGE = + "Protocol Violation. %s is OneOf type %s but %s is null.\n%s"; + + /** + * Constructs a new instance of {@link StreamVerificationHandlerImpl}. + * + * @param subscriptionHandler the subscription handler + * @param notifier the notifier + * @param metricsService the metrics service + * @param serviceStatus the service status + * @param blockVerificationService the block verification service + */ + @Inject + public StreamVerificationHandlerImpl( + @NonNull final SubscriptionHandler subscriptionHandler, + @NonNull final Notifier notifier, + @NonNull final MetricsService metricsService, + @NonNull final ServiceStatus serviceStatus, + @NonNull final BlockVerificationService blockVerificationService) { + + this.subscriptionHandler = subscriptionHandler; + this.notifier = notifier; + this.metricsService = metricsService; + this.serviceStatus = serviceStatus; + this.blockVerificationService = blockVerificationService; + } + + /** + * Handles the event from the ring buffer, unpacks it and uses the BlockVerificationService to verify the block items. + */ + @Override + public void onEvent(ObjectEvent event, long l, boolean b) { + + try { + + if (!serviceStatus.isRunning()) { + LOGGER.log(ERROR, "Service is not running. Block item will not be processed further."); + } + + final SubscribeStreamResponseUnparsed subscribeStreamResponse = event.get(); + final OneOf oneOfTypeOneOf = + subscribeStreamResponse.response(); + switch (oneOfTypeOneOf.kind()) { + case BLOCK_ITEMS -> { + if (subscribeStreamResponse.blockItems() == null) { + final String message = PROTOCOL_VIOLATION_MESSAGE.formatted( + "SubscribeStreamResponse", "BLOCK_ITEM", "block_item", subscribeStreamResponse); + LOGGER.log(ERROR, message); + metricsService.get(VerificationBlocksError).increment(); + throw new BlockStreamProtocolException(message); + } else { + List blockItems = + subscribeStreamResponse.blockItems().blockItems(); + blockVerificationService.onBlockItemsReceived(blockItems); + } + } + case STATUS -> LOGGER.log(DEBUG, "Unexpected received a status message rather than a block item"); + default -> { + final String message = "Unknown response type: " + oneOfTypeOneOf.kind(); + LOGGER.log(ERROR, message); + metricsService.get(VerificationBlocksError).increment(); + throw new BlockStreamProtocolException(message); + } + } + } catch (final Exception e) { + + LOGGER.log(ERROR, "Failed to verify BlockItems: ", e); + // Trigger the server to stop accepting new requests + serviceStatus.stopRunning(getClass().getName()); + + // Unsubscribe from the mediator to avoid additional onEvent calls. + subscriptionHandler.unsubscribe(this); + + // Broadcast the problem to the notifier + notifier.notifyUnrecoverableError(); + } + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/VerificationConfig.java b/server/src/main/java/com/hedera/block/server/verification/VerificationConfig.java new file mode 100644 index 00000000..512e212d --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/VerificationConfig.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.verification; + +import com.hedera.block.server.verification.session.BlockVerificationSessionType; +import com.swirlds.config.api.ConfigData; +import com.swirlds.config.api.ConfigProperty; + +/** + * Configuration for the verification module. + * + * @param enabled whether the verification module is enabled + * @param sessionType the type of the verification session + * @param hashCombineBatchSize the size of the batch used to combine hashes + */ +@ConfigData("verification") +public record VerificationConfig( + @ConfigProperty(defaultValue = "true") boolean enabled, + @ConfigProperty(defaultValue = "ASYNC") BlockVerificationSessionType sessionType, + @ConfigProperty(defaultValue = "32") int hashCombineBatchSize) { + + /** + * Constructs a new instance of {@link VerificationConfig}. + */ + private static final System.Logger LOGGER = System.getLogger(VerificationConfig.class.getName()); + + /** + * Constructs a new instance of {@link VerificationConfig}. + * + * @param enabled whether the verification module is enabled + * @param sessionType the type of the verification session + * @param hashCombineBatchSize the size of the batch used to combine hashes + */ + public VerificationConfig { + // hashCombineBatchSize must be even and greater than 2 + if (hashCombineBatchSize % 2 != 0 || hashCombineBatchSize < 2) { + throw new IllegalArgumentException("hashCombineBatchSize must be even and greater than 2"); + } + + // Log the actual configuration + LOGGER.log(System.Logger.Level.INFO, "Verification configuration enabled: " + enabled); + LOGGER.log(System.Logger.Level.INFO, "Verification configuration sessionType: " + sessionType); + LOGGER.log( + System.Logger.Level.INFO, "Verification configuration hashCombineBatchSize: " + hashCombineBatchSize); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/VerificationInjectionModule.java b/server/src/main/java/com/hedera/block/server/verification/VerificationInjectionModule.java new file mode 100644 index 00000000..8cc2f4ad --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/VerificationInjectionModule.java @@ -0,0 +1,80 @@ +/* + * 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.verification; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.block.server.verification.service.BlockVerificationServiceImpl; +import com.hedera.block.server.verification.service.NoOpBlockVerificationService; +import com.hedera.block.server.verification.session.BlockVerificationSessionFactory; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.block.server.verification.signature.SignatureVerifierDummy; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import javax.inject.Singleton; + +/** + * The module used to inject the verification service and signature verifier into the application. + */ +@Module +public interface VerificationInjectionModule { + + /** + * Provides the executor service. + * + * @return the executor service + */ + @Provides + static ExecutorService provideExecutorService() { + return ForkJoinPool.commonPool(); + } + + /** + * Provides the signature verifier. + * + * @param signatureVerifier the signature verifier to be used + * @return the signature verifier + */ + @Binds + @Singleton + SignatureVerifier bindSignatureVerifier(SignatureVerifierDummy signatureVerifier); + + /** + * Provides the block verification service. + * + * @param verificationConfig the verification configuration to be used + * @param metricsService the metrics service to be used + * @param blockVerificationSessionFactory the block verification session factory to be used + * @return the block verification service + */ + @Provides + @Singleton + static BlockVerificationService provideBlockVerificationService( + @NonNull final VerificationConfig verificationConfig, + @NonNull final MetricsService metricsService, + @NonNull final BlockVerificationSessionFactory blockVerificationSessionFactory) { + if (verificationConfig.enabled()) { + return new BlockVerificationServiceImpl(metricsService, blockVerificationSessionFactory); + } else { + return new NoOpBlockVerificationService(); + } + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/VerificationResult.java b/server/src/main/java/com/hedera/block/server/verification/VerificationResult.java new file mode 100644 index 00000000..503e3a25 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/VerificationResult.java @@ -0,0 +1,30 @@ +/* + * 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.verification; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A record representing the result of a block verification. + * + * @param blockNumber the block number + * @param blockHash the block hash + * @param status the verification status + */ +public record VerificationResult( + @NonNull Long blockNumber, @NonNull Bytes blockHash, @NonNull BlockVerificationStatus status) {} diff --git a/server/src/main/java/com/hedera/block/server/verification/hasher/CommonUtils.java b/server/src/main/java/com/hedera/block/server/verification/hasher/CommonUtils.java new file mode 100644 index 00000000..a5e7d1f5 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/hasher/CommonUtils.java @@ -0,0 +1,181 @@ +/* + * 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.verification.hasher; + +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.BlockProof; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.crypto.DigestType; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + * Provides common utility methods for hashing and combining hashes. + */ +public final class CommonUtils { + private CommonUtils() { + throw new UnsupportedOperationException("Utility Class"); + } + + /** + * The size of an SHA-384 hash in bytes. + */ + public static final int HASH_SIZE = DigestType.SHA_384.digestLength(); + + /** + * The tag for the SHA-384 algorithm. + */ + private static final String sha384HashTag = "SHA-384"; + + /** + * Returns the SHA-384 hash of the given bytes. + * @param bytes the bytes to hash + * @return the SHA-384 hash of the given bytes + */ + public static Bytes noThrowSha384HashOf(final Bytes bytes) { + return Bytes.wrap(noThrowSha384HashOf(bytes.toByteArray())); + } + + /** + * Returns the SHA-384 hash of the given byte array. + * @param byteArray the byte array to hash + * @return the SHA-384 hash of the given byte array + */ + public static byte[] noThrowSha384HashOf(final byte[] byteArray) { + try { + return MessageDigest.getInstance(sha384HashTag).digest(byteArray); + } catch (final NoSuchAlgorithmException fatal) { + throw new IllegalStateException(fatal); + } + } + + /** + * Returns a {@link MessageDigest} instance for the SHA-384 algorithm, throwing an unchecked exception if the + * algorithm is not found. + * @return a {@link MessageDigest} instance for the SHA-384 algorithm + */ + public static MessageDigest sha384DigestOrThrow() { + try { + return MessageDigest.getInstance(DigestType.SHA_384.algorithmName()); + } catch (final NoSuchAlgorithmException fatal) { + throw new IllegalStateException(fatal); + } + } + + /** + * Hashes the given left and right hashes. + * @param leftHash the left hash + * @param rightHash the right hash + * @return the combined hash + */ + public static Bytes combine(@NonNull final Bytes leftHash, @NonNull final Bytes rightHash) { + return Bytes.wrap(combine(leftHash.toByteArray(), rightHash.toByteArray())); + } + + /** + * Hashes the given left and right hashes. + * @param leftHash the left hash + * @param rightHash the right hash + * @return the combined hash + */ + public static byte[] combine(final byte[] leftHash, final byte[] rightHash) { + try { + final var digest = MessageDigest.getInstance(DigestType.SHA_384.algorithmName()); + digest.update(leftHash); + digest.update(rightHash); + return digest.digest(); + } catch (final NoSuchAlgorithmException fatal) { + throw new IllegalStateException(fatal); + } + } + + /** + * Returns the Hashes (input and output) of a list of block items. + * @param blockItems the block items + * @return the Hashes of the block items + */ + public static Hashes getBlockHashes(List blockItems) { + int numInputs = 0; + int numOutputs = 0; + int itemSize = blockItems.size(); + for (int i = 0; i < itemSize; i++) { + final BlockItemUnparsed item = blockItems.get(i); + final BlockItemUnparsed.ItemOneOfType kind = item.item().kind(); + switch (kind) { + case EVENT_HEADER, EVENT_TRANSACTION -> numInputs++; + case TRANSACTION_RESULT, TRANSACTION_OUTPUT, STATE_CHANGES -> numOutputs++; + } + } + + final var inputHashes = ByteBuffer.allocate(HASH_SIZE * numInputs); + final var outputHashes = ByteBuffer.allocate(HASH_SIZE * numOutputs); + final var digest = sha384DigestOrThrow(); + for (int i = 0; i < itemSize; i++) { + final BlockItemUnparsed item = blockItems.get(i); + final BlockItemUnparsed.ItemOneOfType kind = item.item().kind(); + switch (kind) { + case EVENT_HEADER, EVENT_TRANSACTION -> inputHashes.put( + digest.digest(BlockItemUnparsed.PROTOBUF.toBytes(item).toByteArray())); + case TRANSACTION_RESULT, TRANSACTION_OUTPUT, STATE_CHANGES -> outputHashes.put( + digest.digest(BlockItemUnparsed.PROTOBUF.toBytes(item).toByteArray())); + } + } + + inputHashes.flip(); + outputHashes.flip(); + + return new Hashes(inputHashes, outputHashes); + } + + /** + * returns the ByteBuffer of the hash of the given block item. + * @param blockItemUnparsed the block item + * @return the ByteBuffer of the hash of the given block item + */ + public static ByteBuffer getBlockItemHash(BlockItemUnparsed blockItemUnparsed) { + final var digest = sha384DigestOrThrow(); + ByteBuffer buffer = ByteBuffer.allocate(HASH_SIZE); + buffer.put(digest.digest( + BlockItemUnparsed.PROTOBUF.toBytes(blockItemUnparsed).toByteArray())); + buffer.flip(); + return buffer; + } + + /** + * Computes the final block hash from the given block proof and tree hashers. + * @param blockProof the block proof + * @param inputTreeHasher the input tree hasher + * @param outputTreeHasher the output tree hasher + * @return the final block hash + */ + public static Bytes computeFinalBlockHash( + @NonNull final BlockProof blockProof, + @NonNull final StreamingTreeHasher inputTreeHasher, + @NonNull final StreamingTreeHasher outputTreeHasher) { + Bytes inputHash = inputTreeHasher.rootHash().join(); + Bytes outputHash = outputTreeHasher.rootHash().join(); + Bytes providedLasBlockHash = blockProof.previousBlockRootHash(); + Bytes providedBlockStartStateHash = blockProof.startOfBlockStateRootHash(); + + final var leftParent = combine(providedLasBlockHash, inputHash); + final var rightParent = combine(outputHash, providedBlockStartStateHash); + return combine(leftParent, rightParent); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasher.java b/server/src/main/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasher.java new file mode 100644 index 00000000..4bed1a9b --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasher.java @@ -0,0 +1,261 @@ +/* + * 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.verification.hasher; + +import static com.hedera.block.server.verification.hasher.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +/** + * A {@link StreamingTreeHasher} that computes the root hash of a perfect binary Merkle tree of {@link Bytes} leaves + * using a concurrent algorithm that hashes leaves in parallel and combines the resulting hashes in parallel. + *

+ * Important: This class is not thread-safe, and client code must not make concurrent calls to + * {@link StreamingTreeHasher#addLeaf(ByteBuffer)} or {@link #rootHash()}. + */ +public class ConcurrentStreamingTreeHasher implements StreamingTreeHasher { + /** + * The default number of leaves to batch before combining the resulting hashes. + */ + private static final int DEFAULT_HASH_COMBINE_BATCH_SIZE = 8; + + /** + * The base {@link HashCombiner} that combines the hashes of the leaves of the tree, at height zero. + */ + private final HashCombiner combiner = new HashCombiner(0); + /** + * The {@link ExecutorService} used to parallelize the hashing and combining of the leaves of the tree. + */ + private final ExecutorService executorService; + /** + * The size of the batches of hashes to schedule for combination. + *

Important: This must be an even number so we can safely assume that any odd number + * of scheduled hashes to combine can be padded with appropriately nested combination of hashes + * whose descendants are all empty leaves. + */ + private final int hashCombineBatchSize; + + /** + * The number of leaves added to the tree. + */ + private int numLeaves; + /** + * Set once before the root hash is requested, to the height of the tree implied by the number of leaves. + */ + private int rootHeight; + /** + * Whether the tree has been finalized by requesting the root hash. + */ + private boolean rootHashRequested = false; + + /** + * Constructs a new {@link ConcurrentStreamingTreeHasher} with the given {@link ExecutorService}. + * + * @param executorService the executor service to use for parallelizing the hashing and combining of the tree + */ + public ConcurrentStreamingTreeHasher(@NonNull final ExecutorService executorService) { + this(executorService, DEFAULT_HASH_COMBINE_BATCH_SIZE); + } + + /** + * Constructs a new {@link ConcurrentStreamingTreeHasher} with the given {@link ExecutorService} and hash combine + * batch size. + * + * @param executorService the executor service to use for parallelizing the hashing and combining of the tree + * @param hashCombineBatchSize the size of the batches of hashes to schedule for combination + * @throws IllegalArgumentException if the hash combine batch size is an odd number + */ + public ConcurrentStreamingTreeHasher( + @NonNull final ExecutorService executorService, final int hashCombineBatchSize) { + this.executorService = requireNonNull(executorService); + if (hashCombineBatchSize % 2 == 1) { + throw new IllegalArgumentException("Hash combine batch size must be an even number"); + } + this.hashCombineBatchSize = hashCombineBatchSize; + } + + @Override + public void addLeaf(@NonNull final ByteBuffer hash) { + requireNonNull(hash); + if (rootHashRequested) { + throw new IllegalStateException("Cannot add leaves after requesting the root hash"); + } + if (hash.remaining() < HASH_LENGTH) { + throw new IllegalArgumentException("Buffer has less than " + HASH_LENGTH + " bytes remaining"); + } + numLeaves++; + final var bytes = new byte[HASH_LENGTH]; + hash.get(bytes); + combiner.combine(bytes); + } + + @Override + public CompletableFuture rootHash() { + rootHashRequested = true; + rootHeight = rootHeightFor(numLeaves); + return combiner.finalCombination(); + } + + @Override + public Status status() { + if (numLeaves == 0) { + return Status.EMPTY; + } else { + final var rightmostHashes = new ArrayList(); + combiner.flushAvailable(rightmostHashes, rootHeightFor(numLeaves + 1)); + return new Status(numLeaves, rightmostHashes); + } + } + + /** + * Computes the root hash of a perfect binary Merkle tree of {@link Bytes} leaves (padded on the right with + * empty leaves to reach a power of two), given the penultimate status of the tree and the hash of the last + * leaf added to the tree. + * + * @param penultimateStatus the penultimate status of the tree + * @param lastLeafHash the last leaf hash added to the tree + * @return the root hash of the tree + */ + public static Bytes rootHashFrom(@NonNull final Status penultimateStatus, @NonNull final Bytes lastLeafHash) { + requireNonNull(lastLeafHash); + var hash = lastLeafHash.toByteArray(); + final var rootHeight = rootHeightFor(penultimateStatus.numLeaves() + 1); + for (int i = 0; i < rootHeight; i++) { + final var rightmostHash = penultimateStatus.rightmostHashes().get(i); + if (rightmostHash.length() == 0) { + hash = CommonUtils.combine(hash, HashCombiner.EMPTY_HASHES[i]); + } else { + hash = CommonUtils.combine(rightmostHash.toByteArray(), hash); + } + } + return Bytes.wrap(hash); + } + + private class HashCombiner { + private static final ThreadLocal DIGESTS = + ThreadLocal.withInitial(CommonUtils::sha384DigestOrThrow); + private static final int MAX_DEPTH = 24; + private static final int MIN_TO_SCHEDULE = 16; + + private static final byte[][] EMPTY_HASHES = new byte[MAX_DEPTH][]; + + static { + EMPTY_HASHES[0] = noThrowSha384HashOf(new byte[0]); + for (int i = 1; i < MAX_DEPTH; i++) { + EMPTY_HASHES[i] = CommonUtils.combine(EMPTY_HASHES[i - 1], EMPTY_HASHES[i - 1]); + } + } + + private final int height; + + private HashCombiner delegate; + private List pendingHashes = new ArrayList<>(); + private CompletableFuture combination = CompletableFuture.completedFuture(null); + + private HashCombiner(final int height) { + if (height >= MAX_DEPTH) { + throw new IllegalArgumentException("Cannot combine hashes at height " + height); + } + this.height = height; + } + + public void combine(@NonNull final byte[] hash) { + pendingHashes.add(hash); + if (pendingHashes.size() == hashCombineBatchSize) { + schedulePendingWork(); + } + } + + public CompletableFuture finalCombination() { + if (height == rootHeight) { + final var rootHash = pendingHashes.isEmpty() ? EMPTY_HASHES[0] : pendingHashes.getFirst(); + return CompletableFuture.completedFuture(Bytes.wrap(rootHash)); + } else { + if (!pendingHashes.isEmpty()) { + schedulePendingWork(); + } + return combination.thenCompose(ignore -> delegate.finalCombination()); + } + } + + public void flushAvailable(@NonNull final List rightmostHashes, final int stopHeight) { + if (height < stopHeight) { + final var newPendingHash = pendingHashes.size() % 2 == 0 ? null : pendingHashes.removeLast(); + schedulePendingWork(); + combination.join(); + if (newPendingHash != null) { + pendingHashes.add(newPendingHash); + rightmostHashes.add(Bytes.wrap(newPendingHash)); + } else { + rightmostHashes.add(Bytes.EMPTY); + } + delegate.flushAvailable(rightmostHashes, stopHeight); + } + } + + private void schedulePendingWork() { + if (delegate == null) { + delegate = new HashCombiner(height + 1); + } + final CompletableFuture> pendingCombination; + if (pendingHashes.size() < MIN_TO_SCHEDULE) { + pendingCombination = CompletableFuture.completedFuture(combine(pendingHashes)); + } else { + final var hashes = pendingHashes; + pendingCombination = CompletableFuture.supplyAsync(() -> combine(hashes), executorService); + } + combination = combination.thenCombine(pendingCombination, (ignore, combined) -> { + combined.forEach(delegate::combine); + return null; + }); + pendingHashes = new ArrayList<>(); + } + + private List combine(@NonNull final List hashes) { + final List result = new ArrayList<>(); + final var digest = DIGESTS.get(); + for (int i = 0, m = hashes.size(); i < m; i += 2) { + final var left = hashes.get(i); + final var right = i + 1 < m ? hashes.get(i + 1) : EMPTY_HASHES[height]; + digest.update(left); + digest.update(right); + result.add(digest.digest()); + } + return result; + } + } + + private static int rootHeightFor(final int numLeaves) { + final var numPerfectLeaves = containingPowerOfTwo(numLeaves); + return numPerfectLeaves == 0 ? 0 : Integer.numberOfTrailingZeros(numPerfectLeaves); + } + + private static int containingPowerOfTwo(final int n) { + if ((n & (n - 1)) == 0) { + return n; + } + return Integer.highestOneBit(n) << 1; + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/hasher/Hashes.java b/server/src/main/java/com/hedera/block/server/verification/hasher/Hashes.java new file mode 100644 index 00000000..cd036623 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/hasher/Hashes.java @@ -0,0 +1,28 @@ +/* + * 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.verification.hasher; + +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; + +/** + * Holds the input and output hashes of a list of block items. + * + * @param inputHashes the input hashes + * @param outputHashes the output hashes + */ +public record Hashes(@NonNull ByteBuffer inputHashes, @NonNull ByteBuffer outputHashes) {} diff --git a/server/src/main/java/com/hedera/block/server/verification/hasher/NaiveStreamingTreeHasher.java b/server/src/main/java/com/hedera/block/server/verification/hasher/NaiveStreamingTreeHasher.java new file mode 100644 index 00000000..ea5175e3 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/hasher/NaiveStreamingTreeHasher.java @@ -0,0 +1,87 @@ +/* + * 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.verification.hasher; + +import static com.hedera.block.server.verification.hasher.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; + +/** + * A naive implementation of {@link StreamingTreeHasher} that computes the root hash of a perfect binary Merkle tree of + * {@link ByteBuffer} leaves. Used to test the correctness of more efficient implementations. + */ +public class NaiveStreamingTreeHasher implements StreamingTreeHasher { + private static final byte[] EMPTY_HASH = noThrowSha384HashOf(new byte[0]); + + private final List leafHashes = new ArrayList<>(); + private boolean rootHashRequested = false; + + /** + * Constructor for the {@link NaiveStreamingTreeHasher}. + */ + public NaiveStreamingTreeHasher() {} + + @Override + public void addLeaf(@NonNull final ByteBuffer hash) { + if (rootHashRequested) { + throw new IllegalStateException("Root hash already requested"); + } + if (hash.remaining() < HASH_LENGTH) { + throw new IllegalArgumentException("Buffer has less than " + HASH_LENGTH + " bytes remaining"); + } + final var bytes = new byte[HASH_LENGTH]; + hash.get(bytes); + leafHashes.add(bytes); + } + + @Override + public CompletableFuture rootHash() { + rootHashRequested = true; + if (leafHashes.isEmpty()) { + return CompletableFuture.completedFuture(Bytes.wrap(EMPTY_HASH)); + } + Queue hashes = new LinkedList<>(leafHashes); + final int n = hashes.size(); + if ((n & (n - 1)) != 0) { + final var paddedN = Integer.highestOneBit(n) << 1; + while (hashes.size() < paddedN) { + hashes.add(EMPTY_HASH); + } + } + while (hashes.size() > 1) { + final Queue newLeafHashes = new LinkedList<>(); + while (!hashes.isEmpty()) { + final byte[] left = hashes.poll(); + final byte[] right = hashes.poll(); + final byte[] combined = new byte[left.length + requireNonNull(right).length]; + System.arraycopy(left, 0, combined, 0, left.length); + System.arraycopy(right, 0, combined, left.length, right.length); + newLeafHashes.add(noThrowSha384HashOf(combined)); + } + hashes = newLeafHashes; + } + return CompletableFuture.completedFuture(Bytes.wrap(requireNonNull(hashes.poll()))); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/hasher/StreamingTreeHasher.java b/server/src/main/java/com/hedera/block/server/verification/hasher/StreamingTreeHasher.java new file mode 100644 index 00000000..03e2671b --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/hasher/StreamingTreeHasher.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.verification.hasher; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.crypto.DigestType; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Defines a streaming hash computation for a perfect binary Merkle tree of {@link Bytes} leaves; where the leaves + * given before calling {@link #rootHash()} are right-padded with empty leaves as needed to ensure the final tree is + * a perfect binary tree. + */ +public interface StreamingTreeHasher { + /** + * The length of the hash produced by this hasher. + */ + int HASH_LENGTH = DigestType.SHA_384.digestLength(); + + /** + * Describes the status of the tree hash computation. + * @param numLeaves the number of leaves added to the tree + * @param rightmostHashes the rightmost hashes of the tree at each depth + */ + record Status(int numLeaves, @NonNull List rightmostHashes) { + public static Status EMPTY = new Status(0, List.of()); + + /** + * Returns whether the tree is empty. + * @return whether the tree is empty + */ + public boolean isEmpty() { + return numLeaves == 0; + } + } + + /** + * Adds a leaf hash to the implicit tree of items from the given buffer. The buffer's new position + * will be the current position plus {@link #HASH_LENGTH}. + * @param hash the leaf hash to add + * @throws IllegalStateException if the root hash has already been requested + * @throws IllegalArgumentException if the buffer does not have at least {@link #HASH_LENGTH} bytes remaining + */ + void addLeaf(@NonNull ByteBuffer hash); + + /** + * Returns a future that completes with the root hash of the tree of items. Once called, this hasher will not accept + * any more leaf items. + * @return a future that completes with the root hash of the tree of items + */ + CompletableFuture rootHash(); + + /** + * If supported, blocks until this hasher can give a deterministic summary of the status of the + * tree hash computation. + * @return the status of the tree hash computation + * @throws UnsupportedOperationException if the implementation does not support status reporting + */ + default Status status() { + throw new UnsupportedOperationException(); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationService.java b/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationService.java new file mode 100644 index 00000000..119a65fd --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationService.java @@ -0,0 +1,34 @@ +/* + * 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.verification.service; + +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.pbj.runtime.ParseException; +import java.util.List; + +/** + * Service that handles the verification of block items, it receives items from the handler. + */ +public interface BlockVerificationService { + /** + * Everytime the handler receives a block item, it will call this method to verify the block item. + * + * @param blockItems the block items to add to the verification service + * @throws ParseException if the block items are invalid + */ + void onBlockItemsReceived(List blockItems) throws ParseException; +} diff --git a/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationServiceImpl.java b/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationServiceImpl.java new file mode 100644 index 00000000..61f64e98 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/service/BlockVerificationServiceImpl.java @@ -0,0 +1,102 @@ +/* + * 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.verification.service; + +import static java.lang.System.Logger.Level.WARNING; + +import com.hedera.block.server.metrics.BlockNodeMetricTypes; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.session.BlockVerificationSession; +import com.hedera.block.server.verification.session.BlockVerificationSessionFactory; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import javax.inject.Inject; + +/** + * Service that handles the verification of block items, it receives items from the handler. + */ +public class BlockVerificationServiceImpl implements BlockVerificationService { + + private final System.Logger LOGGER = System.getLogger(getClass().getName()); + private final MetricsService metricsService; + + private final BlockVerificationSessionFactory sessionFactory; + private BlockVerificationSession currentSession; + + /** + * Constructs a new BlockVerificationServiceImpl. + * + * @param metricsService the metrics service + * @param sessionFactory the session factory + */ + @Inject + public BlockVerificationServiceImpl( + @NonNull final MetricsService metricsService, + @NonNull final BlockVerificationSessionFactory sessionFactory) { + this.metricsService = metricsService; + this.sessionFactory = sessionFactory; + } + + /** + * Everytime the handler receives block items, it will call this method to verify the block items. + * + * @param blockItems the block items to add to the verification service + * @throws ParseException if the block items are invalid + */ + @Override + public void onBlockItemsReceived(List blockItems) throws ParseException { + + final BlockItemUnparsed firstItem = blockItems.getFirst(); + + // If we have a new block header, that means a new block has started + if (firstItem.hasBlockHeader()) { + metricsService + .get(BlockNodeMetricTypes.Counter.VerificationBlocksReceived) + .increment(); + BlockHeader blockHeader = BlockHeader.PROTOBUF.parse(firstItem.blockHeader()); + + // double check last block hash with prev of current block + if (currentSession != null) { + currentSession.getVerificationResult().thenAccept(result -> { + if (!result.blockHash().equals(blockHeader.previousBlockHash())) { + LOGGER.log(WARNING, "blockHeader.previousBlockHash does not match calculated previous hash."); + metricsService + .get(BlockNodeMetricTypes.Counter.VerificationBlocksFailed) + .increment(); + } + }); + } else { + LOGGER.log(WARNING, "No previous session to compare block hashes."); + } + + // start new session and set it as current + currentSession = sessionFactory.createSession(blockHeader); + currentSession.appendBlockItems(blockItems); + + } else { // If we don't have a block header, we should have a current session, otherwise ignore + if (currentSession == null) { + LOGGER.log(WARNING, "Received block items before a block header. Ignoring."); + return; + } + // Append to current session + currentSession.appendBlockItems(blockItems); + } + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/service/NoOpBlockVerificationService.java b/server/src/main/java/com/hedera/block/server/verification/service/NoOpBlockVerificationService.java new file mode 100644 index 00000000..55cc20aa --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/service/NoOpBlockVerificationService.java @@ -0,0 +1,39 @@ +/* + * 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.verification.service; + +import com.hedera.hapi.block.BlockItemUnparsed; +import java.util.List; + +/** No-op implementation of the {@link BlockVerificationService}. */ +public class NoOpBlockVerificationService implements BlockVerificationService { + + /** + * Constructs a no-op block verification service. + */ + public NoOpBlockVerificationService() { + // no-op + } + + /** + * Does nothing + */ + @Override + public void onBlockItemsReceived(List blockItems) { + // no-op + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSession.java b/server/src/main/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSession.java new file mode 100644 index 00000000..523bd8fa --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSession.java @@ -0,0 +1,190 @@ +/* + * 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.verification.session; + +import static java.lang.System.Logger.Level.INFO; + +import com.hedera.block.server.metrics.BlockNodeMetricTypes; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.BlockVerificationStatus; +import com.hedera.block.server.verification.VerificationResult; +import com.hedera.block.server.verification.hasher.CommonUtils; +import com.hedera.block.server.verification.hasher.Hashes; +import com.hedera.block.server.verification.hasher.StreamingTreeHasher; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.BlockProof; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * An abstract base class providing common functionality for block verification sessions. + * Concrete classes handle how block items are appended and processed (synchronously or asynchronously). + */ +public abstract class AbstractBlockVerificationSession implements BlockVerificationSession { + + /** + * The logger for this class. + */ + protected final System.Logger LOGGER = System.getLogger(getClass().getName()); + + /** + * The metrics service. + */ + protected final MetricsService metricsService; + /** + * The signature verifier. + */ + protected final SignatureVerifier signatureVerifier; + /** + * The block number being verified. + */ + protected final long blockNumber; + /** + * The tree hasher for input hashes. + */ + protected final StreamingTreeHasher inputTreeHasher; + /** + * The tree hasher for output hashes. + */ + protected final StreamingTreeHasher outputTreeHasher; + /** + * The time when block verification started. + */ + protected final long blockWorkStartTime; + /** + * A flag indicating whether the session is running. + */ + protected volatile boolean running = true; + /** + * The future for the verification result. + */ + protected final CompletableFuture verificationResultFuture = new CompletableFuture<>(); + + /** + * Constructs the session with shared initialization logic. + * + * @param blockHeader the block header + * @param metricsService the metrics service + * @param signatureVerifier the signature verifier + * @param inputTreeHasher the input tree hasher (e.g. naive or concurrent) + * @param outputTreeHasher the output tree hasher (e.g. naive or concurrent) + */ + protected AbstractBlockVerificationSession( + @NonNull final BlockHeader blockHeader, + @NonNull final MetricsService metricsService, + @NonNull final SignatureVerifier signatureVerifier, + @NonNull final StreamingTreeHasher inputTreeHasher, + @NonNull final StreamingTreeHasher outputTreeHasher) { + this.blockNumber = blockHeader.number(); + this.metricsService = metricsService; + this.signatureVerifier = signatureVerifier; + this.inputTreeHasher = inputTreeHasher; + this.outputTreeHasher = outputTreeHasher; + + this.blockWorkStartTime = System.nanoTime(); + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public CompletableFuture getVerificationResult() { + return verificationResultFuture; + } + + /** + * Processes the provided block items by updating the tree hashers. + * If the last item has a block proof, final verification is triggered. + * + * @param blockItems the block items to process + * @throws ParseException if a parsing error occurs + */ + protected void processBlockItems(List blockItems) throws ParseException { + + Hashes hashes = CommonUtils.getBlockHashes(blockItems); + while (hashes.inputHashes().hasRemaining()) { + inputTreeHasher.addLeaf(hashes.inputHashes()); + } + while (hashes.outputHashes().hasRemaining()) { + outputTreeHasher.addLeaf(hashes.outputHashes()); + } + + // Check if this batch contains the final block proof + final BlockItemUnparsed lastItem = blockItems.getLast(); + if (lastItem.hasBlockProof()) { + BlockProof blockProof = BlockProof.PROTOBUF.parse(lastItem.blockProof()); + finalizeVerification(blockProof); + } + } + + /** + * Finalizes the block verification by computing the final block hash, + * verifying its signature, and updating metrics accordingly. + * + * @param blockProof the block proof + */ + protected void finalizeVerification(BlockProof blockProof) { + Bytes blockHash = CommonUtils.computeFinalBlockHash(blockProof, inputTreeHasher, outputTreeHasher); + VerificationResult result; + boolean verified = signatureVerifier.verifySignature(blockHash, blockProof.blockSignature()); + if (verified) { + long verificationLatency = System.nanoTime() - blockWorkStartTime; + metricsService + .get(BlockNodeMetricTypes.Counter.VerificationBlockTime) + .add(verificationLatency); + metricsService + .get(BlockNodeMetricTypes.Counter.VerificationBlocksVerified) + .increment(); + + result = new VerificationResult(blockNumber, blockHash, BlockVerificationStatus.VERIFIED); + } else { + LOGGER.log(INFO, "Block verification failed for block number: {0}", blockNumber); + metricsService + .get(BlockNodeMetricTypes.Counter.VerificationBlocksFailed) + .increment(); + + result = new VerificationResult(blockNumber, blockHash, BlockVerificationStatus.SIGNATURE_INVALID); + } + shutdownSession(); + verificationResultFuture.complete(result); + } + + /** + * Shuts down this session, marking it as no longer running. + */ + protected void shutdownSession() { + running = false; + } + + /** + * A helper method that handles errors encountered during block item processing. + * + * @param ex the exception encountered + */ + protected void handleProcessingError(Throwable ex) { + LOGGER.log(System.Logger.Level.ERROR, "Error processing block items", ex); + metricsService.get(BlockNodeMetricTypes.Counter.VerificationBlocksError).increment(); + verificationResultFuture.completeExceptionally(ex); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSession.java b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSession.java new file mode 100644 index 00000000..56658a2d --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSession.java @@ -0,0 +1,50 @@ +/* + * 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.verification.session; + +import com.hedera.block.server.verification.VerificationResult; +import com.hedera.hapi.block.BlockItemUnparsed; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Defines the contract for a block verification session. + */ +public interface BlockVerificationSession { + + /** + * Append new block items to be processed by this verification session. + * + * @param blockItems the list of block items to process. + */ + void appendBlockItems(List blockItems); + + /** + * Indicates whether the verification session is still running. + * + * @return true if running; false otherwise. + */ + boolean isRunning(); + + /** + * Returns a future that completes with the verification result of the entire block + * once verification is complete. + * + * @return a CompletableFuture for the verification result. + */ + CompletableFuture getVerificationResult(); +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsync.java b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsync.java new file mode 100644 index 00000000..f652071e --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsync.java @@ -0,0 +1,99 @@ +/* + * 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.verification.session; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.hasher.ConcurrentStreamingTreeHasher; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.output.BlockHeader; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * An asynchronous implementation of the BlockVerificationSession. It processes the block items + * asynchronously using an executor. + */ +public class BlockVerificationSessionAsync extends AbstractBlockVerificationSession { + + private final ExecutorService taskExecutor; + private final int hashCombineBatchSize = 32; + + /** + * Constructs an asynchronous block verification session. + * + * @param blockHeader the header of the block being verified + * @param metricsService the service to record metrics + * @param signatureVerifier the signature verifier + * @param executorService the executor service to use for processing block items + * @param hashCombineBatchSize the batch size for combining hashes + */ + public BlockVerificationSessionAsync( + @NonNull final BlockHeader blockHeader, + @NonNull final MetricsService metricsService, + @NonNull final SignatureVerifier signatureVerifier, + @NonNull final ExecutorService executorService, + final int hashCombineBatchSize) { + + super( + blockHeader, + metricsService, + signatureVerifier, + new ConcurrentStreamingTreeHasher(executorService, hashCombineBatchSize), + new ConcurrentStreamingTreeHasher(executorService, hashCombineBatchSize)); + + this.taskExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "block-verification-session-" + this.blockNumber); + t.setDaemon(true); + return t; + }); + } + + /** + * Appends new block items to be processed by this verification session. + * The block items are processed asynchronously. + * + * @param blockItems the list of block items to process. + */ + @Override + public void appendBlockItems(List blockItems) { + if (!isRunning()) { + LOGGER.log(System.Logger.Level.ERROR, "Block verification session is not running"); + return; + } + + // Submit a task that processes the block items asynchronously + Callable task = () -> { + try { + processBlockItems(blockItems); + } catch (Exception ex) { + handleProcessingError(ex); + } + return null; + }; + taskExecutor.submit(task); + } + + @Override + protected void shutdownSession() { + super.shutdownSession(); + this.taskExecutor.shutdown(); + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactory.java b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactory.java new file mode 100644 index 00000000..c7fa13c2 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactory.java @@ -0,0 +1,76 @@ +/* + * 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.verification.session; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.VerificationConfig; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.stream.output.BlockHeader; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.ExecutorService; +import javax.inject.Inject; + +/** + * A factory for creating block verification sessions. + */ +public class BlockVerificationSessionFactory { + + private final VerificationConfig config; + private final MetricsService metricsService; + private final SignatureVerifier signatureVerifier; + private final ExecutorService executorService; + private final int hashCombineBatchSize; + + /** + * Constructs a block verification session factory. + * + * @param verificationConfig the verification configuration + * @param metricsService the metrics service + * @param signatureVerifier the signature verifier + * @param executorService the executor service + */ + @Inject + public BlockVerificationSessionFactory( + @NonNull final VerificationConfig verificationConfig, + @NonNull final MetricsService metricsService, + @NonNull final SignatureVerifier signatureVerifier, + @NonNull final ExecutorService executorService) { + this.config = verificationConfig; + this.metricsService = metricsService; + this.signatureVerifier = signatureVerifier; + this.executorService = executorService; + this.hashCombineBatchSize = verificationConfig.hashCombineBatchSize(); + } + + /** + * Creates a new block verification session. + * + * @param blockHeader the block header + * @return the block verification session + */ + public BlockVerificationSession createSession(@NonNull final BlockHeader blockHeader) { + + BlockVerificationSessionType type = + BlockVerificationSessionType.valueOf(config.sessionType().name()); + + return switch (type) { + case ASYNC -> new BlockVerificationSessionAsync( + blockHeader, metricsService, signatureVerifier, executorService, hashCombineBatchSize); + case SYNC -> new BlockVerificationSessionSync(blockHeader, metricsService, signatureVerifier); + }; + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionSync.java b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionSync.java new file mode 100644 index 00000000..3c2b21a5 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionSync.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.verification.session; + +import static com.hedera.block.server.verification.hasher.CommonUtils.getBlockItemHash; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.hasher.NaiveStreamingTreeHasher; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.BlockProof; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * A synchronous implementation of the BlockVerificationSession. It processes the block items + * synchronously in the calling thread. + */ +public class BlockVerificationSessionSync extends AbstractBlockVerificationSession { + + /** + * Constructs a synchronous block verification session. + * + * @param blockHeader the header of the block being verified + * @param metricsService the service to observe metrics + * @param signatureVerifier the signature verifier + */ + public BlockVerificationSessionSync( + @NonNull final BlockHeader blockHeader, + @NonNull final MetricsService metricsService, + @NonNull final SignatureVerifier signatureVerifier) { + + super( + blockHeader, + metricsService, + signatureVerifier, + new NaiveStreamingTreeHasher(), + new NaiveStreamingTreeHasher()); + } + + @Override + public void appendBlockItems(List blockItems) { + if (!isRunning()) { + LOGGER.log(System.Logger.Level.ERROR, "Block verification session is not running"); + return; + } + + try { + processBlockItems(blockItems); + } catch (Exception ex) { + handleProcessingError(ex); + } + } + + @Override + protected void processBlockItems(List blockItems) throws ParseException { + for (BlockItemUnparsed item : blockItems) { + final BlockItemUnparsed.ItemOneOfType kind = item.item().kind(); + switch (kind) { + case EVENT_HEADER, EVENT_TRANSACTION -> inputTreeHasher.addLeaf(getBlockItemHash(item)); + case TRANSACTION_RESULT, TRANSACTION_OUTPUT, STATE_CHANGES -> outputTreeHasher.addLeaf( + getBlockItemHash(item)); + } + } + + // Check if this batch contains the final block proof + final BlockItemUnparsed lastItem = blockItems.getLast(); + if (lastItem.hasBlockProof()) { + BlockProof blockProof = BlockProof.PROTOBUF.parse(lastItem.blockProof()); + finalizeVerification(blockProof); + } + } +} diff --git a/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionType.java b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionType.java new file mode 100644 index 00000000..b19dc2b1 --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/session/BlockVerificationSessionType.java @@ -0,0 +1,31 @@ +/* + * 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.verification.session; + +/** + * Defines the types of block verification sessions. + */ +public enum BlockVerificationSessionType { + /** + * An asynchronous block verification session, where the verification is done in a separate thread. + */ + ASYNC, + /** + * A synchronous block verification session, where the verification is done in the same thread. + */ + SYNC +} diff --git a/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifier.java b/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifier.java new file mode 100644 index 00000000..d48494db --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifier.java @@ -0,0 +1,34 @@ +/* + * 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.verification.signature; + +import com.hedera.pbj.runtime.io.buffer.Bytes; + +/** + * An interface for verifying signatures. + */ +public interface SignatureVerifier { + + /** + * Verifies the signature of a hash. + * + * @param hash the hash to verify + * @param signature the signature to verify + * @return true if the signature is valid, false otherwise + */ + Boolean verifySignature(Bytes hash, Bytes signature); +} diff --git a/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifierDummy.java b/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifierDummy.java new file mode 100644 index 00000000..0fddeddf --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/verification/signature/SignatureVerifierDummy.java @@ -0,0 +1,46 @@ +/* + * 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.verification.signature; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import javax.inject.Inject; + +/** + * A dummy implementation of {@link SignatureVerifier} that always returns true. + */ +public class SignatureVerifierDummy implements SignatureVerifier { + + /** + * Constructs the dummy verifier. + */ + @Inject + // on actual impl we would need to provide the public key (aka LedgerID) + public SignatureVerifierDummy() {} + + /** + * Verifies the signature of a hash, for the dummy implementation this always returns true. + * + * @param hash the hash to verify + * @param signature the signature to verify + * @return true if the signature is valid, false otherwise + */ + @Override + public Boolean verifySignature(Bytes hash, Bytes signature) { + // Dummy implementation that always returns true + return true; + } +} diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index dda0c6d2..dc6abed4 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -21,6 +21,11 @@ exports com.hedera.block.server.service; exports com.hedera.block.server.pbj; exports com.hedera.block.server.producer; + exports com.hedera.block.server.verification; + exports com.hedera.block.server.verification.hasher; + exports com.hedera.block.server.verification.session; + exports com.hedera.block.server.verification.signature; + exports com.hedera.block.server.verification.service; requires com.hedera.block.common; requires com.hedera.block.stream; 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 001a0857..0c969c3b 100644 --- a/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java +++ b/server/src/test/java/com/hedera/block/server/BlockNodeAppTest.java @@ -30,6 +30,7 @@ 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.block.server.verification.StreamVerificationHandlerImpl; import com.hedera.hapi.block.BlockUnparsed; import com.hedera.hapi.block.SubscribeStreamResponseUnparsed; import com.hedera.pbj.grpc.helidon.PbjRouting; @@ -68,6 +69,9 @@ class BlockNodeAppTest { @Mock private BlockNodeEventHandler> blockNodeEventHandler; + @Mock + private StreamVerificationHandlerImpl streamVerificationHandler; + @Mock private Notifier notifier; @@ -87,7 +91,12 @@ void setup() { serviceStatus, healthService, new PbjBlockStreamServiceProxy( - liveStreamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext), + liveStreamMediator, + serviceStatus, + blockNodeEventHandler, + streamVerificationHandler, + notifier, + blockNodeContext), new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext), webServerBuilder, serverConfig); diff --git a/server/src/test/java/com/hedera/block/server/config/ConfigInjectionModuleTest.java b/server/src/test/java/com/hedera/block/server/config/ConfigInjectionModuleTest.java index 5d2f5882..f9369ada 100644 --- a/server/src/test/java/com/hedera/block/server/config/ConfigInjectionModuleTest.java +++ b/server/src/test/java/com/hedera/block/server/config/ConfigInjectionModuleTest.java @@ -24,6 +24,7 @@ import com.hedera.block.server.notifier.NotifierConfig; import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; import com.hedera.block.server.util.TestConfigUtil; +import com.hedera.block.server.verification.VerificationConfig; import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.platform.prometheus.PrometheusConfig; import com.swirlds.config.api.Configuration; @@ -130,4 +131,17 @@ void testServerConfig() throws IOException { assertNotNull(providedConfig); assertSame(serverConfig, providedConfig); } + + @Test + void testVerificationConfig() throws IOException { + BlockNodeContext context = TestConfigUtil.getTestBlockNodeContext(); + Configuration configuration = context.configuration(); + VerificationConfig verificationConfig = configuration.getConfigData(VerificationConfig.class); + + VerificationConfig providedConfig = ConfigInjectionModule.provideVerificationConfig(configuration); + + // Verify the config + assertNotNull(providedConfig); + assertSame(verificationConfig, providedConfig); + } } diff --git a/server/src/test/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializerTest.java b/server/src/test/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializerTest.java index cbf56264..1ef0dcb9 100644 --- a/server/src/test/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializerTest.java +++ b/server/src/test/java/com/hedera/block/server/config/ServerMappedConfigSourceInitializerTest.java @@ -79,7 +79,12 @@ class ServerMappedConfigSourceInitializerTest { // Prometheus Config (externally managed, but we need this mapping) new ConfigMapping("prometheus.endpointEnabled", "PROMETHEUS_ENDPOINT_ENABLED"), - new ConfigMapping("prometheus.endpointPortNumber", "PROMETHEUS_ENDPOINT_PORT_NUMBER") + new ConfigMapping("prometheus.endpointPortNumber", "PROMETHEUS_ENDPOINT_PORT_NUMBER"), + + // Verification Config + new ConfigMapping("verification.enabled", "VERIFICATION_ENABLED"), + new ConfigMapping("verification.sessionType", "VERIFICATION_SESSION_TYPE"), + new ConfigMapping("verification.hashCombineBatchSize", "VERIFICATION_HASH_COMBINE_BATCH_SIZE") }; /** 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 c8b324cf..5070b767 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 @@ -44,6 +44,9 @@ import com.hedera.block.server.service.ServiceStatusImpl; import com.hedera.block.server.util.TestConfigUtil; import com.hedera.block.server.util.TestUtils; +import com.hedera.block.server.verification.StreamVerificationHandlerImpl; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.block.server.verification.service.NoOpBlockVerificationService; import com.hedera.hapi.block.Acknowledgement; import com.hedera.hapi.block.BlockItemUnparsed; import com.hedera.hapi.block.PublishStreamResponse; @@ -272,9 +275,18 @@ private PbjBlockStreamServiceProxy buildBlockStreamService(final Notifier notifi final var streamMediator = buildStreamMediator(new ConcurrentHashMap<>(32), serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + final BlockVerificationService blockVerificationService = new NoOpBlockVerificationService(); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + streamMediator, notifier, blockNodeContext.metricsService(), serviceStatus, blockVerificationService); return new PbjBlockStreamServiceProxy( - streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); + streamMediator, + serviceStatus, + blockNodeEventHandler, + streamVerificationHandler, + notifier, + blockNodeContext); } private LiveStreamMediator buildStreamMediator( diff --git a/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java index 0f7312d6..d36ebcaa 100644 --- a/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java +++ b/server/src/test/java/com/hedera/block/server/pbj/PbjBlockStreamServiceIntegrationTest.java @@ -49,6 +49,8 @@ import com.hedera.block.server.service.ServiceStatus; import com.hedera.block.server.service.ServiceStatusImpl; import com.hedera.block.server.util.TestConfigUtil; +import com.hedera.block.server.verification.StreamVerificationHandlerImpl; +import com.hedera.block.server.verification.service.BlockVerificationService; import com.hedera.hapi.block.Acknowledgement; import com.hedera.hapi.block.BlockItemSetUnparsed; import com.hedera.hapi.block.BlockItemUnparsed; @@ -392,8 +394,21 @@ public void testSubAndUnsubWhileStreaming() throws InterruptedException { final var streamMediator = buildStreamMediator(consumers, serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + + final StreamVerificationHandlerImpl streamVerificationHandler = new StreamVerificationHandlerImpl( + streamMediator, + notifier, + blockNodeContext.metricsService(), + serviceStatus, + mock(BlockVerificationService.class)); + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( - streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); + streamMediator, + serviceStatus, + blockNodeEventHandler, + streamVerificationHandler, + notifier, + blockNodeContext); final Pipeline producerPipeline = pbjBlockStreamServiceProxy.open( PbjBlockStreamService.BlockStreamMethod.publishBlockStream, options, helidonPublishStreamObserver1); @@ -519,8 +534,21 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure() throws IOExcep final var notifier = new NotifierImpl(streamMediator, blockNodeContext, serviceStatus); final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + + final StreamVerificationHandlerImpl streamVerificationHandler = new StreamVerificationHandlerImpl( + streamMediator, + notifier, + blockNodeContext.metricsService(), + serviceStatus, + mock(BlockVerificationService.class)); + final PbjBlockStreamServiceProxy pbjBlockStreamServiceProxy = new PbjBlockStreamServiceProxy( - streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); + streamMediator, + serviceStatus, + blockNodeEventHandler, + streamVerificationHandler, + notifier, + blockNodeContext); // Register a producer final Pipeline producerPipeline = pbjBlockStreamServiceProxy.open( @@ -539,7 +567,7 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure() throws IOExcep .onNext(buildEmptySubscribeStreamRequest()); // 3 subscribers + 1 streamPersistenceHandler - assertEquals(4, consumers.size()); + assertEquals(5, consumers.size()); // Transmit a BlockItem final Bytes publishStreamRequest = @@ -670,11 +698,24 @@ private PbjBlockStreamServiceProxy buildBlockStreamService(final BlockWriter(32), serviceStatus); final var notifier = new NotifierImpl(streamMediator, blockNodeContext, serviceStatus); + final var blockNodeEventHandler = new StreamPersistenceHandlerImpl( streamMediator, notifier, blockWriter, blockNodeContext, serviceStatus); + final StreamVerificationHandlerImpl streamVerificationHandler = new StreamVerificationHandlerImpl( + streamMediator, + notifier, + blockNodeContext.metricsService(), + serviceStatus, + mock(BlockVerificationService.class)); + return new PbjBlockStreamServiceProxy( - streamMediator, serviceStatus, blockNodeEventHandler, notifier, blockNodeContext); + streamMediator, + serviceStatus, + blockNodeEventHandler, + streamVerificationHandler, + notifier, + blockNodeContext); } private LiveStreamMediator buildStreamMediator( diff --git a/server/src/test/java/com/hedera/block/server/verification/BlockVerificationServiceImplTest.java b/server/src/test/java/com/hedera/block/server/verification/BlockVerificationServiceImplTest.java new file mode 100644 index 00000000..8f3631b2 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/BlockVerificationServiceImplTest.java @@ -0,0 +1,215 @@ +/* + * 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.verification; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.*; + +import com.hedera.block.server.metrics.BlockNodeMetricTypes; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.block.server.verification.service.BlockVerificationServiceImpl; +import com.hedera.block.server.verification.session.BlockVerificationSession; +import com.hedera.block.server.verification.session.BlockVerificationSessionFactory; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.metrics.api.Counter; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class BlockVerificationServiceImplTest { + + @Mock + private MetricsService metricsService; + + @Mock + private BlockVerificationSessionFactory sessionFactory; + + @Mock + private BlockVerificationSession previousSession; + + @Mock + private BlockVerificationSession newSession; + + @Mock + private Counter verificationBlocksReceived; + + @Mock + private Counter verificationBlocksFailed; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(metricsService.get(BlockNodeMetricTypes.Counter.VerificationBlocksReceived)) + .thenReturn(verificationBlocksReceived); + when(metricsService.get(BlockNodeMetricTypes.Counter.VerificationBlocksFailed)) + .thenReturn(verificationBlocksFailed); + } + + @Test + void testOnBlockItemsReceivedWithNewBlockHeaderNoPreviousSession() throws ParseException { + // Given a new block header starting at block #10 + long blockNumber = 10; + BlockItemUnparsed blockHeaderItem = getBlockHeaderUnparsed(blockNumber); + List blockItems = List.of(blockHeaderItem); + + // No previous session + when(sessionFactory.createSession(any())).thenReturn(newSession); + + BlockVerificationService service = new BlockVerificationServiceImpl(metricsService, sessionFactory); + + // When + service.onBlockItemsReceived(blockItems); + + // Then + verify(verificationBlocksReceived).increment(); // new block received + verify(sessionFactory).createSession(getBlockHeader(blockNumber)); + verify(newSession).appendBlockItems(blockItems); + // No previous session, so just logs a warning internally + } + + @Test + void testOnBlockItemsReceivedWithNewBlockHeaderAndPreviousSessionHashMatch() throws ParseException { + // Given a previous verified block #9 and now receiving header for block #10 + long previousBlockNumber = 9; + long newBlockNumber = 10; + + BlockItemUnparsed blockHeaderItem = getBlockHeaderUnparsed(newBlockNumber); + List blockItems = List.of(blockHeaderItem); + + // Previous session result matches the expected previous hash + CompletableFuture future = new CompletableFuture<>(); + future.complete(getVerificationResult(previousBlockNumber)); + when(previousSession.getVerificationResult()).thenReturn(future); + + when(sessionFactory.createSession(getBlockHeader(newBlockNumber))).thenReturn(newSession); + + BlockVerificationServiceImpl service = new BlockVerificationServiceImpl(metricsService, sessionFactory); + setCurrentSession(service, previousSession); + + // When + service.onBlockItemsReceived(blockItems); + + // Then + verify(verificationBlocksReceived).increment(); + verify(verificationBlocksFailed, never()).increment(); + verify(newSession).appendBlockItems(blockItems); + } + + @Test + void testOnBlockItemsReceivedWithNewBlockHeaderAndPreviousSessionHashMismatch() throws ParseException { + // Given a previous block #9 but now we produce a verification result that doesn't match the new header's prev + // hash + long previousBlockNumber = 9; + long newBlockNumber = 10; + + BlockItemUnparsed blockHeaderItem = getBlockHeaderUnparsed(newBlockNumber); + List blockItems = List.of(blockHeaderItem); + + // Make the previous session result have a different hash (e.g., block #99) + CompletableFuture future = new CompletableFuture<>(); + future.complete(getVerificationResult(99)); // This gives hash99, not hash9 + when(previousSession.getVerificationResult()).thenReturn(future); + + when(sessionFactory.createSession(getBlockHeader(newBlockNumber))).thenReturn(newSession); + + BlockVerificationServiceImpl service = new BlockVerificationServiceImpl(metricsService, sessionFactory); + setCurrentSession(service, previousSession); + + // When + service.onBlockItemsReceived(blockItems); + + // Then + verify(verificationBlocksReceived).increment(); + verify(verificationBlocksFailed).increment(); // mismatch should cause increment + verify(newSession).appendBlockItems(blockItems); + } + + @Test + void testOnBlockItemsReceivedNoBlockHeaderNoCurrentSession() throws ParseException { + BlockItemUnparsed normalItem = getNormalBlockItem(); + List blockItems = List.of(normalItem); + + BlockVerificationService service = new BlockVerificationServiceImpl(metricsService, sessionFactory); + + // When + service.onBlockItemsReceived(blockItems); + + // Then + // Just logs a warning. No increments or sessions created. + verifyNoInteractions(sessionFactory); + verifyNoInteractions(verificationBlocksReceived, verificationBlocksFailed); + } + + @Test + void testOnBlockItemsReceivedNoBlockHeaderWithCurrentSession() throws ParseException { + BlockItemUnparsed normalItem = getNormalBlockItem(); + List blockItems = List.of(normalItem); + + BlockVerificationServiceImpl service = new BlockVerificationServiceImpl(metricsService, sessionFactory); + setCurrentSession(service, previousSession); + + // When + service.onBlockItemsReceived(blockItems); + + // Then + verify(previousSession).appendBlockItems(blockItems); + verifyNoInteractions(verificationBlocksReceived, verificationBlocksFailed); + } + + private VerificationResult getVerificationResult(long blockNumber) { + return new VerificationResult( + blockNumber, Bytes.wrap(("hash" + blockNumber).getBytes()), BlockVerificationStatus.VERIFIED); + } + + private BlockHeader getBlockHeader(long blockNumber) { + long previousBlockNumber = blockNumber - 1; + + return BlockHeader.newBuilder() + .previousBlockHash(Bytes.wrap(("hash" + previousBlockNumber).getBytes())) + .number(blockNumber) + .build(); + } + + private BlockItemUnparsed getBlockHeaderUnparsed(long blockNumber) { + return BlockItemUnparsed.newBuilder() + .blockHeader(BlockHeader.PROTOBUF.toBytes(getBlockHeader(blockNumber))) + .build(); + } + + private BlockItemUnparsed getNormalBlockItem() { + // A block item without a block header + return BlockItemUnparsed.newBuilder().build(); + } + + // Helper method to set the currentSession field via reflection since it’s private + private static void setCurrentSession(BlockVerificationServiceImpl service, BlockVerificationSession session) { + try { + var field = BlockVerificationServiceImpl.class.getDeclaredField("currentSession"); + field.setAccessible(true); + field.set(service, session); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Unable to set currentSession via reflection", e); + } + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/StreamVerificationHandlerImplTest.java b/server/src/test/java/com/hedera/block/server/verification/StreamVerificationHandlerImplTest.java new file mode 100644 index 00000000..76d326f1 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/StreamVerificationHandlerImplTest.java @@ -0,0 +1,229 @@ +/* + * 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.verification; + +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlocksError; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.hedera.block.server.events.ObjectEvent; +import com.hedera.block.server.mediator.SubscriptionHandler; +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.notifier.Notifier; +import com.hedera.block.server.service.ServiceStatus; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.hapi.block.BlockItemSetUnparsed; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.SubscribeStreamResponseUnparsed; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.OneOf; +import com.hedera.pbj.runtime.ParseException; +import com.swirlds.metrics.api.Counter; +import java.util.List; +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 StreamVerificationHandlerImplTest { + + @Mock + private SubscriptionHandler subscriptionHandler; + + @Mock + private Notifier notifier; + + @Mock + private MetricsService metricsService; + + @Mock + private Counter verificationBlocksError; + + @Mock + private ServiceStatus serviceStatus; + + @Mock + private BlockVerificationService blockVerificationService; + + private static final int testTimeout = 50; + + @Test + public void testOnEventWhenServiceIsNotRunning() { + when(serviceStatus.isRunning()).thenReturn(false); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + final ObjectEvent event = new ObjectEvent<>(); + final var response = SubscribeStreamResponseUnparsed.newBuilder().build(); // no block items, no status + event.set(response); + + when(metricsService.get(VerificationBlocksError)).thenReturn(verificationBlocksError); + + // Call the handler + streamVerificationHandler.onEvent(event, 0, false); + + verify(serviceStatus, timeout(testTimeout).times(1)).stopRunning(any()); + verify(subscriptionHandler, timeout(testTimeout).times(1)).unsubscribe(any()); + verify(notifier, timeout(testTimeout).times(1)).notifyUnrecoverableError(); + } + + @Test + public void testBlockItemsNullThrowsException() { + when(metricsService.get(VerificationBlocksError)).thenReturn(verificationBlocksError); + + when(serviceStatus.isRunning()).thenReturn(true); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + // Create a SubscribeStreamResponseUnparsed with BLOCK_ITEMS type but null blockItems + final SubscribeStreamResponseUnparsed response = spy(SubscribeStreamResponseUnparsed.newBuilder() + .blockItems((BlockItemSetUnparsed) null) + .build()); + final ObjectEvent event = new ObjectEvent<>(); + event.set(response); + + // Ensure that the response kind is BLOCK_ITEMS but blockItems is null + final OneOf blockItemsOneOf = + new OneOf<>(SubscribeStreamResponseUnparsed.ResponseOneOfType.BLOCK_ITEMS, null); + when(response.response()).thenReturn(blockItemsOneOf); + + // Trigger the event + streamVerificationHandler.onEvent(event, 0, false); + + // We expect a protocol exception, leading to service stop and unsubscribing + verify(metricsService, timeout(testTimeout).times(1)).get(VerificationBlocksError); + verify(serviceStatus, timeout(testTimeout).times(1)).stopRunning(any()); + verify(subscriptionHandler, timeout(testTimeout).times(1)).unsubscribe(any()); + verify(notifier, timeout(testTimeout).times(1)).notifyUnrecoverableError(); + // verify(blockVerificationService, never()).onBlockItemsReceived(any()); + } + + @Test + public void testUnknownResponseTypeThrowsException() { + when(serviceStatus.isRunning()).thenReturn(true); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + final SubscribeStreamResponseUnparsed response = + spy(SubscribeStreamResponseUnparsed.newBuilder().build()); + final ObjectEvent event = new ObjectEvent<>(); + event.set(response); + + // Force an UNKNOWN/UNSET oneOf type + final OneOf unknownOneOf = + new OneOf<>(SubscribeStreamResponseUnparsed.ResponseOneOfType.UNSET, null); + when(response.response()).thenReturn(unknownOneOf); + + streamVerificationHandler.onEvent(event, 0, false); + + verify(metricsService, timeout(testTimeout).times(1)).get(VerificationBlocksError); + verify(serviceStatus, timeout(testTimeout).times(1)).stopRunning(any()); + verify(subscriptionHandler, timeout(testTimeout).times(1)).unsubscribe(any()); + verify(notifier, timeout(testTimeout).times(1)).notifyUnrecoverableError(); + // verify(blockVerificationService, never()).onBlockItemsReceived(any()); + } + + @Test + public void testStatusMessageDoesNotThrow() { + when(serviceStatus.isRunning()).thenReturn(true); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + // Create a subscribeStreamResponse with a STATUS type + final SubscribeStreamResponseUnparsed response = SubscribeStreamResponseUnparsed.newBuilder() + .status(com.hedera.hapi.block.SubscribeStreamResponseCode.READ_STREAM_SUCCESS) + .build(); + final ObjectEvent event = new ObjectEvent<>(); + event.set(response); + + streamVerificationHandler.onEvent(event, 0, false); + + verify(serviceStatus, never()).stopRunning(any()); + verify(subscriptionHandler, never()).unsubscribe(any()); + verify(notifier, never()).notifyUnrecoverableError(); + // verify(blockVerificationService, never()).onBlockItemsReceived(any()); + } + + @Test + public void testValidBlockItemsAreVerified() throws ParseException { + when(serviceStatus.isRunning()).thenReturn(true); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + BlockHeader blockHeader = BlockHeader.newBuilder().number(10).build(); + + // Create a valid blockItems response + List blockItems = List.of(BlockItemUnparsed.newBuilder() + .blockHeader(BlockHeader.PROTOBUF.toBytes(blockHeader)) + .build()); + + final BlockItemSetUnparsed blockItemSet = + BlockItemSetUnparsed.newBuilder().blockItems(blockItems).build(); + final SubscribeStreamResponseUnparsed response = SubscribeStreamResponseUnparsed.newBuilder() + .blockItems(blockItemSet) + .build(); + final ObjectEvent event = new ObjectEvent<>(); + event.set(response); + + streamVerificationHandler.onEvent(event, 0, false); + + verify(blockVerificationService, times(1)).onBlockItemsReceived(blockItems); + verify(serviceStatus, never()).stopRunning(any()); + verify(subscriptionHandler, never()).unsubscribe(any()); + verify(notifier, never()).notifyUnrecoverableError(); + } + + @Test + public void testExceptionInVerificationTriggersErrorResponse() throws ParseException { + when(serviceStatus.isRunning()).thenReturn(true); + + final var streamVerificationHandler = new StreamVerificationHandlerImpl( + subscriptionHandler, notifier, metricsService, serviceStatus, blockVerificationService); + + BlockHeader blockHeader = BlockHeader.newBuilder().number(10).build(); + + // Create a valid blockItems response + List blockItems = List.of(BlockItemUnparsed.newBuilder() + .blockHeader(BlockHeader.PROTOBUF.toBytes(blockHeader)) + .build()); + + final BlockItemSetUnparsed blockItemSet = + BlockItemSetUnparsed.newBuilder().blockItems(blockItems).build(); + final SubscribeStreamResponseUnparsed response = SubscribeStreamResponseUnparsed.newBuilder() + .blockItems(blockItemSet) + .build(); + final ObjectEvent event = new ObjectEvent<>(); + event.set(response); + + // Simulate an exception when verifying block items + doThrow(new RuntimeException("Verification failed")) + .when(blockVerificationService) + .onBlockItemsReceived(blockItems); + + streamVerificationHandler.onEvent(event, 0, false); + + verify(serviceStatus, timeout(testTimeout).times(1)).stopRunning(any()); + verify(subscriptionHandler, timeout(testTimeout).times(1)).unsubscribe(any()); + verify(notifier, timeout(testTimeout).times(1)).notifyUnrecoverableError(); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/VerificationInjectionModuleTest.java b/server/src/test/java/com/hedera/block/server/verification/VerificationInjectionModuleTest.java new file mode 100644 index 00000000..460bb66f --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/VerificationInjectionModuleTest.java @@ -0,0 +1,70 @@ +/* + * 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.verification; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.service.BlockVerificationService; +import com.hedera.block.server.verification.service.BlockVerificationServiceImpl; +import com.hedera.block.server.verification.service.NoOpBlockVerificationService; +import com.hedera.block.server.verification.session.BlockVerificationSessionFactory; +import com.hedera.block.server.verification.session.BlockVerificationSessionType; +import java.io.IOException; +import org.junit.jupiter.api.Assertions; +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) +class VerificationInjectionModuleTest { + + @Mock + BlockVerificationSessionFactory sessionFactory; + + @Mock + MetricsService metricsService; + + @Test + void testProvideBlockVerificationService_enabled() throws IOException { + // given + VerificationConfig verificationConfig = new VerificationConfig(true, BlockVerificationSessionType.ASYNC, 32); + // when + BlockVerificationService blockVerificationService = VerificationInjectionModule.provideBlockVerificationService( + verificationConfig, metricsService, sessionFactory); + // then + Assertions.assertEquals(BlockVerificationServiceImpl.class, blockVerificationService.getClass()); + } + + @Test + void testProvideBlockVerificationService_disabled() throws IOException { + // given + VerificationConfig verificationConfig = new VerificationConfig(false, BlockVerificationSessionType.ASYNC, 32); + // when + BlockVerificationService blockVerificationService = VerificationInjectionModule.provideBlockVerificationService( + verificationConfig, metricsService, sessionFactory); + // then + Assertions.assertEquals(NoOpBlockVerificationService.class, blockVerificationService.getClass()); + } + + @Test + void testProvideExecutorService() { + // when + var executorService = VerificationInjectionModule.provideExecutorService(); + // then + Assertions.assertNotNull(executorService); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasherTest.java b/server/src/test/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasherTest.java new file mode 100644 index 00000000..c7715b08 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/hasher/ConcurrentStreamingTreeHasherTest.java @@ -0,0 +1,77 @@ +/* + * 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.verification.hasher; + +import static com.hedera.block.server.verification.hasher.ConcurrentStreamingTreeHasher.rootHashFrom; +import static com.hedera.block.server.verification.hasher.StreamingTreeHasher.HASH_LENGTH; +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.nio.ByteBuffer; +import java.util.SplittableRandom; +import java.util.concurrent.ForkJoinPool; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ConcurrentStreamingTreeHasherTest { + private static final SplittableRandom RANDOM = new SplittableRandom(); + + private final NaiveStreamingTreeHasher comparison = new NaiveStreamingTreeHasher(); + private final ConcurrentStreamingTreeHasher subject = new ConcurrentStreamingTreeHasher(ForkJoinPool.commonPool()); + + @ParameterizedTest + @ValueSource(ints = {0, 1, 3, 5, 32, 69, 100, 123, 234}) + void testAddLeafAndRootHash(final int numLeaves) { + ByteBuffer lastLeafHash = null; + var status = StreamingTreeHasher.Status.EMPTY; + for (int i = 1; i <= numLeaves; i++) { + final var hash = new byte[HASH_LENGTH]; + RANDOM.nextBytes(hash); + final var leafHash = ByteBuffer.wrap(hash); + subject.addLeaf(ByteBuffer.wrap(hash)); + comparison.addLeaf(ByteBuffer.wrap(hash)); + if (i == numLeaves - 1) { + status = subject.status(); + } else if (i == numLeaves) { + lastLeafHash = leafHash; + } + } + + final var actual = subject.rootHash().join(); + final var expected = comparison.rootHash().join(); + assertEquals(expected, actual); + if (lastLeafHash != null) { + requireNonNull(status); + final var recalculated = rootHashFrom(status, Bytes.wrap(lastLeafHash.array())); + assertEquals(expected, recalculated); + } + } + + @Test + void testAddLeafAfterRootHashRequested() { + final var leaf = ByteBuffer.allocate(48); + subject.addLeaf(leaf); + subject.rootHash(); + assertThrows(IllegalStateException.class, () -> subject.addLeaf(leaf)); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/service/NoOpBlockVerificationServiceTest.java b/server/src/test/java/com/hedera/block/server/verification/service/NoOpBlockVerificationServiceTest.java new file mode 100644 index 00000000..574180f6 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/service/NoOpBlockVerificationServiceTest.java @@ -0,0 +1,31 @@ +/* + * 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.verification.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NoOpBlockVerificationServiceTest { + + @Test + void onBlockItemsReceived() { + NoOpBlockVerificationService noOpBlockVerificationService = new NoOpBlockVerificationService(); + noOpBlockVerificationService.onBlockItemsReceived(null); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSessionTest.java b/server/src/test/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSessionTest.java new file mode 100644 index 00000000..94d6ac68 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/session/AbstractBlockVerificationSessionTest.java @@ -0,0 +1,227 @@ +/* + * 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.verification.session; + +import static com.hedera.block.common.utils.FileUtilities.readGzipFileUnsafe; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlockTime; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlocksError; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlocksFailed; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.VerificationBlocksVerified; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.BlockVerificationStatus; +import com.hedera.block.server.verification.VerificationResult; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.BlockItemUnparsed; +import com.hedera.hapi.block.BlockUnparsed; +import com.hedera.hapi.block.stream.output.BlockHeader; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.metrics.api.Counter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public abstract class AbstractBlockVerificationSessionTest { + + @Mock + protected MetricsService metricsService; + + @Mock + protected SignatureVerifier signatureVerifier; + + @Mock + protected Counter verificationBlocksVerified; + + @Mock + protected Counter verificationBlocksFailed; + + @Mock + protected Counter verificationBlockTime; + + @Mock + protected Counter verificationBlocksError; + + protected final Bytes hashing01BlockHash = Bytes.fromHex( + "006ae77f87ff57df598f4d6536dcb5c0a5c1f840c2fef817b2faebd554d32cfc9a4eaee1d873ed88de668b53b7839117"); + + @BeforeEach + void setUpBase() { + MockitoAnnotations.openMocks(this); + when(metricsService.get(VerificationBlocksVerified)).thenReturn(verificationBlocksVerified); + when(metricsService.get(VerificationBlocksFailed)).thenReturn(verificationBlocksFailed); + when(metricsService.get(VerificationBlockTime)).thenReturn(verificationBlockTime); + when(metricsService.get(VerificationBlocksError)).thenReturn(verificationBlocksError); + } + + protected abstract BlockVerificationSession createSession(BlockHeader blockHeader); + + protected List getTestBlock1Items() throws IOException, ParseException, URISyntaxException { + Path block01Path = + Path.of(getClass().getResource("/test-blocks/hashing-01.blk.gz").toURI()); + Bytes block01Bytes = Bytes.wrap(readGzipFileUnsafe(block01Path)); + BlockUnparsed blockUnparsed = BlockUnparsed.PROTOBUF.parse(block01Bytes); + return blockUnparsed.blockItems(); + } + + @Test + void testSuccessfulVerification() throws Exception { + // Given + List blockItems = getTestBlock1Items(); + BlockHeader blockHeader = + BlockHeader.PROTOBUF.parse(blockItems.getFirst().blockHeader()); + BlockVerificationSession session = createSession(blockHeader); + + when(signatureVerifier.verifySignature(any(Bytes.class), any(Bytes.class))) + .thenReturn(true); + + // When + session.appendBlockItems(blockItems); + CompletableFuture future = session.getVerificationResult(); + VerificationResult result = future.get(); + + // Then + assertEquals(BlockVerificationStatus.VERIFIED, result.status()); + assertEquals(1L, result.blockNumber()); + assertEquals(hashing01BlockHash, result.blockHash()); + assertFalse(session.isRunning()); + verify(verificationBlocksVerified, times(1)).increment(); + verify(verificationBlockTime, times(1)).add(any(Long.class)); + verifyNoMoreInteractions(verificationBlocksFailed); + } + + @Test + void testSuccessfulVerification_multipleAppends() throws Exception { + // Given + List blockItems = getTestBlock1Items(); + // Slice list into 2 parts of different sizes + List blockItems1 = blockItems.subList(0, 3); + List blockItems2 = blockItems.subList(3, blockItems.size()); + BlockHeader blockHeader = + BlockHeader.PROTOBUF.parse(blockItems.getFirst().blockHeader()); + BlockVerificationSession session = createSession(blockHeader); + when(signatureVerifier.verifySignature(any(Bytes.class), any(Bytes.class))) + .thenReturn(true); + + // When + session.appendBlockItems(blockItems1); + session.appendBlockItems(blockItems2); + CompletableFuture future = session.getVerificationResult(); + VerificationResult result = future.get(); + + // Then + assertEquals(BlockVerificationStatus.VERIFIED, result.status()); + assertEquals(1L, result.blockNumber()); + assertEquals(hashing01BlockHash, result.blockHash()); + assertFalse(session.isRunning()); + verify(verificationBlocksVerified, times(1)).increment(); + verify(verificationBlockTime, times(1)).add(any(Long.class)); + verifyNoMoreInteractions(verificationBlocksFailed); + } + + @Test + void testVerificationFailure() throws Exception { + // Given + List blockItems = getTestBlock1Items(); + final Bytes hashing01BlockHash = Bytes.fromHex( + "006ae77f87ff57df598f4d6536dcb5c0a5c1f840c2fef817b2faebd554d32cfc9a4eaee1d873ed88de668b53b7839117"); + BlockHeader blockHeader = + BlockHeader.PROTOBUF.parse(blockItems.getFirst().blockHeader()); + BlockVerificationSession session = createSession(blockHeader); + when(signatureVerifier.verifySignature(any(Bytes.class), any(Bytes.class))) + .thenReturn(false); + + // When + session.appendBlockItems(blockItems); + CompletableFuture future = session.getVerificationResult(); + VerificationResult result = future.get(); + + // Then + assertEquals(BlockVerificationStatus.SIGNATURE_INVALID, result.status()); + assertEquals(1L, result.blockNumber()); + assertEquals(hashing01BlockHash, result.blockHash()); + assertFalse(session.isRunning()); + verifyNoMoreInteractions(verificationBlocksVerified); + verify(verificationBlocksFailed, times(1)).increment(); + } + + @Test + void testAppendBlockItemsNotRunning() throws Exception { + // Given + List blockItems = getTestBlock1Items(); + BlockHeader blockHeader = + BlockHeader.PROTOBUF.parse(blockItems.getFirst().blockHeader()); + BlockVerificationSession session = createSession(blockHeader); + when(signatureVerifier.verifySignature(any(Bytes.class), any(Bytes.class))) + .thenReturn(true); + // send a whole block and wait for the result, the session should be completed. + session.appendBlockItems(blockItems); + CompletableFuture future = session.getVerificationResult(); + VerificationResult result = future.get(); + + // metrics should be 1 + verify(verificationBlocksVerified, times(1)).increment(); + verify(verificationBlockTime, times(1)).add(any(Long.class)); + + // When + // Try to append more items after the session has completed + session.appendBlockItems(blockItems); + + // Then + // counters should still be 1 + verify(verificationBlocksVerified, times(1)).increment(); + verify(verificationBlockTime, times(1)).add(any(Long.class)); + } + + @Test + void testParseException() + throws IOException, ParseException, URISyntaxException, ExecutionException, InterruptedException { + // Given + List blockItems = getTestBlock1Items(); + BlockHeader blockHeader = + BlockHeader.PROTOBUF.parse(blockItems.getFirst().blockHeader()); + blockItems.set( + blockItems.size() - 1, + BlockItemUnparsed.newBuilder().blockProof(Bytes.wrap("invalid")).build()); + BlockVerificationSession session = createSession(blockHeader); + + // When + session.appendBlockItems(blockItems); + CompletableFuture future = session.getVerificationResult(); + assertThrows(ExecutionException.class, future::get); + + // Then + assertTrue(future.isCompletedExceptionally()); + verify(verificationBlocksError, times(1)).increment(); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsyncTest.java b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsyncTest.java new file mode 100644 index 00000000..9d3717e3 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionAsyncTest.java @@ -0,0 +1,29 @@ +/* + * 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.verification.session; + +import com.hedera.hapi.block.stream.output.BlockHeader; +import java.util.concurrent.Executors; + +class BlockVerificationSessionAsyncTest extends AbstractBlockVerificationSessionTest { + + @Override + protected BlockVerificationSession createSession(BlockHeader blockHeader) { + return new BlockVerificationSessionAsync( + blockHeader, metricsService, signatureVerifier, Executors.newSingleThreadExecutor(), 32); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactoryTest.java b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactoryTest.java new file mode 100644 index 00000000..298c646a --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionFactoryTest.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.verification.session; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.hedera.block.server.metrics.MetricsService; +import com.hedera.block.server.verification.VerificationConfig; +import com.hedera.block.server.verification.signature.SignatureVerifier; +import com.hedera.hapi.block.stream.output.BlockHeader; +import java.util.concurrent.ExecutorService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class BlockVerificationSessionFactoryTest { + + @Mock + private MetricsService mockMetricsService; + + @Mock + private SignatureVerifier mockSignatureVerifier; + + @Mock + private ExecutorService mockExecutorService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + // Default configuration stubs + + } + + @Test + void createSession_whenSessionTypeIsAsync_returnsBlockVerificationSessionAsync() { + // Given + VerificationConfig config = new VerificationConfig(true, BlockVerificationSessionType.ASYNC, 32); + BlockHeader blockHeader = BlockHeader.newBuilder().number(1L).build(); + + BlockVerificationSessionFactory sessionFactory = new BlockVerificationSessionFactory( + config, mockMetricsService, mockSignatureVerifier, mockExecutorService); + + // When + var session = sessionFactory.createSession(blockHeader); + + // Then + assertNotNull(session, "Session should not be null"); + assertInstanceOf( + BlockVerificationSessionAsync.class, + session, + "Session should be an instance of BlockVerificationSessionAsync"); + } + + @Test + void createSession_whenSessionTypeIsSync_returnsBlockVerificationSessionSync() { + // Given + VerificationConfig config = new VerificationConfig(true, BlockVerificationSessionType.SYNC, 32); + BlockHeader blockHeader = BlockHeader.newBuilder().number(1L).build(); + + BlockVerificationSessionFactory sessionFactory = new BlockVerificationSessionFactory( + config, mockMetricsService, mockSignatureVerifier, mockExecutorService); + + // When + var session = sessionFactory.createSession(blockHeader); + + // Then + assertNotNull(session, "Session should not be null"); + assertInstanceOf( + BlockVerificationSessionSync.class, + session, + "Session should be an instance of BlockVerificationSessionSync"); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionSyncTest.java b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionSyncTest.java new file mode 100644 index 00000000..352a05a3 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/session/BlockVerificationSessionSyncTest.java @@ -0,0 +1,27 @@ +/* + * 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.verification.session; + +import com.hedera.hapi.block.stream.output.BlockHeader; + +class BlockVerificationSessionSyncTest extends AbstractBlockVerificationSessionTest { + + @Override + protected BlockVerificationSession createSession(BlockHeader blockHeader) { + return new BlockVerificationSessionSync(blockHeader, metricsService, signatureVerifier); + } +} diff --git a/server/src/test/java/com/hedera/block/server/verification/signature/SignatureVerifierDummyTest.java b/server/src/test/java/com/hedera/block/server/verification/signature/SignatureVerifierDummyTest.java new file mode 100644 index 00000000..3aa73c47 --- /dev/null +++ b/server/src/test/java/com/hedera/block/server/verification/signature/SignatureVerifierDummyTest.java @@ -0,0 +1,28 @@ +/* + * 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.verification.signature; + +import org.junit.jupiter.api.Test; + +class SignatureVerifierDummyTest { + + @Test + void testVerifySignature() { + SignatureVerifierDummy signatureVerifierDummy = new SignatureVerifierDummy(); + signatureVerifierDummy.verifySignature(null, null); + } +} diff --git a/server/src/test/resources/test-blocks/hashing-01.blk.gz b/server/src/test/resources/test-blocks/hashing-01.blk.gz new file mode 100644 index 00000000..750a050a Binary files /dev/null and b/server/src/test/resources/test-blocks/hashing-01.blk.gz differ diff --git a/simulator/src/main/resources/app.properties b/simulator/src/main/resources/app.properties index 03d1649b..3463dfa9 100644 --- a/simulator/src/main/resources/app.properties +++ b/simulator/src/main/resources/app.properties @@ -33,8 +33,8 @@ prometheus.endpointPortNumber=9998 #blockStream.maxBlockItemsToStream=100_000_000 #blockStream.streamingMode=MILLIS_PER_BLOCK -#blockStream.millisecondsPerBlock=500 -#blockStream.blockItemsBatchSize=1_000 +blockStream.millisecondsPerBlock=500 +blockStream.blockItemsBatchSize=1_000 #blockStream.delayBetweenBlockItems=3_000_000 # ----------------------------------------------