v0.2.12-pre.005
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/market_price_runtime.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Provider-neutral market-price presentation runtime owned by SOL Prices Desk.
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(crate) struct MarketPriceProviderRowDto {
|
||||
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.
|
||||
/// Opaque provider identifier accepted by 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>,
|
||||
@@ -98,15 +98,10 @@ impl crate::MarketPriceRuntime {
|
||||
/// 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 = lock_presentation(&self.presentation);
|
||||
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",
|
||||
));
|
||||
},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut rows = std::vec::Vec::with_capacity(registry.len());
|
||||
for entry in registry.entries() {
|
||||
@@ -125,6 +120,90 @@ impl crate::MarketPriceRuntime {
|
||||
}
|
||||
return std::result::Result::Ok(rows);
|
||||
}
|
||||
|
||||
/// Refreshes one opaque provider through Off-chain Transport and updates only its in-memory presentation row.
|
||||
pub(crate) async fn refresh_one(&self, provider_id: String) -> ksp_core_lib::Result<crate::MarketPriceProviderRowDto> {
|
||||
let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new(provider_id);
|
||||
let provider_id = match provider_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let loading = self.set_loading(&provider_id, true);
|
||||
if let std::result::Result::Err(error) = loading {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_OFFCHAIN_TRANSPORT,
|
||||
provider_id = provider_id.as_str(),
|
||||
"started SOL Prices Desk single-provider market-price refresh"
|
||||
);
|
||||
let outcome = self.startup.resolved().service().refresh(&provider_id).await;
|
||||
let outcome = match outcome {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let cleared = self.set_loading(&provider_id, false);
|
||||
if let std::result::Result::Err(clear_error) = cleared {
|
||||
return std::result::Result::Err(clear_error);
|
||||
}
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let row = self.apply_outcome(&outcome);
|
||||
let row = match row {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_OFFCHAIN_TRANSPORT,
|
||||
provider_id = provider_id.as_str(),
|
||||
refreshed = outcome.refreshed(),
|
||||
availability = row.availability.as_str(),
|
||||
"completed SOL Prices Desk single-provider market-price refresh"
|
||||
);
|
||||
return std::result::Result::Ok(row);
|
||||
}
|
||||
|
||||
fn apply_outcome(&self, outcome: &ksp_offchain_transport_lib::MarketPriceRefreshOutcome) -> ksp_core_lib::Result<crate::MarketPriceProviderRowDto> {
|
||||
let registry = self.startup.resolved().service().registry();
|
||||
let registry_entry = registry.entry(outcome.provider_id());
|
||||
let registry_entry = match registry_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 refresh outcome references an unknown registry provider")
|
||||
.with_context("provider_id", outcome.provider_id().as_str()),
|
||||
);
|
||||
},
|
||||
};
|
||||
let presentation = lock_presentation(&self.presentation);
|
||||
let mut presentation = match presentation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let applied = apply_outcome_to_state(&mut presentation, outcome);
|
||||
if let std::result::Result::Err(error) = applied {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let presentation_entry = presentation.entries.get(outcome.provider_id().as_str());
|
||||
return match presentation_entry {
|
||||
std::option::Option::Some(value) => std::result::Result::Ok(project_registry_entry(registry_entry, value)),
|
||||
std::option::Option::None => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "SOL Prices Desk market-price runtime is missing refreshed provider state")
|
||||
.with_context("provider_id", outcome.provider_id().as_str()),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn set_loading(&self, provider_id: &ksp_offchain_transport_lib::MarketPriceProviderId, loading: bool) -> ksp_core_lib::Result<()> {
|
||||
let presentation = lock_presentation(&self.presentation);
|
||||
let mut presentation = match presentation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return set_loading_in_state(&mut presentation, provider_id.as_str(), loading);
|
||||
}
|
||||
}
|
||||
|
||||
struct MarketPricePresentationEntry {
|
||||
@@ -136,6 +215,27 @@ struct MarketPricePresentationState {
|
||||
entries: std::collections::BTreeMap<String, MarketPricePresentationEntry>,
|
||||
}
|
||||
|
||||
fn apply_outcome_to_state(
|
||||
presentation: &mut MarketPricePresentationState,
|
||||
outcome: &ksp_offchain_transport_lib::MarketPriceRefreshOutcome,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let entry = presentation.entries.get_mut(outcome.provider_id().as_str());
|
||||
let entry = match 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 presentation state is missing refresh provider")
|
||||
.with_context("provider_id", outcome.provider_id().as_str()),
|
||||
);
|
||||
},
|
||||
};
|
||||
entry.loading = false;
|
||||
if let std::option::Option::Some(observation) = outcome.observation() {
|
||||
entry.observation = std::option::Option::Some(observation.clone());
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn auth_mode_code(auth_mode: ksp_offchain_transport_lib::MarketPriceProviderAuthMode) -> &'static str {
|
||||
return match auth_mode {
|
||||
ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None => "none",
|
||||
@@ -170,6 +270,18 @@ fn count_to_u32(value: usize, field: &'static str) -> ksp_core_lib::Result<u32>
|
||||
};
|
||||
}
|
||||
|
||||
fn lock_presentation(
|
||||
presentation: &std::sync::Mutex<MarketPricePresentationState>,
|
||||
) -> ksp_core_lib::Result<std::sync::MutexGuard<'_, MarketPricePresentationState>> {
|
||||
return match presentation.lock() {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => 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",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
fn project_registry_entry(
|
||||
entry: &ksp_offchain_transport_lib::MarketPriceProviderRegistryEntry,
|
||||
presentation: &MarketPricePresentationEntry,
|
||||
@@ -204,6 +316,20 @@ fn semantics_code(semantics: ksp_offchain_transport_lib::MarketPriceSemantics) -
|
||||
};
|
||||
}
|
||||
|
||||
fn set_loading_in_state(presentation: &mut MarketPricePresentationState, provider_id: &str, loading: bool) -> ksp_core_lib::Result<()> {
|
||||
let entry = presentation.entries.get_mut(provider_id);
|
||||
return match entry {
|
||||
std::option::Option::Some(value) => {
|
||||
value.loading = loading;
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
std::option::Option::None => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "SOL Prices Desk market-price presentation state is missing provider")
|
||||
.with_context("provider_id", provider_id),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/market_price_runtime.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user