From 12e5ec8d3da4ef26e50c85c01dff061d0e30cc83 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Tue, 5 Mar 2024 14:47:28 +0100 Subject: [PATCH] Validator route pagination (#2101) * little improvements * add get_validators example with optional pagination * the example * fix example doc * update * python: add example * nodejs: add example * update year Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * rust: update example * python: update example * nodejs: update example * python: nit * docs * fix * single letter closures --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Co-authored-by: Thibault Martinez --- .../nodejs/examples/client/get-validators.ts | 42 +++++++++++++++++++ .../python/examples/client/get_validators.py | 24 +++++++++++ sdk/Cargo.toml | 5 +++ .../client/node_api_core/get_validators.rs | 40 ++++++++++++++++++ sdk/src/client/node_api/core/routes.rs | 6 +-- sdk/src/client/node_api/mod.rs | 4 +- sdk/src/types/api/core.rs | 22 +++++----- 7 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 bindings/nodejs/examples/client/get-validators.ts create mode 100644 bindings/python/examples/client/get_validators.py create mode 100644 sdk/examples/client/node_api_core/get_validators.rs diff --git a/bindings/nodejs/examples/client/get-validators.ts b/bindings/nodejs/examples/client/get-validators.ts new file mode 100644 index 0000000000..aab1570964 --- /dev/null +++ b/bindings/nodejs/examples/client/get-validators.ts @@ -0,0 +1,42 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Client, initLogger } from '@iota/sdk'; +require('dotenv').config({ path: '.env' }); + +// Run with command: +// yarn run-example ./client/get-validators.ts [PAGE_SIZE] [CURSOR] + +// This example returns the validators known by the node by querying the corresponding endpoint. +// You can provide a custom PAGE_SIZE and additionally a CURSOR from a previous request. +async function run() { + initLogger(); + for (const envVar of ['NODE_URL']) { + if (!(envVar in process.env)) { + throw new Error(`.env ${envVar} is undefined, see .env.example`); + } + } + + const client = await Client.create({ + // Insert your node URL in the .env. + nodes: [process.env.NODE_URL as string], + }); + + let pageSize = 1; + let cursor = ''; + if (process.argv.length > 1) { + pageSize = parseInt(process.argv[2]); + if (process.argv.length > 2) { + cursor = process.argv[3]; + } + } + + try { + const validators = await client.getValidators(pageSize, cursor); + console.log(validators); + } catch (error) { + console.error('Error: ', error); + } +} + +void run().then(() => process.exit()); diff --git a/bindings/python/examples/client/get_validators.py b/bindings/python/examples/client/get_validators.py new file mode 100644 index 0000000000..6a1a883197 --- /dev/null +++ b/bindings/python/examples/client/get_validators.py @@ -0,0 +1,24 @@ +import dataclasses +import json +import os +import sys + +from dotenv import load_dotenv +from iota_sdk import Client + +load_dotenv() + +node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') +page_size = 1 +cursor = "" + +if len(sys.argv) > 1: + page_size = int(sys.argv[1]) + if len(sys.argv) > 2: + cursor = sys.argv[2] + +# Create a Client instance +client = Client(nodes=[node_url]) + +validators = client.get_validators(page_size, cursor) +print(f'{json.dumps(dataclasses.asdict(validators), indent=4)}') diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 1bc2f68937..b5da33b071 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -485,6 +485,11 @@ name = "node_api_core_get_included_block_raw" path = "examples/client/node_api_core/16_get_included_block_raw.rs" required-features = ["client"] +[[example]] +name = "node_api_core_get_validators" +path = "examples/client/node_api_core/get_validators.rs" +required-features = ["client"] + # Node API indexer examples [[example]] diff --git a/sdk/examples/client/node_api_core/get_validators.rs b/sdk/examples/client/node_api_core/get_validators.rs new file mode 100644 index 0000000000..7467d4f149 --- /dev/null +++ b/sdk/examples/client/node_api_core/get_validators.rs @@ -0,0 +1,40 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! This example returns the validators known by the node by querying the corresponding endpoint. +//! You can provide a custom PAGE_SIZE and additionally a CURSOR from a previous request. +//! +//! Rename `.env.example` to `.env` first, then run the command: +//! ```sh +//! cargo run --release --all-features --example node_api_core_get_validators [PAGE_SIZE] [CURSOR] [NODE_URL] +//! ``` + +use iota_sdk::client::{Client, ClientError}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // If not provided we use the default node from the `.env` file. + dotenvy::dotenv().ok(); + + let page_size = std::env::args().nth(1).map(|s| s.parse::().unwrap()); + let cursor = std::env::args().nth(2); + + // Take the node URL from command line argument or use one from env as default. + let node_url = std::env::args() + .nth(3) + .unwrap_or_else(|| std::env::var("NODE_URL").expect("NODE_URL not set")); + + // Create a node client. + let client = Client::builder() + .with_node(&node_url)? + .with_ignore_node_health() + .finish() + .await?; + + // Get validators. + let validators = client.get_validators(page_size, cursor).await?; + + println!("{validators:#?}"); + + Ok(()) +} diff --git a/sdk/src/client/node_api/core/routes.rs b/sdk/src/client/node_api/core/routes.rs index 93309f2f99..798b73f61b 100644 --- a/sdk/src/client/node_api/core/routes.rs +++ b/sdk/src/client/node_api/core/routes.rs @@ -101,7 +101,7 @@ impl Client { ) -> Result { let bech32_address = account_id.to_bech32(self.get_bech32_hrp().await?); let path = &format!("api/core/v3/accounts/{bech32_address}/congestion"); - let query = query_tuples_to_query_string([work_score.into().map(|i| ("workScore", i.to_string()))]); + let query = query_tuples_to_query_string([work_score.into().map(|s| ("workScore", s.to_string()))]); self.get_request(path, query.as_deref(), false).await } @@ -138,8 +138,8 @@ impl Client { ) -> Result { const PATH: &str = "api/core/v3/validators"; let query = query_tuples_to_query_string([ - page_size.into().map(|i| ("pageSize", i.to_string())), - cursor.into().map(|i| ("cursor", i)), + page_size.into().map(|n| ("pageSize", n.to_string())), + cursor.into().map(|c| ("cursor", c)), ]); self.get_request(PATH, query.as_deref(), false).await diff --git a/sdk/src/client/node_api/mod.rs b/sdk/src/client/node_api/mod.rs index b88a53afae..6526a8a167 100644 --- a/sdk/src/client/node_api/mod.rs +++ b/sdk/src/client/node_api/mod.rs @@ -19,8 +19,8 @@ pub(crate) fn query_tuples_to_query_string( ) -> Option { let query = tuples .into_iter() - .filter_map(|tuple| tuple.map(|(key, value)| format!("{}={}", key, value))) + .filter_map(|tuple| tuple.map(|(key, value)| format!("{key}={value}"))) .collect::>(); - if query.is_empty() { None } else { Some(query.join("&")) } + (!query.is_empty()).then_some(query.join("&")) } diff --git a/sdk/src/types/api/core.rs b/sdk/src/types/api/core.rs index 2c3d49da62..a943e6ff9a 100644 --- a/sdk/src/types/api/core.rs +++ b/sdk/src/types/api/core.rs @@ -205,24 +205,24 @@ pub struct BaseTokenResponse { #[serde(rename_all = "camelCase")] pub struct ValidatorResponse { /// Account address of the validator. - address: Bech32Address, + pub address: Bech32Address, /// The epoch index until which the validator registered to stake. - staking_end_epoch: EpochIndex, + pub staking_end_epoch: EpochIndex, /// The total stake of the pool, including delegators. #[serde(with = "string")] - pool_stake: u64, + pub pool_stake: u64, /// The stake of a validator. #[serde(with = "string")] - validator_stake: u64, + pub validator_stake: u64, /// The fixed cost of the validator, which it receives as part of its Mana rewards. #[serde(with = "string")] - fixed_cost: u64, + pub fixed_cost: u64, /// Shows whether the validator was active recently. - active: bool, + pub active: bool, /// The latest protocol version the validator supported. - latest_supported_protocol_version: u8, + pub latest_supported_protocol_version: u8, /// The protocol hash of the latest supported protocol of the validator. - latest_supported_protocol_hash: ProtocolParametersHash, + pub latest_supported_protocol_hash: ProtocolParametersHash, } /// Response of GET /api/core/v3/blocks/validators. @@ -232,13 +232,13 @@ pub struct ValidatorResponse { #[serde(rename_all = "camelCase")] pub struct ValidatorsResponse { /// List of registered validators ready for the next epoch. - validators: Vec, + pub validators: Vec, /// The number of validators returned per one API request with pagination. - page_size: u32, + pub page_size: u32, /// The cursor that needs to be provided as cursor query parameter to request the next page. If empty, this was the /// last page. #[serde(default, skip_serializing_if = "Option::is_none")] - cursor: Option, + pub cursor: Option, } /// Response of GET /api/core/v3/rewards/{outputId}.