lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 4ec09b6d4d422d04a4762bee02586c45ec2e9006
parent eb827e0a4b4dd97e444ab138c1377b3884855fb7
Author: triesap <tyson@radroots.org>
Date:   Sun,  5 Jul 2026 05:13:51 +0000

status: expose storage checkpoint evidence

- add WAL checkpoint metrics to SDK storage status receipts
- surface checkpoint completeness through public runtime DTOs
- cover memory and directory storage status assertions
- gate relay-only helpers out of narrow performance builds

Diffstat:
Mcrates/sdk/src/lib.rs | 3++-
Mcrates/sdk/src/orders_runtime.rs | 94+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcrates/sdk/src/runtime.rs | 104++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcrates/sdk/tests/orders_runtime.rs | 55++++++++++++++++++++++++++++++++++++-------------------
Mcrates/sdk/tests/runtime_foundation.rs | 32++++++++++++++++++++++++++++++--
Mcrates/sdk/tests/unit/runtime_tests.rs | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 276 insertions(+), 63 deletions(-)

diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -162,7 +162,8 @@ pub use crate::runtime::{ RadrootsSdkTimestamp, RestoreArchive, RestoreReceipt, RestoreRequest, SdkBackupManifest, SdkBackupManifestKind, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus, SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkPublishTransport, SdkRestoreState, - SdkSqliteStoreStatus, SdkStorageKind, StorageStatusReceipt, StorageStatusRequest, + SdkSqliteStoreStatus, SdkSqliteWalCheckpointStatus, SdkStorageKind, StorageStatusReceipt, + StorageStatusRequest, }; #[cfg(all(feature = "runtime", feature = "signer-adapters"))] pub use crate::signer_provider::{ diff --git a/crates/sdk/src/orders_runtime.rs b/crates/sdk/src/orders_runtime.rs @@ -1,11 +1,11 @@ #[cfg(feature = "signer-adapters")] use crate::TradeBuyerClient; -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] use crate::runtime::sdk_now_ms; #[cfg(feature = "runtime")] -use crate::sync_runtime::{ - SyncProjectionRefreshReceipt, SyncProjectionRefreshRequest, refresh_product_projections_for_sdk, -}; +use crate::sync_runtime::SyncProjectionRefreshReceipt; +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] +use crate::sync_runtime::{SyncProjectionRefreshRequest, refresh_product_projections_for_sdk}; #[cfg(feature = "signer-adapters")] use crate::workflow_runtime::enqueue_configured_signed_workflow; #[cfg(any(feature = "signer-adapters", test))] @@ -26,19 +26,21 @@ use crate::{SdkRelayUrlPolicy, workflow_runtime::enqueue_signed_workflow}; use radroots_authority::RadrootsActorContext; #[cfg(all(feature = "runtime", test))] use radroots_authority::RadrootsEventSigner; +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] +use radroots_event_store::RadrootsStoredEventTag; #[cfg(feature = "runtime")] -use radroots_event_store::{RadrootsEventIngest, RadrootsStoredEvent, RadrootsStoredEventTag}; +use radroots_event_store::{RadrootsEventIngest, RadrootsStoredEvent}; #[cfg(feature = "runtime")] use radroots_events::{ RadrootsNostrEvent, contract::RadrootsActorRole, ids::RadrootsEventId, kinds::{ - KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, + KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, KIND_TRADE_TRANSITION_PROOF_RESULT, KIND_TRADE_VALIDATION_RECEIPT, }, - tags::{TAG_D, TAG_E, TAG_P}, + tags::TAG_P, }; #[cfg(any(feature = "signer-adapters", test))] use radroots_events::{ @@ -56,6 +58,11 @@ use radroots_events::{ ids::{RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey}, order::RadrootsOrderEconomics, }; +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] +use radroots_events::{ + kinds::KIND_LISTING, + tags::{TAG_D, TAG_E}, +}; #[cfg(feature = "runtime")] use radroots_events_codec::order::{ order_cancellation_from_event, order_decision_from_event, order_request_from_event, @@ -68,7 +75,7 @@ use radroots_nostr::prelude::{ RadrootsNostrEventId, RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrPublicKey, radroots_nostr_filter_tag, }; -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] use radroots_relay_transport::{ RadrootsNostrClientFetchAdapter, RadrootsRelayFetchAdapter, RadrootsRelayFetchEventReceipt, RadrootsRelayFetchOutcomeKind, RadrootsRelayFetchReceipt, RadrootsRelayFetchRelayOutcome, @@ -78,7 +85,7 @@ use radroots_relay_transport::{ use radroots_trade::dvm::RADROOTS_DVM_TAG_VALIDATION_RECEIPT; #[cfg(feature = "runtime")] use radroots_trade::identity::{RadrootsTradeLocator, RadrootsTradeLocatorCandidate}; -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] use radroots_trade::listing::parse_listing_address; #[cfg(any(feature = "signer-adapters", test))] use radroots_trade::order::{ @@ -93,14 +100,15 @@ use radroots_trade::order::{ RadrootsTradeLocatorProjectionResolution, order_event_record_from_event, order_projection_query_for_trade_locator, }; +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] +use radroots_trade::validation_receipt::RadrootsValidationReceiptError; #[cfg(feature = "runtime")] use radroots_trade::validation_receipt::{ RadrootsTradeCommitmentConfidence, RadrootsTradeValidationAuthority, RadrootsTradeValidationReceipt, RadrootsTradeValidationTrustPolicy, - RadrootsTradeValidationTrustState, RadrootsValidationReceiptError, - RadrootsValidationReceiptExpectedBinding, RadrootsValidationReceiptProofSystem, - RadrootsValidationReceiptResult, RadrootsValidationReceiptTags, - verify_validation_receipt_event, + RadrootsTradeValidationTrustState, RadrootsValidationReceiptExpectedBinding, + RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, + RadrootsValidationReceiptTags, verify_validation_receipt_event, }; #[cfg(feature = "runtime")] use radroots_trade::workflow::RadrootsTradeWorkflowState; @@ -1540,6 +1548,7 @@ impl TradeResyncRequest { Ok(self) } + #[cfg(feature = "relay-runtime")] fn validate(&self) -> Result<(), RadrootsSdkError> { if self.limit == 0 || self.limit > TRADE_STATUS_MAX_LIMIT { return Err(RadrootsSdkError::trade_status_limit_invalid( @@ -1742,6 +1751,7 @@ impl TradeValidationReceiptListRequest { Ok(self) } + #[cfg(feature = "relay-runtime")] fn validate(&self) -> Result<(), RadrootsSdkError> { validate_validation_receipt_limit(self.limit) } @@ -3525,6 +3535,7 @@ impl<'sdk> TradeResyncClient<'sdk> { } #[cfg(not(feature = "relay-runtime"))] { + let _ = self.sdk; let _ = request; Err(RadrootsSdkError::ProductSyncUnsupported { operation: "trade.resync", @@ -3647,7 +3658,7 @@ fn trade_evidence_fetch_request( ) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn trade_evidence_query_plan( locator: RadrootsTradeLocator, limit: u32, @@ -3720,7 +3731,7 @@ fn trade_evidence_query_plan( }) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn trade_evidence_branch( kind: TradeEvidenceQueryBranchKind, event_kinds: Vec<u32>, @@ -3788,13 +3799,13 @@ fn trade_evidence_branch_filter( } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] struct ListingSnapshotFilterParts { seller_pubkey: String, listing_id: String, } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn listing_snapshot_filter_parts( listing_addr: &str, ) -> Result<ListingSnapshotFilterParts, RadrootsSdkError> { @@ -3808,7 +3819,7 @@ fn listing_snapshot_filter_parts( }) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn trade_resync_total_relay_failure( receipt: &RadrootsRelayFetchReceipt, relay_count: usize, @@ -3816,7 +3827,7 @@ fn trade_resync_total_relay_failure( relay_count > 0 && receipt.eose_count == 0 && receipt.closed_count >= relay_count } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn trade_resync_total_failure_message( operation: &str, receipt: &RadrootsRelayFetchReceipt, @@ -3827,7 +3838,7 @@ fn trade_resync_total_failure_message( ) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl TradeResyncEvidenceReceipt { async fn from_fetch( sdk: &crate::RadrootsClient, @@ -3865,7 +3876,7 @@ impl TradeResyncEvidenceReceipt { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] async fn trade_evidence_branch_receipts( sdk: &crate::RadrootsClient, query_plan: &TradeEvidenceQueryPlan, @@ -3902,7 +3913,7 @@ async fn trade_evidence_branch_receipts( .collect()) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] async fn trade_evidence_event_branch( sdk: &crate::RadrootsClient, query_plan: &TradeEvidenceQueryPlan, @@ -3940,7 +3951,7 @@ async fn trade_evidence_event_branch( .unwrap_or(TradeEvidenceQueryBranchKind::RejectedEvidence)) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn trade_evidence_branch_matches_event( branch: &TradeEvidenceQueryBranch, stored_event: &RadrootsStoredEvent, @@ -3966,7 +3977,7 @@ fn trade_evidence_branch_matches_event( } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl TradeEvidenceBranchReceipt { fn from_parts( branch: TradeEvidenceQueryBranchKind, @@ -4016,7 +4027,7 @@ impl TradeEvidenceBranchReceipt { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayFetchEventReceipt> for TradeResyncEventImportReceipt { fn from(receipt: RadrootsRelayFetchEventReceipt) -> Self { Self { @@ -4035,7 +4046,7 @@ impl From<RadrootsRelayFetchEventReceipt> for TradeResyncEventImportReceipt { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayFetchRelayOutcome> for TradeResyncRelayOutcomeReceipt { fn from(receipt: RadrootsRelayFetchRelayOutcome) -> Self { Self { @@ -4047,7 +4058,7 @@ impl From<RadrootsRelayFetchRelayOutcome> for TradeResyncRelayOutcomeReceipt { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayFetchOutcomeKind> for TradeResyncRelayOutcomeKind { fn from(kind: RadrootsRelayFetchOutcomeKind) -> Self { match kind { @@ -4058,7 +4069,7 @@ impl From<RadrootsRelayFetchOutcomeKind> for TradeResyncRelayOutcomeKind { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayOutcomeKind> for TradeResyncRelayTransportOutcomeKind { fn from(kind: RadrootsRelayOutcomeKind) -> Self { match kind { @@ -4096,6 +4107,7 @@ impl<'sdk> TradeValidationReceiptsClient<'sdk> { } #[cfg(not(feature = "relay-runtime"))] { + let _ = self.sdk; let _ = request; Err(RadrootsSdkError::ProductSyncUnsupported { operation: "trade.validation_receipts.list", @@ -4159,6 +4171,7 @@ impl<'sdk> TradeValidationReceiptsClient<'sdk> { } #[cfg(not(feature = "relay-runtime"))] { + let _ = self.sdk; let _ = request; Err(RadrootsSdkError::ProductSyncUnsupported { operation: "trade.validation_receipts.inspect", @@ -4197,6 +4210,7 @@ impl<'sdk> TradeValidationReceiptsClient<'sdk> { } #[cfg(not(feature = "relay-runtime"))] { + let _ = self.sdk; let _ = request; Err(RadrootsSdkError::ProductSyncUnsupported { operation: "trade.validation_receipts.verify", @@ -4270,7 +4284,7 @@ where }) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn validation_receipt_relay_targets( sdk: &crate::RadrootsClient, operation: impl Into<String>, @@ -4361,7 +4375,7 @@ fn validation_receipt_worker_fetch_request( )) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] async fn validation_receipt_events_from_fetch( sdk: &crate::RadrootsClient, events: &[TradeResyncEventImportReceipt], @@ -4395,7 +4409,7 @@ async fn validation_receipt_events_from_fetch( Ok(fetched) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn classify_validation_receipts( events: Vec<RadrootsNostrEvent>, expected_order_id: Option<&str>, @@ -5075,7 +5089,7 @@ struct RawTradeValidationReceiptWorkerResult { worker_role: Option<String>, } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl TradeValidationReceiptRelayEvidenceReceipt { fn from_fetch(receipt: RadrootsRelayFetchReceipt) -> Self { Self { @@ -5094,7 +5108,7 @@ impl TradeValidationReceiptRelayEvidenceReceipt { } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayFetchRelayOutcome> for TradeValidationReceiptRelayOutcomeReceipt { fn from(receipt: RadrootsRelayFetchRelayOutcome) -> Self { Self { @@ -5106,7 +5120,7 @@ impl From<RadrootsRelayFetchRelayOutcome> for TradeValidationReceiptRelayOutcome } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayFetchOutcomeKind> for TradeValidationReceiptRelayOutcomeKind { fn from(kind: RadrootsRelayFetchOutcomeKind) -> Self { match kind { @@ -5117,7 +5131,7 @@ impl From<RadrootsRelayFetchOutcomeKind> for TradeValidationReceiptRelayOutcomeK } } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] impl From<RadrootsRelayOutcomeKind> for TradeValidationReceiptRelayTransportOutcomeKind { fn from(kind: RadrootsRelayOutcomeKind) -> Self { match kind { @@ -5182,7 +5196,7 @@ fn stored_event_to_nostr_event( }) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn validation_receipt_event_order( left: &TradeValidationReceiptEvent, right: &TradeValidationReceiptEvent, @@ -5193,7 +5207,7 @@ fn validation_receipt_event_order( .then_with(|| left.event.id.cmp(&right.event.id)) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn validation_receipt_invalid_order( left: &TradeValidationReceiptInvalidCandidate, right: &TradeValidationReceiptInvalidCandidate, @@ -5204,7 +5218,7 @@ fn validation_receipt_invalid_order( .then_with(|| left.event.id.cmp(&right.event.id)) } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn validate_validation_receipt_limit(limit: u32) -> Result<(), RadrootsSdkError> { if limit == 0 || limit > TRADE_STATUS_MAX_LIMIT { return Err(RadrootsSdkError::trade_status_limit_invalid( @@ -5233,7 +5247,7 @@ where .collect() } -#[cfg(feature = "runtime")] +#[cfg(all(feature = "runtime", feature = "relay-runtime"))] fn validation_receipt_invalid_reason_code(error: &RadrootsValidationReceiptError) -> &'static str { match error { RadrootsValidationReceiptError::InvalidProofMetadata("proof.material") @@ -6055,7 +6069,7 @@ async fn ingest_explicit_trade_mutation_evidence( Ok(()) } -#[cfg(feature = "signer-adapters")] +#[cfg(all(feature = "signer-adapters", feature = "relay-runtime"))] fn require_trade_mutation_online_evidence_clean( operation: &'static str, status: &TradeStatusReceipt, diff --git a/crates/sdk/src/runtime.rs b/crates/sdk/src/runtime.rs @@ -15,7 +15,7 @@ use radroots_event_store::RadrootsEventStore; #[cfg(feature = "runtime")] use radroots_outbox::RadrootsOutbox; #[cfg(feature = "runtime")] -use sqlx::SqlitePool; +use sqlx::{Row, SqlitePool}; #[cfg(feature = "runtime")] use std::{ fs, @@ -166,12 +166,23 @@ pub struct SdkSqliteStoreStatus { pub journal_mode: String, pub foreign_keys_enabled: bool, pub busy_timeout_ms: i64, + pub wal_checkpoint: SdkSqliteWalCheckpointStatus, pub integrity_ok: bool, pub integrity_result: String, } #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SdkSqliteWalCheckpointStatus { + pub wal_enabled: bool, + pub busy: i64, + pub log_frame_count: i64, + pub checkpointed_frame_count: i64, + pub checkpoint_complete: bool, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct SdkEventStoreStorageStatus { pub store: SdkSqliteStoreStatus, pub total_events: i64, @@ -1400,12 +1411,14 @@ async fn sqlite_store_status( foreign_keys_enabled: bool, busy_timeout_ms: i64, ) -> Result<SdkSqliteStoreStatus, RadrootsSdkError> { + let wal_checkpoint = sqlite_wal_checkpoint_status(pool, &journal_mode).await?; let integrity = sqlite_integrity_result(pool).await?; Ok(SdkSqliteStoreStatus { schema_version, journal_mode, foreign_keys_enabled, busy_timeout_ms, + wal_checkpoint, integrity_ok: integrity.ok, integrity_result: integrity.result, }) @@ -1419,18 +1432,107 @@ async fn private_sqlite_store_status( foreign_keys_enabled: bool, busy_timeout_ms: i64, ) -> Result<SdkSqliteStoreStatus, RadrootsSdkError> { + let wal_checkpoint = private_store_sqlite_wal_checkpoint_status(pool, &journal_mode).await?; let integrity = private_store_sqlite_integrity_result(pool).await?; Ok(SdkSqliteStoreStatus { schema_version, journal_mode, foreign_keys_enabled, busy_timeout_ms, + wal_checkpoint, integrity_ok: integrity.ok, integrity_result: integrity.result, }) } #[cfg(feature = "runtime")] +async fn sqlite_wal_checkpoint_status( + pool: &SqlitePool, + journal_mode: &str, +) -> Result<SdkSqliteWalCheckpointStatus, RadrootsSdkError> { + let row = sqlx::query("PRAGMA wal_checkpoint(PASSIVE)") + .fetch_one(pool) + .await + .map_err(|error| RadrootsSdkError::EventStore { + message: error.to_string(), + })?; + let busy = row + .try_get(0) + .map_err(|error| RadrootsSdkError::EventStore { + message: error.to_string(), + })?; + let log_frame_count = row + .try_get(1) + .map_err(|error| RadrootsSdkError::EventStore { + message: error.to_string(), + })?; + let checkpointed_frame_count = + row.try_get(2) + .map_err(|error| RadrootsSdkError::EventStore { + message: error.to_string(), + })?; + Ok(sqlite_wal_checkpoint_status_from_values( + journal_mode, + busy, + log_frame_count, + checkpointed_frame_count, + )) +} + +#[cfg(feature = "runtime")] +async fn private_store_sqlite_wal_checkpoint_status( + pool: &SqlitePool, + journal_mode: &str, +) -> Result<SdkSqliteWalCheckpointStatus, RadrootsSdkError> { + let row = sqlx::query("PRAGMA wal_checkpoint(PASSIVE)") + .fetch_one(pool) + .await + .map_err(|error| RadrootsSdkError::PrivateStore { + message: error.to_string(), + })?; + let busy = row + .try_get(0) + .map_err(|error| RadrootsSdkError::PrivateStore { + message: error.to_string(), + })?; + let log_frame_count = row + .try_get(1) + .map_err(|error| RadrootsSdkError::PrivateStore { + message: error.to_string(), + })?; + let checkpointed_frame_count = + row.try_get(2) + .map_err(|error| RadrootsSdkError::PrivateStore { + message: error.to_string(), + })?; + Ok(sqlite_wal_checkpoint_status_from_values( + journal_mode, + busy, + log_frame_count, + checkpointed_frame_count, + )) +} + +#[cfg(feature = "runtime")] +fn sqlite_wal_checkpoint_status_from_values( + journal_mode: &str, + busy: i64, + log_frame_count: i64, + checkpointed_frame_count: i64, +) -> SdkSqliteWalCheckpointStatus { + let wal_enabled = journal_mode.eq_ignore_ascii_case("wal"); + let checkpoint_complete = busy == 0 + && (!wal_enabled || (log_frame_count >= 0 && log_frame_count == checkpointed_frame_count)); + SdkSqliteWalCheckpointStatus { + wal_enabled, + busy, + log_frame_count, + checkpointed_frame_count, + checkpoint_complete, + } +} + +#[cfg(feature = "runtime")] async fn sqlite_integrity_result( pool: &SqlitePool, ) -> Result<SqliteIntegrityResult, RadrootsSdkError> { diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs @@ -8,19 +8,19 @@ use std::time::{Duration, Instant}; #[cfg(feature = "relay-runtime")] use futures::future::BoxFuture; +#[cfg(feature = "relay-runtime")] use nostr::JsonUtil; use radroots_authority::RadrootsActorContext; use radroots_core::{ RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, }; use radroots_event_store::{RadrootsEventIngest, RadrootsEventStore}; +#[cfg(feature = "relay-runtime")] +use radroots_events::ids::RadrootsPublicKey; use radroots_events::{ RadrootsNostrEvent, RadrootsNostrEventPtr, contract::RadrootsActorRole, - ids::{ - RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsOrderRevisionId, - RadrootsPublicKey, - }, + ids::{RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsOrderRevisionId}, kinds::{ KIND_LISTING, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, KIND_POST, KIND_TRADE_TRANSITION_PROOF_RESULT, KIND_TRADE_VALIDATION_RECEIPT, @@ -38,36 +38,42 @@ use radroots_nostr::prelude::{ radroots_nostr_build_event, }; use radroots_outbox::RadrootsOutbox; -use radroots_relay_transport::{RadrootsMockRelayFetchAdapter, RadrootsRelayFetchItem}; #[cfg(feature = "relay-runtime")] use radroots_relay_transport::{ - RadrootsRelayFetchAdapter, RadrootsRelayFetchRequest, RadrootsRelayTransportError, + RadrootsMockRelayFetchAdapter, RadrootsRelayFetchAdapter, RadrootsRelayFetchItem, + RadrootsRelayFetchRequest, RadrootsRelayTransportError, }; use radroots_sdk::{ - AckPolicy, DvmValidationReceiptIngestRequest, PublishMode, RadrootsClient, RadrootsSdkError, - RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, RadrootsTradeValidationTrustPolicy, - RadrootsTradeValidationTrustState, RelayResolutionPolicy, SdkMutationState, SdkRelayTargetSet, - SdkRelayUrlPolicy, SdkTradeStatusIssue, SdkTradeStatusIssueKind, SdkTradeStatusSource, - TRADE_STATUS_DEFAULT_LIMIT, TRADE_STATUS_MAX_LIMIT, TRADE_STATUS_WATCH_MAX_CAPACITY, - TRADE_SUBMIT_OPERATION_KIND, TradeAcceptRequest, TradeCancelRequest, TradeDeclineRequest, - TradeEvidenceIngestRequest, TradeEvidenceMode, TradeEvidenceQueryBranchKind, - TradeMutationOutcome, TradeProposeRequest, TradeRequestEvidenceIngestRequest, - TradeResyncRelayOutcomeKind, TradeResyncRelayTransportOutcomeKind, TradeResyncRequest, - TradeRevisionDecisionRequest, TradeRevisionProposalRequest, TradeSellerInboxRequest, + AckPolicy, PublishMode, RadrootsClient, RadrootsSdkError, RadrootsSdkRecoveryAction, + RadrootsSdkTimestamp, RadrootsTradeValidationTrustPolicy, RadrootsTradeValidationTrustState, + RelayResolutionPolicy, SdkMutationState, SdkRelayTargetSet, SdkRelayUrlPolicy, + SdkTradeStatusIssue, SdkTradeStatusIssueKind, SdkTradeStatusSource, TRADE_STATUS_DEFAULT_LIMIT, + TRADE_STATUS_MAX_LIMIT, TRADE_STATUS_WATCH_MAX_CAPACITY, TRADE_SUBMIT_OPERATION_KIND, + TradeAcceptRequest, TradeCancelRequest, TradeDeclineRequest, TradeEvidenceIngestRequest, + TradeEvidenceMode, TradeMutationOutcome, TradeProposeRequest, + TradeRequestEvidenceIngestRequest, TradeRevisionDecisionRequest, TradeRevisionProposalRequest, TradeStatusKind, TradeStatusNextActionKind, TradeStatusRequest, TradeStatusWatchCancelState, - TradeStatusWatchRequest, TradeValidationReceiptInspectRequest, - TradeValidationReceiptListRequest, TradeValidationReceiptVerifyRequest, + TradeStatusWatchRequest, +}; +#[cfg(feature = "relay-runtime")] +use radroots_sdk::{ + DvmValidationReceiptIngestRequest, TradeEvidenceQueryBranchKind, TradeResyncRelayOutcomeKind, + TradeResyncRelayTransportOutcomeKind, TradeResyncRequest, TradeSellerInboxRequest, + TradeValidationReceiptInspectRequest, TradeValidationReceiptListRequest, + TradeValidationReceiptVerifyRequest, }; use radroots_sdk::{PrivacyPreflightConfirmation, PrivacyPreflightStatus, ProductSensitivityField}; #[cfg(all(feature = "signer-adapters", feature = "local-signer"))] use radroots_sdk::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider}; +#[cfg(feature = "relay-runtime")] +use radroots_trade::identity::RadrootsTradeLocator; +use radroots_trade::order::RadrootsOrderIssue; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, validation_receipt_event_build, validation_receipt_public_values_hash_hex, }; -use radroots_trade::{identity::RadrootsTradeLocator, order::RadrootsOrderIssue}; use serde::Serialize; use serde::ser::{self, SerializeStruct}; @@ -85,6 +91,7 @@ const RELAY: &str = "wss://relay.radroots.test"; #[cfg(any())] const OTHER_PUBLIC_KEY_HEX: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; +#[cfg(feature = "relay-runtime")] const RELAY_B: &str = "wss://relay-b.radroots.test"; const PERF_TOTAL_LOCAL_EVENTS: i64 = 100_000; const PERF_TRADE_RELEVANT_EVENTS: i64 = 25_000; @@ -455,6 +462,7 @@ async fn directory_sdk_and_store() -> (tempfile::TempDir, RadrootsClient, Radroo (tempdir, sdk, store) } +#[cfg(feature = "relay-runtime")] async fn directory_sdk_and_store_with_relays( relays: &[&str], ) -> (tempfile::TempDir, RadrootsClient, RadrootsEventStore) { @@ -2613,6 +2621,7 @@ async fn trade_resync_reports_partial_relay_failure() { ); } +#[cfg(feature = "relay-runtime")] async fn relay_event_item_from_store( source: &RadrootsEventStore, event_id: &RadrootsEventId, @@ -2645,6 +2654,7 @@ async fn event_from_store( radroots_event_from_nostr(&event) } +#[cfg(feature = "relay-runtime")] fn relay_raw_event_item( event: &nostr::Event, relay_url: &str, @@ -2657,12 +2667,14 @@ fn relay_raw_event_item( } } +#[cfg(feature = "relay-runtime")] fn relay_eose(relay_url: &str) -> RadrootsRelayFetchItem { RadrootsRelayFetchItem::Eose { relay_url: relay_url.to_owned(), } } +#[cfg(feature = "relay-runtime")] fn relay_closed(relay_url: &str, message: &str) -> RadrootsRelayFetchItem { RadrootsRelayFetchItem::Closed { relay_url: relay_url.to_owned(), @@ -2670,6 +2682,7 @@ fn relay_closed(relay_url: &str, message: &str) -> RadrootsRelayFetchItem { } } +#[cfg(feature = "relay-runtime")] fn relay_malformed(relay_url: &str) -> RadrootsRelayFetchItem { RadrootsRelayFetchItem::Event { relay_url: relay_url.to_owned(), @@ -3820,6 +3833,7 @@ fn revision_economics() -> RadrootsOrderEconomics { } } +#[cfg(feature = "relay-runtime")] fn signed_raw_validation_receipt_event( raw_order_id: &str, listing_event_id: &RadrootsEventId, @@ -3859,6 +3873,7 @@ fn signed_raw_sp1_validation_receipt_event( ) } +#[cfg(feature = "relay-runtime")] fn validation_receipt_wire_parts( raw_order_id: &str, listing_event_id: &RadrootsEventId, @@ -3996,6 +4011,7 @@ fn signed_raw_sp1_worker_result_event( ) } +#[cfg(feature = "relay-runtime")] fn signed_raw_worker_result_event( raw_order_id: &str, receipt_event_id: &RadrootsEventId, @@ -4169,6 +4185,7 @@ fn signed_order_request_event(raw_order_id: &str, created_at: u32) -> RadrootsNo signed_event(BUYER_SECRET_KEY_HEX, created_at, draft) } +#[cfg(feature = "relay-runtime")] fn signed_raw_order_request_event(raw_order_id: &str, created_at: u32) -> nostr::Event { let draft = radroots_events_codec::order::order_request_event_build( &listing_event_ptr(), diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs @@ -8,8 +8,8 @@ use radroots_sdk::{ SDK_IDEMPOTENCY_KEY_MAX_LEN, SDK_RELAY_TARGET_MAX_COUNT, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus, SdkIdempotencyKey, SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy, - SdkRestoreState, SdkSqliteStoreStatus, SdkStorageKind, StorageStatusReceipt, - StorageStatusRequest, + SdkRestoreState, SdkSqliteStoreStatus, SdkSqliteWalCheckpointStatus, SdkStorageKind, + StorageStatusReceipt, StorageStatusRequest, }; use radroots_trade::identity::RadrootsTradeLocator; use sqlx::Row; @@ -678,6 +678,13 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { journal_mode: "wal".to_owned(), foreign_keys_enabled: true, busy_timeout_ms: 5_000, + wal_checkpoint: SdkSqliteWalCheckpointStatus { + wal_enabled: true, + busy: 0, + log_frame_count: 8, + checkpointed_frame_count: 8, + checkpoint_complete: true, + }, integrity_ok: true, integrity_result: "ok".to_owned(), }; @@ -728,6 +735,13 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, + "wal_checkpoint": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + }, "integrity_ok": true, "integrity_result": "ok" }, @@ -743,6 +757,13 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, + "wal_checkpoint": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + }, "integrity_ok": true, "integrity_result": "ok" }, @@ -762,6 +783,13 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, + "wal_checkpoint": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + }, "integrity_ok": true, "integrity_result": "ok" }, diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs @@ -45,6 +45,13 @@ fn sqlite_status() -> SdkSqliteStoreStatus { journal_mode: "wal".to_owned(), foreign_keys_enabled: true, busy_timeout_ms: 5_000, + wal_checkpoint: SdkSqliteWalCheckpointStatus { + wal_enabled: true, + busy: 0, + log_frame_count: 0, + checkpointed_frame_count: 0, + checkpoint_complete: true, + }, integrity_ok: true, integrity_result: "ok".to_owned(), } @@ -57,6 +64,18 @@ fn private_sqlite_status() -> SdkSqliteStoreStatus { } } +fn assert_wal_checkpoint_ready(status: &SdkSqliteStoreStatus) { + assert_eq!(status.journal_mode, "wal"); + assert!(status.wal_checkpoint.wal_enabled); + assert_eq!(status.wal_checkpoint.busy, 0); + assert!(status.wal_checkpoint.log_frame_count >= 0); + assert_eq!( + status.wal_checkpoint.log_frame_count, + status.wal_checkpoint.checkpointed_frame_count + ); + assert!(status.wal_checkpoint.checkpoint_complete); +} + fn storage_status() -> StorageStatusReceipt { StorageStatusReceipt { storage: SdkStorageKind::Memory, @@ -360,6 +379,30 @@ async fn runtime_public_surface_covers_builders_status_integrity_backup_and_rest .await .expect("memory status"); assert_eq!(memory_status.storage, SdkStorageKind::Memory); + assert!(!memory_status.event_store.store.wal_checkpoint.wal_enabled); + assert!(!memory_status.outbox.store.wal_checkpoint.wal_enabled); + assert!(!memory_status.private_store.store.wal_checkpoint.wal_enabled); + assert!( + memory_status + .event_store + .store + .wal_checkpoint + .checkpoint_complete + ); + assert!( + memory_status + .outbox + .store + .wal_checkpoint + .checkpoint_complete + ); + assert!( + memory_status + .private_store + .store + .wal_checkpoint + .checkpoint_complete + ); let memory_integrity = memory_sdk .integrity(IntegrityRequest::new()) .await @@ -376,6 +419,14 @@ async fn runtime_public_surface_covers_builders_status_integrity_backup_and_rest .await .expect("directory sdk"); assert!(directory_sdk.storage_paths().is_some()); + let directory_status = directory_sdk + .storage_status(StorageStatusRequest::new()) + .await + .expect("directory status"); + assert_eq!(directory_status.storage, SdkStorageKind::Directory); + assert_wal_checkpoint_ready(&directory_status.event_store.store); + assert_wal_checkpoint_ready(&directory_status.outbox.store); + assert_wal_checkpoint_ready(&directory_status.private_store.store); let backup_destination = tempdir.path().join("backup"); let backup = directory_sdk