diff --git a/Cargo.lock b/Cargo.lock index f6c23cf3e..1aa4b5195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2626,6 +2626,7 @@ dependencies = [ "ethrex-core", "ethrex-dev", "ethrex-l2", + "ethrex-metrics", "ethrex-net", "ethrex-rlp", "ethrex-rpc", @@ -2651,6 +2652,7 @@ dependencies = [ "bytes", "cfg-if", "ethrex-core", + "ethrex-metrics", "ethrex-rlp", "ethrex-storage", "ethrex-vm", @@ -2762,6 +2764,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "ethrex-metrics" +version = "0.1.0" +dependencies = [ + "axum", + "ethrex-core", + "prometheus", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "ethrex-net" version = "0.1.0" @@ -5885,6 +5901,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.3", + "protobuf", + "thiserror 1.0.69", +] + [[package]] name = "proptest" version = "1.5.0" @@ -5928,6 +5959,12 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "puffin" version = "0.19.1" diff --git a/cmd/ethrex/Cargo.toml b/cmd/ethrex/Cargo.toml index d32c516c8..47e951aeb 100644 --- a/cmd/ethrex/Cargo.toml +++ b/cmd/ethrex/Cargo.toml @@ -34,6 +34,7 @@ redb = { workspace = true, optional = true } cfg-if = "1.0.0" ethrex-dev = { path = "../../crates/blockchain/dev", optional = true } +ethrex-metrics = { path = "../../crates/blockchain/metrics" } [[bin]] name = "ethrex" @@ -42,6 +43,7 @@ path = "./ethrex.rs" [features] default = ["dep:ethrex-storage", "libmdbx"] dev = ["dep:ethrex-dev"] +metrics = ["ethrex-blockchain/metrics"] libmdbx = ["dep:libmdbx", "ethrex-storage/libmdbx"] redb = ["dep:redb", "ethrex-storage/redb"] l2 = ["dep:ethrex-l2", "ethrex-vm/l2"] diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index bcab7d078..c71be7395 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -116,6 +116,12 @@ pub fn cli() -> Command { .required(false) .value_name("BLOCKS_DIR_PATH"), ) + .arg( + Arg::new("metrics.port") + .long("metrics.port") + .required(false) + .value_name("PROMETHEUS_METRICS_PORT"), + ) .subcommand( Command::new("removedb").about("Remove the database").arg( Arg::new("datadir") diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index c5eb55dd9..047c6491b 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -208,6 +208,17 @@ async fn main() { tracker.spawn(rpc_api); + // Check if the metrics.port is present, else set it to 0 + let metrics_port = matches + .get_one::("metrics.port") + .map_or("0".to_string(), |v| v.clone()); + + // Start the metrics_api with the given metrics.port if it's != 0 + if metrics_port != *"0" { + let metrics_api = ethrex_metrics::api::start_prometheus_metrics_api(metrics_port); + tracker.spawn(metrics_api); + } + // We do not want to start the networking module if the l2 feature is enabled. cfg_if::cfg_if! { if #[cfg(feature = "l2")] { diff --git a/cmd/ethrex_l2/src/commands/test.rs b/cmd/ethrex_l2/src/commands/test.rs index 3551fa423..f6ce4b65f 100644 --- a/cmd/ethrex_l2/src/commands/test.rs +++ b/cmd/ethrex_l2/src/commands/test.rs @@ -1,7 +1,7 @@ use crate::config::EthrexL2Config; use bytes::Bytes; use clap::Subcommand; -use ethereum_types::{Address, H160, H256, U256}; +use ethereum_types::{Address, H256, U256}; use ethrex_blockchain::constants::TX_GAS_COST; use ethrex_l2::utils::eth_client::{eth_sender::Overrides, EthClient}; use keccak_hash::keccak; @@ -72,11 +72,15 @@ async fn transfer_from( let client = EthClient::new(&cfg.network.l2_rpc_url); let private_key = SecretKey::from_slice(pk.parse::().unwrap().as_bytes()).unwrap(); - let mut buffer = [0u8; 32]; - let public_key = private_key.public_key(secp256k1::SECP256K1).serialize(); - buffer.copy_from_slice(&public_key[1..]); + let public_key = private_key + .public_key(secp256k1::SECP256K1) + .serialize_uncompressed(); + let hash = keccak(&public_key[1..]); - let address = H160::from(keccak(buffer)); + // Get the last 20 bytes of the hash + let address_bytes: [u8; 20] = hash.as_ref().get(12..32).unwrap().try_into().unwrap(); + + let address = Address::from(address_bytes); let nonce = client.get_nonce(address).await.unwrap(); let mut retries = 0; diff --git a/crates/blockchain/Cargo.toml b/crates/blockchain/Cargo.toml index 90aa8c6d1..d5e2a09c3 100644 --- a/crates/blockchain/Cargo.toml +++ b/crates/blockchain/Cargo.toml @@ -19,6 +19,8 @@ ethrex-vm = { path = "../vm", default-features = false } k256 = { version = "0.13.3", features = ["ecdh"] } +ethrex-metrics = { path = "./metrics", default-features = false } + [dev-dependencies] serde_json.workspace = true hex = "0.4.3" @@ -28,10 +30,7 @@ path = "./blockchain.rs" [features] default = ["c-kzg"] -libmdbx = [ - "ethrex-core/libmdbx", - "ethrex-storage/default", - "ethrex-vm/libmdbx", -] +libmdbx = ["ethrex-core/libmdbx", "ethrex-storage/default", "ethrex-vm/libmdbx"] levm = ["ethrex-vm/levm"] -c-kzg =["ethrex-core/c-kzg"] +c-kzg = ["ethrex-core/c-kzg"] +metrics = ["ethrex-metrics/api"] diff --git a/crates/blockchain/dev/Dockerfile b/crates/blockchain/dev/Dockerfile index 466580b4b..a39bde1d7 100644 --- a/crates/blockchain/dev/Dockerfile +++ b/crates/blockchain/dev/Dockerfile @@ -22,7 +22,7 @@ COPY --from=planner /ethrex/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN cargo build --release --features dev +RUN cargo build --release --features "dev,metrics" FROM ubuntu:24.04 WORKDIR /usr/local/bin diff --git a/crates/blockchain/dev/docker-compose-dev.yaml b/crates/blockchain/dev/docker-compose-dev.yaml index b95dce07c..b45aeed2f 100644 --- a/crates/blockchain/dev/docker-compose-dev.yaml +++ b/crates/blockchain/dev/docker-compose-dev.yaml @@ -1,3 +1,9 @@ +# If this dev container is run in the same machine as the L2 node +# we have to run this docker-compose with the .overrides file too +# example: docker compose -f docker-compose-dev.yaml -f docker-compose-metrics-l1.override.yaml up +include: + - ../metrics/docker-compose-metrics.yaml + services: ethrex: restart: always @@ -10,4 +16,4 @@ services: - 127.0.0.1:8545:8545 volumes: - ../../../test_data/genesis-l1.json:/genesis-l1.json - command: --network /genesis-l1.json --http.addr 0.0.0.0 --http.port 8545 + command: --network /genesis-l1.json --http.addr 0.0.0.0 --http.port 8545 --metrics.port 3701 diff --git a/crates/blockchain/metrics/Cargo.toml b/crates/blockchain/metrics/Cargo.toml new file mode 100644 index 000000000..df0267e30 --- /dev/null +++ b/crates/blockchain/metrics/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ethrex-metrics" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio.workspace = true +tracing.workspace = true +thiserror.workspace = true +serde_json.workspace = true +serde.workspace = true + +ethrex-core = { path = "../../common", default-features = false } + +prometheus = "0.13.4" + +# TODO: remove? +axum = "0.7.9" + + +[lib] +path = "./mod.rs" + +[features] +default = ["api"] +api = [] diff --git a/crates/blockchain/metrics/README.md b/crates/blockchain/metrics/README.md new file mode 100644 index 000000000..b58e5f6d5 --- /dev/null +++ b/crates/blockchain/metrics/README.md @@ -0,0 +1,37 @@ +# Metrics + +A `docker-compose` is used to bundle prometheus and grafana services, the `*overrides` files define the ports and mounts the prometheus' configuration file. +If a new dashboard is designed just for the L1 or L2, it can be mounted only in that `*overrides` file. + +To run the node with metrics, the next steps should be followed: +1. Build the `ethrex` binary with the `metrics` feature enabled. +2. Set the `--metrics.port` cli arg of the ethrex binary to match the port defined in `metrics/provisioning/prometheus/prometheus*.yaml` +3. Run the docker containers, example with the L2: + +```sh +docker compose -f docker-compose-metrics.yaml -f docker-compose-metrics-l2.override.yaml up +``` + +>[!NOTE] +> The L2's Makefile automatically starts the prometheus and grafana services with `make init`. For the L1 used in dev mode and the L2. + + +- For the L2 we use the following files in conjunction: + - `docker-compose-metrics.yaml` + - `docker-compose-metrics-l2.overrides.yaml` + - The defaults are: + - PORT `3702` → metrics API (used by prometheus) + - PORT `3802` → Grafana + - usr: `admin` + - pwd: `admin` + - PORT `9092` → Prometheus + +- For the L1 dev we use the following files in conjunction: + - `docker-compose-metrics.yaml` + - `docker-compose-metrics-l1-dev.overrides.yaml` + - The defaults are: + - PORT `3701` → metrics API (used by prometheus) + - PORT `3801` → Grafana + - usr: `admin` + - pwd: `admin` + - PORT `9091` → Prometheus diff --git a/crates/blockchain/metrics/api.rs b/crates/blockchain/metrics/api.rs new file mode 100644 index 000000000..8f8689827 --- /dev/null +++ b/crates/blockchain/metrics/api.rs @@ -0,0 +1,19 @@ +use axum::{routing::get, Router}; + +use crate::{metrics_transactions::METRICS_TX, MetricsApiError}; + +pub async fn start_prometheus_metrics_api(port: String) -> Result<(), MetricsApiError> { + let app = Router::new() + .route("/metrics", get(get_metrics)) + .route("/health", get("Service Up")); + + // Start the axum app + let listener = tokio::net::TcpListener::bind(&format!("0.0.0.0:{port}")).await?; + axum::serve(listener, app).await?; + + Ok(()) +} + +async fn get_metrics() -> String { + METRICS_TX.gather_metrics() +} diff --git a/crates/blockchain/metrics/docker-compose-metrics-l1-dev.overrides.yaml b/crates/blockchain/metrics/docker-compose-metrics-l1-dev.overrides.yaml new file mode 100644 index 000000000..1bdafe6b5 --- /dev/null +++ b/crates/blockchain/metrics/docker-compose-metrics-l1-dev.overrides.yaml @@ -0,0 +1,9 @@ +services: + prometheus: + volumes: + - ../metrics/provisioning/prometheus/prometheus_l1_dev.yaml:/etc/prometheus/prometheus.yaml + ports: + - "9091:9090" + grafana: + ports: + - "3801:3000" diff --git a/crates/blockchain/metrics/docker-compose-metrics-l2.overrides.yaml b/crates/blockchain/metrics/docker-compose-metrics-l2.overrides.yaml new file mode 100644 index 000000000..578142ec1 --- /dev/null +++ b/crates/blockchain/metrics/docker-compose-metrics-l2.overrides.yaml @@ -0,0 +1,9 @@ +services: + prometheus: + volumes: + - ../metrics/provisioning/prometheus/prometheus_l2.yaml:/etc/prometheus/prometheus.yaml + ports: + - "9092:9090" + grafana: + ports: + - "3802:3000" diff --git a/crates/blockchain/metrics/docker-compose-metrics.yaml b/crates/blockchain/metrics/docker-compose-metrics.yaml new file mode 100644 index 000000000..a7b3890cc --- /dev/null +++ b/crates/blockchain/metrics/docker-compose-metrics.yaml @@ -0,0 +1,15 @@ +# example: docker compose -f docker-compose-metrics.yaml -f docker-compose-metrics-l2.override.yaml up +services: + prometheus: + image: prom/prometheus + command: --config.file=/etc/prometheus/prometheus.yaml + #volumes: defined in the .overrides file + #ports: defined in the .overrides file + grafana: + image: grafana/grafana + volumes: + - ./provisioning/grafana_provisioning/dashboards:/etc/grafana/provisioning/dashboards + - ./provisioning/grafana_provisioning/datasources:/etc/grafana/provisioning/datasources + #ports: defined in the .overrides file + depends_on: + - prometheus diff --git a/crates/blockchain/metrics/metrics_transactions.rs b/crates/blockchain/metrics/metrics_transactions.rs new file mode 100644 index 000000000..1d2f97d60 --- /dev/null +++ b/crates/blockchain/metrics/metrics_transactions.rs @@ -0,0 +1,146 @@ +use ethrex_core::types::TxType; +use prometheus::{Encoder, IntCounter, IntCounterVec, Opts, Registry, TextEncoder}; +use std::sync::{Arc, LazyLock, Mutex}; + +pub static METRICS_TX: LazyLock = LazyLock::new(MetricsTx::default); + +pub struct MetricsTx { + pub transactions_tracker: Arc>, + pub transactions_total: Arc>, +} + +impl Default for MetricsTx { + fn default() -> Self { + Self::new() + } +} + +impl MetricsTx { + pub fn new() -> Self { + MetricsTx { + transactions_tracker: Arc::new(Mutex::new( + IntCounterVec::new( + Opts::new( + "transactions_tracker", + "Keeps track of all transactions depending on status and tx_type", + ), + &["status", "tx_type"], + ) + .unwrap(), + )), + transactions_total: Arc::new(Mutex::new( + IntCounter::new("transactions_total", "Keeps track of all transactions").unwrap(), + )), + } + } + + pub fn inc_tx_with_status_and_type(&self, status: MetricsTxStatus, tx_type: MetricsTxType) { + let txs = self.transactions_tracker.clone(); + + let txs_lock = match txs.lock() { + Ok(lock) => lock, + Err(e) => { + tracing::error!("Failed to lock mutex: {e}"); + return; + } + }; + + let txs_builder = + match txs_lock.get_metric_with_label_values(&[status.to_str(), tx_type.to_str()]) { + Ok(builder) => builder, + Err(e) => { + tracing::error!("Failed to build Metric: {e}"); + return; + } + }; + + txs_builder.inc(); + } + + pub fn inc_tx(&self) { + let txs = self.transactions_total.clone(); + + let txs_lock = match txs.lock() { + Ok(lock) => lock, + Err(e) => { + tracing::error!("Failed to lock mutex: {e}"); + return; + } + }; + + txs_lock.inc(); + } + + pub fn gather_metrics(&self) -> String { + let r = Registry::new(); + + let txs_tracker = self.transactions_tracker.clone(); + let txs_tracker_lock = match txs_tracker.lock() { + Ok(lock) => lock, + Err(e) => { + tracing::error!("Failed to lock transactions_tracker mutex: {e}"); + return String::new(); + } + }; + + let txs_lock = self.transactions_total.clone(); + let txs_lock = match txs_lock.lock() { + Ok(lock) => lock, + Err(e) => { + tracing::error!("Failed to lock transactions_total mutex: {e}"); + return String::new(); + } + }; + + if r.register(Box::new(txs_lock.clone())).is_err() { + tracing::error!("Failed to register metric"); + return String::new(); + } + if r.register(Box::new(txs_tracker_lock.clone())).is_err() { + tracing::error!("Failed to register metric"); + return String::new(); + } + + let encoder = TextEncoder::new(); + let metric_families = r.gather(); + + let mut buffer = Vec::new(); + if encoder.encode(&metric_families, &mut buffer).is_err() { + tracing::error!("Failed to encode metrics"); + return String::new(); + } + + String::from_utf8(buffer).unwrap_or_else(|e| { + tracing::error!("Failed to convert buffer to String: {e}"); + String::new() + }) + } +} + +pub enum MetricsTxStatus { + Failed, + Succeeded, +} + +impl MetricsTxStatus { + pub fn to_str(&self) -> &str { + match self { + MetricsTxStatus::Failed => "failed", + MetricsTxStatus::Succeeded => "succedded", + } + } +} + +pub struct MetricsTxType(pub TxType); + +impl MetricsTxType { + pub fn to_str(&self) -> &str { + match self.0 { + ethrex_core::types::TxType::Legacy => "Legacy", + ethrex_core::types::TxType::EIP2930 => "EIP2930", + ethrex_core::types::TxType::EIP1559 => "EIP1559", + ethrex_core::types::TxType::EIP4844 => "EIP4844", + ethrex_core::types::TxType::Privileged => "Privileged", + } + } +} diff --git a/crates/blockchain/metrics/mod.rs b/crates/blockchain/metrics/mod.rs new file mode 100644 index 000000000..ca78d93c7 --- /dev/null +++ b/crates/blockchain/metrics/mod.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "api")] +pub mod api; +#[cfg(feature = "api")] +pub mod metrics_transactions; + +/// A macro to conditionally enable metrics-related code. +/// +/// This macro wraps the provided code block with a `#[cfg(feature = "metrics")]` attribute. +/// The enclosed code will only be compiled and executed if the `metrics` feature is enabled in the +/// `Cargo.toml` file. +/// +/// ## Usage +/// +/// If the `metrics` feature is enabled, the code inside the macro will be executed. Otherwise, +/// it will be excluded from the compilation. This is useful for enabling/disabling metrics collection +/// code without cluttering the source with manual feature conditionals. +/// +/// ## In `Cargo.toml` +/// The `metrics` feature has to be set in the Cargo.toml of the crate we desire to take metrics from. +/// +/// To enable the `metrics` feature, add the following to your `Cargo.toml`: +/// ```toml +/// ethrex-metrics = { path = "./metrics", default-features = false } +/// [features] +/// metrics = ["ethrex-metrics/api"] +/// ``` +/// +/// In this way, when the `metrics` feature is enabled for that crate, the macro is triggered and the metrics_api is also used. +/// +/// Example In Code: +/// ```sh +/// use ethrex_metrics::metrics; +// #[cfg(feature = "metrics")] +// use ethrex_metrics::metrics_transactions::{METRICS_TX}; +/// +/// metrics!(METRICS_TX.inc()); +/// ``` +/// +/// If you build without the `metrics` feature, the code inside `metrics!` will not be compiled, nor will the Prometheus crate. +#[macro_export] +macro_rules! metrics { + ($($code:tt)*) => { + #[cfg(feature = "metrics")] + { + $($code)* + } + }; +} + +#[derive(Debug, thiserror::Error)] +pub enum MetricsApiError { + #[error("{0}")] + TcpError(#[from] std::io::Error), +} diff --git a/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/dashboard.yaml b/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/dashboard.yaml new file mode 100644 index 000000000..95021bafd --- /dev/null +++ b/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/dashboard.yaml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: "Custom Prometheus Dashboards" + orgId: 1 + folder: "" + type: file + disableDeletion: true + editable: true + options: + path: /etc/grafana/provisioning/dashboards/demo_dashboards diff --git a/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/demo_dashboards/dashboard.json b/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/demo_dashboards/dashboard.json new file mode 100644 index 000000000..7289519ff --- /dev/null +++ b/crates/blockchain/metrics/provisioning/grafana_provisioning/dashboards/demo_dashboards/dashboard.json @@ -0,0 +1,419 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "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": "prom-001" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "transactions_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Transactions", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "description": "Simple demo\n", + "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 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(transactions_total[$__rate_interval])", + "format": "table", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Transactions Per Second", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 9 + }, + "id": 3, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "transactions_tracker{status=\"succedded\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{tx_type}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Succeeded Transactions By Type", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 5, + "y": 9 + }, + "id": 5, + "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": "prom-001" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "transactions_tracker", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{tx_type}} || {{status}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Transactions", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prom-001" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 17, + "y": 9 + }, + "id": 4, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "transactions_tracker", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{tx_type}} || {{status}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Status of Transactions", + "type": "piechart" + } + ], + "preload": false, + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Ethrex - Transactions", + "uid": "ae6t2gxjoyx34c", + "version": 1, + "weekStart": "" +} diff --git a/crates/blockchain/metrics/provisioning/grafana_provisioning/datasources/datasource.yaml b/crates/blockchain/metrics/provisioning/grafana_provisioning/datasources/datasource.yaml new file mode 100644 index 000000000..7feaf24d0 --- /dev/null +++ b/crates/blockchain/metrics/provisioning/grafana_provisioning/datasources/datasource.yaml @@ -0,0 +1,13 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + uid: prom-001 + access: proxy + url: http://prometheus:9090 + isDefault: true + jsonData: + httpMethod: POST + readOnly: false + editable: true diff --git a/crates/blockchain/metrics/provisioning/prometheus/prometheus_l1_dev.yaml b/crates/blockchain/metrics/provisioning/prometheus/prometheus_l1_dev.yaml new file mode 100644 index 000000000..a699fd87d --- /dev/null +++ b/crates/blockchain/metrics/provisioning/prometheus/prometheus_l1_dev.yaml @@ -0,0 +1,8 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: "ethrex L1" + static_configs: + # Use the name defined in the docker-compose.yaml + - targets: ["ethrex:3701"] diff --git a/crates/blockchain/metrics/provisioning/prometheus/prometheus_l2.yaml b/crates/blockchain/metrics/provisioning/prometheus/prometheus_l2.yaml new file mode 100644 index 000000000..d391f0945 --- /dev/null +++ b/crates/blockchain/metrics/provisioning/prometheus/prometheus_l2.yaml @@ -0,0 +1,8 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: "ethrex L2" + static_configs: + # Use the name defined in the docker-compose.yaml + - targets: ["host.docker.internal:3702"] diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 01ba173c5..49ef4f88c 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -20,6 +20,11 @@ use ethrex_vm::{ }; use sha3::{Digest, Keccak256}; +use ethrex_metrics::metrics; + +#[cfg(feature = "metrics")] +use ethrex_metrics::metrics_transactions::{MetricsTxStatus, MetricsTxType, METRICS_TX}; + use crate::{ constants::{ GAS_LIMIT_BOUND_DIVISOR, GAS_PER_BLOB, MAX_BLOB_GAS_PER_BLOCK, MIN_GAS_LIMIT, @@ -323,6 +328,12 @@ pub fn fill_transactions(context: &mut PayloadBuildContext) -> Result<(), ChainE )?; continue; } + + // Increment the total transaction counter + // CHECK: do we want it here to count every processed transaction + // or we want it before the return? + metrics!(METRICS_TX.inc_tx()); + // Execute tx let receipt = match apply_transaction(&head_tx, context) { Ok(receipt) => { @@ -334,11 +345,20 @@ pub fn fill_transactions(context: &mut PayloadBuildContext) -> Result<(), ChainE .store() .ok_or(ChainError::StoreError(StoreError::MissingStore))?, )?; + + metrics!(METRICS_TX.inc_tx_with_status_and_type( + MetricsTxStatus::Succeeded, + MetricsTxType(head_tx.tx_type()) + )); receipt } // Ignore following txs from sender Err(e) => { debug!("Failed to execute transaction: {}, {e}", tx_hash); + metrics!(METRICS_TX.inc_tx_with_status_and_type( + MetricsTxStatus::Failed, + MetricsTxType(head_tx.tx_type()) + )); txs.pop(); continue; } diff --git a/crates/l2/.env.example b/crates/l2/.env.example index f2dcbe0d0..0912ba1c4 100644 --- a/crates/l2/.env.example +++ b/crates/l2/.env.example @@ -22,14 +22,14 @@ L1_WATCHER_L2_PROPOSER_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6 ENGINE_API_RPC_URL=http://localhost:8552 ENGINE_API_JWT_PATH=./jwt.hex PROVER_SERVER_LISTEN_IP=127.0.0.1 -PROVER_SERVER_LISTEN_PORT=3000 +PROVER_SERVER_LISTEN_PORT=3900 # Not the same account as the COMMITTER_L1 Account # The proposer is in charge of blob commitments. # The prover_server is in charge of verifying the zkProofs. PROVER_SERVER_VERIFIER_ADDRESS=0xE25583099BA105D9ec0A67f5Ae86D90e50036425 PROVER_SERVER_VERIFIER_PRIVATE_KEY=0x39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d PROVER_SERVER_DEV_MODE=true -PROVER_CLIENT_PROVER_SERVER_ENDPOINT=localhost:3000 +PROVER_CLIENT_PROVER_SERVER_ENDPOINT=localhost:3900 PROVER_CLIENT_INTERVAL_MS=5000 COMMITTER_ON_CHAIN_PROPOSER_ADDRESS=0xe9927d77c931f8648da4cc6751ef4e5e2ce74608 COMMITTER_L1_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 52365e321..657ec44da 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -11,7 +11,7 @@ help: ## ๐Ÿ“š Show help for each of the Makefile recipes init: init-local-l1 deploy-l1 init-l2 ## ๐Ÿš€ Initializes a localnet with Lambda ethrex client as both L1 and L2 -down: down-local-l1 down-l2 ## ๐Ÿ›‘ Shuts down the localnet +down: down-local-l1 down-l2 down-metrics## ๐Ÿ›‘ Shuts down the localnet clean: clean-contract-deps ## ๐Ÿงน Cleans the localnet @@ -39,6 +39,10 @@ ethrex_PATH=$(shell pwd)/../.. ethrex_BIN_PATH=$(ethrex_PATH)/target/release/ethrex ethrex_DEV_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain/dev/docker-compose-dev.yaml ethrex_L2_DOCKER_COMPOSE_PATH=./docker-compose-l2.yaml +ethrex_METRICS_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain/metrics/docker-compose-metrics.yaml +ethrex_METRICS_OVERRIDES_L1_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain/metrics/docker-compose-metrics-l1-dev.overrides.yaml +ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain/metrics/docker-compose-metrics-l2.overrides.yaml + ethrex_L2_CONTRACTS_PATH=./contracts L1_RPC_URL=http://localhost:8545 @@ -52,14 +56,17 @@ L1_AUTH_PORT=8551 # Used in the .env file. Ensure the same port is used for `ENGINE_API_RPC_URL`. L2_AUTH_PORT=8552 +# Matches the ports used by the blockchain/metrics dir +L2_PROMETHEUS_METRICS_PORT = 3702 + # Local L1 .PHONY: init-local-l1 init-l1 down-local-l1 restart-local-l1 rm-db-l1 init-local-l1: ## ๐Ÿš€ Initializes an L1 Lambda ethrex Client with Docker (Used with make init) - docker compose -f ${ethrex_DEV_DOCKER_COMPOSE_PATH} up -d + docker compose -f ${ethrex_DEV_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L1_DOCKER_COMPOSE_PATH} up -d init-l1: ## ๐Ÿš€ Initializes an L1 Lambda ethrex Client - cargo run --release --manifest-path ../../Cargo.toml --bin ethrex --features dev -- \ + cargo run --release --manifest-path ../../Cargo.toml --bin ethrex --features "dev" -- \ --network ${L1_GENESIS_FILE_PATH} \ --http.port ${L1_PORT} \ --http.addr 0.0.0.0 \ @@ -67,7 +74,7 @@ init-l1: ## ๐Ÿš€ Initializes an L1 Lambda ethrex Client --datadir ${ethrex_L1_DEV_LIBMDBX} down-local-l1: ## ๐Ÿ›‘ Shuts down the L1 Lambda ethrex Client - docker compose -f ${ethrex_DEV_DOCKER_COMPOSE_PATH} down + docker compose -f ${ethrex_DEV_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L1_DOCKER_COMPOSE_PATH} down docker compose -f docker-compose-l2.yaml down restart-local-l1: down-local-l1 init-local-l1 ## ๐Ÿ”„ Restarts the L1 Lambda ethrex Client @@ -90,13 +97,20 @@ deploy-l1: ## ๐Ÿ“œ Deploys the L1 contracts # L2 PHONY: init-l2 down-l2 restart-l2 init-prover rm-db-l2 -init-l2: ## ๐Ÿš€ Initializes an L2 Lambda ethrex Client - cargo run --release --manifest-path ../../Cargo.toml --bin ethrex --features l2 -- \ +init-l2: init-metrics ## ๐Ÿš€ Initializes an L2 Lambda ethrex Client + cargo run --release --manifest-path ../../Cargo.toml --bin ethrex --features "l2,metrics" -- \ --network ${L2_GENESIS_FILE_PATH} \ --http.port ${L2_PORT} \ --http.addr 0.0.0.0 \ --authrpc.port ${L2_AUTH_PORT} \ + --metrics.port ${L2_PROMETHEUS_METRICS_PORT} \ --datadir ${ethrex_L2_DEV_LIBMDBX} + +init-metrics: ## ๐Ÿš€ Initializes Grafana and Prometheus with containers + docker compose -f ${ethrex_METRICS_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH} up -d + +down-metrics: ## ๐Ÿ›‘ Shuts down the metrics' containers + docker compose -f ${ethrex_METRICS_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH} down down-l2: ## ๐Ÿ›‘ Shuts down the L2 Lambda ethrex Client pkill -f ethrex || exit 0 diff --git a/crates/l2/docker-compose-l2.yaml b/crates/l2/docker-compose-l2.yaml index 1f48484ec..14761acfb 100644 --- a/crates/l2/docker-compose-l2.yaml +++ b/crates/l2/docker-compose-l2.yaml @@ -32,8 +32,10 @@ services: context: ../../ dockerfile: ./crates/l2/Dockerfile ports: + # RPC - 127.0.0.1:1729:1729 - - 3000:3000 + # Proposer + - 3900:3900 environment: - ETH_RPC_URL=http://ethrex_l1:8545 - ENV_FILE=/.env