// file: crates/ksp-app-store-desk/src/store_runtime.rs // version: 5 //! Composite-selected Store readiness, status and bounded shutdown lifecycle owned by Store Desk. /// Startup result for Store, including safe diagnostics when the desktop shell remains available without an opened runtime. pub(crate) struct StoreStartup { backend_kind: std::option::Option, diagnostic: std::option::Option, network: std::option::Option, profile_id: std::option::Option, runtime: std::option::Option, } impl crate::StoreStartup { /// Builds a fresh backend-neutral runtime/health projection for the Overview screen. pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.status().await, std::option::Option::None => crate::StoreRuntimeStatusDto { backend_kind: self.backend_kind.clone(), diagnostic: self.diagnostic.clone(), health_state: "unavailable".to_owned(), migration_version_decimal: std::option::Option::None, network: self.network.clone(), pending_migration_count: 0, pool_available: 0, pool_capacity: 0, pool_size: 0, pool_waiting: 0, profile_id: self.profile_id.clone(), store_open: false, }, }; } /// Executes one bounded server-side RAW account-state inspection query. pub(crate) async fn query_accounts(&self, request: crate::StoreAccountQueryRequestDto) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.query_accounts(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Loads one explicit bounded RAW account-state detail projection. pub(crate) async fn account_detail(&self, request: crate::StoreAccountDetailRequestDto) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.account_detail(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Executes one bounded metadata-only observation inspection for an explicit RAW account state. pub(crate) async fn query_account_observations( &self, request: crate::StoreAccountObservationQueryRequestDto, ) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.query_account_observations(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Executes one bounded server-side RAW transaction inspection query. pub(crate) async fn query_transactions( &self, request: crate::StoreTransactionQueryRequestDto, ) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.query_transactions(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Loads one explicit bounded RAW transaction detail projection. pub(crate) async fn transaction_detail(&self, request: crate::StoreTransactionDetailRequestDto) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.transaction_detail(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Executes one bounded metadata-only observation inspection for an explicit RAW transaction. pub(crate) async fn query_transaction_observations( &self, request: crate::StoreTransactionObservationQueryRequestDto, ) -> ksp_core_lib::Result { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.query_transaction_observations(request).await, std::option::Option::None => std::result::Result::Err(store_runtime_unavailable()), }; } /// Closes the retained Store runtime if startup reached the physical Store-open phase. pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> { let runtime = self.runtime.as_ref(); return match runtime { std::option::Option::Some(value) => value.close().await, std::option::Option::None => std::result::Result::Ok(()), }; } } /// Executable Store runtime retained behind an async read/write lock so read-only Desk queries may coexist while shutdown remains exclusive. pub(crate) struct StoreRuntime { profile_id: String, store: tokio::sync::RwLock>, } impl crate::StoreRuntime { /// Builds one fresh safe Store status from the retained facade without exposing backend connection details. pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => { return crate::StoreRuntimeStatusDto { backend_kind: std::option::Option::None, diagnostic: std::option::Option::None, health_state: "closed".to_owned(), migration_version_decimal: std::option::Option::None, network: std::option::Option::None, pending_migration_count: 0, pool_available: 0, pool_capacity: 0, pool_size: 0, pool_waiting: 0, profile_id: std::option::Option::Some(self.profile_id.clone()), store_open: false, }; }, }; let runtime = store.runtime_snapshot(); let health = store.health().await; let health_state = store_health_state_code(health.state()).to_owned(); let diagnostic = non_ready_health_diagnostic(&health); return crate::StoreRuntimeStatusDto { backend_kind: std::option::Option::Some(runtime.backend_kind().code().to_owned()), diagnostic, health_state, migration_version_decimal: health.migration_version().map(|value| return value.to_string()), network: std::option::Option::Some(runtime.network().as_str().to_owned()), pending_migration_count: health.pending_migration_count(), pool_available: runtime.pool_available(), pool_capacity: runtime.pool_capacity(), pool_size: runtime.pool_size(), pool_waiting: runtime.pool_waiting(), profile_id: std::option::Option::Some(self.profile_id.clone()), store_open: true, }; } /// Executes one validated random-access account-state inspection query while retaining the Store read guard. pub(crate) async fn query_accounts(&self, request: crate::StoreAccountQueryRequestDto) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let query = account_query_from_request(store, &request); let query = match query { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let page = ksp_store_lib::RawAccountStateInspectionRead::inspect_raw_account_states(store, &query).await; let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let records_total_decimal = page.total_items().to_string(); let records_filtered_decimal = page.filtered_items().to_string(); let items = page.into_items(); let mut rows = std::vec::Vec::with_capacity(items.len()); for item in items { rows.push(account_row_dto(&item)); } return std::result::Result::Ok(crate::StoreAccountQueryResponseDto { records_filtered_decimal, records_total_decimal, rows }); } /// Loads one account-state detail by application-owned row identity without exposing Store network selection to the frontend. pub(crate) async fn account_detail(&self, request: crate::StoreAccountDetailRequestDto) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let reference = account_reference_from_request(store, &request); let reference = match reference { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let state = ksp_store_lib::RawAccountStateRead::get_raw_account_state(store, &reference).await; let state = match state { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => return std::result::Result::Err(store_account_not_found()), std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(account_detail_dto(&state)); } /// Executes one validated metadata-only observation query for one exact account-state identity. pub(crate) async fn query_account_observations( &self, request: crate::StoreAccountObservationQueryRequestDto, ) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let query = account_observation_query_from_request(store, &request); let query = match query { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let page = ksp_store_lib::RawAccountObservationInspectionRead::inspect_raw_account_observations(store, &query).await; let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let records_total_decimal = page.total_items().to_string(); let records_filtered_decimal = page.filtered_items().to_string(); let items = page.into_items(); let mut rows = std::vec::Vec::with_capacity(items.len()); for item in items { rows.push(account_observation_row_dto(&item)); } return std::result::Result::Ok(crate::StoreAccountObservationQueryResponseDto { records_filtered_decimal, records_total_decimal, rows }); } /// Executes one validated random-access transaction inspection query while retaining the Store read guard. pub(crate) async fn query_transactions( &self, request: crate::StoreTransactionQueryRequestDto, ) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let query = transaction_query_from_request(store, &request); let query = match query { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let page = ksp_store_lib::RawTransactionInspectionRead::inspect_raw_transactions(store, &query).await; let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let records_total_decimal = page.total_items().to_string(); let records_filtered_decimal = page.filtered_items().to_string(); let items = page.into_items(); let mut rows = std::vec::Vec::with_capacity(items.len()); for item in items { rows.push(transaction_row_dto(&item)); } return std::result::Result::Ok(crate::StoreTransactionQueryResponseDto { records_filtered_decimal, records_total_decimal, rows }); } /// Loads one transaction detail by application-owned row identity without exposing Store network selection to the frontend. pub(crate) async fn transaction_detail(&self, request: crate::StoreTransactionDetailRequestDto) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let reference = transaction_reference_from_request(store, request.signature.as_str()); let reference = match reference { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let retention = ksp_store_lib::RawTransactionRetentionRead::get_raw_transaction_retention_state(store, &reference).await; let retention = match retention { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => return std::result::Result::Err(store_transaction_not_found()), std::result::Result::Err(error) => return std::result::Result::Err(error), }; return match retention { ksp_store_lib::RawRetentionState::Full | ksp_store_lib::RawRetentionState::Archived => { transaction_detail_with_payload(store, &reference, retention).await }, ksp_store_lib::RawRetentionState::Purged => transaction_detail_from_tombstone(store, &reference).await, _ => std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_INVALID, "Store Desk cannot project an unsupported transaction retention state", )), }; } /// Executes one validated metadata-only observation query for one exact transaction identity. pub(crate) async fn query_transaction_observations( &self, request: crate::StoreTransactionObservationQueryRequestDto, ) -> ksp_core_lib::Result { let locked = self.store.read().await; let store = locked.as_ref(); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Err(store_runtime_unavailable()), }; let query = transaction_observation_query_from_request(store, &request); let query = match query { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let page = ksp_store_lib::RawTransactionObservationInspectionRead::inspect_raw_transaction_observations(store, &query).await; let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let records_total_decimal = page.total_items().to_string(); let records_filtered_decimal = page.filtered_items().to_string(); let items = page.into_items(); let mut rows = std::vec::Vec::with_capacity(items.len()); for item in items { rows.push(transaction_observation_row_dto(&item)); } return std::result::Result::Ok(crate::StoreTransactionObservationQueryResponseDto { records_filtered_decimal, records_total_decimal, rows }); } /// Explicitly closes the Store exactly once through its backend-neutral facade after all read guards have drained. pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> { let mut locked = self.store.write().await; let store = locked.take(); drop(locked); let store = match store { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Ok(()), }; let snapshot = store.runtime_snapshot(); let backend_kind = snapshot.backend_kind().code().to_owned(); let network = snapshot.network().as_str().to_owned(); let closed = store.close().await; return match closed { std::result::Result::Ok(()) => { ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_STORE, store_profile = self.profile_id.as_str(), backend = backend_kind.as_str(), network = network.as_str(), "closed Store Desk Store runtime" ); std::result::Result::Ok(()) }, std::result::Result::Err(error) => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_SHUTDOWN_FAILED, "Store Desk Store shutdown failed").with_source(error), ), }; } } const ACCOUNT_DETAIL_PREVIEW_BYTES: usize = 512; const TRANSACTION_DETAIL_PREVIEW_BYTES: usize = 512; fn inspection_page_from_request(offset_text: &str, limit_value: u32) -> ksp_core_lib::Result { let limit = match limit_value { 25 | 50 | 100 => ksp_store_lib::RawPageLimit::new(u64::from(limit_value)), _ => return std::result::Result::Err(store_query_invalid()), }; let limit = match limit { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; let offset = offset_text.parse::(); let offset = match offset { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; return std::result::Result::Ok(ksp_store_lib::RawInspectionPageRequest::new(offset, limit)); } fn account_observation_query_from_request( store: &ksp_store_lib::Store, request: &crate::StoreAccountObservationQueryRequestDto, ) -> ksp_core_lib::Result { let page = inspection_page_from_request(request.offset.as_str(), request.limit); let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let detail_request = crate::StoreAccountDetailRequestDto { pubkey: request.pubkey.clone(), slot: request.slot.clone(), state_hash: request.state_hash.clone() }; let reference = account_reference_from_request(store, &detail_request); let reference = match reference { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let network = store.runtime_snapshot().network().clone(); let query = ksp_store_lib::RawAccountObservationInspectionQuery::try_new( network, std::option::Option::Some(reference), ksp_store_lib::RawSortDirection::Descending, page, ); return match query { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(_) => std::result::Result::Err(store_query_invalid()), }; } fn transaction_observation_query_from_request( store: &ksp_store_lib::Store, request: &crate::StoreTransactionObservationQueryRequestDto, ) -> ksp_core_lib::Result { let page = inspection_page_from_request(request.offset.as_str(), request.limit); let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let reference = transaction_reference_from_request(store, request.signature.as_str()); let reference = match reference { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let network = store.runtime_snapshot().network().clone(); let query = ksp_store_lib::RawTransactionObservationInspectionQuery::try_new( network, std::option::Option::Some(reference), ksp_store_lib::RawSortDirection::Descending, page, ); return match query { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(_) => std::result::Result::Err(store_query_invalid()), }; } fn account_query_from_request( store: &ksp_store_lib::Store, request: &crate::StoreAccountQueryRequestDto, ) -> ksp_core_lib::Result { let page = inspection_page_from_request(request.offset.as_str(), request.limit); let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let pubkey = parse_optional_pubkey(request.pubkey.as_deref()); let pubkey = match pubkey { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slot_min = parse_optional_decimal_u64(request.slot_min.as_deref()); let slot_min = match slot_min { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slot_max = parse_optional_decimal_u64(request.slot_max.as_deref()); let slot_max = match slot_max { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slots = ksp_store_lib::RawSlotRange::new(slot_min, slot_max); let slots = match slots { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; let direction = match request.direction.as_str() { "ascending" => ksp_store_lib::RawSortDirection::Ascending, "descending" => ksp_store_lib::RawSortDirection::Descending, _ => return std::result::Result::Err(store_query_invalid()), }; let network = store.runtime_snapshot().network().clone(); return std::result::Result::Ok(ksp_store_lib::RawAccountStateInspectionQuery::new(network, pubkey, slots, direction, page)); } fn account_reference_from_request( store: &ksp_store_lib::Store, request: &crate::StoreAccountDetailRequestDto, ) -> ksp_core_lib::Result { let pubkey = request.pubkey.parse::(); let pubkey = match pubkey { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; let slot = request.slot.parse::(); let slot = match slot { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; let state_hash = decode_hex_32(request.state_hash.as_str()); let state_hash = match state_hash { std::result::Result::Ok(value) => ksp_store_lib::RawContentHash::new(value), std::result::Result::Err(error) => return std::result::Result::Err(error), }; let network = store.runtime_snapshot().network().clone(); return std::result::Result::Ok(ksp_store_lib::RawAccountStateReference::new(network, pubkey, slot, state_hash)); } fn account_detail_dto(state: &ksp_store_lib::RawAccountState) -> crate::StoreAccountDetailDto { let data = state.data(); let preview_len = std::cmp::min(data.len(), ACCOUNT_DETAIL_PREVIEW_BYTES); let state_hash = state.reference().state_hash(); return crate::StoreAccountDetailDto { data_length_decimal: state.data_len().to_string(), data_preview_hex: encode_hex(&data[..preview_len]), data_preview_truncated: data.len() > preview_len, executable: state.executable(), lamports_decimal: state.lamports().to_string(), owner: state.owner().to_string(), pubkey: state.reference().pubkey().to_string(), rent_epoch_decimal: state.rent_epoch().to_string(), slot_decimal: state.reference().slot().to_string(), state_hash: encode_hex(state_hash.as_bytes()), }; } fn account_row_dto(summary: &ksp_store_lib::RawAccountStateSummary) -> crate::StoreAccountRowDto { let state_hash = summary.reference().state_hash(); return crate::StoreAccountRowDto { data_length_decimal: summary.data_length_bytes().to_string(), executable: summary.executable(), lamports_decimal: summary.lamports().to_string(), owner: summary.owner().to_string(), pubkey: summary.reference().pubkey().to_string(), rent_epoch_decimal: summary.rent_epoch().to_string(), slot_decimal: summary.reference().slot().to_string(), state_hash: encode_hex(state_hash.as_bytes()), }; } fn observation_provenance_dto(provenance: &ksp_store_lib::RawAcquisitionProvenance) -> crate::StoreObservationProvenanceDto { return crate::StoreObservationProvenanceDto { acquisition_method: provenance.acquisition_method().as_str().to_owned(), capture_session_id: provenance.capture_session_id().map(|value| return value.as_str().to_owned()), commitment: provenance.commitment().map(|value| return value.as_str().to_owned()), endpoint_id: provenance.endpoint_id().map(|value| return value.as_str().to_owned()), filter_id: provenance.filter_id().map(|value| return value.as_str().to_owned()), observed_at_unix_millis_decimal: provenance.observed_at().map(|value| return value.unix_millis().to_string()), origin: acquisition_origin_code(provenance.origin()).to_owned(), protocol: provenance.protocol().as_str().to_owned(), provider: provenance.provider().as_str().to_owned(), received_at_unix_millis_decimal: provenance.received_at().unix_millis().to_string(), source_payload_hash: provenance.source_payload_hash().map(|value| return encode_hex(value.as_bytes())), source_payload_size_decimal: provenance.source_payload_size_bytes().map(|value| return value.to_string()), }; } fn account_observation_row_dto(summary: &ksp_store_lib::RawAccountObservationSummary) -> crate::StoreAccountObservationRowDto { return crate::StoreAccountObservationRowDto { is_startup: summary.is_startup(), observation_key: encode_hex(summary.observation_key().as_bytes()), provenance: observation_provenance_dto(summary.provenance()), transaction_signature: summary.transaction_signature().map(|value| return encode_hex(value.as_bytes())), write_version_decimal: summary.write_version().map(|value| return value.to_string()), }; } fn transaction_observation_row_dto(summary: &ksp_store_lib::RawTransactionObservationSummary) -> crate::StoreTransactionObservationRowDto { return crate::StoreTransactionObservationRowDto { observation_key: encode_hex(summary.observation_key().as_bytes()), provenance: observation_provenance_dto(summary.provenance()), }; } fn acquisition_origin_code(origin: ksp_store_lib::RawAcquisitionOrigin) -> &'static str { return match origin { ksp_store_lib::RawAcquisitionOrigin::Backfill => "backfill", ksp_store_lib::RawAcquisitionOrigin::Import => "import", ksp_store_lib::RawAcquisitionOrigin::Live => "live", ksp_store_lib::RawAcquisitionOrigin::Repair => "repair", ksp_store_lib::RawAcquisitionOrigin::Replay => "replay", _ => "unknown", }; } fn transaction_query_from_request( store: &ksp_store_lib::Store, request: &crate::StoreTransactionQueryRequestDto, ) -> ksp_core_lib::Result { let page = inspection_page_from_request(request.offset.as_str(), request.limit); let page = match page { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slot_min = parse_optional_decimal_u64(request.slot_min.as_deref()); let slot_min = match slot_min { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slot_max = parse_optional_decimal_u64(request.slot_max.as_deref()); let slot_max = match slot_max { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let slots = ksp_store_lib::RawSlotRange::new(slot_min, slot_max); let slots = match slots { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::result::Result::Err(store_query_invalid()), }; let direction = match request.direction.as_str() { "ascending" => ksp_store_lib::RawSortDirection::Ascending, "descending" => ksp_store_lib::RawSortDirection::Descending, _ => return std::result::Result::Err(store_query_invalid()), }; let network = store.runtime_snapshot().network().clone(); return std::result::Result::Ok(ksp_store_lib::RawTransactionInspectionQuery::new(network, slots, direction, page)); } fn transaction_reference_from_request(store: &ksp_store_lib::Store, signature_text: &str) -> ksp_core_lib::Result { let signature = decode_hex_64(signature_text); let signature = match signature { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let network = store.runtime_snapshot().network().clone(); return std::result::Result::Ok(ksp_store_lib::RawTransactionReference::new(network, ksp_store_lib::RawTransactionSignature::new(signature))); } async fn transaction_detail_with_payload( store: &ksp_store_lib::Store, reference: &ksp_store_lib::RawTransactionReference, retention: ksp_store_lib::RawRetentionState, ) -> ksp_core_lib::Result { let transaction = ksp_store_lib::RawTransactionRead::get_raw_transaction(store, reference).await; let transaction = match transaction { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_INVALID, "Store Desk transaction retention state and payload availability disagree", )); }, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let payload = transaction.payload(); let payload_bytes = payload.bytes(); let preview_len = std::cmp::min(payload_bytes.len(), TRANSACTION_DETAIL_PREVIEW_BYTES); let preview = encode_hex(&payload_bytes[..preview_len]); return std::result::Result::Ok(crate::StoreTransactionDetailDto { block_time_unix_millis_decimal: transaction.block_time().map(|value| return value.unix_millis().to_string()), content_hash: encode_hex(payload.content_hash().as_bytes()), format_id: payload.format_id().as_str().to_owned(), format_version: payload.format_version(), payload_preview_hex: std::option::Option::Some(preview), payload_preview_truncated: payload_bytes.len() > preview_len, payload_size_decimal: std::option::Option::Some(payload.byte_len().to_string()), retention_state: retention_state_code(retention).to_owned(), signature: encode_hex(reference.signature().as_bytes()), slot_decimal: transaction.slot().to_string(), }); } async fn transaction_detail_from_tombstone( store: &ksp_store_lib::Store, reference: &ksp_store_lib::RawTransactionReference, ) -> ksp_core_lib::Result { let tombstone = ksp_store_lib::RawTransactionRetentionRead::get_raw_transaction_tombstone(store, reference).await; let tombstone = match tombstone { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_INVALID, "Store Desk purged transaction is missing its durable tombstone", )); }, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::StoreTransactionDetailDto { block_time_unix_millis_decimal: std::option::Option::None, content_hash: encode_hex(tombstone.content_hash().as_bytes()), format_id: tombstone.format_id().as_str().to_owned(), format_version: tombstone.format_version(), payload_preview_hex: std::option::Option::None, payload_preview_truncated: false, payload_size_decimal: std::option::Option::None, retention_state: retention_state_code(ksp_store_lib::RawRetentionState::Purged).to_owned(), signature: encode_hex(reference.signature().as_bytes()), slot_decimal: tombstone.slot().to_string(), }); } fn transaction_row_dto(summary: &ksp_store_lib::RawTransactionSummary) -> crate::StoreTransactionRowDto { return crate::StoreTransactionRowDto { block_time_unix_millis_decimal: summary.block_time().map(|value| return value.unix_millis().to_string()), content_hash: encode_hex(summary.content_hash().as_bytes()), format_id: summary.format_id().as_str().to_owned(), format_version: summary.format_version(), payload_size_decimal: summary.payload_size_bytes().map(|value| return value.to_string()), retention_state: retention_state_code(summary.retention_state()).to_owned(), signature: encode_hex(summary.reference().signature().as_bytes()), slot_decimal: summary.slot().to_string(), }; } fn retention_state_code(state: ksp_store_lib::RawRetentionState) -> &'static str { return match state { ksp_store_lib::RawRetentionState::Full => "full", ksp_store_lib::RawRetentionState::Compacted => "compacted", ksp_store_lib::RawRetentionState::Archived => "archived", ksp_store_lib::RawRetentionState::Purged => "purged", _ => "unknown", }; } fn parse_optional_pubkey(value: std::option::Option<&str>) -> ksp_core_lib::Result> { return match value { std::option::Option::Some(text) => match text.parse::() { std::result::Result::Ok(parsed) => std::result::Result::Ok(std::option::Option::Some(parsed)), std::result::Result::Err(_) => std::result::Result::Err(store_query_invalid()), }, std::option::Option::None => std::result::Result::Ok(std::option::Option::None), }; } fn parse_optional_decimal_u64(value: std::option::Option<&str>) -> ksp_core_lib::Result> { return match value { std::option::Option::Some(text) => match text.parse::() { std::result::Result::Ok(parsed) => std::result::Result::Ok(std::option::Option::Some(parsed)), std::result::Result::Err(_) => std::result::Result::Err(store_query_invalid()), }, std::option::Option::None => std::result::Result::Ok(std::option::Option::None), }; } fn decode_hex_32(value: &str) -> ksp_core_lib::Result<[u8; 32]> { if value.len() != 64 || !value.is_ascii() { return std::result::Result::Err(store_query_invalid()); } let source = value.as_bytes(); let mut decoded = [0_u8; 32]; for index in 0..32 { let high = decode_hex_nibble(source[index * 2]); let low = decode_hex_nibble(source[index * 2 + 1]); let (high, low) = match (high, low) { (std::option::Option::Some(high), std::option::Option::Some(low)) => (high, low), _ => return std::result::Result::Err(store_query_invalid()), }; decoded[index] = (high << 4) | low; } return std::result::Result::Ok(decoded); } fn decode_hex_64(value: &str) -> ksp_core_lib::Result<[u8; 64]> { if value.len() != 128 || !value.is_ascii() { return std::result::Result::Err(store_query_invalid()); } let source = value.as_bytes(); let mut decoded = [0_u8; 64]; for index in 0..64 { let high = decode_hex_nibble(source[index * 2]); let low = decode_hex_nibble(source[index * 2 + 1]); let (high, low) = match (high, low) { (std::option::Option::Some(high), std::option::Option::Some(low)) => (high, low), _ => return std::result::Result::Err(store_query_invalid()), }; decoded[index] = (high << 4) | low; } return std::result::Result::Ok(decoded); } fn decode_hex_nibble(value: u8) -> std::option::Option { return match value { b'0'..=b'9' => std::option::Option::Some(value - b'0'), b'a'..=b'f' => std::option::Option::Some(value - b'a' + 10), b'A'..=b'F' => std::option::Option::Some(value - b'A' + 10), _ => std::option::Option::None, }; } fn encode_hex(bytes: &[u8]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut encoded = String::with_capacity(bytes.len() * 2); for byte in bytes { let value = *byte; encoded.push(char::from(HEX[usize::from(value >> 4)])); encoded.push(char::from(HEX[usize::from(value & 0x0f)])); } return encoded; } fn store_account_not_found() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_ACCOUNT_NOT_FOUND, "Store Desk account state was not found"); } fn store_query_invalid() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_QUERY_INVALID, "Store Desk RAW inspection/detail request is invalid"); } fn store_runtime_unavailable() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_RUNTIME_UNAVAILABLE, "Store Desk Store runtime is unavailable"); } fn store_transaction_not_found() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_TRANSACTION_NOT_FOUND, "Store Desk transaction was not found"); } /// Resolves the composite Store target, opens Store through the facade and captures one initial readiness probe. pub(crate) async fn initialize_store(management: &ksp_config_lib::ConfigManagement) -> crate::StoreStartup { let environment = ksp_config_lib::ConfigEnvironment::load(); let environment = match environment { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error), }; let composite = crate::load_store_desk_composite(management); let composite = match composite { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error), }; let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE); let profile = match profile { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return unavailable_startup( std::option::Option::Some(composite.profile_id().to_owned()), std::option::Option::None, std::option::Option::None, error, ); }, }; let resolved = management.engine().resolve_store_config_profile(&profile, &environment); let resolved = match resolved { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return unavailable_startup( std::option::Option::Some(profile.profile_id().to_owned()), std::option::Option::None, std::option::Option::None, error, ); }, }; let profile_id = resolved.profile_id().to_owned(); let backend_kind = resolved.settings().backend_kind().code().to_owned(); let network = resolved.settings().network().as_str().to_owned(); let store = ksp_store_lib::Store::open(resolved.into_settings()).await; let store = match store { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return unavailable_startup( std::option::Option::Some(profile_id), std::option::Option::Some(backend_kind), std::option::Option::Some(network), error, ); }, }; let health = store.health().await; ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_STORE, store_profile = profile_id.as_str(), backend = backend_kind.as_str(), network = network.as_str(), health_state = store_health_state_code(health.state()), pending_migration_count = health.pending_migration_count(), "initialized Store Desk Store from composite-managed configuration" ); return crate::StoreStartup { backend_kind: std::option::Option::Some(backend_kind), diagnostic: non_ready_health_diagnostic(&health), network: std::option::Option::Some(network), profile_id: std::option::Option::Some(profile_id.clone()), runtime: std::option::Option::Some(crate::StoreRuntime { profile_id, store: tokio::sync::RwLock::new(std::option::Option::Some(store)) }), }; } fn non_ready_health_diagnostic(health: &ksp_store_lib::StoreHealthSnapshot) -> std::option::Option { if matches!(health.state(), ksp_store_lib::StoreHealthState::Ready) { return std::option::Option::None; } let code = health.last_error_code(); return match code { std::option::Option::Some(value) => std::option::Option::Some(crate::CommandErrorDto { code: value.code().to_owned(), domain: value.domain().to_owned(), message: "Store health probe did not prove readiness".to_owned(), }), std::option::Option::None => std::option::Option::Some(crate::CommandErrorDto { code: "store_not_ready".to_owned(), domain: "store_desk".to_owned(), message: "Store health probe did not prove readiness".to_owned(), }), }; } fn store_health_state_code(state: ksp_store_lib::StoreHealthState) -> &'static str { return match state { ksp_store_lib::StoreHealthState::Ready => "ready", ksp_store_lib::StoreHealthState::NotReady => "not_ready", _ => "unknown", }; } fn unavailable_startup( profile_id: std::option::Option, backend_kind: std::option::Option, network: std::option::Option, error: ksp_core_lib::Error, ) -> crate::StoreStartup { let diagnostic = crate::CommandErrorDto::from_error(&error); ksp_logging_lib::warn!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_STORE, error_domain = diagnostic.domain.as_str(), error_code = diagnostic.code.as_str(), "Store Desk Store readiness is unavailable; keeping desktop shell available" ); return crate::StoreStartup { backend_kind, diagnostic: std::option::Option::Some(diagnostic), network, profile_id, runtime: std::option::Option::None, }; }