v0.1.0-pre.076
This commit is contained in:
@@ -1,8 +1,22 @@
|
||||
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# CHANGELOG — kb-app-demo-desktop
|
||||
|
||||
## 0.1.0-pre.076
|
||||
|
||||
- correction de l’architecture Tauri : `tauri.rs` reste l’unique propriétaire des `#[tauri::command]` et délègue via la façade `crate::*` ;
|
||||
- suppression des modules intermédiaires `tauri_*` ajoutés à tort ;
|
||||
- suppression de 43 exports `pub(crate)` qui ne franchissaient aucune frontière de module ;
|
||||
- renommage des fonctions `*_inner` en noms fonctionnels directs ;
|
||||
- suppression du helper IPC utilisé une seule fois et simplification de l’initialisation de l’état ;
|
||||
- complétion des rustdoc manquantes dans `lib.rs` ;
|
||||
- correction de l’audit statique pour contrôler la minceur sémantique de `tauri.rs` plutôt qu’un seuil arbitraire de lignes ;
|
||||
- réduction de `tauri.rs` à l’assemblage runtime et aux wrappers strictement nécessaires ;
|
||||
- répartition des commandes Tauri par domaine dans des modules dédiés ;
|
||||
- validation Mainnet de la persistance de session WebSocket après fermeture et réouverture de `demo_ws` ;
|
||||
- ajout du scénario borné de backfill `mainnet_research`.
|
||||
|
||||
## 0.1.0-pre.075
|
||||
|
||||
- ajout de l’audit statique Tauri/frontend et du scénario de validation du cycle de vie WebSocket ;
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
<!-- file: kb-app-demo-desktop/TODO.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# TODO — kb-app-demo-desktop
|
||||
|
||||
## Bloquants avant alignement `0.4.6`
|
||||
|
||||
- [ ] Architecture - déplacer hors de `tauri.rs` l’orchestration pipeline, stockage et exécution encore substantielle.
|
||||
- [ ] Architecture - conserver dans `tauri.rs` uniquement les wrappers Tauri, l’assemblage runtime et les adaptations minimales.
|
||||
|
||||
- [ ] Validation - vérifier intégralement la synchronisation des adaptateurs Tauri et des payloads TS-RS avec les APIs réutilisables.
|
||||
- [ ] Validation - couvrir explicitement les cycles d’ouverture, fermeture et réouverture des fenêtres concernées.
|
||||
- [ ] WebSocket - valider que la fermeture de `demo_ws` ne ferme pas une session WebSocket active.
|
||||
- [ ] WebSocket - valider que la réouverture de `demo_ws` restaure l’état courant de la session WebSocket.
|
||||
|
||||
## Entre `0.4.6` et `0.4.7`
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_backfill.rs
|
||||
// version: 12
|
||||
// version: 15
|
||||
|
||||
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
|
||||
|
||||
@@ -227,7 +227,103 @@ impl std::ops::Drop for crate::DemoBackfillRunGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_role_options(
|
||||
pub(crate) fn demo_backfill_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
||||
let running = state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
state
|
||||
.demo_backfill_cancel_requested()
|
||||
.store(true, std::sync::atomic::Ordering::Release);
|
||||
return running;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_backfill_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> crate::DemoBackfillOptionsPayload {
|
||||
let roles = build_role_options(state.http_pool().snapshot());
|
||||
let default_role = if roles.iter().any(|item| return item.role == "history_backfill") {
|
||||
std::option::Option::Some("history_backfill".to_string())
|
||||
} else {
|
||||
roles.first().map(|item| return item.role.clone())
|
||||
};
|
||||
let programs = kb_program_ids::registered_program_ids()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return crate::DemoBackfillProgramOption {
|
||||
code: entry.code().to_string(),
|
||||
program_id: entry.program_id().to_string(),
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
return crate::DemoBackfillOptionsPayload {
|
||||
roles,
|
||||
programs,
|
||||
default_role,
|
||||
default_commitment: "confirmed".to_string(),
|
||||
default_page_size: 100,
|
||||
default_max_pages: 20,
|
||||
default_max_concurrent_requests: 4,
|
||||
default_max_retries: 2,
|
||||
running: state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_backfill_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoBackfillRequest,
|
||||
) -> std::result::Result<crate::DemoBackfillSummaryPayload, std::string::String> {
|
||||
let acquire_result = state.demo_backfill_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
return std::result::Result::Err("a backfill campaign is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoBackfillRunGuard { running: state.demo_backfill_running() };
|
||||
state
|
||||
.demo_backfill_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let pipeline_request = match build_pipeline_request(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile = state.active_profile();
|
||||
let store_options = match kb_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
profile.database.postgres.connect_timeout_ms,
|
||||
false,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match kb_store::PostgresStore::connect(store_options).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let observer = crate::DemoBackfillObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_backfill_cancel_requested(),
|
||||
};
|
||||
let summary = match kb_pipeline::execute_http_backfill(
|
||||
state.http_pool(),
|
||||
&store,
|
||||
&pipeline_request,
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
fn build_role_options(
|
||||
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
|
||||
) -> std::vec::Vec<crate::DemoBackfillRoleOption> {
|
||||
let mut role_methods = std::collections::BTreeMap::<
|
||||
@@ -266,7 +362,7 @@ pub(crate) fn build_role_options(
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn build_demo_backfill_pipeline_request(
|
||||
fn build_pipeline_request(
|
||||
request: DemoBackfillRequest,
|
||||
) -> std::result::Result<kb_pipeline::BackfillRequest, std::string::String> {
|
||||
let source_result = match request.mode.trim() {
|
||||
@@ -385,9 +481,7 @@ fn address_source(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn demo_backfill_summary_payload(
|
||||
summary: kb_pipeline::BackfillSummary,
|
||||
) -> DemoBackfillSummaryPayload {
|
||||
fn summary_payload(summary: kb_pipeline::BackfillSummary) -> DemoBackfillSummaryPayload {
|
||||
return crate::DemoBackfillSummaryPayload {
|
||||
capture_session_id: summary.capture_session_id,
|
||||
filter_code: summary.filter_code,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_core_extraction.rs
|
||||
// version: 11
|
||||
// version: 13
|
||||
|
||||
//! Tauri commands and UI payloads for canonical transaction to core extraction.
|
||||
|
||||
@@ -194,7 +194,7 @@ pub(crate) fn demo_core_extraction_options_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_demo_core_extraction(
|
||||
pub(crate) async fn demo_core_extraction_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: &crate::AppState,
|
||||
request: crate::DemoCoreExtractionRequest,
|
||||
@@ -216,7 +216,7 @@ pub(crate) async fn execute_demo_core_extraction(
|
||||
state
|
||||
.demo_core_extraction_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let pipeline_request_result = crate::build_demo_core_extraction_pipeline_request(request);
|
||||
let pipeline_request_result = build_pipeline_request(request);
|
||||
let pipeline_request = match pipeline_request_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -236,10 +236,10 @@ pub(crate) async fn execute_demo_core_extraction(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(crate::demo_core_extraction_summary_payload(summary));
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_demo_core_extraction(state: &crate::AppState) -> bool {
|
||||
pub(crate) fn demo_core_extraction_cancel(state: &crate::AppState) -> bool {
|
||||
let running = state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
state
|
||||
.demo_core_extraction_cancel_requested()
|
||||
@@ -247,7 +247,7 @@ pub(crate) fn cancel_demo_core_extraction(state: &crate::AppState) -> bool {
|
||||
return running;
|
||||
}
|
||||
|
||||
pub(crate) fn build_demo_core_extraction_pipeline_request(
|
||||
fn build_pipeline_request(
|
||||
request: crate::DemoCoreExtractionRequest,
|
||||
) -> std::result::Result<kb_pipeline::CoreExtractionRequest, std::string::String> {
|
||||
let source_result = match request.mode.trim() {
|
||||
@@ -308,6 +308,25 @@ pub(crate) fn build_demo_core_extraction_pipeline_request(
|
||||
return std::result::Result::Ok(pipeline_request);
|
||||
}
|
||||
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline::CoreExtractionSummary,
|
||||
) -> crate::DemoCoreExtractionSummaryPayload {
|
||||
return crate::DemoCoreExtractionSummaryPayload {
|
||||
processor_version: summary.processor_version,
|
||||
selected: summary.selected,
|
||||
started: summary.started,
|
||||
completed: summary.completed,
|
||||
skipped: summary.skipped,
|
||||
extracted: summary.extracted,
|
||||
failed: summary.failed,
|
||||
cancelled_candidates: summary.cancelled_candidates,
|
||||
not_started: summary.not_started,
|
||||
cancelled: summary.cancelled,
|
||||
started_at: summary.started_at,
|
||||
finished_at: summary.finished_at,
|
||||
};
|
||||
}
|
||||
|
||||
fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
|
||||
let source = match text {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -327,25 +346,6 @@ fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::strin
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_core_extraction_summary_payload(
|
||||
summary: kb_pipeline::CoreExtractionSummary,
|
||||
) -> crate::DemoCoreExtractionSummaryPayload {
|
||||
return crate::DemoCoreExtractionSummaryPayload {
|
||||
processor_version: summary.processor_version,
|
||||
selected: summary.selected,
|
||||
started: summary.started,
|
||||
completed: summary.completed,
|
||||
skipped: summary.skipped,
|
||||
extracted: summary.extracted,
|
||||
failed: summary.failed,
|
||||
cancelled_candidates: summary.cancelled_candidates,
|
||||
not_started: summary.not_started,
|
||||
cancelled: summary.cancelled,
|
||||
started_at: summary.started_at,
|
||||
finished_at: summary.finished_at,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
|
||||
// version: 33
|
||||
// version: 37
|
||||
|
||||
//! Tauri commands and UI payloads for contextual instruction decode replay.
|
||||
|
||||
@@ -357,7 +357,289 @@ impl std::ops::Drop for crate::DemoDecodeReplayRunGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_active_campaign(
|
||||
/// Returns available decoders and default replay bounds.
|
||||
pub(crate) fn demo_decode_replay_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> crate::DemoDecodeReplayOptionsPayload {
|
||||
let decoders = available_decoders();
|
||||
let decoder_options = decoders
|
||||
.iter()
|
||||
.map(|decoder| {
|
||||
let identity = decoder.identity();
|
||||
return crate::DemoDecodeReplayDecoderOption {
|
||||
name: identity.name,
|
||||
version: identity.version,
|
||||
program_ids: decoder
|
||||
.surfaces()
|
||||
.iter()
|
||||
.map(|surface| return surface.program_id.to_string())
|
||||
.collect(),
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let materializers = available_materializers();
|
||||
let materializer_names = materializers
|
||||
.iter()
|
||||
.map(|materializer| {
|
||||
let identity = materializer.identity();
|
||||
return format!("{}@{}", identity.name, identity.version);
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let programs = kb_program_ids::registered_program_ids()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return crate::DemoDecodeReplayProgramOption {
|
||||
code: entry.code().to_string(),
|
||||
program_id: entry.program_id().to_string(),
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "load_options",
|
||||
pipeline_version = kb_pipeline::DECODE_PIPELINE_VERSION,
|
||||
decoder_count = decoder_options.len(),
|
||||
materializer_names = ?materializer_names,
|
||||
default_limit = 100_u32,
|
||||
default_max_concurrent_inputs = 4_u32,
|
||||
running,
|
||||
"return contextual decode replay options"
|
||||
);
|
||||
return crate::DemoDecodeReplayOptionsPayload {
|
||||
pipeline_version: kb_pipeline::DECODE_PIPELINE_VERSION.to_string(),
|
||||
decoders: decoder_options,
|
||||
materializer_names,
|
||||
programs,
|
||||
default_limit: 100,
|
||||
default_max_concurrent_inputs: 4,
|
||||
running,
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes one bounded contextual decode and optional materialization campaign.
|
||||
pub(crate) async fn demo_decode_replay_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoDecodeReplayRequest,
|
||||
) -> std::result::Result<crate::DemoDecodeReplaySummaryPayload, std::string::String> {
|
||||
let campaign_id = kb_pipeline::new_decode_campaign_id();
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute",
|
||||
campaign_id = %campaign_id,
|
||||
instruction_state = %request.instruction_state,
|
||||
signature_line_count = optional_line_count(&request.signatures_text),
|
||||
program_id = ?request.program_id,
|
||||
instruction_paths_text = ?request.instruction_paths_text,
|
||||
decoder_names = ?request.decoder_names,
|
||||
limit = request.limit,
|
||||
max_concurrent_inputs = request.max_concurrent_inputs,
|
||||
all_compatible = request.all_compatible,
|
||||
force_replay = request.force_replay,
|
||||
force_replay_all_matching = request.force_replay_all_matching,
|
||||
materialize_after_decode = request.materialize_after_decode,
|
||||
"received contextual decode replay command"
|
||||
);
|
||||
let acquire_result = state.demo_decode_replay_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
tracing::warn!(target: crate::TRACING_TARGET, action = "execute", campaign_id = %campaign_id, accepted = false, reason = "already_running", "reject contextual decode replay command");
|
||||
return std::result::Result::Err(
|
||||
"a contextual decode replay campaign is already running".to_string(),
|
||||
);
|
||||
}
|
||||
let _run_guard = crate::DemoDecodeReplayRunGuard {
|
||||
running: state.demo_decode_replay_running(),
|
||||
campaign_id: state.demo_decode_replay_campaign_id(),
|
||||
};
|
||||
let register_result =
|
||||
register_active_campaign(state.demo_decode_replay_campaign_id(), campaign_id.as_str());
|
||||
if let std::result::Result::Err(error) = register_result {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "register_campaign", campaign_id = %campaign_id, error = %error, "cannot lock active contextual decode campaign slot");
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
state
|
||||
.demo_decode_replay_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let pipeline_request_result = build_pipeline_request(request, campaign_id.clone());
|
||||
let pipeline_request = match pipeline_request_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "build_request", campaign_id = %campaign_id, error = %error, "cannot build contextual decode replay request");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute_pipeline",
|
||||
campaign_id = %pipeline_request.campaign_id,
|
||||
signature_count = pipeline_request.selection.signatures.len(),
|
||||
signature_sample = ?text_sample(pipeline_request.selection.signatures.as_slice(), 5),
|
||||
processing_states = ?pipeline_request.selection.processing_states,
|
||||
min_slot = ?pipeline_request.selection.min_slot,
|
||||
max_slot = ?pipeline_request.selection.max_slot,
|
||||
program_ids = ?pipeline_request.selection.program_ids,
|
||||
instruction_paths = ?pipeline_request.selection.instruction_paths,
|
||||
incomplete_signatures = pipeline_request.selection.incomplete_signatures,
|
||||
limit = pipeline_request.selection.limit,
|
||||
decoder_names = ?pipeline_request.decoder_names,
|
||||
dispatch_policy = ?pipeline_request.dispatch_policy,
|
||||
max_concurrent_inputs = pipeline_request.max_concurrent_inputs,
|
||||
force_replay = pipeline_request.force_replay,
|
||||
force_replay_all_matching = pipeline_request.force_replay_all_matching,
|
||||
materialize_after_decode = pipeline_request.materialize_after_decode,
|
||||
"start contextual decode replay pipeline"
|
||||
);
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "connect_store", campaign_id = %pipeline_request.campaign_id, error = %error, "cannot connect contextual decode replay store");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let decoders = available_decoders();
|
||||
let materializers = available_materializers();
|
||||
let observer = crate::DemoDecodeReplayObserver {
|
||||
app_handle,
|
||||
campaign_id: pipeline_request.campaign_id.clone(),
|
||||
cancel_requested: state.demo_decode_replay_cancel_requested(),
|
||||
};
|
||||
let summary_result = kb_pipeline::execute_decode_replay(
|
||||
&store,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await;
|
||||
let summary = match summary_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "execute_pipeline", campaign_id = %pipeline_request.campaign_id, error = %error, "contextual decode replay pipeline failed");
|
||||
return std::result::Result::Err(error.to_string());
|
||||
},
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute_pipeline",
|
||||
campaign_id = %summary.campaign_id,
|
||||
selected = summary.selected,
|
||||
started = summary.started,
|
||||
completed = summary.completed,
|
||||
unmatched = summary.unmatched,
|
||||
not_started = summary.not_started,
|
||||
failed_inputs = summary.failed_inputs,
|
||||
processing_error_inputs = summary.processing_error_inputs,
|
||||
cancelled = summary.cancelled,
|
||||
processors = ?summary.processors,
|
||||
"contextual decode replay command completed"
|
||||
);
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
/// Requests cooperative cancellation of the current decode replay campaign.
|
||||
pub(crate) fn demo_decode_replay_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
||||
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
state
|
||||
.demo_decode_replay_cancel_requested()
|
||||
.store(true, std::sync::atomic::Ordering::Release);
|
||||
let campaign_lock_result = state.demo_decode_replay_campaign_id().lock();
|
||||
let campaign_id = match campaign_lock_result {
|
||||
std::result::Result::Ok(active_campaign_id) => active_campaign_id.clone(),
|
||||
std::result::Result::Err(_) => std::option::Option::None,
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "cancel", campaign_id = ?campaign_id, running, cancellation_requested = true, "contextual decode replay cancellation command handled");
|
||||
return running;
|
||||
}
|
||||
|
||||
/// Loads read-only decode, materialization, ledger and coverage diagnostics.
|
||||
pub(crate) async fn demo_decode_replay_diagnostics(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoDecodeDiagnosticsPayload, std::string::String> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", coverage_limit = 500_u32, "load contextual decode replay diagnostics");
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let table_result = store.known_table_diagnostics().await;
|
||||
let all_tables = match table_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let selected_tables: std::vec::Vec<crate::DemoSqlTableSnapshot> = all_tables
|
||||
.iter()
|
||||
.filter(|table| {
|
||||
return table.table_name.starts_with("kb_sol_decode_")
|
||||
|| table.table_name.starts_with("kb_sol_mat_")
|
||||
|| table.table_name == "kb_sol_ops_processing_ledger";
|
||||
})
|
||||
.map(crate::table_snapshot_from_pg)
|
||||
.collect();
|
||||
let coverage_result = kb_store::DecodePipelineStore::list_decode_coverage_summary(
|
||||
&store,
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
500,
|
||||
)
|
||||
.await;
|
||||
let coverage = match coverage_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", table_count = selected_tables.len(), coverage_count = coverage.len(), "contextual decode replay diagnostics loaded");
|
||||
return std::result::Result::Ok(crate::DemoDecodeDiagnosticsPayload {
|
||||
tables: selected_tables,
|
||||
coverage: coverage.into_iter().map(coverage_payload).collect(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Loads a bounded journal of committed SPL Memo transaction annotations.
|
||||
pub(crate) async fn demo_decode_replay_annotations(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoTransactionAnnotationRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoTransactionAnnotationRow>, std::string::String> {
|
||||
let filter_result = kb_store::MaterializedEventFilter::new(
|
||||
std::option::Option::Some("materializer.transaction.annotations".to_string()),
|
||||
std::option::Option::Some("transaction_annotation".to_string()),
|
||||
request.signature_contains,
|
||||
request.limit,
|
||||
);
|
||||
let filter = match filter_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", signature_contains = ?filter.signature_contains, limit = filter.limit, "load bounded committed transaction annotation journal");
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rows_result =
|
||||
kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await;
|
||||
let rows = match rows_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mapped = annotation_payload(row);
|
||||
match mapped {
|
||||
std::result::Result::Ok(value) => output.push(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", row_count = output.len(), "bounded committed transaction annotation journal loaded");
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn register_active_campaign(
|
||||
campaign_slot: &std::sync::Mutex<std::option::Option<std::string::String>>,
|
||||
campaign_id: &str,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
@@ -374,8 +656,7 @@ pub(crate) fn register_active_campaign(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
pub(crate) fn available_materializers()
|
||||
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
|
||||
fn available_materializers() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
|
||||
return std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
|
||||
@@ -388,8 +669,7 @@ pub(crate) fn available_materializers()
|
||||
];
|
||||
}
|
||||
|
||||
pub(crate) fn available_decoders()
|
||||
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
|
||||
fn available_decoders() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
|
||||
return std::vec![
|
||||
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder),
|
||||
@@ -401,7 +681,7 @@ pub(crate) fn available_decoders()
|
||||
];
|
||||
}
|
||||
|
||||
pub(crate) fn build_demo_decode_replay_pipeline_request(
|
||||
fn build_pipeline_request(
|
||||
request: crate::DemoDecodeReplayRequest,
|
||||
campaign_id: std::string::String,
|
||||
) -> std::result::Result<kb_pipeline::DecodeReplayRequest, std::string::String> {
|
||||
@@ -474,7 +754,7 @@ pub(crate) fn build_demo_decode_replay_pipeline_request(
|
||||
return std::result::Result::Ok(pipeline_request);
|
||||
}
|
||||
|
||||
pub(crate) fn optional_line_count(value: &std::option::Option<std::string::String>) -> usize {
|
||||
fn optional_line_count(value: &std::option::Option<std::string::String>) -> usize {
|
||||
return match value {
|
||||
std::option::Option::Some(text) => {
|
||||
text.lines().filter(|line| return !line.trim().is_empty()).count()
|
||||
@@ -483,7 +763,7 @@ pub(crate) fn optional_line_count(value: &std::option::Option<std::string::Strin
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn text_sample(values: &[std::string::String], limit: usize) -> std::vec::Vec<&str> {
|
||||
fn text_sample(values: &[std::string::String], limit: usize) -> std::vec::Vec<&str> {
|
||||
return values.iter().take(limit).map(std::string::String::as_str).collect();
|
||||
}
|
||||
|
||||
@@ -538,7 +818,7 @@ fn split_lines(text: std::option::Option<&str>) -> std::vec::Vec<std::string::St
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_decode_replay_summary_payload(
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline::DecodeReplaySummary,
|
||||
) -> crate::DemoDecodeReplaySummaryPayload {
|
||||
return crate::DemoDecodeReplaySummaryPayload {
|
||||
@@ -576,7 +856,7 @@ pub(crate) fn demo_decode_replay_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn coverage_payload(
|
||||
fn coverage_payload(
|
||||
value: kb_store::DecodeCoverageSummaryRow,
|
||||
) -> crate::DemoDecodeCoverageSummaryPayload {
|
||||
return crate::DemoDecodeCoverageSummaryPayload {
|
||||
@@ -597,7 +877,7 @@ pub(crate) fn coverage_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn annotation_payload(
|
||||
fn annotation_payload(
|
||||
row: kb_store::MaterializedEventQueryRow,
|
||||
) -> std::result::Result<crate::DemoTransactionAnnotationRow, std::string::String> {
|
||||
if row.processor_name != "materializer.transaction.annotations"
|
||||
@@ -742,7 +1022,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
|
||||
let decoders = crate::available_decoders();
|
||||
let decoders = super::available_decoders();
|
||||
assert_eq!(decoders.len(), 7);
|
||||
let mut names = decoders
|
||||
.iter()
|
||||
@@ -764,7 +1044,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() {
|
||||
let materializers = crate::available_materializers();
|
||||
let materializers = super::available_materializers();
|
||||
assert_eq!(materializers.len(), 8);
|
||||
let names = materializers
|
||||
.iter()
|
||||
@@ -798,7 +1078,7 @@ mod tests {
|
||||
),
|
||||
] {
|
||||
let observation = ata_observation(entry);
|
||||
let owners = crate::available_materializers()
|
||||
let owners = super::available_materializers()
|
||||
.iter()
|
||||
.filter(|materializer| return materializer.accepts_observation(&observation))
|
||||
.map(|materializer| return materializer.identity().name)
|
||||
@@ -836,7 +1116,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn force_replay_requires_signatures_or_all_matching_authorization() {
|
||||
let result = crate::build_demo_decode_replay_pipeline_request(
|
||||
let result = super::build_pipeline_request(
|
||||
request(std::option::Option::None, true, false, false),
|
||||
"decode-test".to_string(),
|
||||
);
|
||||
@@ -845,7 +1125,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn force_replay_all_matching_is_forwarded() {
|
||||
let result = crate::build_demo_decode_replay_pipeline_request(
|
||||
let result = super::build_pipeline_request(
|
||||
request(std::option::Option::None, true, true, false),
|
||||
"decode-test".to_string(),
|
||||
);
|
||||
@@ -880,7 +1160,7 @@ mod tests {
|
||||
#[test]
|
||||
fn register_active_campaign_sets_slot_without_exposing_guard() {
|
||||
let campaign_id = std::sync::Mutex::new(std::option::Option::None);
|
||||
let result = crate::register_active_campaign(&campaign_id, "decode-test");
|
||||
let result = super::register_active_campaign(&campaign_id, "decode-test");
|
||||
assert!(result.is_ok());
|
||||
let lock_result = campaign_id.lock();
|
||||
let active_campaign_id = match lock_result {
|
||||
@@ -894,10 +1174,7 @@ mod tests {
|
||||
fn incomplete_signature_state_uses_actionable_states_and_expansion() {
|
||||
let mut request = request(std::option::Option::None, false, false, true);
|
||||
request.instruction_state = "incomplete_signatures".to_string();
|
||||
let result = crate::build_demo_decode_replay_pipeline_request(
|
||||
request,
|
||||
"decode-incomplete".to_string(),
|
||||
);
|
||||
let result = super::build_pipeline_request(request, "decode-incomplete".to_string());
|
||||
let pipeline_request = match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("request mapping failed: {error}"),
|
||||
@@ -944,7 +1221,7 @@ mod tests {
|
||||
created_at: "created".to_string(),
|
||||
updated_at: "updated".to_string(),
|
||||
};
|
||||
let result = crate::annotation_payload(row);
|
||||
let result = super::annotation_payload(row);
|
||||
let mapped = match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("annotation mapping failed: {error}"),
|
||||
@@ -971,6 +1248,6 @@ mod tests {
|
||||
created_at: "created".to_string(),
|
||||
updated_at: "updated".to_string(),
|
||||
};
|
||||
assert!(crate::annotation_payload(row).is_err());
|
||||
assert!(super::annotation_payload(row).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,6 @@ pub(crate) struct DemoExecutionDevnetStoreReadinessPayload {
|
||||
pub(crate) expected_tables: u32,
|
||||
}
|
||||
|
||||
/// Converts one collection length to the bounded UI counter type.
|
||||
fn bounded_u32(value: usize) -> u32 {
|
||||
return match u32::try_from(value) {
|
||||
std::result::Result::Ok(converted) => converted,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts the reusable scenario readiness report to the desktop TS-RS payload.
|
||||
pub(crate) fn devnet_store_readiness_payload(
|
||||
readiness: kb_pipeline_demo_scenarios::DevnetProfileStoreReadiness,
|
||||
@@ -48,3 +40,24 @@ pub(crate) fn devnet_store_readiness_payload(
|
||||
expected_tables: bounded_u32(readiness.expected_tables),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes a diagnostic value to indented JSON without panicking.
|
||||
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
return match serde_json::to_string_pretty(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
serde_json::json!({"serializationError": error.to_string()}).to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts one collection length to the bounded UI counter type.
|
||||
fn bounded_u32(value: usize) -> u32 {
|
||||
return match u32::try_from(value) {
|
||||
std::result::Result::Ok(converted) => converted,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
|
||||
// version: 8
|
||||
// version: 12
|
||||
|
||||
//! Tauri adapter for bounded Solana Core execution on Devnet.
|
||||
|
||||
@@ -368,7 +368,263 @@ impl std::ops::Drop for crate::DemoExecutionSolanaCoreRunGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn devnet_profile_options(
|
||||
pub(crate) fn select_devnet_profile(
|
||||
config: &kb_config::AppConfig,
|
||||
profile_name: &str,
|
||||
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
|
||||
return kb_pipeline_demo_scenarios::resolve_demo_devnet_profile(
|
||||
config,
|
||||
std::option::Option::Some(profile_name),
|
||||
)
|
||||
.map_err(|error| return error.to_string());
|
||||
}
|
||||
|
||||
pub(crate) fn execution_cluster_code(
|
||||
cluster: kb_lib::ExApiExecutionCluster,
|
||||
) -> std::string::String {
|
||||
return match cluster {
|
||||
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn confirmation_status_code(
|
||||
status: kb_lib::ExApiExecutionConfirmationStatus,
|
||||
) -> std::string::String {
|
||||
return match status {
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile.
|
||||
pub(crate) async fn demo_execution_devnet_prepare_profile(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
profile_name: std::string::String,
|
||||
) -> std::result::Result<crate::DemoExecutionDevnetStoreReadinessPayload, std::string::String> {
|
||||
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let readiness =
|
||||
match kb_pipeline_demo_scenarios::prepare_demo_devnet_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(crate::devnet_store_readiness_payload(readiness));
|
||||
}
|
||||
|
||||
/// Returns compatible Devnet profiles and conservative defaults.
|
||||
pub(crate) fn demo_execution_solana_core_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> crate::DemoExecutionSolanaCoreOptionsPayload {
|
||||
let profiles = devnet_profile_options(state.app_config());
|
||||
let default_profile_name = profiles.first().map(|profile| return profile.name.clone());
|
||||
return crate::DemoExecutionSolanaCoreOptionsPayload {
|
||||
profiles,
|
||||
default_profile_name,
|
||||
default_transfer_lamports: 1_000_000,
|
||||
running: state
|
||||
.demo_execution_solana_core_running()
|
||||
.load(std::sync::atomic::Ordering::Acquire),
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a disposable recipient public key without exposing its private key.
|
||||
pub(crate) fn demo_execution_solana_core_generate_recipient()
|
||||
-> std::result::Result<crate::DemoExecutionSolanaCoreGeneratedRecipientPayload, std::string::String>
|
||||
{
|
||||
let alias = match kb_wallet::WalletAlias::parse("demo-devnet-recipient") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let wallet = kb_wallet::TemporaryWallet::generate(alias);
|
||||
return std::result::Result::Ok(crate::DemoExecutionSolanaCoreGeneratedRecipientPayload {
|
||||
public_key: wallet.public_key(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Executes one bounded System transfer simulation or Devnet submission.
|
||||
pub(crate) async fn demo_execution_solana_core_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoExecutionSolanaCoreRequest,
|
||||
) -> std::result::Result<crate::DemoExecutionSolanaCoreSummaryPayload, std::string::String> {
|
||||
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
return std::result::Result::Err("a Solana Core execution is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
||||
running: state.demo_execution_solana_core_running(),
|
||||
};
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let initialize_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = initialize_result {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSystemTransferRequest::new(
|
||||
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
kb_lib::MdPubkey(request.recipient.trim().to_string()),
|
||||
request.lamports,
|
||||
);
|
||||
pipeline_request.airdrop_lamports = request.airdrop_lamports;
|
||||
pipeline_request.submit = request.submit;
|
||||
pipeline_request.operator_confirmed = request.operator_confirmed;
|
||||
pipeline_request.post_validation_max_retries = 20;
|
||||
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
|
||||
pipeline_request.materialize_after_decode = request.materialize_after_decode;
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
||||
std::vec![std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
|
||||
if request.materialize_after_decode {
|
||||
std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer,),
|
||||
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtStakingMaterializer),
|
||||
]
|
||||
} else {
|
||||
std::vec::Vec::new()
|
||||
};
|
||||
let observer = crate::DemoExecutionSolanaCoreObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match kb_pipeline_demo_scenarios::execute_devnet_system_transfer(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) async fn demo_execution_spl_memo_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoExecutionMemoRequest,
|
||||
) -> std::result::Result<crate::DemoExecutionMemoSummaryPayload, std::string::String> {
|
||||
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
||||
running: state.demo_execution_solana_core_running(),
|
||||
};
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new(
|
||||
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
request.message,
|
||||
);
|
||||
pipeline_request.include_wallet_as_memo_signer = request.include_wallet_as_memo_signer;
|
||||
pipeline_request.submit = request.submit;
|
||||
pipeline_request.operator_confirmed = request.operator_confirmed;
|
||||
pipeline_request.post_validation_max_retries = 20;
|
||||
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
||||
std::vec![std::sync::Arc::new(kb_lib::DcSplMemoDecoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
|
||||
std::vec![std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer,)];
|
||||
let observer = crate::DemoExecutionSolanaCoreObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match kb_pipeline_demo_scenarios::execute_devnet_memo(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(memo_summary_payload(summary));
|
||||
}
|
||||
|
||||
/// Requests cooperative cancellation before the next execution stage.
|
||||
pub(crate) fn demo_execution_solana_core_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
||||
let running = state
|
||||
.demo_execution_solana_core_running()
|
||||
.load(std::sync::atomic::Ordering::Acquire);
|
||||
if !running {
|
||||
return false;
|
||||
}
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(true, std::sync::atomic::Ordering::Release);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn devnet_profile_options(
|
||||
config: &kb_config::AppConfig,
|
||||
) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> {
|
||||
let mut output = std::vec::Vec::new();
|
||||
@@ -391,18 +647,7 @@ pub(crate) fn devnet_profile_options(
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn select_devnet_profile(
|
||||
config: &kb_config::AppConfig,
|
||||
profile_name: &str,
|
||||
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
|
||||
return kb_pipeline_demo_scenarios::resolve_demo_devnet_profile(
|
||||
config,
|
||||
std::option::Option::Some(profile_name),
|
||||
)
|
||||
.map_err(|error| return error.to_string());
|
||||
}
|
||||
|
||||
pub(crate) fn demo_execution_solana_core_summary_payload(
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSystemTransferSummary,
|
||||
) -> crate::DemoExecutionSolanaCoreSummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
@@ -483,7 +728,7 @@ pub(crate) fn demo_execution_solana_core_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn demo_execution_solana_core_memo_summary_payload(
|
||||
fn memo_summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetMemoExecutionSummary,
|
||||
) -> crate::DemoExecutionMemoSummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
@@ -610,42 +855,6 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn execution_cluster_code(
|
||||
cluster: kb_lib::ExApiExecutionCluster,
|
||||
) -> std::string::String {
|
||||
return match cluster {
|
||||
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn confirmation_status_code(
|
||||
status: kb_lib::ExApiExecutionConfirmationStatus,
|
||||
) -> std::string::String {
|
||||
return match status {
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
return match serde_json::to_string_pretty(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
serde_json::json!({"serializationError": error.to_string()}).to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
@@ -655,7 +864,7 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
|
||||
};
|
||||
let profiles = crate::devnet_profile_options(&config);
|
||||
let profiles = super::devnet_profile_options(&config);
|
||||
assert_eq!(profiles.len(), 1);
|
||||
assert_eq!(profiles[0].name, "local_devnet");
|
||||
assert!(profiles[0].send_enabled);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_spl.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Dedicated Tauri window for bounded SPL execution on Devnet.
|
||||
|
||||
@@ -27,6 +27,45 @@ pub(crate) struct DevnetSplValidationScenarioPayload {
|
||||
pub required_fixture_variables: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
|
||||
pub(crate) fn demo_execution_spl_validation_scenarios()
|
||||
-> std::vec::Vec<crate::DevnetSplValidationScenarioPayload> {
|
||||
return kb_pipeline_demo_scenarios::devnet_spl_validation_scenarios()
|
||||
.into_iter()
|
||||
.map(|scenario| {
|
||||
return crate::DevnetSplValidationScenarioPayload {
|
||||
id: scenario.id,
|
||||
label: scenario.label,
|
||||
family: match scenario.family {
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Public => {
|
||||
"token_2022_public".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::ElGamalRegistry => {
|
||||
"elgamal_registry".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Confidential => {
|
||||
"token_2022_confidential".to_string()
|
||||
},
|
||||
},
|
||||
operation_code: scenario.operation_code,
|
||||
implementation_status: match scenario.implementation_status {
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::Executable => {
|
||||
"executable".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::BackendReady => {
|
||||
"backend_ready".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::ProofFixtureRequired => {
|
||||
"proof_fixture_required".to_string()
|
||||
},
|
||||
},
|
||||
proof_required: scenario.proof_required,
|
||||
required_fixture_variables: scenario.required_fixture_variables,
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_http.rs
|
||||
// version: 9
|
||||
// version: 13
|
||||
|
||||
//! HTTP JSON-RPC demo commands.
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) struct DemoHttpExecutionPayload {
|
||||
pub(crate) response_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_http_execute_request_inner(
|
||||
pub(crate) async fn demo_http_execute_request(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoHttpRequest,
|
||||
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
|
||||
@@ -119,7 +119,7 @@ pub(crate) async fn demo_http_execute_request_inner(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let params = match build_demo_http_params(
|
||||
let params = match build_http_params(
|
||||
&method,
|
||||
request.first_arg.as_deref(),
|
||||
config_json_value,
|
||||
@@ -155,7 +155,22 @@ pub(crate) async fn demo_http_execute_request_inner(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn build_http_role_options(
|
||||
pub(crate) fn demo_http_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot> {
|
||||
return state.http_pool().snapshot();
|
||||
}
|
||||
|
||||
pub(crate) fn demo_http_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> crate::DemoHttpOptionsPayload {
|
||||
return crate::DemoHttpOptionsPayload {
|
||||
roles: build_http_role_options(state.http_pool().snapshot()),
|
||||
methods: build_http_method_options(),
|
||||
};
|
||||
}
|
||||
|
||||
fn build_http_role_options(
|
||||
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
|
||||
) -> std::vec::Vec<crate::DemoHttpRoleOption> {
|
||||
let mut roles = std::collections::BTreeMap::<
|
||||
@@ -183,7 +198,7 @@ pub(crate) fn build_http_role_options(
|
||||
return options;
|
||||
}
|
||||
|
||||
pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> {
|
||||
fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> {
|
||||
let methods = [
|
||||
("getAccountInfo", "Compte Solana", true, true),
|
||||
("getBalance", "Balance SOL", true, true),
|
||||
@@ -250,53 +265,7 @@ pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethod
|
||||
return options;
|
||||
}
|
||||
|
||||
fn parse_optional_json(
|
||||
config_json: std::option::Option<std::string::String>,
|
||||
) -> std::result::Result<std::option::Option<serde_json::Value>, std::string::String> {
|
||||
let config_text = match config_json {
|
||||
std::option::Option::Some(value) => value.trim().to_string(),
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if config_text.is_empty() {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
let parse_result = serde_json::from_str::<serde_json::Value>(&config_text);
|
||||
return match parse_result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(format!("invalid configJson: {error}"))
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_optional_params_json(
|
||||
params_json: std::option::Option<std::string::String>,
|
||||
) -> std::result::Result<std::option::Option<std::vec::Vec<serde_json::Value>>, std::string::String>
|
||||
{
|
||||
let params_text = match params_json {
|
||||
std::option::Option::Some(value) => value.trim().to_string(),
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if params_text.is_empty() {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
let parse_result = serde_json::from_str::<serde_json::Value>(¶ms_text);
|
||||
let value = match parse_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid paramsJson: {error}"));
|
||||
},
|
||||
};
|
||||
let array = match value.as_array() {
|
||||
std::option::Option::Some(array) => array.clone(),
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("paramsJson must be a JSON array".to_string());
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(std::option::Option::Some(array));
|
||||
}
|
||||
|
||||
fn build_demo_http_params(
|
||||
fn build_http_params(
|
||||
method: &str,
|
||||
first_arg: std::option::Option<&str>,
|
||||
config_json: std::option::Option<serde_json::Value>,
|
||||
@@ -352,6 +321,52 @@ fn build_demo_http_params(
|
||||
return std::result::Result::Ok(params);
|
||||
}
|
||||
|
||||
fn parse_optional_json(
|
||||
config_json: std::option::Option<std::string::String>,
|
||||
) -> std::result::Result<std::option::Option<serde_json::Value>, std::string::String> {
|
||||
let config_text = match config_json {
|
||||
std::option::Option::Some(value) => value.trim().to_string(),
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if config_text.is_empty() {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
let parse_result = serde_json::from_str::<serde_json::Value>(&config_text);
|
||||
return match parse_result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(format!("invalid configJson: {error}"))
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_optional_params_json(
|
||||
params_json: std::option::Option<std::string::String>,
|
||||
) -> std::result::Result<std::option::Option<std::vec::Vec<serde_json::Value>>, std::string::String>
|
||||
{
|
||||
let params_text = match params_json {
|
||||
std::option::Option::Some(value) => value.trim().to_string(),
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if params_text.is_empty() {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
let parse_result = serde_json::from_str::<serde_json::Value>(¶ms_text);
|
||||
let value = match parse_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid paramsJson: {error}"));
|
||||
},
|
||||
};
|
||||
let array = match value.as_array() {
|
||||
std::option::Option::Some(array) => array.clone(),
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("paramsJson must be a JSON array".to_string());
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(std::option::Option::Some(array));
|
||||
}
|
||||
|
||||
fn method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -> &'static str {
|
||||
return match method_class {
|
||||
kb_onchain_transport::HttpMethodClass::GeneralRpc => "GeneralRpc",
|
||||
@@ -364,7 +379,7 @@ fn method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -
|
||||
mod tests {
|
||||
#[test]
|
||||
fn raw_params_override_first_argument_and_config() {
|
||||
let params = super::build_demo_http_params(
|
||||
let params = super::build_http_params(
|
||||
"getBalance",
|
||||
std::option::Option::Some("ignored"),
|
||||
std::option::Option::Some(serde_json::json!({"commitment":"confirmed"})),
|
||||
@@ -375,7 +390,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn required_first_argument_fails_closed() {
|
||||
let params = super::build_demo_http_params(
|
||||
let params = super::build_http_params(
|
||||
"getBalance",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
@@ -386,7 +401,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn method_inventory_is_unique_and_classified() {
|
||||
let options = crate::build_http_method_options();
|
||||
let options = super::build_http_method_options();
|
||||
let mut names = std::collections::BTreeSet::new();
|
||||
for option in &options {
|
||||
assert!(names.insert(option.method.as_str()));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
|
||||
// version: 7
|
||||
// version: 11
|
||||
|
||||
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
|
||||
|
||||
@@ -157,7 +157,193 @@ pub(crate) struct DemoSplAtaJournalRow {
|
||||
pub(crate) payload_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) async fn load_profile_wallet(
|
||||
/// Derives the representative profile wallet ATA without exposing private key material.
|
||||
pub(crate) async fn demo_spl_ata_derive(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSplAtaDerivationRequest,
|
||||
) -> std::result::Result<crate::DemoSplAtaDerivationPayload, std::string::String> {
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet = match load_profile_wallet(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet_owner = wallet.public_key();
|
||||
let token_program = match parse_token_program(&request.token_program) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let associated_token_account =
|
||||
match derive_ata(wallet_owner.as_str(), request.mint.trim(), token_program.program_id()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSplAtaDerivationPayload {
|
||||
payer: wallet_owner.clone(),
|
||||
wallet_owner,
|
||||
token_program_id: token_program.program_id().to_string(),
|
||||
associated_token_account,
|
||||
});
|
||||
}
|
||||
|
||||
/// Executes one representative Create or CreateIdempotent ATA flow.
|
||||
pub(crate) async fn demo_execution_spl_ata_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoExecutionSplAtaRequest,
|
||||
) -> std::result::Result<crate::DemoExecutionSplAtaSummaryPayload, std::string::String> {
|
||||
let acquired = state.demo_execution_solana_core_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquired.is_err() {
|
||||
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
||||
running: state.demo_execution_solana_core_running(),
|
||||
};
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile_wallet = match load_profile_wallet(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if request.wallet_owner.trim() != profile_wallet.public_key() {
|
||||
return std::result::Result::Err(
|
||||
"the representative ATA panel requires the profile wallet as wallet owner".to_string(),
|
||||
);
|
||||
}
|
||||
let token_program = match parse_token_program(&request.token_program) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet_owner = kb_lib::MdPubkey(request.wallet_owner.trim().to_string());
|
||||
let mint = kb_lib::MdPubkey(request.mint.trim().to_string());
|
||||
let operation = match request.mode.as_str() {
|
||||
"create" => kb_lib::ExSplAssociatedTokenAccountOperation::Create {
|
||||
wallet_owner,
|
||||
mint,
|
||||
token_program,
|
||||
},
|
||||
"create_idempotent" => kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
|
||||
wallet_owner,
|
||||
mint,
|
||||
token_program,
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"ATA mode must be create or create_idempotent".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let mut pipeline_request =
|
||||
kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionRequest::new(
|
||||
format!("demo-spl-ata-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
operation,
|
||||
);
|
||||
pipeline_request.submit = request.submit;
|
||||
pipeline_request.operator_confirmed = request.operator_confirmed;
|
||||
pipeline_request.post_validation_max_retries = 20;
|
||||
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
|
||||
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> = std::vec![
|
||||
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcSplTokenDecoder),
|
||||
];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
||||
];
|
||||
let observer = crate::DemoExecutionSolanaCoreObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
crate::workspace_root_dir().as_path(),
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
/// Loads a bounded journal of ATA-owned lifecycle facts.
|
||||
pub(crate) async fn demo_spl_ata_journal(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSplAtaJournalRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSplAtaJournalRow>, std::string::String> {
|
||||
if request.limit == 0 || request.limit > 500 {
|
||||
return std::result::Result::Err("ATA journal limit must be between 1 and 500".to_string());
|
||||
}
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let filter = match kb_store::MaterializedEventFilter::new(
|
||||
std::option::Option::Some("materializer.token.accounts".to_string()),
|
||||
std::option::Option::None,
|
||||
request.signature_contains.clone(),
|
||||
500,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let mut output = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
if row.source_decoder_name != "spl.associated_token_account"
|
||||
|| !journal_matches(&row.payload_json, &request)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
output.push(journal_row(row));
|
||||
if output.len() >= request.limit as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
async fn load_profile_wallet(
|
||||
profile: &kb_config::ProfileConfig,
|
||||
) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> {
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
@@ -180,7 +366,7 @@ pub(crate) async fn load_profile_wallet(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn parse_token_program(
|
||||
fn parse_token_program(
|
||||
value: &str,
|
||||
) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> {
|
||||
return match value.trim() {
|
||||
@@ -190,7 +376,7 @@ pub(crate) fn parse_token_program(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn derive_ata(
|
||||
fn derive_ata(
|
||||
wallet: &str,
|
||||
mint: &str,
|
||||
token_program: &str,
|
||||
@@ -223,7 +409,7 @@ pub(crate) fn derive_ata(
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_ata_summary_payload(
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary,
|
||||
) -> crate::DemoExecutionSplAtaSummaryPayload {
|
||||
let (associated_token_account, token_program_id) = match summary.plan.instructions.first() {
|
||||
@@ -356,10 +542,7 @@ pub(crate) fn demo_spl_ata_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn journal_matches(
|
||||
payload: &serde_json::Value,
|
||||
request: &crate::DemoSplAtaJournalRequest,
|
||||
) -> bool {
|
||||
fn journal_matches(payload: &serde_json::Value, request: &crate::DemoSplAtaJournalRequest) -> bool {
|
||||
for (expected, key) in [
|
||||
(&request.operation, "operation"),
|
||||
(&request.mint, "mint"),
|
||||
@@ -375,7 +558,7 @@ pub(crate) fn journal_matches(
|
||||
return true;
|
||||
}
|
||||
|
||||
pub(crate) fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow {
|
||||
fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow {
|
||||
let text = |key: &str| {
|
||||
return row
|
||||
.payload_json
|
||||
@@ -404,9 +587,9 @@ mod tests {
|
||||
fn classic_and_token_2022_derivations_are_distinct_and_canonical() {
|
||||
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
|
||||
let mint = "So11111111111111111111111111111111111111112";
|
||||
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
|
||||
let classic = super::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
|
||||
.unwrap_or_else(|error| panic!("classic derivation failed: {error}"));
|
||||
let token_2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
|
||||
let token_2022 = super::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
|
||||
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
|
||||
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
|
||||
assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
|
||||
@@ -422,7 +605,7 @@ mod tests {
|
||||
operation: std::option::Option::Some("create_idempotent".to_string()),
|
||||
limit: 100,
|
||||
};
|
||||
assert!(crate::journal_matches(
|
||||
assert!(super::journal_matches(
|
||||
&serde_json::json!({
|
||||
"operation": "create_idempotent",
|
||||
"mint": "mint111",
|
||||
@@ -430,7 +613,7 @@ mod tests {
|
||||
}),
|
||||
&request,
|
||||
));
|
||||
assert!(!crate::journal_matches(
|
||||
assert!(!super::journal_matches(
|
||||
&serde_json::json!({
|
||||
"operation": "create",
|
||||
"mint": "mint111",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token.rs
|
||||
// version: 4
|
||||
// version: 8
|
||||
|
||||
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
|
||||
|
||||
@@ -153,7 +153,152 @@ pub(crate) struct DemoSplTokenJournalRow {
|
||||
pub(crate) updated_at: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token_summary_payload(
|
||||
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) async fn demo_execution_spl_token_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoExecutionSplTokenRequest,
|
||||
) -> std::result::Result<crate::DemoExecutionSplTokenSummaryPayload, std::string::String> {
|
||||
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
||||
running: state.demo_execution_solana_core_running(),
|
||||
};
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let amount_raw = request.amount_raw.trim().to_string();
|
||||
let operation = kb_lib::ExSplClassicTokenOperation::Instruction {
|
||||
value: kb_lib::ExSplClassicTokenSingleOperation::TransferChecked {
|
||||
source: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
|
||||
authority: kb_lib::ExSplClassicTokenAuthority {
|
||||
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
|
||||
multisig_signers: std::vec::Vec::new(),
|
||||
},
|
||||
amount: kb_lib::ExSplClassicTokenAmount(amount_raw.clone()),
|
||||
decimals: request.decimals,
|
||||
},
|
||||
};
|
||||
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplTokenExecutionRequest::new(
|
||||
format!("demo-spl-token-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
operation,
|
||||
);
|
||||
pipeline_request.submit = request.submit;
|
||||
pipeline_request.operator_confirmed = request.operator_confirmed;
|
||||
pipeline_request.post_validation_max_retries = 20;
|
||||
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
||||
std::vec![std::sync::Arc::new(kb_lib::DcSplTokenDecoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
||||
];
|
||||
let observer = crate::DemoExecutionSolanaCoreObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(summary_payload(summary, amount_raw, request.decimals));
|
||||
}
|
||||
|
||||
/// Loads a bounded, typed journal of committed classic SPL Token projections.
|
||||
pub(crate) async fn demo_spl_token_journal(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSplTokenJournalRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSplTokenJournalRow>, std::string::String> {
|
||||
let validated = match validate_journal_request(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), validated.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let needs_payload_filter =
|
||||
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
|
||||
let query_limit = if needs_payload_filter {
|
||||
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
|
||||
} else {
|
||||
validated.limit
|
||||
};
|
||||
let filter = match kb_store::MaterializedEventFilter::new(
|
||||
std::option::Option::None,
|
||||
validated.family.clone(),
|
||||
validated.signature_contains.clone(),
|
||||
query_limit,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let mut output = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
if row.source_decoder_name != "spl.token" || !payload_matches(&row.payload_json, &validated)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
output.push(journal_row(row));
|
||||
if output.len() >= validated.limit as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplTokenExecutionSummary,
|
||||
amount_raw: std::string::String,
|
||||
decimals: u8,
|
||||
@@ -267,7 +412,7 @@ fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json:
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn validate_journal_request(
|
||||
fn validate_journal_request(
|
||||
request: crate::DemoSplTokenJournalRequest,
|
||||
) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> {
|
||||
if request.limit == 0 || request.limit > kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
|
||||
@@ -331,7 +476,7 @@ fn bounded_optional(
|
||||
return std::result::Result::Ok(trimmed);
|
||||
}
|
||||
|
||||
pub(crate) fn payload_matches(
|
||||
fn payload_matches(
|
||||
payload: &serde_json::Value,
|
||||
request: &crate::DemoSplTokenJournalRequest,
|
||||
) -> bool {
|
||||
@@ -354,9 +499,7 @@ pub(crate) fn payload_matches(
|
||||
return true;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token_journal_row(
|
||||
row: kb_store::MaterializedEventQueryRow,
|
||||
) -> DemoSplTokenJournalRow {
|
||||
fn journal_row(row: kb_store::MaterializedEventQueryRow) -> DemoSplTokenJournalRow {
|
||||
let operation = row
|
||||
.payload_json
|
||||
.get("operation")
|
||||
@@ -457,12 +600,12 @@ mod tests {
|
||||
{"role": "mint", "accountKey": "mint111"}
|
||||
]
|
||||
});
|
||||
assert!(crate::payload_matches(&payload, &request));
|
||||
assert!(super::payload_matches(&payload, &request));
|
||||
assert_eq!(
|
||||
payload.get("amountRaw").and_then(serde_json::Value::as_str),
|
||||
std::option::Option::Some("18446744073709551615")
|
||||
);
|
||||
let row = crate::demo_spl_token_journal_row(kb_store::MaterializedEventQueryRow {
|
||||
let row = super::journal_row(kb_store::MaterializedEventQueryRow {
|
||||
processor_name: "materializer.token.accounts".to_string(),
|
||||
processor_version: "0.4.4".to_string(),
|
||||
input_key: "input".to_string(),
|
||||
@@ -492,6 +635,6 @@ mod tests {
|
||||
operation: std::option::Option::None,
|
||||
limit: 501,
|
||||
};
|
||||
assert!(crate::validate_journal_request(request).is_err());
|
||||
assert!(super::validate_journal_request(request).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
|
||||
// version: 7
|
||||
// version: 11
|
||||
|
||||
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
|
||||
|
||||
@@ -131,7 +131,195 @@ pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) fn operation_from_request(
|
||||
/// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) async fn demo_execution_spl_token_2022_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoExecutionSplToken2022Request,
|
||||
) -> std::result::Result<crate::DemoExecutionSplToken2022SummaryPayload, std::string::String> {
|
||||
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
||||
}
|
||||
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
||||
running: state.demo_execution_solana_core_running(),
|
||||
};
|
||||
state
|
||||
.demo_execution_solana_core_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let profile =
|
||||
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store = match crate::connect_postgres_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let operation = match operation_from_request(&request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let operation_code = operation.operation_code().to_string();
|
||||
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionRequest::new(
|
||||
format!("demo-spl-token-2022-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
operation,
|
||||
);
|
||||
pipeline_request.submit = request.submit;
|
||||
pipeline_request.operator_confirmed = request.operator_confirmed;
|
||||
pipeline_request.post_validation_max_retries = 20;
|
||||
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
||||
std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtFeesMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
|
||||
];
|
||||
let observer = crate::DemoExecutionSolanaCoreObserver {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token_2022(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(summary_payload(request.scenario_id, operation_code, summary));
|
||||
}
|
||||
|
||||
/// Loads public Token-2022 fixture values without reading private key bytes.
|
||||
pub(crate) fn demo_spl_token_2022_fixture(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
profile_name: std::string::String,
|
||||
) -> std::result::Result<crate::DemoSplToken2022FixturePayload, std::string::String> {
|
||||
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let wallet_dir = if configured.is_absolute() {
|
||||
configured
|
||||
} else {
|
||||
crate::workspace_root_dir().join(configured)
|
||||
};
|
||||
let fixture_path = wallet_dir.join("spl_token_2022_validation").join("fixture.env");
|
||||
let contents = match std::fs::read_to_string(&fixture_path) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"unable to read Token-2022 fixture {}: {error}",
|
||||
fixture_path.display()
|
||||
));
|
||||
},
|
||||
};
|
||||
let values = parse_fixture(contents.as_str());
|
||||
let required = |name: &str| -> std::result::Result<std::string::String, std::string::String> {
|
||||
return match values.get(name) {
|
||||
std::option::Option::Some(value) if !value.trim().is_empty() => {
|
||||
std::result::Result::Ok(value.clone())
|
||||
},
|
||||
_ => std::result::Result::Err(format!("Token-2022 fixture is missing {name}")),
|
||||
};
|
||||
};
|
||||
let program_id = match required("TOKEN_2022_PROGRAM") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint = match required("TOKEN_2022_MINT") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source = match required("TOKEN_2022_SOURCE") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let destination = match required("TOKEN_2022_DESTINATION") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let close_account = match required("TOKEN_2022_CLOSE_ACCOUNT") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let delegate = match required("TOKEN_2022_DELEGATE") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let authority = match required("TOKEN_2022_AUTHORITY") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let freeze_authority = match required("TOKEN_2022_FREEZE_AUTHORITY") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let decimals_text = match required("TOKEN_2022_DECIMALS") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let decimals = match decimals_text.parse::<u8>() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid TOKEN_2022_DECIMALS value: {error}"));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload {
|
||||
fixture_path: fixture_path.display().to_string(),
|
||||
program_id,
|
||||
mint,
|
||||
source,
|
||||
destination,
|
||||
close_account,
|
||||
delegate,
|
||||
authority,
|
||||
freeze_authority,
|
||||
decimals,
|
||||
mint_amount_raw: match required("TOKEN_2022_MINT_AMOUNT_RAW") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
transfer_amount_raw: match required("TOKEN_2022_TRANSFER_AMOUNT_RAW") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
approve_amount_raw: match required("TOKEN_2022_APPROVE_AMOUNT_RAW") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
burn_amount_raw: match required("TOKEN_2022_BURN_AMOUNT_RAW") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn operation_from_request(
|
||||
request: &crate::DemoExecutionSplToken2022Request,
|
||||
) -> std::result::Result<kb_lib::ExSplToken2022Operation, std::string::String> {
|
||||
let authority = kb_lib::ExSplTokenAuthority {
|
||||
@@ -203,7 +391,7 @@ pub(crate) fn operation_from_request(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token_2022_summary_payload(
|
||||
fn summary_payload(
|
||||
scenario_id: std::string::String,
|
||||
operation: std::string::String,
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary,
|
||||
@@ -269,7 +457,7 @@ pub(crate) fn demo_spl_token_2022_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn parse_fixture(
|
||||
fn parse_fixture(
|
||||
contents: &str,
|
||||
) -> std::collections::BTreeMap<std::string::String, std::string::String> {
|
||||
let mut values = std::collections::BTreeMap::new();
|
||||
@@ -403,7 +591,7 @@ mod tests {
|
||||
"token_2022_thaw_account",
|
||||
"token_2022_close_destination",
|
||||
] {
|
||||
assert!(crate::operation_from_request(&request(scenario)).is_ok());
|
||||
assert!(super::operation_from_request(&request(scenario)).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_common.rs
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
//! Shared SQL demo helpers and serializable payloads.
|
||||
|
||||
@@ -211,6 +211,11 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
|
||||
emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice());
|
||||
}
|
||||
|
||||
/// Formats debug enums for UI display.
|
||||
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
|
||||
return format!("{value:?}");
|
||||
}
|
||||
|
||||
fn emit_sql_startup_table_report(
|
||||
splash_window: &tauri::WebviewWindow,
|
||||
before_tables: &[kb_store::PostgresTableDiagnostics],
|
||||
@@ -305,8 +310,3 @@ fn emit_sql_startup_splash(
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats debug enums for UI display.
|
||||
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
|
||||
return format!("{value:?}");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_diag.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! SQL diagnostic demo commands.
|
||||
|
||||
@@ -36,3 +36,38 @@ pub(crate) struct DemoSqlDiagPayload {
|
||||
/// Known raw/core table diagnostics.
|
||||
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
|
||||
}
|
||||
|
||||
/// Loads read-only SQL diagnostics from the active profile.
|
||||
pub(crate) async fn load_demo_sql_diag(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> {
|
||||
let profile = state.active_profile().clone();
|
||||
let store_result = crate::connect_postgres_store(&profile).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let diagnostics_result = store.backend_diagnostics().await;
|
||||
let diagnostics = match diagnostics_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let tables_result = store.known_table_diagnostics().await;
|
||||
let tables = match tables_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSqlDiagPayload {
|
||||
config_path: state.config_path().to_string(),
|
||||
active_profile_name: profile.name.clone(),
|
||||
backend: profile.database.backend.clone(),
|
||||
masked_dsn: store.options().masked_dsn(),
|
||||
current_schema: diagnostics.descriptor.current_schema,
|
||||
health_status: crate::debug_status(diagnostics.health.status),
|
||||
health_message: diagnostics.health.message,
|
||||
migration_status: crate::debug_status(diagnostics.migrations.status),
|
||||
migration_message: diagnostics.migrations.message,
|
||||
server_version: diagnostics.server_version,
|
||||
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_pg_core.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
//! PostgreSQL core store demo commands.
|
||||
|
||||
@@ -20,3 +20,25 @@ pub(crate) struct DemoSqlPgCorePayload {
|
||||
/// Core table diagnostics.
|
||||
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
|
||||
}
|
||||
|
||||
/// Loads read-only core table diagnostics from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_pg_core(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> {
|
||||
let profile = state.active_profile().clone();
|
||||
let store_result = crate::connect_postgres_store(&profile).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let tables_result = store.core_table_diagnostics().await;
|
||||
let tables = match tables_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSqlPgCorePayload {
|
||||
active_profile_name: profile.name.clone(),
|
||||
masked_dsn: store.options().masked_dsn(),
|
||||
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_pg_raw.rs
|
||||
// version: 5
|
||||
// version: 7
|
||||
|
||||
//! PostgreSQL canonical acquisition store demo commands.
|
||||
|
||||
@@ -20,3 +20,25 @@ pub(crate) struct DemoSqlPgRawPayload {
|
||||
/// Raw table diagnostics.
|
||||
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
|
||||
}
|
||||
|
||||
/// Loads read-only raw table diagnostics from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_pg_raw(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> {
|
||||
let profile = state.active_profile().clone();
|
||||
let store_result = crate::connect_postgres_store(&profile).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let tables_result = store.raw_table_diagnostics().await;
|
||||
let tables = match tables_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSqlPgRawPayload {
|
||||
active_profile_name: profile.name.clone(),
|
||||
masked_dsn: store.options().masked_dsn(),
|
||||
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs
|
||||
// version: 8
|
||||
// version: 12
|
||||
|
||||
//! Read-only SQL replay candidate browser commands.
|
||||
|
||||
@@ -202,7 +202,187 @@ pub(crate) struct DemoSqlReplayEntityRow {
|
||||
pub(crate) max_slot: i64,
|
||||
}
|
||||
|
||||
pub(crate) fn transaction_row_from_pg(
|
||||
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
|
||||
pub(crate) async fn export_demo_sql_replay_csv(
|
||||
file_name: std::string::String,
|
||||
content: std::string::String,
|
||||
) -> std::result::Result<std::string::String, std::string::String> {
|
||||
if content.is_empty() {
|
||||
return std::result::Result::Err("CSV export content must not be empty".to_owned());
|
||||
}
|
||||
if content.len() > 16 * 1024 * 1024 {
|
||||
return std::result::Result::Err("CSV export content exceeds 16 MiB".to_owned());
|
||||
}
|
||||
let normalized_name_result = validated_csv_file_name(&file_name);
|
||||
let normalized_name = match normalized_name_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let export_dir = crate::workspace_root_dir().join("data").join("exports_csv");
|
||||
let create_result = tokio::fs::create_dir_all(&export_dir).await;
|
||||
if let std::result::Result::Err(error) = create_result {
|
||||
return std::result::Result::Err(format!("cannot create CSV export directory: {error}"));
|
||||
}
|
||||
let export_path_result = available_csv_export_path(&export_dir, normalized_name).await;
|
||||
let export_path = match export_path_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut bytes = std::vec::Vec::with_capacity(content.len() + 3);
|
||||
bytes.extend_from_slice(&[0xEF, 0xBB, 0xBF]);
|
||||
bytes.extend_from_slice(content.as_bytes());
|
||||
let write_result = tokio::fs::write(&export_path, bytes).await;
|
||||
if let std::result::Result::Err(error) = write_result {
|
||||
return std::result::Result::Err(format!("cannot write CSV export: {error}"));
|
||||
}
|
||||
let display_path = export_path.to_string_lossy().into_owned();
|
||||
tracing::info!(target: crate::TRACING_TARGET, path = %display_path, "CSV replay candidate export written");
|
||||
return std::result::Result::Ok(display_path);
|
||||
}
|
||||
|
||||
/// Loads static browser options from the active PostgreSQL profile.
|
||||
pub(crate) async fn demo_sql_replay_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> {
|
||||
let profile = state.active_profile().clone();
|
||||
let options_result = crate::postgres_store_options_from_profile(&profile, false);
|
||||
let options = match options_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let mut known_programs =
|
||||
std::vec::Vec::with_capacity(kb_program_ids::registered_program_ids().len());
|
||||
for entry in kb_program_ids::registered_program_ids() {
|
||||
known_programs.push(crate::DemoSqlReplayKnownProgramOption {
|
||||
code: entry.code().to_owned(),
|
||||
program_id: entry.program_id().to_owned(),
|
||||
});
|
||||
}
|
||||
return std::result::Result::Ok(crate::DemoSqlReplayOptionsPayload {
|
||||
active_profile_name: profile.name,
|
||||
masked_dsn: options.masked_dsn(),
|
||||
maximum_limit: kb_store::MAX_REPLAY_CANDIDATE_ROWS,
|
||||
known_programs,
|
||||
});
|
||||
}
|
||||
|
||||
/// Loads bounded transaction replay candidates from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_replay_transactions(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayTransactionRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> {
|
||||
let program_scope_result = program_scope_from_code(request.program_scope.as_str());
|
||||
let program_scope = match program_scope_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let entity_kind_result = optional_entity_kind_from_code(request.entity_kind.as_deref());
|
||||
let entity_kind = match entity_kind_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let filter_result = kb_store::PostgresReplayTransactionFilter::new(
|
||||
request.signature_contains,
|
||||
request.min_slot,
|
||||
request.max_slot,
|
||||
request.raw_processing_state,
|
||||
request.ledger_status,
|
||||
request.program_id,
|
||||
program_scope,
|
||||
entity_kind,
|
||||
request.entity_value,
|
||||
request.limit,
|
||||
request.newest_first,
|
||||
);
|
||||
let filter = match filter_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rows_result = store.replay_transaction_candidates(&filter).await;
|
||||
let rows = match rows_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay transaction candidates");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(transaction_row_from_pg(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
/// Loads bounded program summaries from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_replay_programs(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayProgramRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> {
|
||||
let filter_result =
|
||||
kb_store::PostgresReplayProgramFilter::new(request.program_id_contains, request.limit);
|
||||
let filter = match filter_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rows_result = store.replay_program_summaries(&filter).await;
|
||||
let rows = match rows_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay program summaries");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(program_row_from_pg(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
|
||||
pub(crate) async fn load_demo_sql_replay_entities(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayEntityRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> {
|
||||
let entity_kind_result = entity_kind_from_code(request.entity_kind.as_str());
|
||||
let entity_kind = match entity_kind_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let filter_result = kb_store::PostgresReplayEntityFilter::new(
|
||||
entity_kind,
|
||||
request.entity_value_contains,
|
||||
request.limit,
|
||||
);
|
||||
let filter = match filter_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rows_result = store.replay_entity_summaries(&filter).await;
|
||||
let rows = match rows_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay entity summaries");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(entity_row_from_pg(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn transaction_row_from_pg(
|
||||
row: kb_store::PostgresReplayTransactionCandidate,
|
||||
) -> crate::DemoSqlReplayTransactionRow {
|
||||
return crate::DemoSqlReplayTransactionRow {
|
||||
@@ -223,7 +403,7 @@ pub(crate) fn transaction_row_from_pg(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn program_row_from_pg(
|
||||
fn program_row_from_pg(
|
||||
row: kb_store::PostgresReplayProgramSummary,
|
||||
) -> crate::DemoSqlReplayProgramRow {
|
||||
let program_code = kb_program_ids::find_registered_program_id(&row.program_id)
|
||||
@@ -240,9 +420,7 @@ pub(crate) fn program_row_from_pg(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn entity_row_from_pg(
|
||||
row: kb_store::PostgresReplayEntitySummary,
|
||||
) -> crate::DemoSqlReplayEntityRow {
|
||||
fn entity_row_from_pg(row: kb_store::PostgresReplayEntitySummary) -> crate::DemoSqlReplayEntityRow {
|
||||
return crate::DemoSqlReplayEntityRow {
|
||||
entity_kind: row.entity_kind,
|
||||
entity_value: row.entity_value,
|
||||
@@ -253,7 +431,7 @@ pub(crate) fn entity_row_from_pg(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn validated_csv_file_name(
|
||||
fn validated_csv_file_name(
|
||||
file_name: &str,
|
||||
) -> std::result::Result<&'static str, std::string::String> {
|
||||
return match file_name {
|
||||
@@ -266,7 +444,7 @@ pub(crate) fn validated_csv_file_name(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn available_csv_export_path(
|
||||
async fn available_csv_export_path(
|
||||
export_dir: &std::path::Path,
|
||||
file_name: &str,
|
||||
) -> std::result::Result<std::path::PathBuf, std::string::String> {
|
||||
@@ -300,7 +478,7 @@ pub(crate) async fn available_csv_export_path(
|
||||
return std::result::Result::Err("cannot allocate a unique CSV export path".to_owned());
|
||||
}
|
||||
|
||||
pub(crate) fn program_scope_from_code(
|
||||
fn program_scope_from_code(
|
||||
code: &str,
|
||||
) -> std::result::Result<kb_store::PostgresReplayProgramScope, std::string::String> {
|
||||
return match code {
|
||||
@@ -312,7 +490,7 @@ pub(crate) fn program_scope_from_code(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn optional_entity_kind_from_code(
|
||||
fn optional_entity_kind_from_code(
|
||||
code: std::option::Option<&str>,
|
||||
) -> std::result::Result<std::option::Option<kb_store::PostgresReplayEntityKind>, std::string::String>
|
||||
{
|
||||
@@ -330,7 +508,7 @@ pub(crate) fn optional_entity_kind_from_code(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn entity_kind_from_code(
|
||||
fn entity_kind_from_code(
|
||||
code: &str,
|
||||
) -> std::result::Result<kb_store::PostgresReplayEntityKind, std::string::String> {
|
||||
return match code {
|
||||
@@ -345,27 +523,27 @@ pub(crate) fn entity_kind_from_code(
|
||||
mod tests {
|
||||
#[test]
|
||||
fn program_scope_parser_accepts_logs() {
|
||||
let result = crate::program_scope_from_code("logs");
|
||||
let result = super::program_scope_from_code("logs");
|
||||
assert_eq!(result, std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Logs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_kind_parser_rejects_unknown_code() {
|
||||
let result = crate::entity_kind_from_code("unknown");
|
||||
let result = super::entity_kind_from_code("unknown");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csv_file_name_rejects_unknown_name() {
|
||||
let result = crate::validated_csv_file_name("arbitrary.csv");
|
||||
let result = super::validated_csv_file_name("arbitrary.csv");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csv_file_name_accepts_split_entity_exports() {
|
||||
let mint_result = crate::validated_csv_file_name("replay_mints.csv");
|
||||
let owner_result = crate::validated_csv_file_name("replay_owners.csv");
|
||||
let account_result = crate::validated_csv_file_name("replay_account_keys.csv");
|
||||
let mint_result = super::validated_csv_file_name("replay_mints.csv");
|
||||
let owner_result = super::validated_csv_file_name("replay_owners.csv");
|
||||
let account_result = super::validated_csv_file_name("replay_account_keys.csv");
|
||||
assert!(mint_result.is_ok());
|
||||
assert!(owner_result.is_ok());
|
||||
assert!(account_result.is_ok());
|
||||
@@ -382,7 +560,7 @@ mod tests {
|
||||
min_slot: 1,
|
||||
max_slot: 1,
|
||||
};
|
||||
let converted = crate::program_row_from_pg(row);
|
||||
let converted = super::program_row_from_pg(row);
|
||||
assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_ws.rs
|
||||
// version: 17
|
||||
// version: 19
|
||||
|
||||
//! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`.
|
||||
|
||||
@@ -179,72 +179,8 @@ pub(crate) struct DemoWsMessagePayload {
|
||||
pub(crate) payload_json: std::string::String,
|
||||
}
|
||||
|
||||
const DEMO_WS_UI_RATE_WINDOW_MS: u64 = 1000;
|
||||
const DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW: u32 = 12;
|
||||
const DEMO_WS_UI_MAX_PAYLOAD_CHARS: usize = 8_000;
|
||||
const DEMO_WS_RECONNECT_ATTEMPTS: u32 = 3;
|
||||
const DEMO_WS_RECONNECT_INITIAL_DELAY_MS: u64 = 250;
|
||||
const DEMO_WS_RECONNECT_MAX_DELAY_MS: u64 = 2_000;
|
||||
|
||||
struct DemoWsUiRateLimiter {
|
||||
window_started_at: std::time::Instant,
|
||||
emitted_in_window: u32,
|
||||
dropped_in_window: u32,
|
||||
}
|
||||
|
||||
impl DemoWsUiRateLimiter {
|
||||
fn new() -> Self {
|
||||
return Self {
|
||||
window_started_at: std::time::Instant::now(),
|
||||
emitted_in_window: 0,
|
||||
dropped_in_window: 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn should_emit(&mut self, app_handle: &tauri::AppHandle) -> bool {
|
||||
let now = std::time::Instant::now();
|
||||
if now.duration_since(self.window_started_at)
|
||||
>= std::time::Duration::from_millis(DEMO_WS_UI_RATE_WINDOW_MS)
|
||||
{
|
||||
if self.dropped_in_window > 0 {
|
||||
emit_demo_ws_message(
|
||||
app_handle,
|
||||
"throttled",
|
||||
format!(
|
||||
"{} WebSocket notification(s) masquée(s) pendant la dernière fenêtre UI.",
|
||||
self.dropped_in_window
|
||||
),
|
||||
);
|
||||
}
|
||||
self.window_started_at = now;
|
||||
self.emitted_in_window = 0;
|
||||
self.dropped_in_window = 0;
|
||||
}
|
||||
if self.emitted_in_window >= DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW {
|
||||
self.dropped_in_window = self.dropped_in_window.saturating_add(1);
|
||||
return false;
|
||||
}
|
||||
self.emitted_in_window = self.emitted_in_window.saturating_add(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_ws_status_inner(
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let guard = state.demo_ws_session().lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
return match session {
|
||||
std::option::Option::Some(session) => {
|
||||
std::result::Result::Ok(status_from_snapshot(session.snapshot().await))
|
||||
},
|
||||
std::option::Option::None => std::result::Result::Ok(disconnected_status()),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_ws_connect_inner(
|
||||
/// Connects if needed, then subscribes through `kb_onchain_transport::WsSession`.
|
||||
pub(crate) async fn demo_ws_connect(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: &crate::AppState,
|
||||
request: crate::DemoWsRequest,
|
||||
@@ -274,7 +210,7 @@ pub(crate) async fn demo_ws_connect_inner(
|
||||
std::result::Result::Ok(client) => client,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let session = match ensure_demo_ws_session(&app_handle, state, selected_client.clone()).await {
|
||||
let session = match ensure_session(&app_handle, state, selected_client.clone()).await {
|
||||
std::result::Result::Ok(session) => session,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -290,7 +226,7 @@ pub(crate) async fn demo_ws_connect_inner(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
emit_demo_ws_status(&app_handle, session.snapshot().await);
|
||||
emit_status(&app_handle, session.snapshot().await);
|
||||
return std::result::Result::Ok(crate::DemoWsExecutionPayload {
|
||||
endpoint_name: selected_client.endpoint_name().to_string(),
|
||||
provider: selected_client.provider().to_string(),
|
||||
@@ -304,7 +240,148 @@ pub(crate) async fn demo_ws_connect_inner(
|
||||
});
|
||||
}
|
||||
|
||||
async fn ensure_demo_ws_session(
|
||||
/// Returns the current WebSocket demo session status.
|
||||
pub(crate) async fn demo_ws_status(
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let guard = state.demo_ws_session().lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
return match session {
|
||||
std::option::Option::Some(session) => {
|
||||
std::result::Result::Ok(status_from_snapshot(session.snapshot().await))
|
||||
},
|
||||
std::option::Option::None => std::result::Result::Ok(disconnected_status()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Lists WebSocket endpoints available through the configured pool.
|
||||
pub(crate) fn demo_ws_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<
|
||||
std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>,
|
||||
std::string::String,
|
||||
> {
|
||||
let pool = match state.demo_ws_pool() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(pool.snapshot());
|
||||
}
|
||||
|
||||
/// Lists selectable WebSocket roles and methods for the demo UI.
|
||||
pub(crate) fn demo_ws_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoWsOptionsPayload, std::string::String> {
|
||||
return std::result::Result::Ok(crate::DemoWsOptionsPayload {
|
||||
roles: build_ws_role_options(match state.demo_ws_pool() {
|
||||
std::result::Result::Ok(pool) => pool.snapshot(),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
}),
|
||||
methods: build_ws_method_options(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Unsubscribes one subscription while keeping the WebSocket connection open.
|
||||
pub(crate) async fn demo_ws_unsubscribe(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: &crate::AppState,
|
||||
remote_subscription_id: u64,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let guard = state.demo_ws_session().lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
let session = match session {
|
||||
std::option::Option::Some(session) => session,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("demo WebSocket session is not connected".to_string());
|
||||
},
|
||||
};
|
||||
let unsubscribe_result = session.unsubscribe(remote_subscription_id).await;
|
||||
if let std::result::Result::Err(error) = unsubscribe_result {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let snapshot = session.snapshot().await;
|
||||
emit_status(&app_handle, snapshot.clone());
|
||||
return std::result::Result::Ok(status_from_snapshot(snapshot));
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_ws_disconnect(
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let mut guard = state.demo_ws_session().lock().await;
|
||||
guard.take()
|
||||
};
|
||||
if let std::option::Option::Some(session) = session {
|
||||
if let std::result::Result::Err(error) = session.disconnect().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(disconnected_status());
|
||||
}
|
||||
|
||||
/// Disconnects the demo WebSocket session from application lifecycle hooks.
|
||||
pub(crate) async fn disconnect_demo_ws_app_state(state: &crate::AppState, _wait_for_relay: bool) {
|
||||
let disconnect_result = demo_ws_disconnect(state).await;
|
||||
if let std::result::Result::Err(error) = disconnect_result {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, "demo ws lifecycle disconnect failed: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
const DEMO_WS_UI_RATE_WINDOW_MS: u64 = 1000;
|
||||
const DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW: u32 = 12;
|
||||
const DEMO_WS_UI_MAX_PAYLOAD_CHARS: usize = 8_000;
|
||||
const DEMO_WS_RECONNECT_ATTEMPTS: u32 = 3;
|
||||
const DEMO_WS_RECONNECT_INITIAL_DELAY_MS: u64 = 250;
|
||||
const DEMO_WS_RECONNECT_MAX_DELAY_MS: u64 = 2_000;
|
||||
|
||||
struct DemoWsUiRateLimiter {
|
||||
window_started_at: std::time::Instant,
|
||||
emitted_in_window: u32,
|
||||
dropped_in_window: u32,
|
||||
}
|
||||
|
||||
impl DemoWsUiRateLimiter {
|
||||
fn new() -> Self {
|
||||
return Self {
|
||||
window_started_at: std::time::Instant::now(),
|
||||
emitted_in_window: 0,
|
||||
dropped_in_window: 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn should_emit(&mut self, app_handle: &tauri::AppHandle) -> bool {
|
||||
let now = std::time::Instant::now();
|
||||
if now.duration_since(self.window_started_at)
|
||||
>= std::time::Duration::from_millis(DEMO_WS_UI_RATE_WINDOW_MS)
|
||||
{
|
||||
if self.dropped_in_window > 0 {
|
||||
emit_message(
|
||||
app_handle,
|
||||
"throttled",
|
||||
format!(
|
||||
"{} WebSocket notification(s) masquée(s) pendant la dernière fenêtre UI.",
|
||||
self.dropped_in_window
|
||||
),
|
||||
);
|
||||
}
|
||||
self.window_started_at = now;
|
||||
self.emitted_in_window = 0;
|
||||
self.dropped_in_window = 0;
|
||||
}
|
||||
if self.emitted_in_window >= DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW {
|
||||
self.dropped_in_window = self.dropped_in_window.saturating_add(1);
|
||||
return false;
|
||||
}
|
||||
self.emitted_in_window = self.emitted_in_window.saturating_add(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_session(
|
||||
app_handle: &tauri::AppHandle,
|
||||
state: &crate::AppState,
|
||||
selected_client: kb_onchain_transport::WsClient,
|
||||
@@ -351,7 +428,7 @@ async fn ensure_demo_ws_session(
|
||||
std::result::Result::Ok(session) => std::sync::Arc::new(session),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
spawn_demo_ws_event_bridge(app_handle.clone(), session.clone());
|
||||
spawn_event_bridge(app_handle.clone(), session.clone());
|
||||
{
|
||||
let mut guard = state.demo_ws_session().lock().await;
|
||||
*guard = std::option::Option::Some(session.clone());
|
||||
@@ -359,54 +436,7 @@ async fn ensure_demo_ws_session(
|
||||
return std::result::Result::Ok(session);
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_ws_unsubscribe_inner(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: &crate::AppState,
|
||||
remote_subscription_id: u64,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let guard = state.demo_ws_session().lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
let session = match session {
|
||||
std::option::Option::Some(session) => session,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("demo WebSocket session is not connected".to_string());
|
||||
},
|
||||
};
|
||||
let unsubscribe_result = session.unsubscribe(remote_subscription_id).await;
|
||||
if let std::result::Result::Err(error) = unsubscribe_result {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let snapshot = session.snapshot().await;
|
||||
emit_demo_ws_status(&app_handle, snapshot.clone());
|
||||
return std::result::Result::Ok(status_from_snapshot(snapshot));
|
||||
}
|
||||
|
||||
pub(crate) async fn demo_ws_disconnect_inner(
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
||||
let session = {
|
||||
let mut guard = state.demo_ws_session().lock().await;
|
||||
guard.take()
|
||||
};
|
||||
if let std::option::Option::Some(session) = session {
|
||||
if let std::result::Result::Err(error) = session.disconnect().await {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(disconnected_status());
|
||||
}
|
||||
|
||||
/// Disconnects the demo WebSocket session from application lifecycle hooks.
|
||||
pub(crate) async fn disconnect_demo_ws_app_state(state: &crate::AppState, _wait_for_relay: bool) {
|
||||
let disconnect_result = demo_ws_disconnect_inner(state).await;
|
||||
if let std::result::Result::Err(error) = disconnect_result {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, "demo ws lifecycle disconnect failed: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_demo_ws_event_bridge(
|
||||
fn spawn_event_bridge(
|
||||
app_handle: tauri::AppHandle,
|
||||
session: std::sync::Arc<kb_onchain_transport::WsSession>,
|
||||
) {
|
||||
@@ -420,7 +450,7 @@ fn spawn_demo_ws_event_bridge(
|
||||
std::result::Result::Err(tokio::sync::broadcast::error::RecvError::Lagged(
|
||||
count,
|
||||
)) => {
|
||||
emit_demo_ws_message(
|
||||
emit_message(
|
||||
&app_handle,
|
||||
"lagged",
|
||||
format!("{count} événement(s) WebSocket interne(s) perdu(s)"),
|
||||
@@ -447,43 +477,39 @@ fn spawn_demo_ws_event_bridge(
|
||||
std::result::Result::Ok(payload) => payload,
|
||||
std::result::Result::Err(_) => compact_payload,
|
||||
};
|
||||
emit_demo_ws_message(
|
||||
&app_handle,
|
||||
"notification",
|
||||
truncate_payload(ui_payload),
|
||||
);
|
||||
emit_message(&app_handle, "notification", truncate_payload(ui_payload));
|
||||
}
|
||||
},
|
||||
kb_onchain_transport::WsSessionEvent::Diagnostic { code, message } => {
|
||||
emit_demo_ws_message(&app_handle, &code, truncate_payload(message));
|
||||
emit_message(&app_handle, &code, truncate_payload(message));
|
||||
},
|
||||
kb_onchain_transport::WsSessionEvent::Reconnecting {
|
||||
attempt,
|
||||
maximum_attempts,
|
||||
} => {
|
||||
emit_demo_ws_message(
|
||||
emit_message(
|
||||
&app_handle,
|
||||
"reconnecting",
|
||||
format!("tentative {attempt}/{maximum_attempts}"),
|
||||
);
|
||||
emit_demo_ws_status(&app_handle, session.snapshot().await);
|
||||
emit_status(&app_handle, session.snapshot().await);
|
||||
},
|
||||
kb_onchain_transport::WsSessionEvent::Reconnected { reconnect_count } => {
|
||||
emit_demo_ws_message(
|
||||
emit_message(
|
||||
&app_handle,
|
||||
"reconnected",
|
||||
format!("reconnexion numéro {reconnect_count}"),
|
||||
);
|
||||
emit_demo_ws_status(&app_handle, session.snapshot().await);
|
||||
emit_status(&app_handle, session.snapshot().await);
|
||||
},
|
||||
kb_onchain_transport::WsSessionEvent::Connected
|
||||
| kb_onchain_transport::WsSessionEvent::SubscriptionAdded(_)
|
||||
| kb_onchain_transport::WsSessionEvent::SubscriptionRemapped { .. }
|
||||
| kb_onchain_transport::WsSessionEvent::SubscriptionRemoved(_) => {
|
||||
emit_demo_ws_status(&app_handle, session.snapshot().await);
|
||||
emit_status(&app_handle, session.snapshot().await);
|
||||
},
|
||||
kb_onchain_transport::WsSessionEvent::Disconnected => {
|
||||
emit_demo_ws_status(&app_handle, session.snapshot().await);
|
||||
emit_status(&app_handle, session.snapshot().await);
|
||||
break;
|
||||
},
|
||||
}
|
||||
@@ -491,10 +517,7 @@ fn spawn_demo_ws_event_bridge(
|
||||
});
|
||||
}
|
||||
|
||||
fn emit_demo_ws_status(
|
||||
app_handle: &tauri::AppHandle,
|
||||
snapshot: kb_onchain_transport::WsSessionSnapshot,
|
||||
) {
|
||||
fn emit_status(app_handle: &tauri::AppHandle, snapshot: kb_onchain_transport::WsSessionSnapshot) {
|
||||
let window = match app_handle.get_webview_window("demo_ws") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => return,
|
||||
@@ -505,11 +528,7 @@ fn emit_demo_ws_status(
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_demo_ws_message(
|
||||
app_handle: &tauri::AppHandle,
|
||||
kind: &str,
|
||||
payload_json: std::string::String,
|
||||
) {
|
||||
fn emit_message(app_handle: &tauri::AppHandle, kind: &str, payload_json: std::string::String) {
|
||||
let window = match app_handle.get_webview_window("demo_ws") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => return,
|
||||
@@ -575,7 +594,7 @@ fn disconnected_status() -> crate::DemoWsStatusPayload {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn build_ws_role_options(
|
||||
fn build_ws_role_options(
|
||||
snapshots: std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>,
|
||||
) -> std::vec::Vec<crate::DemoWsRoleOption> {
|
||||
let mut roles = std::collections::BTreeMap::<
|
||||
@@ -603,7 +622,7 @@ pub(crate) fn build_ws_role_options(
|
||||
return options;
|
||||
}
|
||||
|
||||
pub(crate) fn build_ws_method_options() -> std::vec::Vec<crate::DemoWsMethodOption> {
|
||||
fn build_ws_method_options() -> std::vec::Vec<crate::DemoWsMethodOption> {
|
||||
let labels = std::collections::BTreeMap::from([
|
||||
("accountSubscribe", ("Compte", true, false, true)),
|
||||
("blockSubscribe", ("Bloc", false, true, true)),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 15
|
||||
// version: 25
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -36,6 +36,8 @@ pub use self::tauri::run;
|
||||
|
||||
/// Shared application state managed by Tauri.
|
||||
pub(crate) use self::app_state::AppState;
|
||||
/// Canonical tracing target for the desktop demo application.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
/// Backfill observer forwarding pipeline progress to the Tauri window.
|
||||
pub(crate) use self::demo_backfill::DemoBackfillObserver;
|
||||
/// Initial options shown by the backfill window.
|
||||
@@ -52,12 +54,12 @@ pub(crate) use self::demo_backfill::DemoBackfillRoleOption;
|
||||
pub(crate) use self::demo_backfill::DemoBackfillRunGuard;
|
||||
/// Final UI-safe summary for one backfill campaign.
|
||||
pub(crate) use self::demo_backfill::DemoBackfillSummaryPayload;
|
||||
/// Builds one validated pipeline request from the UI contract.
|
||||
pub(crate) use self::demo_backfill::build_demo_backfill_pipeline_request;
|
||||
/// Builds selectable endpoint roles from the HTTP pool snapshot.
|
||||
pub(crate) use self::demo_backfill::build_role_options;
|
||||
/// Converts a pipeline summary to the UI-safe summary.
|
||||
pub(crate) use self::demo_backfill::demo_backfill_summary_payload;
|
||||
/// Requests cooperative cancellation of the active backfill campaign.
|
||||
pub(crate) use self::demo_backfill::demo_backfill_cancel;
|
||||
/// Executes one bounded backfill campaign and emits progress events.
|
||||
pub(crate) use self::demo_backfill::demo_backfill_execute;
|
||||
/// Returns endpoint roles, known programs and conservative backfill defaults.
|
||||
pub(crate) use self::demo_backfill::demo_backfill_options;
|
||||
/// Configuration payload shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigPayload;
|
||||
/// Builds the configuration payload from shared application state.
|
||||
@@ -77,15 +79,11 @@ pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request;
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_cancel;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::cancel_demo_core_extraction;
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_execute;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_options_payload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::execute_demo_core_extraction;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
@@ -112,35 +110,29 @@ pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::annotation_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_decoders;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_materializers;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::coverage_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::optional_line_count;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::register_active_campaign;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::text_sample;
|
||||
/// Loads bounded materialized transaction annotations for the replay diagnostics panel.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_annotations;
|
||||
/// Requests cooperative cancellation of the active decode replay campaign.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_cancel;
|
||||
/// Loads aggregate diagnostics for the decode replay panel.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_diagnostics;
|
||||
/// Executes one bounded contextual decode replay campaign.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_execute;
|
||||
/// Returns available decoders and default replay bounds.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
|
||||
/// Converts the reusable scenario readiness report to the desktop TS-RS payload.
|
||||
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
|
||||
/// Serializes a diagnostic value to indented JSON without panicking.
|
||||
pub(crate) use self::demo_devnet_common::pretty_json;
|
||||
/// Request sent by the Memo v4 Devnet execution panel.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
|
||||
/// UI-safe result of one Memo v4 Devnet execution orchestration.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoSummaryPayload;
|
||||
/// Public key generated for a disposable Devnet recipient.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload;
|
||||
/// ????
|
||||
/// Bridges reusable execution progress to desktop events.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreObserver;
|
||||
/// Initial options shown by the Solana Core execution demo.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload;
|
||||
@@ -150,26 +142,32 @@ pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProfileO
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
|
||||
/// Request sent by the execution demo.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
|
||||
/// ????
|
||||
/// Restores the single-execution state flag when a run finishes.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard;
|
||||
/// UI-safe result of one execution orchestration.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload;
|
||||
/// ????
|
||||
/// Maps a confirmation status to its stable UI code.
|
||||
pub(crate) use self::demo_execution_solana_core::confirmation_status_code;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_memo_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::devnet_profile_options;
|
||||
/// ????
|
||||
/// Returns compatible Devnet profiles and conservative defaults.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_devnet_prepare_profile;
|
||||
/// Requests cooperative cancellation before the next execution stage.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_cancel;
|
||||
/// Executes one bounded System transfer simulation or Devnet submission.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_execute;
|
||||
/// Generates a disposable recipient public key without exposing its private key.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_generate_recipient;
|
||||
/// Returns compatible Devnet profiles and conservative defaults.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_options;
|
||||
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_spl_memo_execute;
|
||||
/// Maps an execution cluster to its stable UI code.
|
||||
pub(crate) use self::demo_execution_solana_core::execution_cluster_code;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::pretty_json;
|
||||
/// ????
|
||||
/// Selects and validates one Devnet profile by name.
|
||||
pub(crate) use self::demo_execution_solana_core::select_devnet_profile;
|
||||
/// Frontend payload for one independent Devnet SPL validation scenario.
|
||||
pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload;
|
||||
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
|
||||
pub(crate) use self::demo_execution_spl::demo_execution_spl_validation_scenarios;
|
||||
/// HTTP demo response payload.
|
||||
pub(crate) use self::demo_http::DemoHttpExecutionPayload;
|
||||
/// One selectable HTTP JSON-RPC method.
|
||||
@@ -180,12 +178,12 @@ pub(crate) use self::demo_http::DemoHttpOptionsPayload;
|
||||
pub(crate) use self::demo_http::DemoHttpRequest;
|
||||
/// One selectable HTTP endpoint role.
|
||||
pub(crate) use self::demo_http::DemoHttpRoleOption;
|
||||
/// Builds the HTTP method inventory.
|
||||
pub(crate) use self::demo_http::build_http_method_options;
|
||||
/// Builds the HTTP role inventory from pool snapshots.
|
||||
pub(crate) use self::demo_http::build_http_role_options;
|
||||
/// Executes one raw HTTP JSON-RPC request.
|
||||
pub(crate) use self::demo_http::demo_http_execute_request_inner;
|
||||
pub(crate) use self::demo_http::demo_http_execute_request;
|
||||
/// Returns the configured HTTP pool client snapshots.
|
||||
pub(crate) use self::demo_http::demo_http_list_pool_clients;
|
||||
/// Returns HTTP roles, methods and conservative defaults.
|
||||
pub(crate) use self::demo_http::demo_http_options;
|
||||
/// Request for one representative ATA creation on Devnet.
|
||||
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaRequest;
|
||||
/// UI-safe ATA execution result.
|
||||
@@ -198,18 +196,12 @@ pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationRequest;
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRequest;
|
||||
/// One UI-safe ATA lifecycle journal row.
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRow;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::demo_spl_ata_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::derive_ata;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::journal_matches;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::journal_row;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::load_profile_wallet;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::parse_token_program;
|
||||
/// Executes one representative Create or CreateIdempotent ATA flow.
|
||||
pub(crate) use self::demo_spl_ata::demo_execution_spl_ata_execute;
|
||||
/// Derives the representative profile wallet ATA without exposing private key material.
|
||||
pub(crate) use self::demo_spl_ata::demo_spl_ata_derive;
|
||||
/// Loads a bounded journal of ATA-owned lifecycle facts.
|
||||
pub(crate) use self::demo_spl_ata::demo_spl_ata_journal;
|
||||
/// Request for one checked classic SPL Token transfer on Devnet.
|
||||
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenRequest;
|
||||
/// UI-safe result of one checked SPL Token transfer orchestration.
|
||||
@@ -218,26 +210,20 @@ pub(crate) use self::demo_spl_token::DemoExecutionSplTokenSummaryPayload;
|
||||
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRequest;
|
||||
/// UI-safe materialized classic SPL Token journal row.
|
||||
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRow;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token::demo_spl_token_journal_row;
|
||||
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) use self::demo_spl_token::demo_spl_token_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token::payload_matches;
|
||||
pub(crate) use self::demo_spl_token::demo_execution_spl_token_execute;
|
||||
/// Loads a bounded, typed journal of committed classic SPL Token projections.
|
||||
pub(crate) use self::demo_spl_token::validate_journal_request;
|
||||
pub(crate) use self::demo_spl_token::demo_spl_token_journal;
|
||||
/// Request for one independent public Token-2022 Devnet scenario.
|
||||
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022Request;
|
||||
/// UI-safe result of one public Token-2022 Devnet orchestration.
|
||||
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022SummaryPayload;
|
||||
/// Public values loaded from one persisted Token-2022 validation fixture.
|
||||
pub(crate) use self::demo_spl_token_2022::DemoSplToken2022FixturePayload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token_2022::operation_from_request;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token_2022::parse_fixture;
|
||||
/// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) use self::demo_spl_token_2022::demo_execution_spl_token_2022_execute;
|
||||
/// Loads public Token-2022 fixture values without reading private key bytes.
|
||||
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_fixture;
|
||||
/// UI-safe SQL table diagnostic snapshot.
|
||||
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
|
||||
/// Connects to the configured PostgreSQL store.
|
||||
@@ -254,10 +240,16 @@ pub(crate) use self::demo_sql_common::table_snapshot_from_pg;
|
||||
pub(crate) use self::demo_sql_common::table_snapshots_from_pg;
|
||||
/// SQL diagnostic payload.
|
||||
pub(crate) use self::demo_sql_diag::DemoSqlDiagPayload;
|
||||
/// Loads read-only SQL diagnostics from the active profile.
|
||||
pub(crate) use self::demo_sql_diag::load_demo_sql_diag;
|
||||
/// PostgreSQL core-table payload.
|
||||
pub(crate) use self::demo_sql_pg_core::DemoSqlPgCorePayload;
|
||||
/// Loads read-only core table diagnostics from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_pg_core::load_demo_sql_pg_core;
|
||||
/// PostgreSQL raw-table payload.
|
||||
pub(crate) use self::demo_sql_pg_raw::DemoSqlPgRawPayload;
|
||||
/// Loads read-only raw table diagnostics from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_pg_raw::load_demo_sql_pg_raw;
|
||||
/// SQL replay entity request.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRequest;
|
||||
/// SQL replay entity row.
|
||||
@@ -274,22 +266,16 @@ pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRow;
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest;
|
||||
/// SQL replay transaction row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRow;
|
||||
/// Resolves an available CSV path.
|
||||
pub(crate) use self::demo_sql_replay_candidates::available_csv_export_path;
|
||||
/// Parses an entity-kind code.
|
||||
pub(crate) use self::demo_sql_replay_candidates::entity_kind_from_code;
|
||||
/// Converts a PostgreSQL entity row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::entity_row_from_pg;
|
||||
/// Parses an optional entity-kind code.
|
||||
pub(crate) use self::demo_sql_replay_candidates::optional_entity_kind_from_code;
|
||||
/// Converts a PostgreSQL program row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::program_row_from_pg;
|
||||
/// Parses a program-scope code.
|
||||
pub(crate) use self::demo_sql_replay_candidates::program_scope_from_code;
|
||||
/// Converts a PostgreSQL transaction row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::transaction_row_from_pg;
|
||||
/// Validates a CSV filename.
|
||||
pub(crate) use self::demo_sql_replay_candidates::validated_csv_file_name;
|
||||
/// Loads static browser options from the active PostgreSQL profile.
|
||||
pub(crate) use self::demo_sql_replay_candidates::demo_sql_replay_options;
|
||||
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
|
||||
pub(crate) use self::demo_sql_replay_candidates::export_demo_sql_replay_csv;
|
||||
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_entities;
|
||||
/// Loads bounded program summaries from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_programs;
|
||||
/// Loads bounded transaction replay candidates from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_transactions;
|
||||
/// WebSocket execution response payload.
|
||||
pub(crate) use self::demo_ws::DemoWsExecutionPayload;
|
||||
/// WebSocket message emitted to the frontend.
|
||||
@@ -308,18 +294,18 @@ pub(crate) use self::demo_ws::DemoWsStatusPayload;
|
||||
pub(crate) use self::demo_ws::DemoWsSubscriptionStatusPayload;
|
||||
/// WebSocket unsubscribe request.
|
||||
pub(crate) use self::demo_ws::DemoWsUnsubscribeRequest;
|
||||
/// Builds the standard WebSocket method inventory.
|
||||
pub(crate) use self::demo_ws::build_ws_method_options;
|
||||
/// Builds selectable WebSocket roles from pool snapshots.
|
||||
pub(crate) use self::demo_ws::build_ws_role_options;
|
||||
/// Connects and subscribes through the persistent WebSocket session.
|
||||
pub(crate) use self::demo_ws::demo_ws_connect_inner;
|
||||
pub(crate) use self::demo_ws::demo_ws_connect;
|
||||
/// Disconnects the persistent WebSocket session.
|
||||
pub(crate) use self::demo_ws::demo_ws_disconnect_inner;
|
||||
pub(crate) use self::demo_ws::demo_ws_disconnect;
|
||||
/// Lists WebSocket endpoints available through the configured pool.
|
||||
pub(crate) use self::demo_ws::demo_ws_list_pool_clients;
|
||||
/// Lists selectable WebSocket roles and methods for the demo UI.
|
||||
pub(crate) use self::demo_ws::demo_ws_options;
|
||||
/// Returns the current WebSocket session status.
|
||||
pub(crate) use self::demo_ws::demo_ws_status_inner;
|
||||
pub(crate) use self::demo_ws::demo_ws_status;
|
||||
/// Unsubscribes one active WebSocket subscription.
|
||||
pub(crate) use self::demo_ws::demo_ws_unsubscribe_inner;
|
||||
pub(crate) use self::demo_ws::demo_ws_unsubscribe;
|
||||
/// Disconnects the persistent WebSocket session during application shutdown.
|
||||
pub(crate) use self::demo_ws::disconnect_demo_ws_app_state;
|
||||
/// Frontend logging payload.
|
||||
@@ -341,10 +327,4 @@ pub(crate) use self::splash::SplashOrder;
|
||||
/// Emits one order to the splash frontend.
|
||||
pub(crate) use self::splash::emit_splash_order;
|
||||
/// Runs the startup sequence after the splash frontend is ready.
|
||||
pub(crate) use self::splash::splash_frontend_ready_inner;
|
||||
/// Waits for the minimum splash duration.
|
||||
pub(crate) use self::splash::wait_until_minimum;
|
||||
|
||||
// Keep the canonical tracing target as the final facade export.
|
||||
/// Canonical tracing target for the desktop demo application.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
pub(crate) use self::splash::splash_frontend_ready;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/splash.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Splash-window payloads and startup sequencing helpers.
|
||||
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn emit_splash_order(
|
||||
}
|
||||
|
||||
/// Runs the startup sequence after the splash frontend installed its listener.
|
||||
pub(crate) async fn splash_frontend_ready_inner(
|
||||
pub(crate) async fn splash_frontend_ready(
|
||||
app: tauri::AppHandle,
|
||||
window: tauri::WebviewWindow,
|
||||
state: &crate::AppState,
|
||||
@@ -120,7 +120,7 @@ pub(crate) async fn splash_frontend_ready_inner(
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
||||
wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"fadeout",
|
||||
@@ -142,7 +142,7 @@ pub(crate) async fn splash_frontend_ready_inner(
|
||||
}
|
||||
|
||||
/// Waits until the configured minimum splash duration has elapsed.
|
||||
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
|
||||
async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
|
||||
let minimum = std::time::Duration::from_millis(minimum_ms);
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed >= minimum {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user