v0.2.12-pre.004
This commit is contained in:
209
crates/ksp-app-solprices-desk/src/market_price_runtime.rs
Normal file
209
crates/ksp-app-solprices-desk/src/market_price_runtime.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/market_price_runtime.rs
|
||||
// version: 1
|
||||
|
||||
//! Provider-neutral market-price presentation runtime owned by SOL Prices Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Safe provider-neutral row projected to the SOL Prices Desk frontend.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_solprices_desk/market_price_runtime/MarketPriceProviderRowDto.ts")]
|
||||
pub(crate) struct MarketPriceProviderRowDto {
|
||||
/// Generic authentication capability code for the configured provider mode.
|
||||
pub(crate) auth_mode: String,
|
||||
/// Generic current availability code.
|
||||
pub(crate) availability: String,
|
||||
/// Safe provider display name supplied by Off-chain Transport.
|
||||
pub(crate) display_name: String,
|
||||
/// Whether one refresh is currently in flight for this provider.
|
||||
pub(crate) loading: bool,
|
||||
/// Stable V1 pair label.
|
||||
pub(crate) pair: String,
|
||||
/// Exact canonical SOL/USD decimal string, when an observation exists.
|
||||
pub(crate) price: std::option::Option<String>,
|
||||
/// Opaque provider identifier accepted by future refresh commands.
|
||||
pub(crate) provider_id: String,
|
||||
/// Provider-supplied observation timestamp in exact Unix milliseconds, when genuinely supplied.
|
||||
pub(crate) provider_timestamp_unix_millis: std::option::Option<String>,
|
||||
/// KSP receipt timestamp in exact Unix milliseconds, when an observation exists.
|
||||
pub(crate) received_at_unix_millis: std::option::Option<String>,
|
||||
/// Whether this row currently retains at least one successful observation.
|
||||
pub(crate) refreshed: bool,
|
||||
/// Known next retry timestamp in exact Unix milliseconds for cooldown/temporary states.
|
||||
pub(crate) retry_at_unix_millis: std::option::Option<String>,
|
||||
/// Generic price-semantics code retained from the provider descriptor.
|
||||
pub(crate) semantics: String,
|
||||
}
|
||||
|
||||
/// Backend application runtime that owns the Config-selected Off-chain service and current in-memory presentation state.
|
||||
pub(crate) struct MarketPriceRuntime {
|
||||
presentation: std::sync::Mutex<MarketPricePresentationState>,
|
||||
startup: crate::OffchainTransportStartup,
|
||||
}
|
||||
|
||||
impl crate::MarketPriceRuntime {
|
||||
/// Builds the application presentation runtime from the already Config-resolved Off-chain startup.
|
||||
pub(crate) fn new(startup: crate::OffchainTransportStartup) -> ksp_core_lib::Result<Self> {
|
||||
let registry = startup.resolved().service().registry();
|
||||
let mut entries = std::collections::BTreeMap::new();
|
||||
for entry in registry.entries() {
|
||||
let previous = entries
|
||||
.insert(entry.descriptor().id().as_str().to_owned(), MarketPricePresentationEntry { loading: false, observation: std::option::Option::None });
|
||||
if previous.is_some() {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "SOL Prices Desk market-price runtime contains duplicate provider state")
|
||||
.with_context("provider_id", entry.descriptor().id().as_str()),
|
||||
);
|
||||
}
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_OFFCHAIN_TRANSPORT,
|
||||
provider_count = entries.len(),
|
||||
"initialized SOL Prices Desk market-price presentation runtime"
|
||||
);
|
||||
return std::result::Result::Ok(Self { presentation: std::sync::Mutex::new(MarketPricePresentationState { entries }), startup });
|
||||
}
|
||||
|
||||
/// Returns the Config/bootstrap metadata retained by the price runtime.
|
||||
#[must_use]
|
||||
pub(crate) const fn startup(&self) -> &crate::OffchainTransportStartup {
|
||||
return &self.startup;
|
||||
}
|
||||
|
||||
/// Returns total, ready and unavailable provider counts from the detached generic registry snapshot.
|
||||
pub(crate) fn provider_counts(&self) -> ksp_core_lib::Result<(u32, u32, u32)> {
|
||||
let registry = self.startup.resolved().service().registry();
|
||||
let total = count_to_u32(registry.len(), "provider_count");
|
||||
let total = match total {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut ready_count = 0usize;
|
||||
for entry in registry.entries() {
|
||||
if matches!(entry.state().availability(), ksp_offchain_transport_lib::MarketPriceProviderAvailability::Ready) {
|
||||
ready_count += 1;
|
||||
}
|
||||
}
|
||||
let ready = count_to_u32(ready_count, "ready_provider_count");
|
||||
let ready = match ready {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let unavailable = total.saturating_sub(ready);
|
||||
return std::result::Result::Ok((total, ready, unavailable));
|
||||
}
|
||||
|
||||
/// Projects the current registry and in-memory observation state as deterministic frontend rows without network dispatch.
|
||||
pub(crate) fn list_rows(&self) -> ksp_core_lib::Result<std::vec::Vec<crate::MarketPriceProviderRowDto>> {
|
||||
let registry = self.startup.resolved().service().registry();
|
||||
let presentation = self.presentation.lock();
|
||||
let presentation = match presentation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_LOCK_FAILED,
|
||||
"SOL Prices Desk market-price presentation state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
let mut rows = std::vec::Vec::with_capacity(registry.len());
|
||||
for entry in registry.entries() {
|
||||
let provider_id = entry.descriptor().id().as_str();
|
||||
let presentation_entry = presentation.entries.get(provider_id);
|
||||
let presentation_entry = match presentation_entry {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "SOL Prices Desk market-price runtime is missing one registry row")
|
||||
.with_context("provider_id", provider_id),
|
||||
);
|
||||
},
|
||||
};
|
||||
rows.push(project_registry_entry(entry, presentation_entry));
|
||||
}
|
||||
return std::result::Result::Ok(rows);
|
||||
}
|
||||
}
|
||||
|
||||
struct MarketPricePresentationEntry {
|
||||
loading: bool,
|
||||
observation: std::option::Option<ksp_offchain_transport_lib::MarketPriceObservation>,
|
||||
}
|
||||
|
||||
struct MarketPricePresentationState {
|
||||
entries: std::collections::BTreeMap<String, MarketPricePresentationEntry>,
|
||||
}
|
||||
|
||||
fn auth_mode_code(auth_mode: ksp_offchain_transport_lib::MarketPriceProviderAuthMode) -> &'static str {
|
||||
return match auth_mode {
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None => "none",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAuthMode::OptionalApiKey => "optional_api_key",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAuthMode::RequiredApiKey => "required_api_key",
|
||||
_ => "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
fn availability_code(availability: ksp_offchain_transport_lib::MarketPriceProviderAvailability) -> &'static str {
|
||||
return match availability {
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::AuthenticationUnavailable => "authentication_unavailable",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::CoolingDown { .. } => "cooling_down",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::Disabled => "disabled",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::Misconfigured => "misconfigured",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::QuotaUnavailable => "quota_unavailable",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::Ready => "ready",
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAvailability::TemporarilyUnavailable { .. } => "temporarily_unavailable",
|
||||
_ => "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
fn count_to_u32(value: usize, field: &'static str) -> ksp_core_lib::Result<u32> {
|
||||
let converted = u32::try_from(value);
|
||||
return match converted {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "SOL Prices Desk market-price runtime count exceeds DTO bounds")
|
||||
.with_context("field", field)
|
||||
.with_source(error),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn project_registry_entry(
|
||||
entry: &ksp_offchain_transport_lib::MarketPriceProviderRegistryEntry,
|
||||
presentation: &MarketPricePresentationEntry,
|
||||
) -> crate::MarketPriceProviderRowDto {
|
||||
let descriptor = entry.descriptor();
|
||||
let availability = entry.state().availability();
|
||||
let observation = presentation.observation.as_ref();
|
||||
return crate::MarketPriceProviderRowDto {
|
||||
auth_mode: auth_mode_code(descriptor.auth_mode()).to_owned(),
|
||||
availability: availability_code(availability).to_owned(),
|
||||
display_name: descriptor.display_name().to_owned(),
|
||||
loading: presentation.loading,
|
||||
pair: ksp_offchain_transport_lib::MarketPricePair::SolUsd.code().to_owned(),
|
||||
price: observation.map(|value| return value.price().to_canonical_string()),
|
||||
provider_id: descriptor.id().as_str().to_owned(),
|
||||
provider_timestamp_unix_millis: observation.and_then(|value| return value.provider_timestamp()).map(|value| return value.unix_millis().to_string()),
|
||||
received_at_unix_millis: observation.map(|value| return value.received_at().unix_millis().to_string()),
|
||||
refreshed: observation.is_some(),
|
||||
retry_at_unix_millis: availability.retry_at().map(|value| return value.unix_millis().to_string()),
|
||||
semantics: semantics_code(descriptor.semantics()).to_owned(),
|
||||
};
|
||||
}
|
||||
|
||||
fn semantics_code(semantics: ksp_offchain_transport_lib::MarketPriceSemantics) -> &'static str {
|
||||
return match semantics {
|
||||
ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket => "aggregated_market",
|
||||
ksp_offchain_transport_lib::MarketPriceSemantics::DexPairUsd => "dex_pair_usd",
|
||||
ksp_offchain_transport_lib::MarketPriceSemantics::ExchangeLastTrade => "exchange_last_trade",
|
||||
ksp_offchain_transport_lib::MarketPriceSemantics::SolanaHeuristic => "solana_heuristic",
|
||||
ksp_offchain_transport_lib::MarketPriceSemantics::SolanaSpot => "solana_spot",
|
||||
_ => "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/market_price_runtime.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user