Skip to content

feat(staking-cli): add OpenMetrics support and fix flaky tests#3926

Merged
sveitser merged 2 commits intomainfrom
ma/staking-cli-metadata-openmetrics
Feb 3, 2026
Merged

feat(staking-cli): add OpenMetrics support and fix flaky tests#3926
sveitser merged 2 commits intomainfrom
ma/staking-cli-metadata-openmetrics

Conversation

@sveitser
Copy link
Collaborator

@sveitser sveitser commented Feb 3, 2026

Add support for fetching validator metadata from sequencer node's /status/metrics endpoint (OpenMetrics format) as an alternative to hosting a separate JSON metadata file.

Changes:

  • Add --node-url CLI option for fetching metadata from node metrics
  • Add OpenMetrics parsing (prometheus-parse crate)
  • Auto-detect JSON vs OpenMetrics format via Content-Type header
  • Add serve_on_random_port() helper to fix flaky tests caused by race conditions with portpicker

The OpenMetrics parsing code is copied from staking-ui-service and marked with TODO comments for future consolidation into a shared crate.

Manually tested the ignored tests against our mainnet node.

Add support for fetching validator metadata from sequencer node's
/status/metrics endpoint (OpenMetrics format) as an alternative to
hosting a separate JSON metadata file.

Changes:
- Add --node-url CLI option for fetching metadata from node metrics
- Add OpenMetrics parsing (prometheus-parse crate)
- Auto-detect JSON vs OpenMetrics format via Content-Type header
- Add serve_on_random_port() helper to fix flaky tests caused by
  race conditions with portpicker

The OpenMetrics parsing code is copied from staking-ui-service and
marked with TODO comments for future consolidation into a shared crate.
@sveitser
Copy link
Collaborator Author

sveitser commented Feb 3, 2026

/gemini review

@gemini-code-assist
Copy link

Summary of Changes

Hello @sveitser, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the staking-cli's ability to handle validator metadata by integrating support for the OpenMetrics format. This allows validators to serve their metadata directly from their sequencer node's metrics endpoint, simplifying the setup process. The changes also include intelligent content-type detection for metadata and crucial fixes to improve test reliability by preventing port-related race conditions.

Highlights

  • OpenMetrics Support: The staking-cli now supports fetching validator metadata from sequencer nodes' /status/metrics endpoints, utilizing the OpenMetrics format as an alternative to traditional JSON files.
  • New CLI Option --node-url: A new command-line option --node-url has been introduced, allowing users to specify a node's URL from which metadata will be automatically fetched and parsed.
  • Automatic Metadata Format Detection: The CLI can now automatically detect whether the fetched metadata is in JSON or OpenMetrics format by inspecting the Content-Type HTTP header.
  • Flaky Test Fixes: A new helper function serve_on_random_port() was added to address and fix flaky tests that were caused by race conditions when selecting network ports.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Cargo.lock
    • Added prometheus-parse dependency.
  • Cargo.toml
    • Adjusted the order of bytes and bon dependencies.
  • crates/cliquenet/Cargo.toml
    • Reordered bon and bytes dependencies.
    • Moved hotshot-types dependency to be optional.
  • staking-cli/Cargo.toml
    • Added prometheus-parse dependency.
  • staking-cli/README.md
    • Updated documentation to introduce the --node-url option for fetching metadata from node metrics.
    • Provided examples for registering and updating metadata using --node-url.
    • Detailed the Prometheus metrics used for metadata extraction.
    • Clarified the use of --metadata-uri for custom JSON files and noted the mutual exclusivity of --node-url and --metadata-uri.
  • staking-cli/src/claim.rs
    • Removed an unused warp::Filter import.
    • Refactored test code to use the new setup_reward_claim_not_found_mock() helper, improving test reliability.
  • staking-cli/src/deploy.rs
    • Added tokio::net::TcpListener import.
    • Introduced serve_on_random_port() helper function to reliably bind a warp server to an available port, mitigating race conditions in tests.
    • Updated setup_reward_claim_mock() and setup_reward_claim_not_found_mock() to be async and utilize serve_on_random_port().
  • staking-cli/src/lib.rs
    • Made fetch_metadata public for integration tests.
    • Introduced new modules metadata_types and openmetrics.
    • Modified MetadataUriArgs to include a node_url field and added a new MetadataSource enum to abstract different metadata origins.
    • Updated TryFrom<MetadataUriArgs> implementations to support the new MetadataSource enum.
  • staking-cli/src/metadata.rs
    • Refactored metadata handling by moving schema types to metadata_types.rs.
    • Introduced fetch_metadata() to automatically detect and parse JSON or OpenMetrics based on Content-Type.
    • Updated validate_metadata_uri() to use the new fetch_metadata().
    • Removed a redundant url() method from MetadataUri.
    • Updated validation tests to use serve_on_random_port() and added new tests for OpenMetrics parsing and HTTP redirect handling.
  • staking-cli/src/metadata_types.rs
    • New file: Defines NodeMetadataContent, ImageSet, and RatioSet structs, which represent the structure of validator metadata. These types were moved from staking-cli/src/metadata.rs.
  • staking-cli/src/openmetrics.rs
    • New file: Implements parse_openmetrics() for extracting validator metadata from Prometheus/OpenMetrics formatted text.
    • Includes detailed logic for parsing various metrics like consensus_node, consensus_node_identity_general, consensus_version, and consensus_node_identity_icon.
    • Contains comprehensive unit tests for parsing different OpenMetrics scenarios, including minimal, complete, and malformed inputs.
  • staking-cli/tests/cli.rs
    • Imported fetch_metadata and Url.
    • Updated start_metadata_server() to use deploy::serve_on_random_port().
    • Introduced MetadataFormat enum and start_metadata_server_format() to enable parameterized testing for both JSON and OpenMetrics metadata sources.
    • Converted test_cli_register_validator_metadata_validation_success and test_cli_register_validator_metadata_validation_wrong_pub_key to use rstest for format parametrization.
    • Added an ignored integration test test_real_mainnet_node_metadata to validate against a live mainnet node.
    • Updated test_cli_claim_rewards and test_cli_unclaimed_rewards to correctly call the async version of setup_reward_claim_not_found_mock().
    • Converted test_cli_update_metadata_uri_validation_success and test_cli_update_metadata_uri_validation_wrong_pub_key to use rstest for format parametrization.
Activity
  • The pull request introduces new features and fixes, indicating active development.
  • The author, sveitser, has manually tested ignored tests against the mainnet node, ensuring real-world compatibility.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for fetching validator metadata from an OpenMetrics endpoint, which is a great alternative to hosting a separate JSON file. The implementation is solid, with auto-detection of the metadata format based on the Content-Type header. The addition of the serve_on_random_port test helper is a significant improvement for test reliability, effectively addressing flaky tests caused by port allocation race conditions. The code is well-structured, and the new functionality is thoroughly tested with unit and integration tests, including a parameterized test suite for both JSON and OpenMetrics formats. I've included a couple of suggestions to make parts of the new OpenMetrics parsing code more idiomatic and maintainable. Overall, this is an excellent contribution.

Comment on lines +47 to +66
if let Some(v) = sample.labels.get("name") {
if !v.is_empty() {
name = Some(v.to_string());
}
}
if let Some(v) = sample.labels.get("description") {
if !v.is_empty() {
description = Some(v.to_string());
}
}
if let Some(v) = sample.labels.get("company_name") {
if !v.is_empty() {
company_name = Some(v.to_string());
}
}
if let Some(v) = sample.labels.get("company_website") {
if !v.is_empty() {
company_website = Url::parse(v).ok();
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for extracting optional labels is repeated for multiple fields. This can be made more concise and idiomatic by using Option::filter to check for non-empty values. This improves readability and reduces code duplication.

                if let Some(v) = sample.labels.get("name").filter(|v| !v.is_empty()) {
                    name = Some(v.to_string());
                }
                if let Some(v) = sample.labels.get("description").filter(|v| !v.is_empty()) {
                    description = Some(v.to_string());
                }
                if let Some(v) = sample.labels.get("company_name").filter(|v| !v.is_empty()) {
                    company_name = Some(v.to_string());
                }
                if let Some(v) = sample.labels.get("company_website").filter(|v| !v.is_empty()) {
                    company_website = Url::parse(v).ok();
                }

Comment on lines +76 to +80
for (label, value) in sample.labels.iter() {
if !value.is_empty() {
icon_urls.insert(label.to_string(), value.to_string());
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The loop for collecting icon URLs can be expressed more concisely and idiomatically using iterators and the extend method. This improves readability and aligns better with functional programming patterns in Rust.

                icon_urls.extend(
                    sample.labels
                        .iter()
                        .filter(|(_, value)| !value.is_empty())
                        .map(|(label, value)| (label.to_string(), value.to_string())),
                );

bs58 = "0.5.1"
bytes = { workspace = true }
ed25519-compact = "2.2.0"
# optional:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cargo sort

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for fetching validator metadata from an OpenMetrics endpoint, which is a great alternative to hosting a separate JSON file. The implementation is solid, with auto-detection of the metadata format based on the Content-Type header and good test coverage for the new functionality. I particularly appreciate the addition of serve_on_random_port to fix flaky tests by avoiding race conditions with portpicker; this is a robust solution. The code duplication from staking-ui-service is clearly marked with TODOs for future consolidation, which is a good practice. Overall, this is a high-quality contribution that improves both functionality and test stability.

}
if let Some(v) = sample.labels.get("company_website") {
if !v.is_empty() {
company_website = Url::parse(v).ok();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with how icon URLs are handled, it would be beneficial to also log a warning when a company_website URL is malformed. This would help diagnose configuration issues. Currently, an invalid URL is silently ignored.

                        company_website = Url::parse(v)
                            .inspect_err(|e| {
                                tracing::warn!("skipping malformed company_website URL: {e}");
                            })
                            .ok();

@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2026

Created new PR to fix cargo audit.

PR: #3924

Please merge that PR first to fix cargo-audit.

@sveitser sveitser enabled auto-merge (squash) February 3, 2026 21:38
@sveitser sveitser requested a review from Ayiga February 3, 2026 22:05
Copy link
Member

@jbearer jbearer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality looks good, but I don't understand the need for some of the added complexity with the additional --node-url option and the MetadataSource enum.


/// URL of the node's API. Metadata will be fetched from /status/metrics.
#[clap(long, env = "NODE_URL")]
pub node_url: Option<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand the point of this option. Whenever I would do --node-url URL couldn't I just as easily do --metadata-uri URL/status/metrics?

Comment on lines +282 to +293
match self {
MetadataSource::Uri(url) => Some(url.clone()),
MetadataSource::NodeUrl(url) => match url.join("status/metrics") {
Ok(metrics_url) => Some(metrics_url),
Err(e) => {
tracing::warn!("failed to construct metrics URL from {url}: {e}");
None
},
},
MetadataSource::None => None,
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this seems to duplicate a lot of the logic from just above. Perhaps could be implemented as self.metadata_uri().inspect_err(|e| tracing::warn!("failed to construct metrics URL from {url}: {e})).ok()

@sveitser sveitser merged commit 787ae9d into main Feb 3, 2026
122 of 123 checks passed
@sveitser sveitser deleted the ma/staking-cli-metadata-openmetrics branch February 3, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants