v0.2.11-pre.002-fix.003

This commit is contained in:
2026-08-25 20:37:09 +02:00
parent 126e8abfcd
commit 2dbddfe367
15 changed files with 499 additions and 271 deletions

View File

@@ -1,20 +1,20 @@
// file: crates/ksp-offchain-transport-lib/src/market_price_provider.rs
// version: 2
// version: 3
/// Maximum UTF-8 byte length of one provider display name.
pub const PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96;
pub const MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96;
/// Maximum byte length of one opaque provider identifier.
pub const PRICE_PROVIDER_ID_MAX_BYTES: usize = 64;
pub const MARKET_PRICE_PROVIDER_ID_MAX_BYTES: usize = 64;
/// Only price pair exposed by the `0.2.11` V1 public contract.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PricePair {
pub enum MarketPricePair {
/// Native SOL quoted directly in US dollars according to one provider's documented semantics.
SolUsd,
}
impl PricePair {
impl MarketPricePair {
/// Returns the stable human-readable pair code.
#[must_use]
pub const fn code(&self) -> &'static str {
@@ -24,10 +24,10 @@ impl PricePair {
}
}
/// Price semantics retained so normalized observations do not imply cross-provider equivalence.
/// Market-price semantics retained so normalized observations do not imply cross-provider equivalence.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceSemantics {
pub enum MarketPriceSemantics {
/// Aggregated market price produced by a multi-market data provider.
AggregatedMarket,
/// USD price associated with one explicitly configured DEX pair.
@@ -43,7 +43,7 @@ pub enum PriceSemantics {
/// Generic authentication capability exposed by one configured provider.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceProviderAuthMode {
pub enum MarketPriceProviderAuthMode {
/// No credential is required for the configured access mode.
None,
/// Provider accepts an API key but also supports an unauthenticated mode selected by configuration.
@@ -55,7 +55,7 @@ pub enum PriceProviderAuthMode {
/// Scope to which a provider documents a request limit.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceProviderRateLimitScope {
pub enum MarketPriceProviderRateLimitScope {
/// Limit is associated with the configured account or API key.
Account,
/// Limit is associated with the source IP address.
@@ -69,7 +69,7 @@ pub enum PriceProviderRateLimitScope {
/// Shape of one generic provider request-limit capability.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceProviderRateLimitKind {
pub enum MarketPriceProviderRateLimitKind {
/// Dynamic or server-driven limit that cannot be represented as one safe fixed local cadence.
Dynamic,
/// Locally enforceable fixed request budget over a documented window.
@@ -78,23 +78,28 @@ pub enum PriceProviderRateLimitKind {
/// Generic provider request-limit capability with validated fixed-limit values.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct PriceProviderRateLimit {
pub struct MarketPriceProviderRateLimit {
burst: std::option::Option<u32>,
kind: crate::PriceProviderRateLimitKind,
kind: crate::MarketPriceProviderRateLimitKind,
requests: std::option::Option<u32>,
scope: crate::PriceProviderRateLimitScope,
scope: crate::MarketPriceProviderRateLimitScope,
window_seconds: std::option::Option<u32>,
}
impl PriceProviderRateLimit {
impl MarketPriceProviderRateLimit {
/// Creates a validated fixed request limit.
pub fn fixed(requests: u32, window_seconds: u32, burst: std::option::Option<u32>, scope: crate::PriceProviderRateLimitScope) -> ksp_core_lib::Result<Self> {
pub fn fixed(
requests: u32,
window_seconds: u32,
burst: std::option::Option<u32>,
scope: crate::MarketPriceProviderRateLimitScope,
) -> ksp_core_lib::Result<Self> {
if requests == 0 || window_seconds == 0 || burst == std::option::Option::Some(0) {
return std::result::Result::Err(provider_descriptor_error());
}
return std::result::Result::Ok(Self {
burst,
kind: crate::PriceProviderRateLimitKind::Fixed,
kind: crate::MarketPriceProviderRateLimitKind::Fixed,
requests: std::option::Option::Some(requests),
scope,
window_seconds: std::option::Option::Some(window_seconds),
@@ -103,10 +108,10 @@ impl PriceProviderRateLimit {
/// Creates a dynamic/server-driven request-limit descriptor.
#[must_use]
pub const fn dynamic(scope: crate::PriceProviderRateLimitScope) -> Self {
pub const fn dynamic(scope: crate::MarketPriceProviderRateLimitScope) -> Self {
return Self {
burst: std::option::Option::None,
kind: crate::PriceProviderRateLimitKind::Dynamic,
kind: crate::MarketPriceProviderRateLimitKind::Dynamic,
requests: std::option::Option::None,
scope,
window_seconds: std::option::Option::None,
@@ -121,7 +126,7 @@ impl PriceProviderRateLimit {
/// Returns whether the limit is fixed or dynamic/server-driven.
#[must_use]
pub const fn kind(&self) -> crate::PriceProviderRateLimitKind {
pub const fn kind(&self) -> crate::MarketPriceProviderRateLimitKind {
return self.kind;
}
@@ -133,7 +138,7 @@ impl PriceProviderRateLimit {
/// Returns the documented limit scope.
#[must_use]
pub const fn scope(&self) -> crate::PriceProviderRateLimitScope {
pub const fn scope(&self) -> crate::MarketPriceProviderRateLimitScope {
return self.scope;
}
@@ -147,7 +152,7 @@ impl PriceProviderRateLimit {
/// Period used by one documented long-term provider quota.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceProviderQuotaPeriod {
pub enum MarketPriceProviderQuotaPeriod {
/// Quota resets on a provider-defined daily period.
Day,
/// Quota resets on a provider-defined monthly period.
@@ -157,7 +162,7 @@ pub enum PriceProviderQuotaPeriod {
/// Unit used by one documented long-term provider quota.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PriceProviderQuotaUnit {
pub enum MarketPriceProviderQuotaUnit {
/// Provider-specific credits, not assumed to equal HTTP requests.
Credits,
/// HTTP/API requests.
@@ -166,15 +171,15 @@ pub enum PriceProviderQuotaUnit {
/// Long-term provider quota descriptor exposed as non-authoritative capability metadata.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct PriceProviderLongTermQuota {
pub struct MarketPriceProviderLongTermQuota {
amount: u64,
period: crate::PriceProviderQuotaPeriod,
unit: crate::PriceProviderQuotaUnit,
period: crate::MarketPriceProviderQuotaPeriod,
unit: crate::MarketPriceProviderQuotaUnit,
}
impl PriceProviderLongTermQuota {
impl MarketPriceProviderLongTermQuota {
/// Creates a non-zero documented quota descriptor.
pub fn new(amount: u64, period: crate::PriceProviderQuotaPeriod, unit: crate::PriceProviderQuotaUnit) -> ksp_core_lib::Result<Self> {
pub fn new(amount: u64, period: crate::MarketPriceProviderQuotaPeriod, unit: crate::MarketPriceProviderQuotaUnit) -> ksp_core_lib::Result<Self> {
if amount == 0 {
return std::result::Result::Err(provider_descriptor_error());
}
@@ -189,13 +194,13 @@ impl PriceProviderLongTermQuota {
/// Returns the provider-defined quota period.
#[must_use]
pub const fn period(&self) -> crate::PriceProviderQuotaPeriod {
pub const fn period(&self) -> crate::MarketPriceProviderQuotaPeriod {
return self.period;
}
/// Returns the documented quota unit.
#[must_use]
pub const fn unit(&self) -> crate::PriceProviderQuotaUnit {
pub const fn unit(&self) -> crate::MarketPriceProviderQuotaUnit {
return self.unit;
}
}
@@ -203,9 +208,9 @@ impl PriceProviderLongTermQuota {
/// Opaque validated provider identifier owned by Off-chain Transport.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
#[serde(try_from = "std::string::String", into = "std::string::String")]
pub struct PriceProviderId(std::string::String);
pub struct MarketPriceProviderId(std::string::String);
impl PriceProviderId {
impl MarketPriceProviderId {
/// Creates one bounded stable provider identifier.
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider identifier");
@@ -213,7 +218,7 @@ impl PriceProviderId {
if !valid_provider_id(value.as_str()) {
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "provider_id", "rejected invalid market-price provider identifier");
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_PROVIDER_ID_INVALID,
crate::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID,
"invalid off-chain market-price provider identifier",
));
}
@@ -227,47 +232,47 @@ impl PriceProviderId {
}
}
impl std::fmt::Display for PriceProviderId {
impl std::fmt::Display for MarketPriceProviderId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str(self.0.as_str());
}
}
impl std::convert::TryFrom<std::string::String> for PriceProviderId {
impl std::convert::TryFrom<std::string::String> for MarketPriceProviderId {
type Error = ksp_core_lib::Error;
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
return crate::PriceProviderId::new(value);
return crate::MarketPriceProviderId::new(value);
}
}
impl std::convert::From<PriceProviderId> for std::string::String {
fn from(value: PriceProviderId) -> Self {
impl std::convert::From<MarketPriceProviderId> for std::string::String {
fn from(value: MarketPriceProviderId) -> Self {
return value.0;
}
}
/// Provider capability and presentation descriptor consumed by provider-agnostic callers.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct PriceProviderDescriptor {
auth_mode: crate::PriceProviderAuthMode,
pub struct MarketPriceProviderDescriptor {
auth_mode: crate::MarketPriceProviderAuthMode,
display_name: std::string::String,
id: crate::PriceProviderId,
long_term_quota: std::option::Option<crate::PriceProviderLongTermQuota>,
rate_limit: crate::PriceProviderRateLimit,
semantics: crate::PriceSemantics,
id: crate::MarketPriceProviderId,
long_term_quota: std::option::Option<crate::MarketPriceProviderLongTermQuota>,
rate_limit: crate::MarketPriceProviderRateLimit,
semantics: crate::MarketPriceSemantics,
supports_sol_usd: bool,
}
impl PriceProviderDescriptor {
impl MarketPriceProviderDescriptor {
/// Creates a validated provider-neutral descriptor.
pub fn new(
id: crate::PriceProviderId,
id: crate::MarketPriceProviderId,
display_name: impl std::convert::Into<std::string::String>,
semantics: crate::PriceSemantics,
auth_mode: crate::PriceProviderAuthMode,
rate_limit: crate::PriceProviderRateLimit,
long_term_quota: std::option::Option<crate::PriceProviderLongTermQuota>,
semantics: crate::MarketPriceSemantics,
auth_mode: crate::MarketPriceProviderAuthMode,
rate_limit: crate::MarketPriceProviderRateLimit,
long_term_quota: std::option::Option<crate::MarketPriceProviderLongTermQuota>,
supports_sol_usd: bool,
) -> ksp_core_lib::Result<Self> {
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider descriptor");
@@ -281,7 +286,7 @@ impl PriceProviderDescriptor {
/// Returns the configured authentication capability.
#[must_use]
pub const fn auth_mode(&self) -> crate::PriceProviderAuthMode {
pub const fn auth_mode(&self) -> crate::MarketPriceProviderAuthMode {
return self.auth_mode;
}
@@ -293,25 +298,25 @@ impl PriceProviderDescriptor {
/// Returns the opaque provider identifier.
#[must_use]
pub const fn id(&self) -> &crate::PriceProviderId {
pub const fn id(&self) -> &crate::MarketPriceProviderId {
return &self.id;
}
/// Returns optional long-term quota metadata without exposing a local remaining counter.
#[must_use]
pub const fn long_term_quota(&self) -> std::option::Option<crate::PriceProviderLongTermQuota> {
pub const fn long_term_quota(&self) -> std::option::Option<crate::MarketPriceProviderLongTermQuota> {
return self.long_term_quota;
}
/// Returns the configured request-limit capability.
#[must_use]
pub const fn rate_limit(&self) -> crate::PriceProviderRateLimit {
pub const fn rate_limit(&self) -> crate::MarketPriceProviderRateLimit {
return self.rate_limit;
}
/// Returns the documented price semantics.
#[must_use]
pub const fn semantics(&self) -> crate::PriceSemantics {
pub const fn semantics(&self) -> crate::MarketPriceSemantics {
return self.semantics;
}
@@ -325,13 +330,13 @@ impl PriceProviderDescriptor {
/// Generic runtime availability state exposed without provider-specific error parsing.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(tag = "state", rename_all = "snake_case")]
pub enum PriceProviderAvailability {
pub enum MarketPriceProviderAvailability {
/// Required authentication material is unavailable or rejected.
AuthenticationUnavailable,
/// Provider is locally cooling down until the supplied timestamp.
CoolingDown {
/// Earliest known wall-clock timestamp at which a new attempt may be admitted.
retry_at: crate::PriceTimestamp,
retry_at: crate::MarketPriceTimestamp,
},
/// Provider is disabled by runtime configuration.
Disabled,
@@ -344,43 +349,43 @@ pub enum PriceProviderAvailability {
/// Transport/provider failure is transient; retry time is present only when actually known.
TemporarilyUnavailable {
/// Optional next retry timestamp derived from safe runtime/provider information.
retry_at: std::option::Option<crate::PriceTimestamp>,
retry_at: std::option::Option<crate::MarketPriceTimestamp>,
},
}
/// Current provider-neutral runtime state projection.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct PriceProviderState {
availability: crate::PriceProviderAvailability,
provider_id: crate::PriceProviderId,
pub struct MarketPriceProviderState {
availability: crate::MarketPriceProviderAvailability,
provider_id: crate::MarketPriceProviderId,
}
impl PriceProviderState {
impl MarketPriceProviderState {
/// Creates one generic state projection for a configured provider.
#[must_use]
pub fn new(provider_id: crate::PriceProviderId, availability: crate::PriceProviderAvailability) -> Self {
pub fn new(provider_id: crate::MarketPriceProviderId, availability: crate::MarketPriceProviderAvailability) -> Self {
return Self { availability, provider_id };
}
/// Returns the generic availability classification.
#[must_use]
pub const fn availability(&self) -> crate::PriceProviderAvailability {
pub const fn availability(&self) -> crate::MarketPriceProviderAvailability {
return self.availability;
}
/// Returns the opaque provider identifier.
#[must_use]
pub const fn provider_id(&self) -> &crate::PriceProviderId {
pub const fn provider_id(&self) -> &crate::MarketPriceProviderId {
return &self.provider_id;
}
}
fn provider_descriptor_error() -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor");
return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor");
}
fn valid_display_name(value: &str) -> bool {
if value.is_empty() || value.len() > crate::PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value {
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value {
return false;
}
for character in value.chars() {
@@ -392,7 +397,7 @@ fn valid_display_name(value: &str) -> bool {
}
fn valid_provider_id(value: &str) -> bool {
if value.is_empty() || value.len() > crate::PRICE_PROVIDER_ID_MAX_BYTES {
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_ID_MAX_BYTES {
return false;
}
for byte in value.bytes() {