Feature/chain status api (#5539)

* nym-api endpoint to return latest block information

* attached chain health to health query

* fixed serde casing

* one of the most nastiest work arounds in test code
This commit is contained in:
Jędrzej Stuczyński
2025-03-03 10:47:40 +00:00
committed by GitHub
parent 128f69a5d6
commit 4f7124e661
15 changed files with 517 additions and 21 deletions
Generated
+2
View File
@@ -4814,6 +4814,7 @@ dependencies = [
"sha2 0.9.9",
"sqlx",
"tempfile",
"tendermint 0.40.1",
"thiserror 2.0.11",
"time",
"tokio",
@@ -4838,6 +4839,7 @@ dependencies = [
"cosmwasm-std",
"ecdsa",
"getset",
"humantime-serde",
"nym-compact-ecash",
"nym-config",
"nym-contracts-common",
@@ -28,7 +28,6 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::time::SystemTime;
use tendermint_rpc::endpoint::block::Response as BlockResponse;
use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
@@ -63,6 +62,7 @@ pub use cw3;
pub use cw4;
pub use cw_controllers;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
+1
View File
@@ -46,6 +46,7 @@ tokio-stream = { workspace = true }
tokio-util = { workspace = true }
url = { workspace = true }
tendermint = { workspace = true }
ts-rs = { workspace = true, optional = true }
anyhow = { workspace = true }
+1
View File
@@ -13,6 +13,7 @@ cosmwasm-std = { workspace = true }
getset = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"] }
humantime-serde = { workspace = true }
serde_json = { workspace = true }
sha2.workspace = true
tendermint = { workspace = true }
+16
View File
@@ -1215,6 +1215,7 @@ impl From<Wireguard> for WireguardDetails {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct ApiHealthResponse {
pub status: ApiStatus,
pub chain_status: ChainStatus,
pub uptime: u64,
}
@@ -1224,10 +1225,25 @@ pub enum ApiStatus {
Up,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ChainStatus {
Synced,
Unknown,
Stalled {
#[serde(
serialize_with = "humantime_serde::serialize",
deserialize_with = "humantime_serde::deserialize"
)]
approximate_amount: Duration,
},
}
impl ApiHealthResponse {
pub fn new_healthy(uptime: Duration) -> Self {
ApiHealthResponse {
status: ApiStatus::Up,
chain_status: ChainStatus::Synced,
uptime: uptime.as_secs(),
}
}
+26 -8
View File
@@ -14,7 +14,8 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::ApiStatusState;
use crate::support::caching::cache::SharedCache;
use crate::support::config;
use crate::support::http::state::{AppState, ForcedRefresh};
use crate::support::http::state::{AppState, ChainStatusCache, ForcedRefresh};
use crate::support::nyxd::Client;
use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use axum::Router;
@@ -46,7 +47,7 @@ use nym_coconut_dkg_common::types::{
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_compact_ecash::BlindedSignature;
use nym_compact_ecash::{ttp_keygen, VerificationKeyAuth};
use nym_config::defaults::NymNetworkDetails;
use nym_config::defaults::{NymNetworkDetails, ValidatorDetails};
use nym_contracts_common::IdentityKey;
use nym_credentials::IssuanceTicketBook;
use nym_credentials_interface::TicketType;
@@ -70,6 +71,7 @@ use std::ops::Deref;
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tempfile::{tempdir, TempDir};
use time::Date;
use tokio::sync::RwLock;
@@ -1264,8 +1266,14 @@ struct TestFixture {
}
impl TestFixture {
fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState {
fn build_app_state(
storage: NymApiStorage,
ecash_state: EcashState,
nyxd_client: Client,
) -> AppState {
AppState {
nyxd_client,
chain_status_cache: ChainStatusCache::new(Duration::from_secs(42)),
forced_refresh: ForcedRefresh::new(true),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
@@ -1337,12 +1345,22 @@ impl TestFixture {
TaskClient::dummy(),
);
// ideally this would have been generic, but that's way too much work
// since then `AppState` would have had to be made generic
// also, this is such a disgusting workaround to make it 'work'. yuck
let mut dummy = NymNetworkDetails::new_empty();
dummy.endpoints = vec![ValidatorDetails::new(
"http://127.0.0.1:26657",
Some("http://why-do-we-even-need-api-url-set-here.wtf"),
None,
)];
dummy.export_to_env();
let another_fake_nyxd_client = Client::new(&config).unwrap();
TestFixture {
axum: TestServer::new(
Router::new()
.nest("/v1/ecash", ecash_routes())
.with_state(Self::build_app_state(storage.clone(), ecash_state)),
)
axum: TestServer::new(Router::new().nest("/v1/ecash", ecash_routes()).with_state(
Self::build_app_state(storage.clone(), ecash_state, another_fake_nyxd_client),
))
.unwrap(),
storage,
chain_state,
+27 -2
View File
@@ -1,9 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::{ContractInformation, NetworkDetails};
use crate::network::models::{ChainStatusResponse, ContractInformation, NetworkDetails};
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::{extract, Router};
use axum::extract::State;
use axum::{extract, Json, Router};
use nym_contracts_common::ContractBuildInformation;
use std::collections::HashMap;
use tower_http::compression::CompressionLayer;
@@ -12,6 +14,7 @@ use utoipa::ToSchema;
pub(crate) fn nym_network_routes() -> Router<AppState> {
Router::new()
.route("/details", axum::routing::get(network_details))
.route("/chain-status", axum::routing::get(chain_status))
.route("/nym-contracts", axum::routing::get(nym_contracts))
.route(
"/nym-contracts-detailed",
@@ -34,6 +37,28 @@ async fn network_details(
state.network_details().to_owned().into()
}
#[utoipa::path(
tag = "network",
get,
path = "/v1/network/chain-status",
responses(
(status = 200, body = ChainStatusResponse)
)
)]
async fn chain_status(State(state): State<AppState>) -> AxumResult<Json<ChainStatusResponse>> {
let chain_status = state
.chain_status_cache
.get_or_refresh(&state.nyxd_client)
.await?;
let connected_nyxd = state.network_details.connected_nyxd;
Ok(Json(ChainStatusResponse {
connected_nyxd,
status: chain_status,
}))
}
// it's used for schema generation so dead_code is fine
#[allow(dead_code)]
#[derive(ToSchema)]
+291
View File
@@ -1,7 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::tendermint_types::{AbciInfo, BlockHeader, BlockId};
use nym_config::defaults::NymNetworkDetails;
use nym_validator_client::nyxd::BlockResponse;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -27,3 +29,292 @@ pub struct ContractInformation<T> {
pub(crate) address: Option<String>,
pub(crate) details: Option<T>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatusResponse {
pub connected_nyxd: String,
pub status: ChainStatus,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatus {
pub abci: AbciInfo,
pub latest_block: BlockInfo,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockInfo {
pub block_id: BlockId,
pub block: FullBlockInfo,
// if necessary we might put block data here later too
}
impl From<BlockResponse> for BlockInfo {
fn from(value: BlockResponse) -> Self {
BlockInfo {
block_id: value.block_id.into(),
block: FullBlockInfo {
header: value.block.header.into(),
},
}
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct FullBlockInfo {
pub header: BlockHeader,
}
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
pub mod tendermint_types {
use nym_validator_client::nyxd::Hash;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tendermint::abci::response::Info;
use tendermint::block;
use tendermint::block::header::Version;
use utoipa::ToSchema;
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct AbciInfo {
/// Some arbitrary information.
pub data: String,
/// The application software semantic version.
pub version: String,
/// The application protocol version.
pub app_version: u64,
/// The latest block for which the app has called [`Commit`].
pub last_block_height: u64,
/// The latest result of [`Commit`].
pub last_block_app_hash: String,
}
impl From<Info> for AbciInfo {
fn from(value: Info) -> Self {
AbciInfo {
data: value.data,
version: value.version,
app_version: value.app_version,
last_block_height: value.last_block_height.value(),
last_block_app_hash: value.last_block_app_hash.to_string(),
}
}
}
/// `Version` contains the protocol version for the blockchain and the
/// application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
)]
pub struct HeaderVersion {
/// Block version
pub block: u64,
/// App version
pub app: u64,
}
impl From<tendermint::block::header::Version> for HeaderVersion {
fn from(value: Version) -> Self {
HeaderVersion {
block: value.block,
app: value.app,
}
}
}
/// Block identifiers which contain two distinct Merkle roots of the block,
/// as well as the number of parts in the block.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
///
/// Default implementation is an empty Id as defined by the Go implementation in
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
///
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
#[derive(
Serialize,
Deserialize,
Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
JsonSchema,
ToSchema,
)]
pub struct BlockId {
/// The block's main hash is the Merkle root of all the fields in the
/// block header.
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
/// Parts header (if available) is used for secure gossipping of the block
/// during consensus. It is the Merkle root of the complete serialized block
/// cut into parts.
///
/// PartSet is used to split a byteslice of data into parts (pieces) for
/// transmission. By splitting data into smaller parts and computing a
/// Merkle root hash on the list, you can verify that a part is
/// legitimately part of the complete data, and the part can be forwarded
/// to other peers before all the parts are known. In short, it's a fast
/// way to propagate a large file over a gossip network.
///
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
///
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
/// annotations. This does not translate to Rust, but we can indicate this
/// in the domain type.
pub part_set_header: PartSetHeader,
}
impl From<block::Id> for BlockId {
fn from(value: block::Id) -> Self {
BlockId {
hash: value.hash,
part_set_header: value.part_set_header.into(),
}
}
}
/// Block parts header
#[derive(
Clone,
Copy,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
Deserialize,
Serialize,
JsonSchema,
ToSchema,
)]
#[non_exhaustive]
pub struct PartSetHeader {
/// Number of parts in this block
pub total: u32,
/// Hash of the parts set header,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
}
impl From<tendermint::block::parts::Header> for PartSetHeader {
fn from(value: block::parts::Header) -> Self {
PartSetHeader {
total: value.total,
hash: value.hash,
}
}
}
/// Block `Header` values contain metadata about the block and about the
/// consensus, as well as commitments to the data in the current block, the
/// previous block, and the results returned by the application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockHeader {
/// Header version
pub version: HeaderVersion,
/// Chain ID
pub chain_id: String,
/// Current block height
pub height: u64,
/// Current timestamp
#[schemars(with = "String")]
#[schema(value_type = String)]
pub time: tendermint::Time,
/// Previous block info
pub last_block_id: Option<BlockId>,
/// Commit from validators from the last block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_commit_hash: Option<Hash>,
/// Merkle root of transaction hashes
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub data_hash: Option<Hash>,
/// Validators for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub validators_hash: Hash,
/// Validators for the next block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub next_validators_hash: Hash,
/// Consensus params for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub consensus_hash: Hash,
/// State after txs from the previous block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub app_hash: Hash,
/// Root hash of all results from the txs from the previous block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_results_hash: Option<Hash>,
/// Hash of evidence included in the block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub evidence_hash: Option<Hash>,
/// Original proposer of the block
#[serde(with = "nym_serde_helpers::hex")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub proposer_address: Vec<u8>,
}
impl From<block::Header> for BlockHeader {
fn from(value: block::Header) -> Self {
BlockHeader {
version: value.version.into(),
chain_id: value.chain_id.to_string(),
height: value.height.value(),
time: value.time,
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash,
data_hash: value.data_hash,
validators_hash: value.validators_hash,
next_validators_hash: value.next_validators_hash,
consensus_hash: value.consensus_hash,
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
last_results_hash: value.last_results_hash,
evidence_hash: value.evidence_hash,
proposer_address: value.proposer_address.as_bytes().to_vec(),
}
}
}
}
+10
View File
@@ -14,6 +14,7 @@ use nym_contracts_common::NaiveFloat;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{IdentityKey, NodeId};
use nym_serde_helpers::date::DATE_FORMAT;
use nym_validator_client::nyxd::error::NyxdError;
use reqwest::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -452,6 +453,15 @@ impl From<RedemptionError> for AxumErrorResponse {
}
}
impl From<NyxdError> for AxumErrorResponse {
fn from(value: NyxdError) -> Self {
Self {
message: RequestError::new(value.to_string()),
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
#[derive(Debug, Error)]
pub enum NymApiStorageError {
#[error("could not find status report associated with mixnode {mix_id}")]
+33 -4
View File
@@ -7,9 +7,13 @@ use crate::support::http::state::AppState;
use axum::extract::State;
use axum::Json;
use axum::Router;
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
use nym_api_requests::models::{
ApiHealthResponse, ApiStatus, ChainStatus, SignerInformationResponse,
};
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_compact_ecash::Base58;
use std::time::Duration;
use time::OffsetDateTime;
pub(crate) fn api_status_routes() -> Router<AppState> {
Router::new()
@@ -29,9 +33,34 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
(status = 200, body = ApiHealthResponse)
)
)]
async fn health(State(state): State<ApiStatusState>) -> Json<ApiHealthResponse> {
let uptime = state.startup_time.elapsed();
let health = ApiHealthResponse::new_healthy(uptime);
async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60);
let uptime = state.api_status.startup_time.elapsed();
let chain_status = match state
.chain_status_cache
.get_or_refresh(&state.nyxd_client)
.await
{
Ok(res) => {
let now = OffsetDateTime::now_utc();
let block_time: OffsetDateTime = res.latest_block.block.header.time.into();
let diff = now - block_time;
if diff > CHAIN_STALL_THRESHOLD {
ChainStatus::Stalled {
approximate_amount: diff.unsigned_abs(),
}
} else {
ChainStatus::Synced
}
}
Err(_) => ChainStatus::Unknown,
};
let health = ApiHealthResponse {
status: ApiStatus::Up,
chain_status,
uptime: uptime.as_secs(),
};
Json(health)
}
+4 -2
View File
@@ -19,9 +19,9 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::{ApiStatusState, SignerState};
use crate::support::caching::cache::SharedCache;
use crate::support::config::helpers::try_load_current_config;
use crate::support::config::Config;
use crate::support::config::{Config, DEFAULT_CHAIN_STATUS_CACHE_TTL};
use crate::support::http::state::{
AppState, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
AppState, ChainStatusCache, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
};
use crate::support::http::RouterBuilder;
use crate::support::nyxd;
@@ -191,6 +191,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
ecash_state.spawn_background_cleaner();
let router = router.with_state(AppState {
nyxd_client: nyxd_client.clone(),
chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL),
forced_refresh: ForcedRefresh::new(
config.topology_cacher.debug.node_describe_allow_illegal_ips,
),
+3
View File
@@ -56,6 +56,9 @@ const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(
pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from_secs(4500);
pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50;
// TODO: make it configurable
pub(crate) const DEFAULT_CHAIN_STATUS_CACHE_TTL: Duration = Duration::from_secs(60);
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
+89 -1
View File
@@ -3,7 +3,7 @@
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::ecash::state::EcashState;
use crate::network::models::NetworkDetails;
use crate::network::models::{ChainStatus, NetworkDetails};
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::handlers::unstable;
use crate::node_status_api::models::AxumErrorResponse;
@@ -12,6 +12,7 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::ApiStatusState;
use crate::support::caching::cache::SharedCache;
use crate::support::caching::Cache;
use crate::support::nyxd::Client;
use crate::support::storage;
use axum::extract::FromRef;
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
@@ -20,6 +21,7 @@ use nym_task::TaskManager;
use nym_topology::CachedEpochRewardedSet;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
@@ -76,6 +78,11 @@ type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
#[derive(Clone)]
pub(crate) struct AppState {
// ideally this would have been made generic to make tests easier,
// however, it'd be a way bigger change (I tried)
pub(crate) nyxd_client: Client,
pub(crate) chain_status_cache: ChainStatusCache,
pub(crate) forced_refresh: ForcedRefresh,
pub(crate) nym_contract_cache: NymContractCache,
pub(crate) node_status_cache: NodeStatusCache,
@@ -127,6 +134,87 @@ impl ForcedRefresh {
}
}
#[derive(Clone)]
pub(crate) struct ChainStatusCache {
cache_ttl: Duration,
inner: Arc<RwLock<Option<ChainStatusCacheInner>>>,
}
impl ChainStatusCache {
pub(crate) fn new(cache_ttl: Duration) -> Self {
ChainStatusCache {
cache_ttl,
inner: Arc::new(Default::default()),
}
}
}
struct ChainStatusCacheInner {
last_refreshed_at: OffsetDateTime,
cache_value: ChainStatus,
}
impl ChainStatusCacheInner {
fn is_valid(&self, ttl: Duration) -> bool {
if self.last_refreshed_at + ttl > OffsetDateTime::now_utc() {
return true;
}
false
}
}
impl ChainStatusCache {
pub(crate) async fn get_or_refresh(
&self,
client: &Client,
) -> Result<ChainStatus, AxumErrorResponse> {
if let Some(cached) = self.check_cache().await {
return Ok(cached);
}
self.refresh(client).await
}
async fn check_cache(&self) -> Option<ChainStatus> {
let guard = self.inner.read().await;
let inner = guard.as_ref()?;
if inner.is_valid(self.cache_ttl) {
return Some(inner.cache_value.clone());
}
None
}
async fn refresh(&self, client: &Client) -> Result<ChainStatus, AxumErrorResponse> {
// 1. attempt to get write lock permit
let mut guard = self.inner.write().await;
// 2. check if another task hasn't already updated the cache whilst we were waiting for the permit
if let Some(cached) = guard.as_ref() {
if cached.is_valid(self.cache_ttl) {
return Ok(cached.cache_value.clone());
}
}
// 3. attempt to query the chain for the chain data
let abci = client.abci_info().await?;
let block = client
.block_info(abci.last_block_height.value() as u32)
.await?;
let status = ChainStatus {
abci: abci.into(),
latest_block: block.into(),
};
*guard = Some(ChainStatusCacheInner {
last_refreshed_at: OffsetDateTime::now_utc(),
cache_value: status.clone(),
});
Ok(status)
}
}
impl AppState {
pub(crate) fn nym_contract_cache(&self) -> &NymContractCache {
&self.nym_contract_cache
+10 -1
View File
@@ -44,7 +44,7 @@ use nym_validator_client::nyxd::{
PagedMixnetQueryClient, PagedMultisigQueryClient, PagedVestingQueryClient,
},
cosmwasm_client::types::ExecuteResult,
CosmWasmClient, Fee,
BlockResponse, CosmWasmClient, Fee, TendermintRpcClient,
};
use nym_validator_client::nyxd::{
hash::{Hash, SHA256_HASH_SIZE},
@@ -56,6 +56,7 @@ use nym_validator_client::{
use nym_vesting_contract_common::AccountVestingCoins;
use serde::Deserialize;
use std::sync::Arc;
use tendermint::abci::response::Info;
use tokio::sync::{RwLock, RwLockReadGuard};
#[macro_export]
@@ -132,6 +133,14 @@ impl Client {
self.inner.read().await
}
pub(crate) async fn abci_info(&self) -> Result<Info, NyxdError> {
Ok(nyxd_query!(self, abci_info().await?))
}
pub(crate) async fn block_info(&self, height: u32) -> Result<BlockResponse, NyxdError> {
Ok(nyxd_query!(self, block(height).await?))
}
pub(crate) async fn client_address(&self) -> Option<AccountId> {
let guard = self.inner.read().await;
match &*guard {
+3 -2
View File
@@ -2250,9 +2250,9 @@ dependencies = [
[[package]]
name = "hickory-resolver"
version = "0.24.3"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf287bde7b776e85d7188e6e5db7cf410a2f9531fe82817eb87feed034c8d14"
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
dependencies = [
"cfg-if",
"futures-util",
@@ -3267,6 +3267,7 @@ dependencies = [
"cosmwasm-std",
"ecdsa",
"getset",
"humantime-serde",
"nym-compact-ecash",
"nym-config",
"nym-contracts-common",