app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit a8d9f9114b73872f605679cc31cd8420786f512f
parent 33ed23a220643548f8816b8098ebe8f8cf7133e6
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 01:19:08 +0000

runtime: align SDK event evidence boundary

- convert revision-decision SDK evidence into signed-event ingest requests
- replace Studio event-envelope field access and construction with typed APIs
- refresh the Studio lockfile and Rust floor for the SDK/runtime stack

Diffstat:
MCargo.lock | 17+++++++++++++++++
MCargo.toml | 2+-
Mcrates/desktop/src/runtime.rs | 178+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcrates/runtime/src/sdk.rs | 44++++++++++++++++++++++++++++++++++++++------
Mcrates/store/src/interop.rs | 248+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mrust-toolchain.toml | 2+-
6 files changed, 303 insertions(+), 188 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -5110,6 +5110,7 @@ version = "0.1.0-alpha.2" dependencies = [ "hex", "radroots_core", + "secp256k1", "serde", "serde_json", "sha2", @@ -5267,6 +5268,13 @@ dependencies = [ ] [[package]] +name = "radroots_protocol_contract_v1" +version = "0.1.0-alpha.2" +dependencies = [ + "serde", +] + +[[package]] name = "radroots_runtime" version = "0.1.0-alpha.2" dependencies = [ @@ -5289,6 +5297,14 @@ dependencies = [ ] [[package]] +name = "radroots_runtime_contract_v1" +version = "0.1.0" +dependencies = [ + "radroots_protocol_contract_v1", + "serde", +] + +[[package]] name = "radroots_runtime_paths" version = "0.1.0-alpha.2" dependencies = [ @@ -5322,6 +5338,7 @@ dependencies = [ "radroots_nostr_connect", "radroots_nostr_signer", "radroots_outbox", + "radroots_runtime_contract_v1", "radroots_runtime_paths", "radroots_trade", "radroots_transport", diff --git a/Cargo.toml b/Cargo.toml @@ -18,7 +18,7 @@ resolver = "2" version = "0.1.0" edition = "2024" authors = ["Radroots Authors"] -rust-version = "1.92.0" +rust-version = "1.97.0" license = "GPL-3.0-or-later" repository = "https://github.com/radrootslabs/studio_app" homepage = "https://radroots.org" diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs @@ -11,7 +11,7 @@ use radroots_core::{ RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; use radroots_event::{ - RadrootsEventEnvelope, RadrootsEventPtr, + RadrootsEventEnvelope, RadrootsEventEnvelopeParts, RadrootsEventPtr, farm::{RadrootsFarm, RadrootsFarmPublicLocation, RadrootsFarmRef}, ids::{ RadrootsDTag, RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, @@ -5648,9 +5648,9 @@ impl DesktopAppRuntimeState { ) -> Result<ResolvedAppOrderLifecycleEvidence, AppSqliteError> { let mut events = self.collect_order_lifecycle_signed_events()?; events.sort_by(|left, right| { - left.created_at - .cmp(&right.created_at) - .then_with(|| left.id.cmp(&right.id)) + left.created_at_u64() + .cmp(&right.created_at_u64()) + .then_with(|| left.id_str().cmp(right.id_str())) }); let mut buckets = AppActiveOrderEvidenceBuckets::default(); @@ -5672,10 +5672,10 @@ impl DesktopAppRuntimeState { { continue; } - let event_id = active_order_event_id(event.id.as_str(), "event_id")?; - let author_pubkey = active_order_pubkey(event.author.as_str(), "author_pubkey")?; + let event_id = active_order_event_id(event.id_str(), "event_id")?; + let author_pubkey = active_order_pubkey(event.author_str(), "author_pubkey")?; signed_events_by_id.insert(event_id.to_string(), event.clone()); - match event.kind { + match event.kind_u32() { KIND_ORDER_DECISION => { let envelope = order_decision_from_event(&event).map_err(|_| { AppSqliteError::InvalidProjection { @@ -5845,7 +5845,7 @@ impl DesktopAppRuntimeState { for event in sqlite_store.load_local_interop_signed_events_by_kind(i64::from(kind))? { - if seen_event_ids.insert(event.id.clone()) { + if seen_event_ids.insert(event.id_str().to_owned()) { events.push(event); } } @@ -5895,7 +5895,7 @@ impl DesktopAppRuntimeState { let Some(event) = signed_event_from_local_record(&record)? else { continue; }; - if seen_event_ids.insert(event.id.clone()) { + if seen_event_ids.insert(event.id_str().to_owned()) { events.push(event); } } @@ -9438,14 +9438,14 @@ fn signed_event_from_local_record( return Ok(None); }; let created_at = record.event_created_at.unwrap_or_default(); - let created_at = u32::try_from(created_at).map_err(|_| AppSqliteError::InvalidProjection { - reason: "signed local event created_at must fit u32", + let created_at = u64::try_from(created_at).map_err(|_| AppSqliteError::InvalidProjection { + reason: "signed local event created_at must be non-negative", })?; let kind = u32::try_from(kind).map_err(|_| AppSqliteError::InvalidProjection { reason: "signed local event kind must fit u32", })?; - Ok(Some(RadrootsEventEnvelope { + Ok(RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: id.to_owned(), author: author.to_owned(), created_at, @@ -9453,7 +9453,8 @@ fn signed_event_from_local_record( tags: event_tags_from_value(record.event_tags_json.as_ref())?, content: content.clone(), sig: sig.to_owned(), - })) + }) + .ok()) } fn event_tags_from_value( @@ -9491,7 +9492,7 @@ fn event_tags_from_value( } fn trade_chain_tag_value(event: &RadrootsEventEnvelope, key: &str) -> Option<String> { - event.tags.iter().find_map(|tag| { + event.tags_as_vec().iter().find_map(|tag| { if tag.first().map(String::as_str) == Some(key) { tag.get(1) .map(String::as_str) @@ -9528,7 +9529,8 @@ fn active_order_event_record_context( event: &RadrootsEventEnvelope, message_type: RadrootsOrderEventType, ) -> Result<(RadrootsPublicKey, RadrootsEventId, RadrootsEventId), AppSqliteError> { - let context = order_event_context_from_tags(message_type, &event.tags).map_err(|_| { + let tags = event.tags_as_vec(); + let context = order_event_context_from_tags(message_type, &tags).map_err(|_| { AppSqliteError::InvalidProjection { reason: "order lifecycle evidence is invalid", } @@ -9587,18 +9589,18 @@ fn insert_seller_order_request_evidence( let app_order_id = projected_order_id_from_trade_request( payload.order_id.as_str(), payload.buyer_pubkey.as_str(), - event.id.as_str(), + event.id_str(), ); if app_order_id != *order_id { return; } matched_requests - .entry(event.id.clone()) + .entry(event.id_str().to_owned()) .or_insert_with(|| ResolvedAppSellerOrderRequest { request_event: event.clone(), - request_event_id: event.id.clone(), - request_author_pubkey: event.author.clone(), - listing_event_id: listing_event_id_from_tags(&event.tags), + request_event_id: event.id_str().to_owned(), + request_author_pubkey: event.author_str().to_owned(), + listing_event_id: listing_event_id_from_tags(&event.tags_as_vec()), payload, }); } @@ -9850,13 +9852,13 @@ mod tests { RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }; - use radroots_event::{RadrootsEventEnvelope, RadrootsEventPtr}; - use radroots_event_codec::{ - order::{ - order_cancellation_event_build, order_decision_event_build, order_request_event_build, - order_revision_proposal_event_build, - }, - wire::WireEventParts, + use radroots_event::{ + RadrootsEventEnvelope, RadrootsEventEnvelopeParts, RadrootsEventPtr, + wire::RadrootsNip01EventWireParts as WireEventParts, + }; + use radroots_event_codec::order::{ + order_cancellation_event_build, order_decision_event_build, order_request_event_build, + order_revision_proposal_event_build, }; use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; use radroots_nostr::prelude::{ @@ -10221,13 +10223,13 @@ mod tests { fn seed_relay_with_sdk_event(relay: &ThreadedAckRelay, event: &RadrootsEventEnvelope) { relay.seed_event(json!({ - "id": event.id, - "pubkey": event.author, - "created_at": event.created_at, - "kind": event.kind, - "tags": event.tags, - "content": event.content, - "sig": event.sig, + "id": event.id_str(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "kind": event.kind_u32(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str(), })); } @@ -10354,15 +10356,16 @@ mod tests { parts: WireEventParts, ) -> RadrootsEventEnvelope { signed_test_event_for_pubkey(event_pubkey, created_at, parts.clone()).unwrap_or_else(|| { - RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event_id, author: event_pubkey.to_owned(), - created_at, + created_at: u64::from(created_at), kind: parts.kind, tags: parts.tags, content: parts.content, sig: test_event_signature_seed(record_id), - } + }) + .expect("test event envelope") }) } @@ -20107,7 +20110,8 @@ mod tests { .resolve_seller_order_request_evidence(order_id) .expect("seller request evidence should resolve") .request_event - .id; + .id_str() + .to_owned(); let payload = AppPublishPayload::OrderDecision(AppOrderDecisionPublishPayload { context: AppPublishContext::new(account_id.clone(), "seller_order_decision"), app_order_id: order_id, @@ -20305,7 +20309,9 @@ mod tests { 1_774_000_010, parts, ); - let stored_event_id = event.id.clone(); + let stored_event_id = event.id_str().to_owned(); + let event_created_at = i64::try_from(event.created_at_u64()).expect("event timestamp"); + let event_tags = event.tags_as_vec(); let relay_delivery_json = RelayDeliveryEvidence::acknowledged( ["wss://relay.example"], ["wss://relay.example"], @@ -20324,24 +20330,25 @@ mod tests { created_at_ms: 1_774_000_010_000, inserted_at_ms: 1_774_000_010_001, owner_account_id: None, - owner_pubkey: Some(event.author.clone()), + owner_pubkey: Some(event.author_str().to_owned()), farm_id: None, listing_addr: Some(listing_addr.to_owned()), local_work_json: None, - event_id: Some(event.id.clone()), - event_kind: Some(i64::from(event.kind)), - event_pubkey: Some(event.author.clone()), - event_created_at: Some(i64::from(event.created_at)), - event_tags_json: Some(json!(event.tags.clone())), - event_content: Some(event.content.clone()), - event_sig: Some(event.sig.clone()), + event_id: Some(event.id_str().to_owned()), + event_kind: Some(i64::from(event.kind_u32())), + event_pubkey: Some(event.author_str().to_owned()), + event_created_at: Some(event_created_at), + event_tags_json: Some(json!(event_tags)), + event_content: Some(event.content().to_owned()), + event_sig: Some(event.sig_str().to_owned()), raw_event_json: Some(json!({ - "id": event.id.clone(), - "kind": event.kind, - "pubkey": event.author.clone(), - "tags": event.tags.clone(), - "content": event.content.clone(), - "sig": event.sig.clone() + "id": event.id_str(), + "kind": event.kind_u32(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str() })), outbox_status: PublishOutboxStatus::Acknowledged, relay_set_fingerprint: Some("relay-set".to_owned()), @@ -20399,7 +20406,9 @@ mod tests { .sign_with_keys(&keys) .expect("order request event should sign"); let event = radroots_event_from_nostr(&signed_event); - let stored_event_id = event.id.clone(); + let stored_event_id = event.id_str().to_owned(); + let event_created_at = i64::try_from(event.created_at_u64()).expect("event timestamp"); + let event_tags = event.tags_as_vec(); let record_id = format!("app:signed_event:order-request:{trade_order_id}"); let relay_delivery_json = RelayDeliveryEvidence::acknowledged( ["wss://relay.example"], @@ -20419,20 +20428,26 @@ mod tests { created_at_ms: 1_774_000_010_000, inserted_at_ms: 1_774_000_010_001, owner_account_id: None, - owner_pubkey: Some(event.author.clone()), + owner_pubkey: Some(event.author_str().to_owned()), farm_id: None, listing_addr: Some(listing_addr.to_owned()), local_work_json: None, - event_id: Some(event.id.clone()), - event_kind: Some(i64::from(event.kind)), - event_pubkey: Some(event.author.clone()), - event_created_at: Some(i64::from(event.created_at)), - event_tags_json: Some(json!(event.tags.clone())), - event_content: Some(event.content.clone()), - event_sig: Some(event.sig.clone()), - raw_event_json: Some( - serde_json::to_value(&event).expect("SDK test event should serialize"), - ), + event_id: Some(event.id_str().to_owned()), + event_kind: Some(i64::from(event.kind_u32())), + event_pubkey: Some(event.author_str().to_owned()), + event_created_at: Some(event_created_at), + event_tags_json: Some(json!(event_tags)), + event_content: Some(event.content().to_owned()), + event_sig: Some(event.sig_str().to_owned()), + raw_event_json: Some(json!({ + "id": event.id_str(), + "kind": event.kind_u32(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str() + })), outbox_status: PublishOutboxStatus::Acknowledged, relay_set_fingerprint: Some("relay-set".to_owned()), relay_delivery_json: Some(relay_delivery_json), @@ -20657,7 +20672,9 @@ mod tests { created_at, parts, ); - let stored_event_id = event.id.clone(); + let stored_event_id = event.id_str().to_owned(); + let event_created_at = i64::try_from(event.created_at_u64()).expect("event timestamp"); + let event_tags = event.tags_as_vec(); let relay_delivery_json = RelayDeliveryEvidence::acknowledged( ["wss://relay.example"], ["wss://relay.example"], @@ -20676,24 +20693,25 @@ mod tests { created_at_ms: i64::from(created_at) * 1000, inserted_at_ms: i64::from(created_at) * 1000 + 1, owner_account_id: None, - owner_pubkey: Some(event.author.clone()), + owner_pubkey: Some(event.author_str().to_owned()), farm_id: None, listing_addr: Some(listing_addr.to_owned()), local_work_json: None, - event_id: Some(event.id.clone()), - event_kind: Some(i64::from(event.kind)), - event_pubkey: Some(event.author.clone()), - event_created_at: Some(i64::from(event.created_at)), - event_tags_json: Some(json!(event.tags.clone())), - event_content: Some(event.content.clone()), - event_sig: Some(event.sig.clone()), + event_id: Some(event.id_str().to_owned()), + event_kind: Some(i64::from(event.kind_u32())), + event_pubkey: Some(event.author_str().to_owned()), + event_created_at: Some(event_created_at), + event_tags_json: Some(json!(event_tags)), + event_content: Some(event.content().to_owned()), + event_sig: Some(event.sig_str().to_owned()), raw_event_json: Some(json!({ - "id": event.id.clone(), - "kind": event.kind, - "pubkey": event.author.clone(), - "tags": event.tags.clone(), - "content": event.content.clone(), - "sig": event.sig.clone() + "id": event.id_str(), + "kind": event.kind_u32(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str() })), outbox_status: PublishOutboxStatus::Acknowledged, relay_set_fingerprint: Some("relay-set".to_owned()), diff --git a/crates/runtime/src/sdk.rs b/crates/runtime/src/sdk.rs @@ -14,6 +14,7 @@ use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner}; use radroots_event::{ RadrootsEventEnvelope, RadrootsEventPtr, contract::RadrootsActorRole, + draft::{RadrootsSignedEvent, RadrootsSignedEventParts}, farm::RadrootsFarm, ids::{ RadrootsAddressableCoordinate, RadrootsListingAddress, RadrootsOrderId, @@ -1699,6 +1700,12 @@ fn trade_revision_decide_with_sdk( RadrootsActorRole::Buyer, )?; let sdk = runtime.block_on(build_sdk_runtime_with_signer(config, request.signer_keys))?; + let evidence = request + .evidence + .into_iter() + .map(signed_trade_evidence_from_envelope) + .map(|event| event.map(TradeEvidenceIngestRequest::new)) + .collect::<Result<Vec<_>, _>>()?; let mut sdk_request = TradeRevisionDecisionRequest::new( actor, request.locator, @@ -1707,12 +1714,7 @@ fn trade_revision_decide_with_sdk( app_trade_target_policy(), app_trade_publish_mode(), app_trade_satisfaction_policy(), - TradeEvidenceMode::require_explicit_evidence( - request - .evidence - .into_iter() - .map(TradeEvidenceIngestRequest::new), - ), + TradeEvidenceMode::require_explicit_evidence(evidence), ) .with_privacy_confirmation(app_trade_privacy_confirmation(request.confirm_public_note)); if let Some(idempotency_key) = request.idempotency_key.as_deref() { @@ -1731,6 +1733,36 @@ fn trade_revision_decide_with_sdk( app_sdk_trade_revision_decision_receipt(outcome, request.actor_pubkey) } +fn signed_trade_evidence_from_envelope( + event: RadrootsEventEnvelope, +) -> Result<RadrootsSignedEvent, AppSdkRuntimeIssue> { + let raw_json = json!({ + "id": event.id_str(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "kind": event.kind_u32(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str(), + }) + .to_string(); + RadrootsSignedEvent::new(RadrootsSignedEventParts { + id: event.id_str().to_owned(), + pubkey: event.author_str().to_owned(), + created_at: event.created_at_u64(), + kind: event.kind_u32(), + tags: event.tags_as_vec(), + content: event.content().to_owned(), + sig: event.sig_str().to_owned(), + raw_json, + }) + .map_err(|error| { + AppSdkRuntimeIssue::from_sdk_error(&RadrootsSdkError::InvalidRequest { + message: format!("trade revision evidence signed event is invalid: {error}"), + }) + }) +} + fn trade_cancel_with_sdk( runtime: &tokio::runtime::Runtime, config: &AppSdkConfig, diff --git a/crates/store/src/interop.rs b/crates/store/src/interop.rs @@ -1,7 +1,7 @@ use std::{fs, path::Path}; use radroots_event::{ - RadrootsEventEnvelope, + RadrootsEventEnvelope, RadrootsEventEnvelopeParts, ids::{RadrootsEventId, RadrootsOrderId, RadrootsPublicKey}, kinds::{ KIND_FARM as RADROOTS_KIND_FARM, KIND_LISTING as RADROOTS_KIND_LISTING, @@ -1141,7 +1141,7 @@ impl<'a> AppLocalInteropRepository<'a> { self.upsert_validation_receipt_projection(&event, &verified.receipt, &verified.tags)?; Ok(Some(ProjectionRecord { kind: "validation_receipt", - projected_id: Some(event.id), + projected_id: Some(event.id_str().to_owned()), })) } @@ -1350,6 +1350,11 @@ impl<'a> AppLocalInteropRepository<'a> { let proof_system = TradeValidationReceiptProofSystem::from_validation_receipt_proof_system( receipt.proof.system, ); + let event_created_at = i64::try_from(event.created_at_u64()).map_err(|_| { + AppSqliteError::InvalidProjection { + reason: "validation receipt event timestamp must fit i64", + } + })?; self.connection .execute( @@ -1382,7 +1387,7 @@ impl<'a> AppLocalInteropRepository<'a> { public_values_hash = excluded.public_values_hash, event_created_at = excluded.event_created_at", params![ - event.id.as_str(), + event.id_str(), order_id.map(|order_id| order_id.to_string()), tags.order_id.as_str(), tags.root_event_id.as_str(), @@ -1394,7 +1399,7 @@ impl<'a> AppLocalInteropRepository<'a> { tags.event_set_root.as_str(), tags.reducer_output_root.as_str(), tags.public_values_hash.as_str(), - i64::from(event.created_at), + event_created_at, ], ) .map_err(|source| AppSqliteError::Query { @@ -3033,15 +3038,15 @@ fn active_order_event_kind(kind: i64) -> bool { } fn active_event_id(event: &RadrootsEventEnvelope) -> Option<RadrootsEventId> { - event.id.parse().ok() + Some(event.id().clone()) } fn active_author_pubkey(event: &RadrootsEventEnvelope) -> Option<RadrootsPublicKey> { - event.author.parse().ok() + Some(event.author().clone()) } fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<ActiveOrderEvidence> { - match i64::from(event.kind) { + match i64::from(event.kind_u32()) { KIND_ORDER_REQUEST => { let envelope = order_request_from_event(event).ok()?; Some(ActiveOrderEvidence::Request(RadrootsOrderRequestRecord { @@ -3052,7 +3057,8 @@ fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<Act } KIND_ORDER_DECISION => { let envelope = order_decision_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; + let tags = event.tags_as_vec(); + let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; Some(ActiveOrderEvidence::Decision(RadrootsOrderDecisionRecord { event_id: active_event_id(event)?, author_pubkey: active_author_pubkey(event)?, @@ -3064,7 +3070,8 @@ fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<Act } KIND_ORDER_REVISION => { let envelope = order_revision_proposal_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; + let tags = event.tags_as_vec(); + let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; Some(ActiveOrderEvidence::RevisionProposal( RadrootsOrderRevisionProposalRecord { event_id: active_event_id(event)?, @@ -3078,7 +3085,8 @@ fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<Act } KIND_ORDER_REVISION_DECISION => { let envelope = order_revision_decision_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; + let tags = event.tags_as_vec(); + let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; Some(ActiveOrderEvidence::RevisionDecision( RadrootsOrderRevisionDecisionRecord { event_id: active_event_id(event)?, @@ -3092,7 +3100,8 @@ fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<Act } KIND_ORDER_CANCEL => { let envelope = order_cancellation_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; + let tags = event.tags_as_vec(); + let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; Some(ActiveOrderEvidence::Cancellation( RadrootsOrderCancellationRecord { event_id: active_event_id(event)?, @@ -3144,7 +3153,7 @@ fn signed_event_from_record( }; let Some(created_at) = record .event_created_at - .and_then(|created_at| u32::try_from(created_at).ok()) + .and_then(|created_at| u64::try_from(created_at).ok()) else { return Ok(None); }; @@ -3159,7 +3168,7 @@ fn signed_event_from_record( let Some(tags) = record.event_tags_json.as_ref().and_then(tags_from_json) else { return Ok(None); }; - Ok(Some(RadrootsEventEnvelope { + Ok(RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: id.to_owned(), author: author.to_owned(), created_at, @@ -3167,7 +3176,8 @@ fn signed_event_from_record( tags, content: record.event_content.clone().unwrap_or_default(), sig: sig.to_owned(), - })) + }) + .ok()) } fn signed_event_record_is_usable(record: &RuntimeStoreRecord) -> bool { @@ -3241,7 +3251,7 @@ fn signed_event_from_local_interop_evidence( }; let Some(created_at) = evidence .event_created_at - .and_then(|created_at| u32::try_from(created_at).ok()) + .and_then(|created_at| u64::try_from(created_at).ok()) else { return Ok(None); }; @@ -3262,7 +3272,7 @@ fn signed_event_from_local_interop_evidence( let Some(tags) = tags_from_json(&tags_value) else { return Ok(None); }; - Ok(Some(RadrootsEventEnvelope { + Ok(RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: id.to_owned(), author: author.to_owned(), created_at, @@ -3270,7 +3280,8 @@ fn signed_event_from_local_interop_evidence( tags, content: evidence.event_content.clone().unwrap_or_default(), sig: sig.to_owned(), - })) + }) + .ok()) } fn tags_from_json(value: &Value) -> Option<Vec<Vec<String>>> { @@ -3860,7 +3871,7 @@ mod tests { RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, }; use radroots_event::{ - RadrootsEventEnvelope, RadrootsEventPtr, + RadrootsEventEnvelope, RadrootsEventEnvelopeParts, RadrootsEventPtr, ids::{ RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, @@ -3872,13 +3883,11 @@ mod tests { RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }, + wire::RadrootsNip01EventWireParts as WireEventParts, }; - use radroots_event_codec::{ - order::{ - order_cancellation_event_build, order_decision_event_build, order_request_event_build, - order_revision_decision_event_build, order_revision_proposal_event_build, - }, - wire::WireEventParts, + use radroots_event_codec::order::{ + order_cancellation_event_build, order_decision_event_build, order_request_event_build, + order_revision_decision_event_build, order_revision_proposal_event_build, }; use radroots_runtime_store::{ PublishOutboxStatus, RelayDeliveryEvidence, RuntimeStore, RuntimeStoreRecordFamily, @@ -4691,36 +4700,69 @@ mod tests { author: &str, parts: WireEventParts, ) -> RadrootsEventEnvelope { + event_from_parts_at(event_id, author, parts, 1_777_665_600) + } + + fn event_from_parts_at( + event_id: &str, + author: &str, + parts: WireEventParts, + created_at: u32, + ) -> RadrootsEventEnvelope { let event_id = event_id .parse::<RadrootsEventId>() .map(|event_id| event_id.to_string()) .unwrap_or_else(|_| test_event_id_seed(event_id)); - RadrootsEventEnvelope { - sig: format!("sig-{event_id}"), + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + sig: test_event_signature_seed(event_id.as_str()), id: event_id, author: author.to_owned(), - created_at: 1_777_665_600, + created_at: u64::from(created_at), kind: parts.kind, tags: parts.tags, content: parts.content, - } + }) + .expect("test event envelope") } - fn event_from_parts_at( - event_id: &str, - author: &str, - parts: WireEventParts, - created_at: u32, + fn event_with_tags( + event: &RadrootsEventEnvelope, + tags: Vec<Vec<String>>, ) -> RadrootsEventEnvelope { - let mut event = event_from_parts(event_id, author, parts); - event.created_at = created_at; - event + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + sig: event.sig_str().to_owned(), + id: event.id_str().to_owned(), + author: event.author_str().to_owned(), + created_at: event.created_at_u64(), + kind: event.kind_u32(), + tags, + content: event.content().to_owned(), + }) + .expect("test event envelope with tags") + } + + fn event_with_kind(event: &RadrootsEventEnvelope, kind: u32) -> RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + sig: event.sig_str().to_owned(), + id: event.id_str().to_owned(), + author: event.author_str().to_owned(), + created_at: event.created_at_u64(), + kind, + tags: event.tags_as_vec(), + content: event.content().to_owned(), + }) + .expect("test event envelope with kind") } fn hex_event_id(seed: u8) -> String { format!("{seed:064x}") } + fn test_event_signature_seed(seed: &str) -> String { + let base = test_event_id_seed(seed); + format!("{base}{base}") + } + fn hash32(seed: u8) -> String { format!("0x{seed:064x}") } @@ -4790,6 +4832,8 @@ mod tests { source_runtime: SourceRuntime, owner_account_id: Option<&str>, ) -> RuntimeStoreRecordInput { + let event_created_at = i64::try_from(event.created_at_u64()).expect("event timestamp"); + let event_tags = event.tags_as_vec(); let relay_delivery_json = RelayDeliveryEvidence::acknowledged( ["ws://127.0.0.1:1234"], ["ws://127.0.0.1:1234"], @@ -4804,28 +4848,28 @@ mod tests { family: RuntimeStoreRecordFamily::SignedEvent, status: RuntimeStoreRecordStatus::Published, source_runtime, - created_at_ms: i64::from(event.created_at) * 1_000, - inserted_at_ms: i64::from(event.created_at) * 1_000 + 1, + created_at_ms: event_created_at * 1_000, + inserted_at_ms: event_created_at * 1_000 + 1, owner_account_id: owner_account_id.map(str::to_owned), - owner_pubkey: Some(event.author.clone()), + owner_pubkey: Some(event.author_str().to_owned()), farm_id: None, listing_addr: Some(listing_addr.to_owned()), local_work_json: None, - event_id: Some(event.id.clone()), - event_kind: Some(i64::from(event.kind)), - event_pubkey: Some(event.author.clone()), - event_created_at: Some(i64::from(event.created_at)), - event_tags_json: Some(json!(event.tags)), - event_content: Some(event.content.clone()), - event_sig: Some(event.sig.clone()), + event_id: Some(event.id_str().to_owned()), + event_kind: Some(i64::from(event.kind_u32())), + event_pubkey: Some(event.author_str().to_owned()), + event_created_at: Some(event_created_at), + event_tags_json: Some(json!(event_tags)), + event_content: Some(event.content().to_owned()), + event_sig: Some(event.sig_str().to_owned()), raw_event_json: Some(json!({ - "id": event.id, - "kind": event.kind, - "pubkey": event.author, - "created_at": event.created_at, - "tags": event.tags, - "content": event.content, - "sig": event.sig, + "id": event.id_str(), + "kind": event.kind_u32(), + "pubkey": event.author_str(), + "created_at": event.created_at_u64(), + "tags": event.tags_as_vec(), + "content": event.content(), + "sig": event.sig_str(), })), outbox_status: PublishOutboxStatus::Acknowledged, relay_set_fingerprint: Some("relay-set".to_owned()), @@ -4889,7 +4933,7 @@ mod tests { .import_shared_runtime_store_from_store(&events) .expect("import signed order request"); let farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, event.id_str()); let orders = app_store .load_orders_list( farm_id, @@ -4924,7 +4968,7 @@ mod tests { .iter() .any(|record| record.projected_kind == "signed_event" && record.event_kind == Some(KIND_ORDER_REQUEST) - && record.event_id.as_deref() == Some(event.id.as_str())) + && record.event_id.as_deref() == Some(event.id_str())) ); assert_eq!(signed_evidence, vec![event.clone()]); assert_eq!(orders.rows.len(), 1); @@ -5154,7 +5198,7 @@ mod tests { .import_shared_runtime_store_from_store(&events) .expect("import app order request"); let buyer_context = BuyerContext::account("acct_buyer"); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); let buyer_orders = app_store .load_buyer_orders(&buyer_context) @@ -5172,8 +5216,8 @@ mod tests { seller_pubkey, ); let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &decision_payload, ) .expect("build order decision event"); @@ -5237,7 +5281,7 @@ mod tests { .provenance .last_event_id .as_deref(), - Some(decision_event.id.as_str()) + Some(decision_event.id_str()) ); assert_eq!( buyer_orders.rows[0].workflow.economics.total_minor_units, @@ -5330,9 +5374,9 @@ mod tests { .expect("import duplicate-root requests"); let first_order_id = - projected_order_id(order_id_raw, buyer_pubkey, first_request_event.id.as_str()); + projected_order_id(order_id_raw, buyer_pubkey, first_request_event.id_str()); let second_order_id = - projected_order_id(order_id_raw, buyer_pubkey, second_request_event.id.as_str()); + projected_order_id(order_id_raw, buyer_pubkey, second_request_event.id_str()); let buyer_context = BuyerContext::account("acct_duplicate_root"); let buyer_orders = app_store .load_buyer_orders(&buyer_context) @@ -5412,7 +5456,7 @@ mod tests { .import_shared_runtime_store_from_store(&events) .expect("import app order request"); let buyer_context = BuyerContext::account("acct_buyer"); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let buyer_orders = app_store .load_buyer_orders(&buyer_context) .expect("load buyer orders after request"); @@ -5429,8 +5473,8 @@ mod tests { seller_pubkey, ); let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &decision_payload, ) .expect("build declined order decision event"); @@ -5551,8 +5595,8 @@ mod tests { seller_pubkey, ); let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &decision_payload, ) .expect("build lifecycle order decision"); @@ -5758,7 +5802,7 @@ mod tests { ); assert_eq!( buyer_detail.validation_receipts[0].event_id, - invalid_event.id + invalid_event.id_str() ); assert_eq!( buyer_detail.validation_receipts[0].result, @@ -5792,7 +5836,10 @@ mod tests { buyer_detail.validation_receipts[0].recorded_at, 1_777_665_604 ); - assert_eq!(buyer_detail.validation_receipts[1].event_id, valid_event.id); + assert_eq!( + buyer_detail.validation_receipts[1].event_id, + valid_event.id_str() + ); assert_eq!( buyer_detail.validation_receipts[1].result, TradeValidationReceiptResult::Valid @@ -5802,7 +5849,7 @@ mod tests { .iter() .any(|record| record.projected_kind == "validation_receipt" && record.event_kind == Some(KIND_VALIDATION_RECEIPT) - && record.event_id.as_deref() == Some(invalid_event.id.as_str())) + && record.event_id.as_deref() == Some(invalid_event.id_str())) ); } @@ -5906,7 +5953,7 @@ mod tests { .import_shared_runtime_store_from_store(&events) .expect("import request after receipt"); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let buyer_context = BuyerContext::account("acct_validation_out_of_order"); let buyer_detail = app_store .load_buyer_order_detail(&buyer_context, order_id) @@ -5927,7 +5974,7 @@ mod tests { ); assert_eq!( buyer_detail.validation_receipts[0].event_id, - receipt_event.id + receipt_event.id_str() ); assert_eq!(buyer_detail.status, BuyerOrderStatus::Placed); assert_eq!(seller_detail.status, OrderStatus::NeedsAction); @@ -5936,7 +5983,7 @@ mod tests { #[test] fn validation_receipt_invalid_candidates_do_not_surface_as_order_evidence() { let fixture = validation_receipt_order_fixture("validation-receipt-invalid-candidates"); - let mut mismatched_tag_event = validation_receipt_event( + let mismatched_tag_event = validation_receipt_event( hex_event_id(61).as_str(), fixture.seller_pubkey.as_str(), fixture.order_id_raw.as_str(), @@ -5946,13 +5993,14 @@ mod tests { RadrootsValidationReceiptResult::Valid, 1_777_665_603, ); - if let Some(tag) = mismatched_tag_event - .tags + let mut mismatched_tags = mismatched_tag_event.tags_as_vec(); + if let Some(tag) = mismatched_tags .iter_mut() .find(|tag| tag.first().map(String::as_str) == Some("event_set_root")) { tag[1] = hash32(99); } + let mismatched_tag_event = event_with_tags(&mismatched_tag_event, mismatched_tags); let wrong_order_event = validation_receipt_event( hex_event_id(62).as_str(), fixture.seller_pubkey.as_str(), @@ -5963,7 +6011,7 @@ mod tests { RadrootsValidationReceiptResult::Valid, 1_777_665_604, ); - let mut buyer_kind_candidate = validation_receipt_event( + let buyer_kind_candidate = validation_receipt_event( hex_event_id(63).as_str(), fixture.buyer_pubkey.as_str(), fixture.order_id_raw.as_str(), @@ -5973,7 +6021,8 @@ mod tests { RadrootsValidationReceiptResult::Valid, 1_777_665_605, ); - buyer_kind_candidate.kind = KIND_ORDER_REQUEST as u32; + let buyer_kind_candidate = + event_with_kind(&buyer_kind_candidate, KIND_ORDER_REQUEST as u32); for (record_id, event) in [ ( @@ -6093,12 +6142,12 @@ mod tests { listing_addr.as_str(), buyer_pubkey, seller_pubkey, - request_event.id.as_str(), - request_event.id.as_str(), + request_event.id_str(), + request_event.id_str(), ); let proposal_parts = order_revision_proposal_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &proposal_payload, ) .expect("build revision proposal"); @@ -6121,7 +6170,7 @@ mod tests { .expect("import revision proposal"); let seller_farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let buyer_context = BuyerContext::account("acct_revision"); let seller_orders = app_store .load_orders_list( @@ -6153,13 +6202,13 @@ mod tests { listing_addr.as_str(), buyer_pubkey, seller_pubkey, - request_event.id.as_str(), - proposal_event.id.as_str(), + request_event.id_str(), + proposal_event.id_str(), RadrootsOrderRevisionOutcome::Accepted, ); let revision_decision_parts = order_revision_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(proposal_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(proposal_event.id_str()), &revision_decision_payload, ) .expect("build revision decision"); @@ -6282,8 +6331,8 @@ mod tests { seller_pubkey, ); let cancel_parts = order_cancellation_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &cancel_payload, ) .expect("build cancellation"); @@ -6302,7 +6351,7 @@ mod tests { .expect("import cancellation"); let seller_farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let buyer_context = BuyerContext::account("acct_cancel"); let buyer_detail = app_store .load_buyer_order_detail(&buyer_context, order_id) @@ -6396,8 +6445,8 @@ mod tests { seller_pubkey, ); let accepted_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &accepted_payload, ) .expect("build accepted conflict decision"); @@ -6413,8 +6462,8 @@ mod tests { seller_pubkey, ); let declined_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &declined_payload, ) .expect("build declined conflict decision"); @@ -6443,8 +6492,7 @@ mod tests { app_store .import_shared_runtime_store_from_store(&events) .expect("import conflicting decisions"); - let order_id = - projected_order_id(order_id_raw, buyer_pubkey, request_event.id.as_str()); + let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); let detail = app_store .load_order_detail( deterministic_farm_id(Some(seller_pubkey), farm_key), @@ -8296,7 +8344,7 @@ mod tests { .expect("load imported records"); let stored = imported .iter() - .find(|record| record.event_id.as_deref() == Some(event.id.as_str())) + .find(|record| record.event_id.as_deref() == Some(event.id_str())) .expect("app order request evidence"); assert_eq!(report.imported_records, 2); @@ -8304,7 +8352,7 @@ mod tests { assert_eq!( imported .iter() - .filter(|record| record.event_id.as_deref() == Some(event.id.as_str())) + .filter(|record| record.event_id.as_deref() == Some(event.id_str())) .count(), 1 ); @@ -8346,8 +8394,8 @@ mod tests { seller_pubkey, ); let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), + &typed_event_id(request_event.id_str()), + &typed_event_id(request_event.id_str()), &decision_payload, ) .expect("build order decision event"); @@ -8386,7 +8434,7 @@ mod tests { .expect("load imported records"); let stored = imported .iter() - .find(|record| record.event_id.as_deref() == Some(decision_event.id.as_str())) + .find(|record| record.event_id.as_deref() == Some(decision_event.id_str())) .expect("app order decision evidence"); assert_eq!(report.imported_records, 1); @@ -8394,7 +8442,7 @@ mod tests { assert_eq!( imported .iter() - .filter(|record| record.event_id.as_deref() == Some(decision_event.id.as_str())) + .filter(|record| record.event_id.as_deref() == Some(decision_event.id_str())) .count(), 1 ); diff --git a/rust-toolchain.toml b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.92.0" +channel = "1.97.0" targets = [ "aarch64-apple-ios", "aarch64-apple-ios-sim",