lib

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

commit 7b7425fa32a47d0f959b5ae21b0e23f566a6ad13
parent b44a2acce87b30a4bce191b7c579a2995fa6ef13
Author: triesap <tyson@radroots.org>
Date:   Tue, 14 Jul 2026 11:05:10 +0000

sdk: align runtime with sealed event DTOs

- migrate SDK runtime and tests to sealed drafts, envelopes, and signed events

- publish source-owned event DTO package shapes without legacy envelope aliases

- send radrootsd publish requests as raw signed event JSON

- harden WASM and source-boundary tests against retired wire aliases

Diffstat:
Mcrates/event_codec_wasm/src/lib.rs | 8+++++---
Mcrates/replica_sync_wasm/src/lib.rs | 68++++++++++++++++++++++++++------------------------------------------
Mcrates/sdk/Cargo.toml | 1+
Mcrates/sdk/examples/sdk_v1_knowledge_prepare.rs | 15+++++++++------
Mcrates/sdk/src/adapters/nostr.rs | 7+++----
Mcrates/sdk/src/adapters/radrootsd.rs | 44++++++++++++++++++++++++++------------------
Mcrates/sdk/src/adapters/signing.rs | 20+++++++++++---------
Mcrates/sdk/src/dvm_runtime.rs | 34++++++++++++++++++----------------
Mcrates/sdk/src/farm.rs | 4++--
Mcrates/sdk/src/farms_runtime.rs | 11++++++-----
Mcrates/sdk/src/knowledge.rs | 79+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mcrates/sdk/src/lib.rs | 12++++++------
Mcrates/sdk/src/listings_runtime.rs | 4++--
Mcrates/sdk/src/order.rs | 28++++++++++++++--------------
Mcrates/sdk/src/orders_runtime.rs | 111+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcrates/sdk/src/signer_provider.rs | 44++++++++++++++++++++++++++++++--------------
Mcrates/sdk/src/sync_runtime.rs | 4++--
Mcrates/sdk/src/workflow_runtime.rs | 23++++-------------------
Mcrates/sdk/tests/dvm_runtime.rs | 57+++++++++++++++++++++++++++++++++++----------------------
Mcrates/sdk/tests/farms_runtime.rs | 28++++++++++++++--------------
Mcrates/sdk/tests/knowledge_public_api.rs | 41+++++++++++++++++++++++------------------
Mcrates/sdk/tests/listings_runtime.rs | 28++++++++++++++--------------
Mcrates/sdk/tests/orders_runtime.rs | 274+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mcrates/sdk/tests/replica_ingest.rs | 11++++++-----
Mcrates/sdk/tests/runtime_foundation.rs | 6+++++-
Mcrates/sdk/tests/source_boundary.rs | 16++++++++++++++++
Mcrates/sdk/tests/support/fixture_signer.rs | 22+++++++++++-----------
Mcrates/sdk/tests/sync_runtime.rs | 24++++++++++++------------
Mcrates/sdk/tests/unit/adapters_nostr_tests.rs | 4++--
Mcrates/sdk/tests/unit/adapters_radrootsd_tests.rs | 54++++++++++++++++++++++++++++--------------------------
Mcrates/sdk/tests/unit/adapters_signing_tests.rs | 6+++---
Mcrates/sdk/tests/unit/dvm_runtime_tests.rs | 46++++++++++++++++++++++++++++++----------------
Mcrates/sdk/tests/unit/orders_runtime_tests.rs | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mcrates/sdk/tests/unit/runtime_tests.rs | 39+++++++++++++++++++++++++++++----------
Mcrates/sdk/tests/unit/signer_provider_tests.rs | 79+++++++++++++++++++------------------------------------------------------------
Mcrates/sdk/tests/unit/sync_runtime_tests.rs | 57+++++++++++++++++++++++++--------------------------------
Mcrates/sdk/tests/unit/workflow_runtime_tests.rs | 61+++++++++++++++++++++++++++++++++++++------------------------
Mtools/sdk_xtask_import/src/contracts.rs | 7++++++-
Mtools/sdk_xtask_import/src/dto_roots.rs | 31++++++++++++++++++++++++++++++-
Mtools/sdk_xtask_import/src/smoke.rs | 9+++++----
40 files changed, 889 insertions(+), 672 deletions(-)

diff --git a/crates/event_codec_wasm/src/lib.rs b/crates/event_codec_wasm/src/lib.rs @@ -607,6 +607,7 @@ mod tests { RadrootsSocialFarmAnchor, RadrootsSocialLocation, RadrootsSocialMediaDimensions, RadrootsSocialMediaMetadata, RadrootsSocialTarget, }; + use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts}; fn sample_listing() -> RadrootsListing { let quantity = @@ -888,10 +889,10 @@ mod tests { .custom_created_at(nostr::Timestamp::from_secs(1_800_000_000)) .sign_with_keys(&keys) .expect("signed event"); - serde_json::to_string(&radroots_event::RadrootsEventEnvelope { + let envelope = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event.id.to_hex(), author: event.pubkey.to_hex(), - created_at: event.created_at.as_secs() as u32, + created_at: event.created_at.as_secs(), kind: u32::from(event.kind.as_u16()), tags: event .tags @@ -902,7 +903,8 @@ mod tests { content: event.content, sig: event.sig.to_string(), }) - .expect("event json") + .expect("event envelope"); + serde_json::to_string(&envelope).expect("event json") } fn social_location() -> RadrootsSocialLocation { diff --git a/crates/replica_sync_wasm/src/lib.rs b/crates/replica_sync_wasm/src/lib.rs @@ -4,7 +4,7 @@ use base64::Engine; #[cfg(target_arch = "wasm32")] use base64::engine::general_purpose::URL_SAFE_NO_PAD; -use radroots_event::RadrootsEventEnvelope; +use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts}; use radroots_replica_sync::RadrootsReplicaSyncRequest; #[cfg(target_arch = "wasm32")] use radroots_replica_sync::{ @@ -36,13 +36,11 @@ impl RadrootsReplicaIdFactory for WasmIdFactory { } #[derive(Deserialize)] -struct NostrEventEnvelope { +#[serde(deny_unknown_fields)] +struct EventEnvelopeInput { id: String, - #[serde(default)] - author: Option<String>, - #[serde(default)] - pubkey: Option<String>, - created_at: u32, + author: String, + created_at: u64, kind: u32, tags: Vec<Vec<String>>, content: String, @@ -54,25 +52,18 @@ pub fn parse_request_model(request_json: &str) -> Result<RadrootsReplicaSyncRequ } pub fn parse_event_model(event_json: &str) -> Result<RadrootsEventEnvelope, String> { - let envelope: NostrEventEnvelope = + let envelope: EventEnvelopeInput = serde_json::from_str(event_json).map_err(|error| error.to_string())?; - let author = match (envelope.author, envelope.pubkey) { - (Some(author), Some(pubkey)) if author != pubkey => { - return Err("author/pubkey mismatch".to_owned()); - } - (Some(author), _) => author, - (None, Some(pubkey)) => pubkey, - (None, None) => return Err("missing author/pubkey".to_owned()), - }; - Ok(RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: envelope.id, - author, + author: envelope.author, created_at: envelope.created_at, kind: envelope.kind, tags: envelope.tags, content: envelope.content, sig: envelope.sig, }) + .map_err(|error| error.to_string()) } #[cfg(target_arch = "wasm32")] @@ -105,12 +96,12 @@ mod tests { fn event_json(author: Option<&str>, pubkey: Option<&str>) -> String { let mut fields = vec![ - r#""id":"event-id""#.to_owned(), + format!(r#""id":"{}""#, "0".repeat(64)), r#""created_at":123"#.to_owned(), r#""kind":30023"#.to_owned(), r#""tags":[["d","one"]]"#.to_owned(), r#""content":"content""#.to_owned(), - r#""sig":"sig""#.to_owned(), + format!(r#""sig":"{}""#, "f".repeat(128)), ]; if let Some(author) = author { fields.push(format!(r#""author":"{author}""#)); @@ -122,35 +113,28 @@ mod tests { } #[test] - fn parse_event_accepts_matching_author_and_pubkey() { - let event = parse_event_model(&event_json(Some("author"), Some("author"))).expect("event"); - assert_eq!(event.author, "author"); - assert_eq!(event.tags, vec![vec!["d".to_owned(), "one".to_owned()]]); - } - - #[test] - fn parse_event_accepts_author_without_pubkey() { - let event = parse_event_model(&event_json(Some("author"), None)).expect("event"); - assert_eq!(event.author, "author"); - } - - #[test] - fn parse_event_accepts_pubkey_without_author() { - let event = parse_event_model(&event_json(None, Some("pubkey"))).expect("event"); - assert_eq!(event.author, "pubkey"); + fn parse_event_accepts_author_domain_envelope() { + let author = "a".repeat(64); + let event = parse_event_model(&event_json(Some(author.as_str()), None)).expect("event"); + assert_eq!(event.author_str(), author); + assert_eq!( + event.tags_as_vec(), + vec![vec!["d".to_owned(), "one".to_owned()]] + ); } #[test] - fn parse_event_rejects_author_pubkey_mismatch() { - let error = - parse_event_model(&event_json(Some("author"), Some("pubkey"))).expect_err("error"); - assert_eq!(error, "author/pubkey mismatch"); + fn parse_event_rejects_pubkey_wire_alias() { + let author = "a".repeat(64); + let error = parse_event_model(&event_json(Some(author.as_str()), Some(author.as_str()))) + .expect_err("error"); + assert!(error.contains("unknown field `pubkey`")); } #[test] - fn parse_event_rejects_missing_author_and_pubkey() { + fn parse_event_rejects_missing_author() { let error = parse_event_model(&event_json(None, None)).expect_err("error"); - assert_eq!(error, "missing author/pubkey"); + assert!(error.contains("missing field `author`")); } #[test] diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -59,6 +59,7 @@ signer-adapters = [ "dep:radroots_nostr_connect", "dep:radroots_nostr_signer", "dep:tokio", + "radroots_event/signature", "radroots_nostr/events", ] runtime = [ diff --git a/crates/sdk/examples/sdk_v1_knowledge_prepare.rs b/crates/sdk/examples/sdk_v1_knowledge_prepare.rs @@ -1,4 +1,5 @@ use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; +use radroots_event::RadrootsEventEnvelopeParts; use radroots_sdk::knowledge::prelude::*; const SECRET_KEY_HEX: &str = "0101010101010101010101010101010101010101010101010101010101010101"; @@ -14,7 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { let manifest = contract_manifest(); let manifest_hash = contract_manifest_sha256()?; - assert_eq!(draft.contract_id, KNOWLEDGE_CLAIM_CONTRACT_ID); + assert_eq!(draft.contract_id(), KNOWLEDGE_CLAIM_CONTRACT_ID); assert_eq!(manifest.contract_count, 11); assert_eq!(manifest_hash.len(), 64); @@ -27,13 +28,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { println!( "prepared knowledge claim draft: {}", - draft.expected_event_id + draft.expected_event_id_str() ); println!("knowledge manifest sha256: {manifest_hash}"); Ok(()) } -fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn std::error::Error>> { +fn sign_parts( + parts: RadrootsNip01EventWireParts, +) -> Result<RadrootsEventEnvelope, Box<dyn std::error::Error>> { let tags = parts .tags .into_iter() @@ -44,10 +47,10 @@ fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn st .tags(tags) .custom_created_at(Timestamp::from_secs(u64::from(CREATED_AT))) .sign_with_keys(&keys)?; - Ok(RadrootsEventEnvelope { + Ok(RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event.id.to_hex(), author: event.pubkey.to_hex(), - created_at: event.created_at.as_secs() as u32, + created_at: event.created_at.as_secs(), kind: u32::from(event.kind.as_u16()), tags: event .tags @@ -57,7 +60,7 @@ fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn st .collect(), content: event.content, sig: event.sig.to_string(), - }) + })?) } fn public_key_hex() -> String { diff --git a/crates/sdk/src/adapters/nostr.rs b/crates/sdk/src/adapters/nostr.rs @@ -1,10 +1,9 @@ use core::time::Duration; -use crate::adapters::signing::SignedEvent; use crate::identity::RadrootsIdentity; use radroots_nostr::prelude::{ - RadrootsNostrClient, RadrootsNostrClientOptions, RadrootsNostrError, RadrootsNostrEventId, - RadrootsNostrOutput, + RadrootsNostrClient, RadrootsNostrClientOptions, RadrootsNostrError, RadrootsNostrEvent, + RadrootsNostrEventId, RadrootsNostrOutput, }; pub fn signerless_client() -> RadrootsNostrClient { @@ -58,7 +57,7 @@ pub async fn connected_relay_urls(client: &RadrootsNostrClient) -> Vec<String> { pub async fn publish_signed_event( client: &RadrootsNostrClient, - event: &SignedEvent, + event: &RadrootsNostrEvent, ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { client.send_event(event).await } diff --git a/crates/sdk/src/adapters/radrootsd.rs b/crates/sdk/src/adapters/radrootsd.rs @@ -3,7 +3,7 @@ use core::time::Duration; use radroots_event::draft::RadrootsSignedEvent; use radroots_transport_publish_protocol::{ - METHOD_EVENT, SignedEventWire, TransportPublishDeliveryPolicy, TransportPublishEventRequest, + METHOD_EVENT, TransportPublishDeliveryPolicy, TransportPublishEventRequest, TransportPublishEventResponse, TransportPublishProtocolError, TransportPublishTargetPolicy, }; use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue}; @@ -81,6 +81,8 @@ impl RadrootsdProxyPublishAdapter { &self, request: RadrootsdProxyPublishRequest, ) -> Result<TransportPublishEventResponse, RadrootsdError> { + let event_identity = + RadrootsdProxyPublishEventIdentity::from_signed_event(&request.signed_event); let request = request.into_protocol_request(); request .validate(SDK_RADROOTSD_PROXY_MAX_TARGETS) @@ -92,7 +94,7 @@ impl RadrootsdProxyPublishAdapter { self.config.timeout, ) .await?; - validate_transport_publish_response_for_request(&request, &response)?; + validate_transport_publish_response_for_request(&request, &event_identity, &response)?; Ok(response) } } @@ -109,7 +111,7 @@ pub struct RadrootsdProxyPublishRequest { impl RadrootsdProxyPublishRequest { fn into_protocol_request(self) -> TransportPublishEventRequest { TransportPublishEventRequest { - event: signed_event_wire(&self.signed_event), + raw_event_json: self.signed_event.raw_json().to_owned(), target_policy: self.target_policy, delivery_policy: self.delivery_policy, idempotency_key: self.idempotency_key, @@ -118,6 +120,23 @@ impl RadrootsdProxyPublishRequest { } } +#[derive(Clone, Debug, PartialEq, Eq)] +struct RadrootsdProxyPublishEventIdentity { + event_id: String, + pubkey: String, + kind: u32, +} + +impl RadrootsdProxyPublishEventIdentity { + fn from_signed_event(event: &RadrootsSignedEvent) -> Self { + Self { + event_id: event.id_str().to_owned(), + pubkey: event.pubkey_str().to_owned(), + kind: event.kind(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum RadrootsdError { InvalidAuthHeader(String), @@ -298,20 +317,9 @@ where decode_jsonrpc_response(method, request_id, body.as_str()) } -fn signed_event_wire(event: &RadrootsSignedEvent) -> SignedEventWire { - SignedEventWire { - id: event.id.clone(), - pubkey: event.pubkey.clone(), - created_at: event.created_at as u64, - kind: event.kind, - tags: event.tags.clone(), - content: event.content.clone(), - sig: event.sig.clone(), - } -} - fn validate_transport_publish_response_for_request( request: &TransportPublishEventRequest, + event_identity: &RadrootsdProxyPublishEventIdentity, response: &TransportPublishEventResponse, ) -> Result<(), RadrootsdError> { response.job.validate().map_err(|error| { @@ -319,13 +327,13 @@ fn validate_transport_publish_response_for_request( "radrootsd transport publish response invalid: {error}" )) })?; - if response.job.event_id != request.event.id { + if response.job.event_id != event_identity.event_id { return Err(response_mismatch("event_id")); } - if response.job.pubkey != request.event.pubkey { + if response.job.pubkey != event_identity.pubkey { return Err(response_mismatch("pubkey")); } - if response.job.event_kind != request.event.kind { + if response.job.event_kind != event_identity.kind { return Err(response_mismatch("event_kind")); } if response.job.delivery_policy != request.delivery_policy { diff --git a/crates/sdk/src/adapters/signing.rs b/crates/sdk/src/adapters/signing.rs @@ -1,27 +1,29 @@ use crate::identity::RadrootsIdentity; -use radroots_event_codec::wire::WireEventParts; -use radroots_nostr::prelude::{RadrootsNostrError, radroots_nostr_build_event}; +use radroots_event::wire::RadrootsNip01EventWireParts; +use radroots_nostr::prelude::{ + RadrootsNostrError, RadrootsNostrEvent, RadrootsNostrEventBuilder, radroots_nostr_build_event, +}; -pub type SignedEvent = radroots_nostr::prelude::RadrootsNostrEvent; -pub type NostrEventBuilder = radroots_nostr::prelude::RadrootsNostrEventBuilder; pub type SigningError = RadrootsNostrError; -pub fn event_builder_from_parts(parts: WireEventParts) -> Result<NostrEventBuilder, SigningError> { +pub fn event_builder_from_parts( + parts: RadrootsNip01EventWireParts, +) -> Result<RadrootsNostrEventBuilder, SigningError> { radroots_nostr_build_event(parts.kind, parts.content, parts.tags) } pub fn sign_parts_with_identity( identity: &RadrootsIdentity, - parts: WireEventParts, -) -> Result<SignedEvent, SigningError> { + parts: RadrootsNip01EventWireParts, +) -> Result<RadrootsNostrEvent, SigningError> { let builder = event_builder_from_parts(parts)?; sign_builder_with_identity(identity, builder) } pub fn sign_builder_with_identity( identity: &RadrootsIdentity, - builder: NostrEventBuilder, -) -> Result<SignedEvent, SigningError> { + builder: RadrootsNostrEventBuilder, +) -> Result<RadrootsNostrEvent, SigningError> { builder.sign_with_keys(identity.keys()).map_err(Into::into) } diff --git a/crates/sdk/src/dvm_runtime.rs b/crates/sdk/src/dvm_runtime.rs @@ -11,15 +11,14 @@ use crate::{ }; #[cfg(feature = "runtime")] use radroots_authority::{RadrootsActorContext, RadrootsEventSigner, authorize_actor_for_draft}; +use radroots_event::wire::RadrootsNip01EventWireParts; #[cfg(feature = "runtime")] use radroots_event::{ - RadrootsEventEnvelope, - draft::RadrootsEventDraft, + draft::{RadrootsEventDraft, RadrootsSignedEvent}, ids::{RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey}, kinds::KIND_TRADE_TRANSITION_PROOF_REQUEST, }; -#[cfg(feature = "runtime")] -use radroots_event_codec::wire::{WireEventParts, canonicalize_tags, to_frozen_draft}; +use radroots_event_codec::wire::canonicalize_tags; #[cfg(feature = "runtime")] use radroots_event_store::RadrootsEventIngest; #[cfg(feature = "runtime")] @@ -329,7 +328,7 @@ pub struct DvmTradeTransitionProofReceipt { #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] pub struct DvmValidationReceiptIngestRequest { - pub event: RadrootsEventEnvelope, + pub event: RadrootsSignedEvent, pub observed_at: Option<RadrootsSdkTimestamp>, pub expected_order_id: Option<RadrootsOrderId>, pub expected_listing_event_id: Option<RadrootsEventId>, @@ -340,7 +339,7 @@ pub struct DvmValidationReceiptIngestRequest { #[cfg(feature = "runtime")] impl DvmValidationReceiptIngestRequest { - pub fn new(event: RadrootsEventEnvelope) -> Self { + pub fn new(event: RadrootsSignedEvent) -> Self { Self { event, observed_at: None, @@ -468,7 +467,7 @@ impl<'sdk> DvmClient<'sdk> { request: DvmValidationReceiptIngestRequest, ) -> Result<DvmValidationReceiptIngestReceipt, RadrootsSdkError> { let verified = verify_validation_receipt_event( - &request.event, + request.event.envelope(), RadrootsValidationReceiptExpectedBinding { order_id: request .expected_order_id @@ -490,7 +489,7 @@ impl<'sdk> DvmClient<'sdk> { }, ) .map_err(validation_receipt_sdk_error)?; - let receipt_event_id = parse_event_id(request.event.id.as_str(), "receipt event id")?; + let receipt_event_id = parse_event_id(request.event.id_str(), "receipt event id")?; let order_id = RadrootsOrderId::parse(verified.tags.order_id.as_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { @@ -576,19 +575,22 @@ fn dvm_trade_transition_proof_plan( ], ]; canonicalize_tags(&mut tags); - let frozen_draft = to_frozen_draft( - WireEventParts { - kind: KIND_TRADE_TRANSITION_PROOF_REQUEST, - content, - tags, - }, + let parts = RadrootsNip01EventWireParts { + kind: KIND_TRADE_TRANSITION_PROOF_REQUEST, + content, + tags, + }; + let frozen_draft = RadrootsEventDraft::new( DVM_TRADE_TRANSITION_PROOF_REQUEST_CONTRACT_ID, + parts.kind, + u64::from(created_at.try_into_nostr_created_at()?), + parts.tags, + parts.content, request.actor.pubkey().as_str(), - created_at.try_into_nostr_created_at()?, ) .expect("DVM proof request draft is valid"); authorize_actor_for_draft(&request.actor, &frozen_draft)?; - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) + let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id_str()) .expect("frozen DVM proof request draft produces a valid event id"); Ok(DvmTradeTransitionProofPlan { worker_pubkey: request.worker_pubkey, diff --git a/crates/sdk/src/farm.rs b/crates/sdk/src/farm.rs @@ -2,8 +2,8 @@ pub use radroots_event::farm::*; pub use radroots_event_codec::error::EventEncodeError; -use radroots_event_codec::wire::WireEventParts; +use radroots_event::wire::RadrootsNip01EventWireParts; -pub fn build_draft(farm: &RadrootsFarm) -> Result<WireEventParts, EventEncodeError> { +pub fn build_draft(farm: &RadrootsFarm) -> Result<RadrootsNip01EventWireParts, EventEncodeError> { radroots_event_codec::farm::encode::to_wire_parts(farm) } diff --git a/crates/sdk/src/farms_runtime.rs b/crates/sdk/src/farms_runtime.rs @@ -23,7 +23,6 @@ use radroots_event::{ listing::RadrootsListingPublicLocation, }; #[cfg(feature = "runtime")] -use radroots_event_codec::wire::to_frozen_draft; #[cfg(feature = "runtime")] pub const FARM_PUBLISH_OPERATION_KIND: &str = "farm.publish.v1"; @@ -740,14 +739,16 @@ fn farm_publish_plan( })?; let farm_addr = farm_addr(actor, farm_value.d_tag.as_str()) .expect("validated farm d tag forms a farm address"); - let frozen_draft = to_frozen_draft( - parts, + let frozen_draft = RadrootsEventDraft::new( FARM_PROFILE_CONTRACT_ID, + parts.kind, + created_at_nostr.into(), + parts.tags, + parts.content, actor.pubkey().as_str(), - created_at_nostr, ) .expect("validated farm publish draft freezes"); - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) + let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id_str()) .expect("frozen farm draft produces a valid event id"); Ok(FarmPublishPlan { farm_addr, diff --git a/crates/sdk/src/knowledge.rs b/crates/sdk/src/knowledge.rs @@ -11,6 +11,7 @@ use std::{ use core::fmt; +pub use radroots_event::wire::RadrootsNip01EventWireParts; pub use radroots_event::{ RadrootsEventEnvelope, RadrootsEventRef, draft::{RadrootsDraftError, RadrootsEventDraft}, @@ -49,7 +50,6 @@ pub use radroots_event_codec::{ RadrootsContractValidatedEvent, RadrootsDecodeError, RadrootsDecodedEvent, RadrootsIdVerifiedEvent, RadrootsNip01VerificationError, RadrootsSignatureVerifiedEvent, }, - wire::WireEventParts, }; use radroots_event::knowledge::{ @@ -66,7 +66,6 @@ use radroots_event_codec::{ wiki_merge_request_to_wire_parts, wiki_redirect_to_wire_parts, }, knowledge_contract_manifest, verify_and_decode_radroots_event as codec_verify_and_decode, - wire::to_frozen_draft, }; pub const WIKI_ARTICLE_CONTRACT_ID: &str = "radroots.wiki.article.v1"; @@ -273,7 +272,7 @@ impl RadrootsWikiArticleBuilder { builder_validated(article, validate_wiki_article) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_wiki_article_event(&self.build()?)?) } @@ -317,7 +316,7 @@ impl RadrootsWikiRedirectBuilder { builder_validated(redirect, validate_wiki_redirect) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_wiki_redirect_event(&self.build()?)?) } @@ -396,7 +395,7 @@ impl RadrootsWikiMergeRequestBuilder { builder_validated(request, validate_wiki_merge_request) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_wiki_merge_request_event(&self.build()?)?) } @@ -512,7 +511,7 @@ impl RadrootsKnowledgeSourceBuilder { builder_validated(source, validate_knowledge_source) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_knowledge_source_event(&self.build()?)?) } @@ -606,7 +605,7 @@ impl RadrootsKnowledgeClaimBuilder { builder_validated(claim, validate_knowledge_claim) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_knowledge_claim_event(&self.build()?)?) } @@ -692,7 +691,7 @@ impl RadrootsKnowledgeRelationBuilder { builder_validated(relation, validate_knowledge_relation) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_knowledge_relation_event(&self.build()?)?) } @@ -775,7 +774,7 @@ impl RadrootsKnowledgeReviewBuilder { builder_validated(review, validate_knowledge_review) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_knowledge_review_event(&self.build()?)?) } @@ -874,7 +873,7 @@ impl RadrootsKnowledgeFieldReportBuilder { builder_validated(report, validate_knowledge_field_report) } - pub fn build_event(self) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + pub fn build_event(self) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(build_knowledge_field_report_event(&self.build()?)?) } @@ -902,56 +901,56 @@ impl KnowledgeEventBuilder { pub fn wiki_article( &self, article: &RadrootsWikiArticle, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_wiki_article_event(article) } pub fn wiki_redirect( &self, redirect: &RadrootsWikiRedirect, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_wiki_redirect_event(redirect) } pub fn wiki_merge_request( &self, request: &RadrootsWikiMergeRequest, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_wiki_merge_request_event(request) } pub fn knowledge_source( &self, source: &RadrootsKnowledgeSource, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_knowledge_source_event(source) } pub fn knowledge_claim( &self, claim: &RadrootsKnowledgeClaim, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_knowledge_claim_event(claim) } pub fn knowledge_relation( &self, relation: &RadrootsKnowledgeRelation, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_knowledge_relation_event(relation) } pub fn knowledge_review( &self, review: &RadrootsKnowledgeReview, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_knowledge_review_event(review) } pub fn knowledge_field_report( &self, report: &RadrootsKnowledgeFieldReport, - ) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { + ) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { build_knowledge_field_report_event(report) } } @@ -1065,49 +1064,49 @@ impl KnowledgeCodec { pub fn build_wiki_article_event( article: &RadrootsWikiArticle, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(wiki_article_to_wire_parts(article)?) } pub fn build_wiki_redirect_event( redirect: &RadrootsWikiRedirect, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(wiki_redirect_to_wire_parts(redirect)?) } pub fn build_wiki_merge_request_event( request: &RadrootsWikiMergeRequest, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(wiki_merge_request_to_wire_parts(request)?) } pub fn build_knowledge_source_event( source: &RadrootsKnowledgeSource, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(knowledge_source_to_wire_parts(source)?) } pub fn build_knowledge_claim_event( claim: &RadrootsKnowledgeClaim, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(knowledge_claim_to_wire_parts(claim)?) } pub fn build_knowledge_relation_event( relation: &RadrootsKnowledgeRelation, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(knowledge_relation_to_wire_parts(relation)?) } pub fn build_knowledge_review_event( review: &RadrootsKnowledgeReview, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(knowledge_review_to_wire_parts(review)?) } pub fn build_knowledge_field_report_event( report: &RadrootsKnowledgeFieldReport, -) -> Result<WireEventParts, RadrootsSdkKnowledgeError> { +) -> Result<RadrootsNip01EventWireParts, RadrootsSdkKnowledgeError> { Ok(knowledge_field_report_to_wire_parts(report)?) } @@ -1234,16 +1233,18 @@ pub fn contract_manifest_sha256() -> Result<String, RadrootsSdkKnowledgeError> { } fn prepare_draft( - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, contract_id: &'static str, expected_pubkey: impl AsRef<str>, created_at: u32, ) -> Result<RadrootsEventDraft, RadrootsSdkKnowledgeError> { - Ok(to_frozen_draft( - parts, + Ok(RadrootsEventDraft::new( contract_id, - expected_pubkey, - created_at, + parts.kind, + u64::from(created_at), + parts.tags, + parts.content, + expected_pubkey.as_ref(), )?) } @@ -1264,7 +1265,9 @@ fn draft_error_code(error: &RadrootsDraftError) -> &'static str { "signed_event_computed_id_mismatch" } RadrootsDraftError::IdParse(_) => "id_parse", - RadrootsDraftError::JsonString(_) => "json_string", + RadrootsDraftError::CanonicalEventId(_) => "canonical_event_id", + RadrootsDraftError::Envelope(_) => "event_envelope", + RadrootsDraftError::SignedEvent(_) => "signed_event", } } @@ -1341,12 +1344,12 @@ pub mod prelude { RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReview, RadrootsKnowledgeReviewBuilder, RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, RadrootsKnowledgeSourceBuilder, RadrootsKnowledgeValidationError, - RadrootsNip01VerificationError, RadrootsRightsAssertion, RadrootsSdkKnowledgeError, - RadrootsSignatureVerifiedEvent, RadrootsWikiArticle, RadrootsWikiArticleBuilder, - RadrootsWikiArticleVersionRef, RadrootsWikiDTagError, RadrootsWikiMergeRequest, - RadrootsWikiMergeRequestBuilder, RadrootsWikiRedirect, RadrootsWikiRedirectBuilder, - WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, - WireEventParts, build_knowledge_claim_event, build_knowledge_field_report_event, + RadrootsNip01EventWireParts, RadrootsNip01VerificationError, RadrootsRightsAssertion, + RadrootsSdkKnowledgeError, RadrootsSignatureVerifiedEvent, RadrootsWikiArticle, + RadrootsWikiArticleBuilder, RadrootsWikiArticleVersionRef, RadrootsWikiDTagError, + RadrootsWikiMergeRequest, RadrootsWikiMergeRequestBuilder, RadrootsWikiRedirect, + RadrootsWikiRedirectBuilder, WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, + WIKI_REDIRECT_CONTRACT_ID, build_knowledge_claim_event, build_knowledge_field_report_event, build_knowledge_relation_event, build_knowledge_review_event, build_knowledge_source_event, build_wiki_article_event, build_wiki_merge_request_event, build_wiki_redirect_event, contract_manifest, contract_manifest_json, contract_manifest_sha256, normalize_wiki_d_tag, diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -118,12 +118,12 @@ pub use crate::knowledge::{ RadrootsKnowledgeRelation, RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReview, RadrootsKnowledgeReviewBuilder, RadrootsKnowledgeReviewScope, RadrootsKnowledgeReviewScore, RadrootsKnowledgeReviewTarget, RadrootsKnowledgeSource, RadrootsKnowledgeSourceBuilder, - RadrootsNip01VerificationError, RadrootsRightsAssertion, RadrootsSdkKnowledgeError, - RadrootsSignatureVerifiedEvent, RadrootsWikiArticle, RadrootsWikiArticleBuilder, - RadrootsWikiArticleVersionRef, RadrootsWikiDTagError, RadrootsWikiMergeRequest, - RadrootsWikiMergeRequestBuilder, RadrootsWikiRedirect, RadrootsWikiRedirectBuilder, - WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, - WireEventParts, build_knowledge_claim_event, build_knowledge_field_report_event, + RadrootsNip01EventWireParts, RadrootsNip01VerificationError, RadrootsRightsAssertion, + RadrootsSdkKnowledgeError, RadrootsSignatureVerifiedEvent, RadrootsWikiArticle, + RadrootsWikiArticleBuilder, RadrootsWikiArticleVersionRef, RadrootsWikiDTagError, + RadrootsWikiMergeRequest, RadrootsWikiMergeRequestBuilder, RadrootsWikiRedirect, + RadrootsWikiRedirectBuilder, WIKI_ARTICLE_CONTRACT_ID, WIKI_MERGE_REQUEST_CONTRACT_ID, + WIKI_REDIRECT_CONTRACT_ID, build_knowledge_claim_event, build_knowledge_field_report_event, build_knowledge_relation_event, build_knowledge_review_event, build_knowledge_source_event, build_wiki_article_event, build_wiki_merge_request_event, build_wiki_redirect_event, contract_manifest, contract_manifest_json, contract_manifest_sha256, normalize_wiki_d_tag, diff --git a/crates/sdk/src/listings_runtime.rs b/crates/sdk/src/listings_runtime.rs @@ -331,8 +331,8 @@ fn listing_publish_plan( let public_listing_addr = canonical.public_listing_addr().clone(); let draft_listing_addr = canonical.draft_listing_addr().clone(); let mutation = RadrootsListingMutation::publish(canonical); - let frozen_draft = build_listing_mutation_draft(&mutation, created_at_nostr)?; - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) + let frozen_draft = build_listing_mutation_draft(&mutation, u64::from(created_at_nostr))?; + let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id_str()) .expect("frozen listing draft produces a valid event id"); Ok(ListingPublishPlan { public_listing_addr, diff --git a/crates/sdk/src/order.rs b/crates/sdk/src/order.rs @@ -5,80 +5,80 @@ pub use radroots_event_codec::order::RadrootsOrderEnvelopeParseError; use radroots_event::RadrootsEventEnvelope; #[cfg(any(feature = "signer-adapters", test))] -use radroots_event::{RadrootsEventPtr, ids::RadrootsEventId, tags::TAG_E}; +use radroots_event::wire::RadrootsNip01EventWireParts; #[cfg(any(feature = "signer-adapters", test))] -use radroots_event_codec::wire::WireEventParts; +use radroots_event::{RadrootsEventPtr, ids::RadrootsEventId, tags::TAG_E}; #[cfg(any(feature = "signer-adapters", test))] #[derive(Debug, Clone)] pub struct RadrootsOrderRequestDraft { - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, } #[cfg(any(feature = "signer-adapters", test))] #[derive(Debug, Clone)] pub struct RadrootsOrderDecisionDraft { - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, } #[cfg(any(feature = "signer-adapters", test))] #[derive(Debug, Clone)] pub struct RadrootsOrderRevisionProposalDraft { - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, } #[cfg(any(feature = "signer-adapters", test))] #[derive(Debug, Clone)] pub struct RadrootsOrderRevisionDecisionDraft { - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, } #[cfg(any(feature = "signer-adapters", test))] #[derive(Debug, Clone)] pub struct RadrootsOrderCancellationDraft { - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, } #[cfg(any(feature = "signer-adapters", test))] impl RadrootsOrderRequestDraft { - pub fn into_wire_parts(self) -> WireEventParts { + pub fn into_wire_parts(self) -> RadrootsNip01EventWireParts { self.parts } } #[cfg(any(feature = "signer-adapters", test))] impl RadrootsOrderDecisionDraft { - pub fn into_wire_parts(self) -> WireEventParts { + pub fn into_wire_parts(self) -> RadrootsNip01EventWireParts { self.parts } } #[cfg(any(feature = "signer-adapters", test))] impl RadrootsOrderRevisionProposalDraft { - pub fn into_wire_parts(self) -> WireEventParts { + pub fn into_wire_parts(self) -> RadrootsNip01EventWireParts { self.parts } } #[cfg(any(feature = "signer-adapters", test))] impl RadrootsOrderRevisionDecisionDraft { - pub fn into_wire_parts(self) -> WireEventParts { + pub fn into_wire_parts(self) -> RadrootsNip01EventWireParts { self.parts } } #[cfg(any(feature = "signer-adapters", test))] impl RadrootsOrderCancellationDraft { - pub fn into_wire_parts(self) -> WireEventParts { + pub fn into_wire_parts(self) -> RadrootsNip01EventWireParts { self.parts } } #[cfg(any(feature = "signer-adapters", test))] fn with_contract_root_event_tag( - mut parts: WireEventParts, + mut parts: RadrootsNip01EventWireParts, root_event_id: &RadrootsEventId, -) -> WireEventParts { +) -> RadrootsNip01EventWireParts { parts .tags .push(vec![TAG_E.to_owned(), root_event_id.as_str().to_owned()]); diff --git a/crates/sdk/src/orders_runtime.rs b/crates/sdk/src/orders_runtime.rs @@ -25,10 +25,12 @@ use crate::{ use radroots_authority::RadrootsActorContext; #[cfg(all(feature = "runtime", test))] use radroots_authority::RadrootsEventSigner; +use radroots_event::wire::RadrootsNip01EventWireParts; #[cfg(feature = "runtime")] use radroots_event::{ - RadrootsEventEnvelope, + RadrootsEventEnvelope, RadrootsEventEnvelopeParts, contract::RadrootsActorRole, + draft::RadrootsSignedEvent, ids::RadrootsEventId, kinds::{ KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, @@ -63,8 +65,6 @@ use radroots_event_codec::order::{ order_cancellation_from_event, order_decision_from_event, order_request_from_event, order_revision_decision_from_event, order_revision_proposal_from_event, }; -#[cfg(any(feature = "signer-adapters", test))] -use radroots_event_codec::wire::{WireEventParts, to_frozen_draft}; #[cfg(all(feature = "runtime", feature = "transport-nostr-runtime"))] use radroots_event_store::RadrootsStoredEventTag; #[cfg(feature = "runtime")] @@ -379,13 +379,13 @@ pub struct TradeSubmitReceipt { #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] pub struct TradeRequestEvidenceIngestRequest { - pub event: RadrootsEventEnvelope, + pub event: RadrootsSignedEvent, pub observed_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] impl TradeRequestEvidenceIngestRequest { - pub fn new(event: RadrootsEventEnvelope) -> Self { + pub fn new(event: RadrootsSignedEvent) -> Self { Self { event, observed_at: None, @@ -414,13 +414,13 @@ pub struct TradeRequestEvidenceIngestReceipt { #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] pub struct TradeEvidenceIngestRequest { - pub event: RadrootsEventEnvelope, + pub event: RadrootsSignedEvent, pub observed_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] impl TradeEvidenceIngestRequest { - pub fn new(event: RadrootsEventEnvelope) -> Self { + pub fn new(event: RadrootsSignedEvent) -> Self { Self { event, observed_at: None, @@ -2573,7 +2573,7 @@ impl<'sdk> TradesClient<'sdk> { &self, request: TradeEvidenceIngestRequest, ) -> Result<TradeEvidenceIngestReceipt, RadrootsSdkError> { - let evidence = parse_order_evidence(&request.event)?; + let evidence = parse_order_evidence(request.event.envelope())?; let observed_at = self.resolved_created_at(request.observed_at)?; let observed_at_ms = sdk_timestamp_ms(observed_at)?; let receipt = self @@ -2598,7 +2598,7 @@ impl<'sdk> TradesClient<'sdk> { &self, request: TradeRequestEvidenceIngestRequest, ) -> Result<TradeRequestEvidenceIngestReceipt, RadrootsSdkError> { - let evidence = parse_order_request_evidence(&request.event)?; + let evidence = parse_order_request_evidence(request.event.envelope())?; let observed_at = self.resolved_created_at(request.observed_at)?; let observed_at_ms = sdk_timestamp_ms(observed_at)?; let receipt = self @@ -4348,8 +4348,8 @@ where ) .await?; let (mut receipts, mut invalid_receipts) = classify_validation_receipts(events, None)?; - receipts.retain(|receipt| receipt.event.id == receipt_event_id.as_str()); - invalid_receipts.retain(|receipt| receipt.event.id == receipt_event_id.as_str()); + receipts.retain(|receipt| receipt.event.id_str() == receipt_event_id.as_str()); + invalid_receipts.retain(|receipt| receipt.event.id_str() == receipt_event_id.as_str()); attach_worker_evidence( sdk, adapter, @@ -4560,7 +4560,7 @@ where let mut selections = worker_evidence_for_receipts(trusted_worker_pubkeys, receipts, events)?; for receipt in receipts { receipt.worker_evidence = selections - .remove(receipt.event.id.as_str()) + .remove(receipt.event.id_str()) .unwrap_or_default(); } Ok(()) @@ -4578,14 +4578,14 @@ fn worker_evidence_for_receipts( .collect::<BTreeSet<_>>(); let binding_by_receipt_id = receipts .iter() - .map(|receipt| (receipt.event.id.as_str(), receipt)) + .map(|receipt| (receipt.event.id_str(), receipt)) .collect::<BTreeMap<_, _>>(); let mut by_receipt = - BTreeMap::<String, Vec<(u32, String, bool, TradeValidationReceiptWorkerEvidence)>>::new(); + BTreeMap::<String, Vec<(u64, String, bool, TradeValidationReceiptWorkerEvidence)>>::new(); for event in events { let payload = - match serde_json::from_str::<RawTradeValidationReceiptWorkerResult>(&event.content) { + match serde_json::from_str::<RawTradeValidationReceiptWorkerResult>(event.content()) { Ok(payload) => payload, Err(_) => continue, }; @@ -4595,12 +4595,12 @@ fn worker_evidence_for_receipts( if !worker_payload_binds_receipt(&payload, binding) { continue; } - let author = RadrootsPublicKey::parse(event.author.as_str()).map_err(|error| { + let author = RadrootsPublicKey::parse(event.author_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("validation receipt worker evidence author is invalid: {error}"), } })?; - let result_event_id = RadrootsEventId::parse(event.id.as_str()).map_err(|error| { + let result_event_id = RadrootsEventId::parse(event.id_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("validation receipt worker evidence event id is invalid: {error}"), } @@ -4629,8 +4629,8 @@ fn worker_evidence_for_receipts( .and_then(RadrootsTradeCommitmentConfidence::from_label), }; by_receipt.entry(receipt_event_id).or_default().push(( - event.created_at, - event.id, + event.created_at_u64(), + event.id_str().to_owned(), trusted, view, )); @@ -4669,7 +4669,7 @@ fn worker_payload_binds_receipt( payload.status == "succeeded" && payload.worker_role.as_deref() == Some("non_authoritative_prover") && payload.receipt_kind == Some(KIND_TRADE_VALIDATION_RECEIPT) - && payload.receipt_event_id == receipt.event.id + && payload.receipt_event_id == receipt.event.id_str() && payload.order_id.as_deref() == Some(receipt.tags.order_id.as_str()) && payload.listing_event_id.as_deref() == Some(receipt.tags.listing_event_id.as_str()) && payload.event_set_root.as_deref() == Some(receipt.tags.event_set_root.as_str()) @@ -4776,7 +4776,7 @@ async fn trade_status_validation_trust_decision( )); } let event = stored_event_to_nostr_event(&stored_event)?; - let receipt_author = match RadrootsPublicKey::parse(event.author.as_str()) { + let receipt_author = match RadrootsPublicKey::parse(event.author_str()) { Ok(author) => author, Err(_) => { return Ok(trade_validation_trust_decision( @@ -5270,7 +5270,7 @@ fn stored_event_to_nostr_event( ), } })?; - Ok(RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: stored_event.event_id.clone(), author: stored_event.pubkey.clone(), created_at: stored_event.created_at, @@ -5279,6 +5279,12 @@ fn stored_event_to_nostr_event( content: stored_event.content.clone(), sig: stored_event.sig.clone(), }) + .map_err(|error| RadrootsSdkError::EventStore { + message: format!( + "stored event {} contains invalid envelope: {error}", + stored_event.event_id + ), + }) } #[cfg(all(feature = "runtime", feature = "transport-nostr-runtime"))] @@ -5287,9 +5293,9 @@ fn validation_receipt_event_order( right: &TradeValidationReceiptEvent, ) -> core::cmp::Ordering { left.event - .created_at - .cmp(&right.event.created_at) - .then_with(|| left.event.id.cmp(&right.event.id)) + .created_at() + .cmp(&right.event.created_at()) + .then_with(|| left.event.id().cmp(right.event.id())) } #[cfg(all(feature = "runtime", feature = "transport-nostr-runtime"))] @@ -5298,9 +5304,9 @@ fn validation_receipt_invalid_order( right: &TradeValidationReceiptInvalidCandidate, ) -> core::cmp::Ordering { left.event - .created_at - .cmp(&right.event.created_at) - .then_with(|| left.event.id.cmp(&right.event.id)) + .created_at() + .cmp(&right.event.created_at()) + .then_with(|| left.event.id().cmp(right.event.id())) } #[cfg(all(feature = "runtime", feature = "transport-nostr-runtime"))] @@ -5625,7 +5631,7 @@ impl<'sdk> TradeSellerClient<'sdk> { ), } })?; - let nostr_event = RadrootsEventEnvelope { + let nostr_event = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event.event_id, author: event.pubkey, created_at: event.created_at, @@ -5633,7 +5639,10 @@ impl<'sdk> TradeSellerClient<'sdk> { tags, content: event.content, sig: event.sig, - }; + }) + .map_err(|error| RadrootsSdkError::Projection { + message: format!("stored trade inbox event envelope is invalid: {error}"), + })?; if let Ok(RadrootsOrderEventRecord::Request(record)) = order_event_record_from_event(&nostr_event) { @@ -6387,12 +6396,12 @@ struct ParsedOrderEvidence { fn parse_order_evidence( event: &RadrootsEventEnvelope, ) -> Result<ParsedOrderEvidence, RadrootsSdkError> { - let event_id = RadrootsEventId::parse(event.id.as_str()).map_err(|error| { + let event_id = RadrootsEventId::parse(event.id_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("order evidence event id is invalid: {error}"), } })?; - let (order_id, listing_addr) = match event.kind { + let (order_id, listing_addr) = match event.kind_u32() { KIND_ORDER_REQUEST => { let payload = order_request_from_event(event) .map_err(order_evidence_parse_error)? @@ -6434,7 +6443,7 @@ fn parse_order_evidence( order_id, listing_addr, event_id, - event_kind: event.kind, + event_kind: event.kind_u32(), }) } @@ -6680,15 +6689,13 @@ fn order_submit_plan( message: format!("order submit draft encode failed: {error}"), } })?; - let frozen_draft = to_frozen_draft( + let (frozen_draft, expected_event_id) = freeze_order_workflow_draft( draft.into_wire_parts(), TRADE_SUBMIT_CONTRACT_ID, order_request.buyer_pubkey.as_str(), created_at_nostr, - ) - .expect("validated order submit draft freezes"); - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) - .expect("frozen order submit draft produces a valid event id"); + "trade.prepare_submit", + ); Ok(TradeSubmitPlan { workflow: order_workflow_plan( TradeWorkflowKind::Submit, @@ -6732,15 +6739,13 @@ fn order_decision_plan( .expect("canonical order decision payload validates"); let draft = order::build_order_decision_draft(&request_event_id, &request_event_id, &decision) .expect("validated order decision draft encodes"); - let frozen_draft = to_frozen_draft( + let (frozen_draft, expected_event_id) = freeze_order_workflow_draft( draft.into_wire_parts(), TRADE_DECISION_CONTRACT_ID, decision.seller_pubkey.as_str(), created_at_nostr, - ) - .expect("validated order decision draft freezes"); - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) - .expect("frozen order decision draft produces a valid event id"); + "trade.prepare_decision", + ); Ok(TradeDecisionPlan { workflow: order_workflow_plan( TradeWorkflowKind::Decision, @@ -7138,15 +7143,22 @@ fn order_workflow_enqueue_receipt( #[cfg(any(feature = "signer-adapters", test))] fn freeze_order_workflow_draft( - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, contract_id: &str, expected_pubkey: &str, created_at: u32, _operation: &'static str, ) -> (RadrootsEventDraft, RadrootsEventId) { - let frozen_draft = to_frozen_draft(parts, contract_id, expected_pubkey, created_at) - .expect("validated order workflow draft freezes"); - let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) + let frozen_draft = RadrootsEventDraft::new( + contract_id, + parts.kind, + u64::from(created_at), + parts.tags, + parts.content, + expected_pubkey, + ) + .expect("validated order workflow draft freezes"); + let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id_str()) .expect("frozen order workflow draft produces a valid event id"); (frozen_draft, expected_event_id) } @@ -7219,7 +7231,7 @@ struct TradeRequestEvidence { fn parse_order_request_evidence( event: &RadrootsEventEnvelope, ) -> Result<TradeRequestEvidence, RadrootsSdkError> { - let request_event_id = RadrootsEventId::parse(event.id.as_str()).map_err(|error| { + let request_event_id = RadrootsEventId::parse(event.id_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("order request evidence event id is invalid: {error}"), } @@ -7983,6 +7995,9 @@ fn projection_error(error: RadrootsOrderStoreQueryError) -> RadrootsSdkError { RadrootsOrderStoreQueryError::Decode { .. } => { "stored order event could not decode as order record" } + RadrootsOrderStoreQueryError::InvalidStoredEnvelope { .. } => { + "stored order event envelope is invalid" + } RadrootsOrderStoreQueryError::Projection(error) => return error.into(), }; RadrootsSdkError::Projection { diff --git a/crates/sdk/src/signer_provider.rs b/crates/sdk/src/signer_provider.rs @@ -12,7 +12,8 @@ use radroots_event::kinds::{ KIND_FARM, KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, }; -use radroots_nostr::prelude::{RadrootsNostrEvent, RadrootsNostrKeys, radroots_event_from_nostr}; +use radroots_event::wire::RadrootsNip01EventWire; +use radroots_nostr::prelude::{RadrootsNostrEvent, RadrootsNostrKeys}; use radroots_nostr_connect::prelude::{ RadrootsNostrConnectClientRequest, RadrootsNostrConnectClientTarget, RadrootsNostrConnectClientTransport, RadrootsNostrConnectClientTransportFuture, @@ -552,16 +553,16 @@ fn sign_event_request_from_frozen_draft( let unsigned_event = UnsignedEvent { id: None, pubkey: public_key, - created_at: Timestamp::from_secs(u64::from(draft.created_at)), + created_at: Timestamp::from_secs(draft.created_at_u64()), kind, tags: Tags::from_list(tags), - content: draft.content.clone(), + content: draft.content().to_owned(), }; Ok(RadrootsNostrConnectRequest::SignEvent(unsigned_event)) } fn nip46_unsigned_event_pubkey(draft: &RadrootsEventDraft) -> Result<PublicKey, RadrootsSdkError> { - PublicKey::parse(draft.expected_pubkey.as_str()).map_err(|error| { + PublicKey::parse(draft.expected_pubkey_str()).map_err(|error| { nip46_sign_event_protocol_error(format!( "failed to parse frozen draft pubkey for NIP-46 unsigned event: {error}" )) @@ -569,7 +570,7 @@ fn nip46_unsigned_event_pubkey(draft: &RadrootsEventDraft) -> Result<PublicKey, } fn nip46_unsigned_event_kind(draft: &RadrootsEventDraft) -> Result<Kind, RadrootsSdkError> { - let kind = u16::try_from(draft.kind).map_err(|error| { + let kind = u16::try_from(draft.kind_u32()).map_err(|error| { nip46_sign_event_protocol_error(format!( "failed to convert frozen draft kind to NIP-46 unsigned event: {error}" )) @@ -578,9 +579,10 @@ fn nip46_unsigned_event_kind(draft: &RadrootsEventDraft) -> Result<Kind, Radroot } fn nip46_unsigned_event_tags(draft: &RadrootsEventDraft) -> Result<Vec<Tag>, RadrootsSdkError> { - let mut tags = Vec::with_capacity(draft.tags.len()); - for raw_tag in &draft.tags { - let tag = Tag::parse(raw_tag.clone()).map_err(|error| { + let raw_tags = draft.tags_as_vec(); + let mut tags = Vec::with_capacity(raw_tags.len()); + for raw_tag in raw_tags { + let tag = Tag::parse(raw_tag).map_err(|error| { nip46_sign_event_protocol_error(format!( "failed to convert frozen draft tags to NIP-46 unsigned event: {error}" )) @@ -604,12 +606,26 @@ fn signed_event_from_nip46_response( match response { RadrootsNostrConnectResponse::SignedEvent(event) => { let raw_json = event.as_json(); - RadrootsSignedEvent::from_event(radroots_event_from_nostr(&event), raw_json).map_err( - |error| RadrootsSdkError::SignerProtocol { + let wire = RadrootsNip01EventWire::parse_json(raw_json.as_str()).map_err(|error| { + RadrootsSdkError::SignerProtocol { mode: RadrootsSdkSignerMode::MycNip46.as_str().to_owned(), - reason: format!("remote signed event is invalid: {error}"), - }, - ) + reason: format!("remote signed event wire is invalid: {error}"), + } + })?; + let signed_event = + RadrootsSignedEvent::from_wire_verified_id(wire, raw_json).map_err(|error| { + RadrootsSdkError::SignerProtocol { + mode: RadrootsSdkSignerMode::MycNip46.as_str().to_owned(), + reason: format!("remote signed event is invalid: {error}"), + } + })?; + signed_event + .verify_signature() + .map(|verified| verified.into_signed_event()) + .map_err(|error| RadrootsSdkError::SignerProtocol { + mode: RadrootsSdkSignerMode::MycNip46.as_str().to_owned(), + reason: format!("remote signed event signature is invalid: {error}"), + }) } RadrootsNostrConnectResponse::Error { error, .. } => { Err(RadrootsSdkError::SignerRequestRejected { @@ -669,7 +685,7 @@ fn sign_receipt( mode, signer_pubkey, remote_signer_pubkey, - signed_event_id: signed_event.id.clone(), + signed_event_id: signed_event.id_str().to_owned(), signed_event, } } diff --git a/crates/sdk/src/sync_runtime.rs b/crates/sdk/src/sync_runtime.rs @@ -1128,7 +1128,7 @@ async fn push_proxy_claimed_outbox_event( idempotency_key: Some(proxy_outbox_idempotency_key( claimed.outbox_event_id, claimed.attempt_count, - signed_event.id.as_str(), + signed_event.id_str(), active_delivery_plan_id(claimed, "radrootsd proxy publish")?, )), timeout_ms: adapter.config().request_timeout_ms, @@ -1686,7 +1686,7 @@ fn proxy_transport_error_receipt( .filter(|target| target.status.is_ready_for_attempt()) .collect::<Vec<_>>(); let target_count = ready_targets.len(); - let event_id = push_receipt_event_id(event.id.as_str(), "proxy transport failure event id")?; + let event_id = push_receipt_event_id(event.id_str(), "proxy transport failure event id")?; Ok(PushOutboxEventReceipt { event_id, outbox_event_id: claimed.outbox_event_id, diff --git a/crates/sdk/src/workflow_runtime.rs b/crates/sdk/src/workflow_runtime.rs @@ -6,7 +6,6 @@ use crate::{ }; use radroots_authority::{RadrootsActorContext, RadrootsEventSigner, sign_authorized_draft}; use radroots_event::{ - RadrootsEventEnvelope, draft::{RadrootsEventDraft, RadrootsSignedEvent}, ids::RadrootsEventId, }; @@ -78,12 +77,12 @@ async fn enqueue_signed_workflow_event( Some(idempotency_key) => idempotency_key, None => SdkIdempotencyKey::derive( request.operation_kind, - request.frozen_draft.expected_event_id.as_str(), - request.frozen_draft.expected_pubkey.as_str(), + request.frozen_draft.expected_event_id_str(), + request.frozen_draft.expected_pubkey_str(), ), }; let observed_at_ms = sdk_now_ms(sdk)?; - let signed_event_id = RadrootsEventId::parse(request.frozen_draft.expected_event_id.as_str()) + let signed_event_id = RadrootsEventId::parse(request.frozen_draft.expected_event_id_str()) .expect("frozen workflow draft has a valid expected event id"); let delivery_plan_value = delivery_plan.delivery_plan; let idempotency_key_for_enqueue = idempotency_key.clone(); @@ -102,15 +101,13 @@ async fn enqueue_signed_workflow_event( .await?; let partial_failure_digest_prefix = digest_prefix(preflight.operation_idempotency_digest.as_str()); - let event = event_from_signed(&signed_event); let local_import_observation = RadrootsTransportObservation::new( RadrootsTransportKind::Local, SDK_LOCAL_EVENT_ENDPOINT_URI, RadrootsTransportObservationType::LocalImport, observed_at_ms, )?; - let ingest = RadrootsEventIngest::new(event, observed_at_ms) - .with_raw_json(signed_event.raw_json.clone()) + let ingest = RadrootsEventIngest::new(signed_event.clone(), observed_at_ms) .with_observation(local_import_observation); let ingest_receipt = sdk._event_store.ingest_event(ingest).await?; let outbox_input = signed_outbox_input( @@ -319,18 +316,6 @@ fn signed_outbox_input( .with_idempotency_key(idempotency_key.into_string()) } -fn event_from_signed(signed_event: &RadrootsSignedEvent) -> RadrootsEventEnvelope { - RadrootsEventEnvelope { - id: signed_event.id.clone(), - author: signed_event.pubkey.clone(), - created_at: signed_event.created_at, - kind: signed_event.kind, - tags: signed_event.tags.clone(), - content: signed_event.content.clone(), - sig: signed_event.sig.clone(), - } -} - #[cfg(test)] #[path = "../tests/unit/workflow_runtime_tests.rs"] mod tests; diff --git a/crates/sdk/tests/dvm_runtime.rs b/crates/sdk/tests/dvm_runtime.rs @@ -20,6 +20,7 @@ use radroots_event::{ RadrootsOrderEconomicLine, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, }, + wire::RadrootsNip01EventWireParts, }; use radroots_event_store::{RadrootsEventIngest, RadrootsEventStore}; use radroots_nostr::prelude::{ @@ -91,6 +92,12 @@ impl RadrootsEventSigner for FixtureSigner { } } +fn signed_event_from_envelope(event: RadrootsEventEnvelope) -> RadrootsSignedEvent { + let wire = event.to_nip01_wire(); + let raw_json = serde_json::to_string(&wire).expect("raw event json"); + RadrootsSignedEvent::from_wire_verified_id(wire, raw_json).expect("signed event") +} + #[tokio::test] async fn dvm_trade_transition_proof_request_enqueues_signed_job() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; @@ -260,16 +267,18 @@ async fn dvm_configured_enqueue_reports_prepare_and_target_errors_without_mutati async fn dvm_validation_receipt_ingest_commits_pending_trade_status() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-dvm-ingest", 10); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event("order-dvm-ingest", &request_event_id, 11); - let decision_event_id = - RadrootsEventId::parse(decision_event.id.as_str()).expect("decision id"); + let decision_event_id = RadrootsEventId::parse(decision_event.id_str()).expect("decision id"); for (event, observed_at_ms) in [ (request_event.clone(), 1_000), (decision_event.clone(), 1_100), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest order event"); } @@ -289,11 +298,11 @@ async fn dvm_validation_receipt_ingest_commits_pending_trade_status() { &decision_event_id, 12, ); - let receipt_event_id = RadrootsEventId::parse(receipt_event.id.as_str()).expect("receipt id"); + let receipt_event_id = RadrootsEventId::parse(receipt_event.id_str()).expect("receipt id"); let ingest = sdk .dvm() .ingest_validation_receipt( - DvmValidationReceiptIngestRequest::new(receipt_event) + DvmValidationReceiptIngestRequest::new(signed_event_from_envelope(receipt_event)) .with_expected_order_id(order_id("order-dvm-ingest")) .with_expected_listing_event_id(listing_event_id.clone()) .with_expected_root_event_id(request_event_id.clone()) @@ -367,7 +376,7 @@ async fn dvm_validation_receipt_ingest_rejects_binding_mismatch_without_mutation let error = sdk .dvm() .ingest_validation_receipt( - DvmValidationReceiptIngestRequest::new(receipt_event) + DvmValidationReceiptIngestRequest::new(signed_event_from_envelope(receipt_event)) .with_expected_order_id(order_id("other-order")), ) .await @@ -448,7 +457,7 @@ async fn dvm_validation_receipt_ingest_reports_timestamp_and_refresh_errors() { let error = sdk .dvm() .ingest_validation_receipt( - DvmValidationReceiptIngestRequest::new(overflow_observed) + DvmValidationReceiptIngestRequest::new(signed_event_from_envelope(overflow_observed)) .with_observed_at(RadrootsSdkTimestamp::from_unix_seconds(u64::MAX)), ) .await @@ -477,7 +486,9 @@ async fn dvm_validation_receipt_ingest_reports_timestamp_and_refresh_errors() { ); let error = sdk .dvm() - .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new(default_observed)) + .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new( + signed_event_from_envelope(default_observed), + )) .await .expect_err("default observed overflow"); assert!(matches!( @@ -505,7 +516,7 @@ async fn dvm_validation_receipt_ingest_reports_timestamp_and_refresh_errors() { let error = sdk .dvm() .ingest_validation_receipt( - DvmValidationReceiptIngestRequest::new(refresh_overflow) + DvmValidationReceiptIngestRequest::new(signed_event_from_envelope(refresh_overflow)) .with_observed_at(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000)), ) .await @@ -554,7 +565,9 @@ async fn dvm_prepare_and_ingest_use_sdk_clock_defaults() { ); let ingest = sdk .dvm() - .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new(receipt_event)) + .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new( + signed_event_from_envelope(receipt_event), + )) .await .expect("default observed timestamp ingest"); @@ -574,7 +587,9 @@ async fn dvm_validation_receipt_ingest_rejects_invalid_verified_order_id() { let error = sdk .dvm() - .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new(receipt_event)) + .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new( + signed_event_from_envelope(receipt_event), + )) .await .expect_err("invalid order id"); @@ -591,23 +606,21 @@ async fn dvm_validation_receipt_ingest_rejects_invalid_verified_order_id() { #[tokio::test] async fn dvm_validation_receipt_ingest_rejects_invalid_receipt_event_id() { - let (_tempdir, sdk, store) = directory_sdk_and_store().await; - let mut receipt_event = signed_validation_receipt_event( + let (_tempdir, _sdk, store) = directory_sdk_and_store().await; + let receipt_event = signed_validation_receipt_event( "order-dvm-bad-receipt-id", &deterministic_event_id("listing-event"), &deterministic_event_id("request-event"), &deterministic_event_id("decision-event"), 12, ); - receipt_event.id = "bad id".to_owned(); - - let error = sdk - .dvm() - .ingest_validation_receipt(DvmValidationReceiptIngestRequest::new(receipt_event)) - .await + let mut wire = receipt_event.to_nip01_wire(); + wire.id = "bad id".to_owned(); + let raw_json = serde_json::to_string(&wire).expect("raw event json"); + let error = RadrootsSignedEvent::from_wire_unchecked(wire, raw_json) .expect_err("invalid receipt event id"); - assert!(matches!(error, RadrootsSdkError::InvalidRequest { .. })); + assert!(error.to_string().contains("event envelope id is invalid")); assert_eq!( store .status_summary() @@ -845,7 +858,7 @@ fn signed_validation_receipt_event( fn signed_event( secret_key_hex: &str, created_at: u32, - parts: radroots_event_codec::wire::WireEventParts, + parts: RadrootsNip01EventWireParts, ) -> RadrootsEventEnvelope { let secret_key = RadrootsNostrSecretKey::from_hex(secret_key_hex).expect("secret key"); let keys = RadrootsNostrKeys::new(secret_key); diff --git a/crates/sdk/tests/farms_runtime.rs b/crates/sdk/tests/farms_runtime.rs @@ -68,29 +68,29 @@ impl RadrootsEventSigner for FixtureSigner { &self, draft: &RadrootsEventDraft, ) -> Result<RadrootsSignedEvent, RadrootsSignerError> { - if self.pubkey().as_str() != draft.expected_pubkey.as_str() { + if self.pubkey().as_str() != draft.expected_pubkey_str() { return Err(RadrootsSignerError::SigningFailed { message: "wrong fixture signer".to_owned(), }); } let sig = "f".repeat(128); let raw_json = serde_json::json!({ - "id": draft.expected_event_id, + "id": draft.expected_event_id_str(), "pubkey": self.pubkey().as_str(), - "created_at": draft.created_at, - "kind": draft.kind, - "tags": draft.tags, - "content": draft.content, + "created_at": draft.created_at_u64(), + "kind": draft.kind_u32(), + "tags": draft.tags_as_vec(), + "content": draft.content(), "sig": sig, }) .to_string(); RadrootsSignedEvent::new(RadrootsSignedEventParts { - id: draft.expected_event_id.clone(), + id: draft.expected_event_id_str().to_owned(), pubkey: self.pubkey().as_str().to_owned(), - created_at: draft.created_at, - kind: draft.kind, - tags: draft.tags.clone(), - content: draft.content.clone(), + created_at: draft.created_at_u64(), + kind: draft.kind_u32(), + tags: draft.tags_as_vec(), + content: draft.content().to_owned(), sig, raw_json, }) @@ -252,11 +252,11 @@ async fn farm_prepare_publish_is_side_effect_free() { let request = FarmPreparePublishRequest::new(farmer_actor(), farm(FARM_A_D_TAG, "North Farm")); let prepared = sdk.farms().prepare_publish(request).expect("prepared"); - assert_eq!(prepared.frozen_draft.kind, KIND_FARM); + assert_eq!(prepared.frozen_draft.kind_u32(), KIND_FARM); assert_eq!(prepared.created_at.unix_seconds(), 1_700_000_000); assert_eq!( prepared.expected_event_id, - prepared.frozen_draft.expected_event_id + prepared.frozen_draft.expected_event_id_str() ); assert_eq!( prepared.farm_addr.as_str(), @@ -684,7 +684,7 @@ async fn farm_enqueue_publish_stores_event_and_queues_signed_outbox_without_prof .expect("outbox event") .expect("outbox event"); assert_eq!(outbox_event.state, RadrootsOutboxEventState::Signed); - assert_eq!(outbox_event.draft.kind, KIND_FARM); + assert_eq!(outbox_event.draft.kind_u32(), KIND_FARM); assert!(outbox_event.signed_event.is_some()); } diff --git a/crates/sdk/tests/knowledge_public_api.rs b/crates/sdk/tests/knowledge_public_api.rs @@ -1,6 +1,7 @@ #![cfg(feature = "knowledge")] use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; +use radroots_event::RadrootsEventEnvelopeParts; use radroots_sdk::knowledge::prelude::*; const SECRET_KEY_HEX: &str = "0101010101010101010101010101010101010101010101010101010101010101"; @@ -60,8 +61,8 @@ fn fluent_builders_auto_fill_schema_and_prepare_drafts() { let claim_draft = claim_builder() .build_draft(public_key_hex(), CREATED_AT) .expect("claim draft"); - assert_eq!(article_draft.contract_id, WIKI_ARTICLE_CONTRACT_ID); - assert_eq!(claim_draft.contract_id, KNOWLEDGE_CLAIM_CONTRACT_ID); + assert_eq!(article_draft.contract_id(), WIKI_ARTICLE_CONTRACT_ID); + assert_eq!(claim_draft.contract_id(), KNOWLEDGE_CLAIM_CONTRACT_ID); } #[test] @@ -329,17 +330,20 @@ fn knowledge_draft_builder_freezes_mvp_drafts_without_runtime() { .knowledge_field_report(&knowledge_field_report()) .expect("field report draft"); - assert_eq!(article.contract_id, WIKI_ARTICLE_CONTRACT_ID); - assert_eq!(redirect.contract_id, WIKI_REDIRECT_CONTRACT_ID); - assert_eq!(merge_request.contract_id, WIKI_MERGE_REQUEST_CONTRACT_ID); - assert_eq!(source.contract_id, KNOWLEDGE_SOURCE_CONTRACT_ID); - assert_eq!(claim.contract_id, KNOWLEDGE_CLAIM_CONTRACT_ID); - assert_eq!(relation.contract_id, KNOWLEDGE_RELATION_CONTRACT_ID); - assert_eq!(review.contract_id, KNOWLEDGE_REVIEW_CONTRACT_ID); - assert_eq!(field_report.contract_id, KNOWLEDGE_FIELD_REPORT_CONTRACT_ID); - assert_eq!(claim.expected_pubkey, public_key_hex()); - assert_eq!(claim.created_at, CREATED_AT); - assert_eq!(claim.kind, KIND_KNOWLEDGE_CLAIM); + assert_eq!(article.contract_id(), WIKI_ARTICLE_CONTRACT_ID); + assert_eq!(redirect.contract_id(), WIKI_REDIRECT_CONTRACT_ID); + assert_eq!(merge_request.contract_id(), WIKI_MERGE_REQUEST_CONTRACT_ID); + assert_eq!(source.contract_id(), KNOWLEDGE_SOURCE_CONTRACT_ID); + assert_eq!(claim.contract_id(), KNOWLEDGE_CLAIM_CONTRACT_ID); + assert_eq!(relation.contract_id(), KNOWLEDGE_RELATION_CONTRACT_ID); + assert_eq!(review.contract_id(), KNOWLEDGE_REVIEW_CONTRACT_ID); + assert_eq!( + field_report.contract_id(), + KNOWLEDGE_FIELD_REPORT_CONTRACT_ID + ); + assert_eq!(claim.expected_pubkey_str(), public_key_hex()); + assert_eq!(claim.created_at_u64(), u64::from(CREATED_AT)); + assert_eq!(claim.kind_u32(), KIND_KNOWLEDGE_CLAIM); } #[test] @@ -397,7 +401,7 @@ fn knowledge_errors_expose_stable_codes() { let draft_error: RadrootsSdkKnowledgeError = RadrootsEventDraft::new( KNOWLEDGE_CLAIM_CONTRACT_ID, KIND_KNOWLEDGE_CLAIM, - CREATED_AT, + u64::from(CREATED_AT), Vec::new(), r#"{"schema":"radroots.knowledge.claim.v1","schema_version":1}"#, public_key_hex(), @@ -408,7 +412,7 @@ fn knowledge_errors_expose_stable_codes() { assert_eq!(draft_error.inner_code(), "missing_tag"); } -fn sign_parts(parts: WireEventParts) -> RadrootsEventEnvelope { +fn sign_parts(parts: RadrootsNip01EventWireParts) -> RadrootsEventEnvelope { let tags = parts .tags .into_iter() @@ -421,10 +425,10 @@ fn sign_parts(parts: WireEventParts) -> RadrootsEventEnvelope { .custom_created_at(Timestamp::from_secs(u64::from(CREATED_AT))) .sign_with_keys(&keys) .expect("signed event"); - RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event.id.to_hex(), author: event.pubkey.to_hex(), - created_at: event.created_at.as_secs() as u32, + created_at: event.created_at.as_secs(), kind: u32::from(event.kind.as_u16()), tags: event .tags @@ -434,7 +438,8 @@ fn sign_parts(parts: WireEventParts) -> RadrootsEventEnvelope { .collect(), content: event.content, sig: event.sig.to_string(), - } + }) + .expect("event envelope") } fn public_key_hex() -> String { diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs @@ -74,29 +74,29 @@ impl RadrootsEventSigner for FixtureSigner { &self, draft: &RadrootsEventDraft, ) -> Result<RadrootsSignedEvent, RadrootsSignerError> { - if self.pubkey().as_str() != draft.expected_pubkey.as_str() { + if self.pubkey().as_str() != draft.expected_pubkey_str() { return Err(RadrootsSignerError::SigningFailed { message: "wrong fixture signer".to_owned(), }); } let sig = "f".repeat(128); let raw_json = serde_json::json!({ - "id": draft.expected_event_id, + "id": draft.expected_event_id_str(), "pubkey": self.pubkey().as_str(), - "created_at": draft.created_at, - "kind": draft.kind, - "tags": draft.tags, - "content": draft.content, + "created_at": draft.created_at_u64(), + "kind": draft.kind_u32(), + "tags": draft.tags_as_vec(), + "content": draft.content(), "sig": sig, }) .to_string(); RadrootsSignedEvent::new(RadrootsSignedEventParts { - id: draft.expected_event_id.clone(), + id: draft.expected_event_id_str().to_owned(), pubkey: self.pubkey().as_str().to_owned(), - created_at: draft.created_at, - kind: draft.kind, - tags: draft.tags.clone(), - content: draft.content.clone(), + created_at: draft.created_at_u64(), + kind: draft.kind_u32(), + tags: draft.tags_as_vec(), + content: draft.content().to_owned(), sig, raw_json, }) @@ -207,11 +207,11 @@ async fn prepare_publish_is_side_effect_free() { let request = ListingPreparePublishRequest::new(actor(), listing(LISTING_A_D_TAG, "Coffee")); let prepared = sdk.listings().prepare_publish(request).expect("prepared"); - assert_eq!(prepared.frozen_draft.kind, KIND_LISTING); + assert_eq!(prepared.frozen_draft.kind_u32(), KIND_LISTING); assert_eq!(prepared.created_at.unix_seconds(), 1_700_000_000); assert_eq!( prepared.expected_event_id, - prepared.frozen_draft.expected_event_id + prepared.frozen_draft.expected_event_id_str() ); assert_eq!( prepared.public_listing_addr.as_str(), @@ -648,7 +648,7 @@ async fn explicit_historical_created_at_does_not_backdate_observed_at_ms() { .await .expect("outbox event") .expect("outbox event"); - assert_eq!(outbox_event.draft.created_at, 1_600_000_000); + assert_eq!(outbox_event.draft.created_at_u64(), 1_600_000_000); assert_eq!( outbox_event.event_store_ingested_at_ms, Some(observed_at_ms) diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs @@ -16,9 +16,11 @@ use radroots_core::{ }; #[cfg(feature = "transport-nostr-runtime")] use radroots_event::ids::RadrootsPublicKey; +use radroots_event::wire::RadrootsNip01EventWireParts; use radroots_event::{ RadrootsEventEnvelope, RadrootsEventPtr, contract::RadrootsActorRole, + draft::RadrootsSignedEvent, ids::{RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsOrderRevisionId}, kinds::{ KIND_LISTING, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, KIND_POST, @@ -31,7 +33,6 @@ use radroots_event::{ RadrootsOrderRevisionOutcome, }, }; -use radroots_event_codec::wire::WireEventParts; use radroots_event_store::{RadrootsEventIngest, RadrootsEventStore}; use radroots_nostr::prelude::{ RadrootsNostrKeys, RadrootsNostrSecretKey, RadrootsNostrTimestamp, radroots_event_from_nostr, @@ -88,6 +89,12 @@ const SELLER_PUBLIC_KEY_HEX: &str = const SERVICE_SECRET_KEY_HEX: &str = "48314941f2c9c01ef99f531df7b1d59a8de23dbeb45a498e5aa5f671e921931f"; const RELAY: &str = "wss://relay.radroots.test"; + +fn signed_event_from_envelope(event: RadrootsEventEnvelope) -> RadrootsSignedEvent { + let wire = event.to_nip01_wire(); + let raw_json = serde_json::to_string(&wire).expect("raw event json"); + RadrootsSignedEvent::from_wire_verified_id(wire, raw_json).expect("signed event") +} #[cfg(any())] const OTHER_PUBLIC_KEY_HEX: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; @@ -689,11 +696,11 @@ async fn order_submit_prepare_is_side_effect_free() { prepared.listing_event_id.as_str(), listing_event.id.as_str() ); - assert_eq!(prepared.frozen_draft.kind, KIND_ORDER_REQUEST); + assert_eq!(prepared.frozen_draft.kind_u32(), KIND_ORDER_REQUEST); assert_eq!(prepared.created_at.unix_seconds(), 1_700_000_000); assert_eq!( prepared.expected_event_id, - prepared.frozen_draft.expected_event_id + prepared.frozen_draft.expected_event_id_str() ); assert_eq!( store @@ -939,7 +946,7 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request .expect("outbox event") .expect("outbox event"); assert_eq!(outbox_event.state, RadrootsOutboxEventState::Signed); - assert_eq!(outbox_event.draft.kind, KIND_ORDER_REQUEST); + assert_eq!(outbox_event.draft.kind_u32(), KIND_ORDER_REQUEST); assert!(outbox_event.signed_event.is_some()); let status = sdk @@ -1221,7 +1228,7 @@ async fn trade_product_clients_resync_committed_after_rhi_validation_receipt() { let ingest = seller_sdk .dvm() .ingest_validation_receipt( - DvmValidationReceiptIngestRequest::new(receipt_event) + DvmValidationReceiptIngestRequest::new(signed_event_from_envelope(receipt_event)) .with_expected_order_id(propose_receipt.order_id.clone()) .with_expected_listing_event_id(propose_receipt.listing_event_id.clone()) .with_expected_root_event_id(propose_receipt.signed_event_id.clone()) @@ -1233,7 +1240,7 @@ async fn trade_product_clients_resync_committed_after_rhi_validation_receipt() { assert_eq!(ingest.receipt_event_id, receipt_event_id); seller_store .ingest_event(RadrootsEventIngest::new( - radroots_event_from_nostr(&worker_raw_event), + signed_event_from_envelope(radroots_event_from_nostr(&worker_raw_event)), 4_050, )) .await @@ -1331,10 +1338,9 @@ async fn trade_status_trust_policy_requires_trusted_cryptographic_receipt_for_co let (_tempdir, sdk, store) = directory_sdk_and_store().await; let order_id = "trade-status-trusted-crypto"; let request_event = signed_order_request_event(order_id, 70); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event(order_id, &request_event_id, 71); - let decision_event_id = - RadrootsEventId::parse(decision_event.id.as_str()).expect("decision id"); + let decision_event_id = RadrootsEventId::parse(decision_event.id_str()).expect("decision id"); let listing_event_id = deterministic_event_id("listing-event"); let receipt_raw_event = signed_raw_sp1_validation_receipt_event( order_id, @@ -1361,7 +1367,10 @@ async fn trade_status_trust_policy_requires_trusted_cryptographic_receipt_for_co (radroots_event_from_nostr(&worker_raw_event), 7_300), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest trade status trust event"); } @@ -1792,7 +1801,7 @@ async fn trade_product_accept_require_explicit_evidence_ingests_supplied_request PublishMode::EnqueueOnly, SatisfactionPolicy::NoWait, TradeEvidenceMode::require_explicit_evidence([ - TradeEvidenceIngestRequest::new(request_event), + TradeEvidenceIngestRequest::new(signed_event_from_envelope(request_event)), ]), ) .try_with_idempotency_key("trade-product-explicit-accept") @@ -1943,10 +1952,7 @@ async fn trade_validation_receipts_fetch_from_relays_and_select_worker_evidence( assert_eq!(list.receipts.len(), 1); assert!(list.invalid_receipts.is_empty()); assert_eq!(list.nostr_evidence.out_of_filter_count, 2); - assert_eq!( - list.receipts[0].event.id.as_str(), - receipt_event_id.as_str() - ); + assert_eq!(list.receipts[0].event.id_str(), receipt_event_id.as_str()); let trusted = list.receipts[0] .worker_evidence .trusted @@ -3300,7 +3306,7 @@ async fn trade_product_propose_dry_run_returns_plan_without_local_side_effects() }; assert_eq!(plan.order_id.as_str(), "trade-product-dry-run"); - assert_eq!(plan.frozen_draft.kind, KIND_ORDER_REQUEST); + assert_eq!(plan.frozen_draft.kind_u32(), KIND_ORDER_REQUEST); assert_eq!(plan.expected_event_id, plan.workflow.expected_event_id); assert_eq!( store @@ -3904,7 +3910,7 @@ fn validation_receipt_wire_parts( listing_event_id: &RadrootsEventId, root_event_id: &RadrootsEventId, target_event_id: &RadrootsEventId, -) -> WireEventParts { +) -> RadrootsNip01EventWireParts { validation_receipt_wire_parts_with_proof( raw_order_id, listing_event_id, @@ -3920,7 +3926,7 @@ fn validation_receipt_wire_parts_with_proof( root_event_id: &RadrootsEventId, target_event_id: &RadrootsEventId, proof_system: RadrootsValidationReceiptProofSystem, -) -> WireEventParts { +) -> RadrootsNip01EventWireParts { let proof = match proof_system { RadrootsValidationReceiptProofSystem::None => RadrootsValidationReceiptProof { inline_proof_base64: None, @@ -4022,7 +4028,7 @@ fn signed_raw_sp1_worker_result_event( signed_raw_event( SERVICE_SECRET_KEY_HEX, created_at, - WireEventParts { + RadrootsNip01EventWireParts { kind: KIND_TRADE_TRANSITION_PROOF_RESULT, content, tags: vec![ @@ -4074,7 +4080,7 @@ fn signed_raw_worker_result_event( signed_raw_event( SERVICE_SECRET_KEY_HEX, created_at, - WireEventParts { + RadrootsNip01EventWireParts { kind: KIND_TRADE_TRANSITION_PROOF_RESULT, content, tags: vec![ @@ -4155,7 +4161,10 @@ async fn ingest_status_noise_events( for index in 0..non_trade_count { store .ingest_event(RadrootsEventIngest::new( - signed_status_noise_post_event(index, 32_000 + index as u32), + signed_event_from_envelope(signed_status_noise_post_event( + index, + 32_000 + index as u32, + )), 1_700_200_000_000 + index, )) .await @@ -4166,7 +4175,10 @@ async fn ingest_status_noise_events( let order_id = format!("status-noise-background-{index:03}"); store .ingest_event(RadrootsEventIngest::new( - signed_order_request_event(&order_id, 33_000 + index as u32), + signed_event_from_envelope(signed_order_request_event( + &order_id, + 33_000 + index as u32, + )), 1_700_200_100_000 + index, )) .await @@ -4185,13 +4197,17 @@ fn hash32(ch: char) -> String { fn signed_event( secret_key_hex: &str, created_at: u32, - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, ) -> RadrootsEventEnvelope { let event = signed_raw_event(secret_key_hex, created_at, parts); radroots_event_from_nostr(&event) } -fn signed_raw_event(secret_key_hex: &str, created_at: u32, parts: WireEventParts) -> nostr::Event { +fn signed_raw_event( + secret_key_hex: &str, + created_at: u32, + parts: RadrootsNip01EventWireParts, +) -> nostr::Event { let secret_key = RadrootsNostrSecretKey::from_hex(secret_key_hex).expect("secret key"); let keys = RadrootsNostrKeys::new(secret_key); radroots_nostr_build_event(parts.kind, parts.content, parts.tags) @@ -4223,7 +4239,7 @@ fn signed_raw_order_request_event(raw_order_id: &str, created_at: u32) -> nostr: #[cfg(any())] fn request_event_ptr(event: &RadrootsEventEnvelope) -> RadrootsEventPtr { RadrootsEventPtr { - id: event.id.clone(), + id: event.id_str().to_owned(), relays: Some(RELAY.to_owned()), } } @@ -4268,7 +4284,7 @@ fn signed_status_noise_post_event(index: i64, created_at: u32) -> RadrootsEventE signed_event( SELLER_SECRET_KEY_HEX, created_at, - WireEventParts { + RadrootsNip01EventWireParts { kind: KIND_POST, content: format!("local status noise {index}"), tags: Vec::new(), @@ -4280,7 +4296,7 @@ fn signed_non_order_event(created_at: u32) -> RadrootsEventEnvelope { signed_event( SELLER_SECRET_KEY_HEX, created_at, - WireEventParts { + RadrootsNip01EventWireParts { kind: KIND_LISTING, content: "{}".to_owned(), tags: vec![vec!["d".to_owned(), "not-an-order".to_owned()]], @@ -4293,9 +4309,10 @@ fn signed_non_order_event(created_at: u32) -> RadrootsEventEnvelope { async fn order_request_evidence_ingest_stores_request_and_enables_decision_enqueue() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-ingested", 39); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); - let ingest_request = TradeRequestEvidenceIngestRequest::new(request_event.clone()) - .with_observed_at(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_039)); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); + let ingest_request = + TradeRequestEvidenceIngestRequest::new(signed_event_from_envelope(request_event.clone())) + .with_observed_at(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_039)); let ingest_receipt = sdk .trades() @@ -4359,13 +4376,15 @@ async fn order_request_evidence_ingest_stores_request_and_enables_decision_enque async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-evidence-ingest", 39); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event("order-evidence-ingest", &request_event_id, 40); let request_receipt = sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(request_event.clone())) + .ingest_evidence(TradeEvidenceIngestRequest::new(signed_event_from_envelope( + request_event.clone(), + ))) .await .expect("request evidence"); assert_eq!(request_receipt.order_id.as_str(), "order-evidence-ingest"); @@ -4375,7 +4394,9 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let decision_receipt = sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(decision_event.clone())) + .ingest_evidence(TradeEvidenceIngestRequest::new(signed_event_from_envelope( + decision_event.clone(), + ))) .await .expect("decision evidence"); assert_eq!(decision_receipt.order_id.as_str(), "order-evidence-ingest"); @@ -4385,7 +4406,9 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let duplicate_receipt = sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(decision_event)) + .ingest_evidence(TradeEvidenceIngestRequest::new(signed_event_from_envelope( + decision_event, + ))) .await .expect("duplicate decision evidence"); assert_eq!(duplicate_receipt.local_event_seq, 2); @@ -4420,7 +4443,9 @@ async fn order_evidence_ingest_rejects_non_order_events() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let error = sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(signed_non_order_event(41))) + .ingest_evidence(TradeEvidenceIngestRequest::new(signed_event_from_envelope( + signed_non_order_event(41), + ))) .await .expect_err("non order event"); @@ -4443,7 +4468,9 @@ async fn order_request_evidence_ingest_rejects_non_request_events() { let error = sdk .trades() - .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(decision_event)) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( + signed_event_from_envelope(decision_event), + )) .await .expect_err("non request event"); @@ -4483,11 +4510,11 @@ async fn order_decision_prepare_accept_and_decline_are_side_effect_free() { assert_eq!(accepted.buyer_pubkey.as_str(), BUYER_PUBLIC_KEY_HEX); assert_eq!(accepted.seller_pubkey.as_str(), SELLER_PUBLIC_KEY_HEX); assert_eq!(accepted.request_event_id, request_event_id); - assert_eq!(accepted.frozen_draft.kind, KIND_ORDER_DECISION); + assert_eq!(accepted.frozen_draft.kind_u32(), KIND_ORDER_DECISION); assert_eq!(accepted.created_at.unix_seconds(), 1_700_000_000); assert_eq!( accepted.expected_event_id, - accepted.frozen_draft.expected_event_id + accepted.frozen_draft.expected_event_id_str() ); let mut declined_payload = order_decision("order-decision-prepare-decline"); @@ -4504,7 +4531,7 @@ async fn order_decision_prepare_accept_and_decline_are_side_effect_free() { .expect("declined plan"); assert_eq!(declined.order_id.as_str(), "order-decision-prepare-decline"); - assert_eq!(declined.frozen_draft.kind, KIND_ORDER_DECISION); + assert_eq!(declined.frozen_draft.kind_u32(), KIND_ORDER_DECISION); assert_eq!( store .status_summary() @@ -4661,7 +4688,10 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { let request_event = signed_order_request_event("order-decision-serialized-enqueue", 45); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_500)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 4_500, + )) .await .expect("ingest request"); let enqueue_request = TradeDecisionEnqueueRequest::new( @@ -4799,7 +4829,7 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { "order_id": receipt.order_id.as_str(), "locator": { "trade_id": receipt.order_id.as_str(), - "root_event_id": request_event.id.as_str(), + "root_event_id": request_event.id_str(), "listing_addr": receipt.listing_addr.as_str(), "buyer_pubkey": BUYER_PUBLIC_KEY_HEX, "seller_pubkey": SELLER_PUBLIC_KEY_HEX @@ -4807,7 +4837,7 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { "listing_addr": receipt.listing_addr.as_str(), "buyer_pubkey": BUYER_PUBLIC_KEY_HEX, "seller_pubkey": SELLER_PUBLIC_KEY_HEX, - "request_event_id": request_event.id.as_str(), + "request_event_id": request_event.id_str(), "expected_event_id": receipt.expected_event_id.as_str(), "signed_event_id": receipt.signed_event_id.as_str(), "local_event_seq": 2, @@ -5069,18 +5099,19 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { let event = signed_order_request_event("order-evidence-dto", 77); let request_evidence = - TradeRequestEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); + TradeRequestEvidenceIngestRequest::new(signed_event_from_envelope(event.clone())) + .with_observed_at(created_at); let request_evidence_json = serde_json::to_value(&request_evidence).expect("request evidence json"); assert_struct_serialize_error_paths(&request_evidence, 2); - assert_eq!(request_evidence_json["event"]["id"], event.id.as_str()); + assert_eq!(request_evidence_json["event"]["id"], event.id_str()); assert_eq!(request_evidence_json["observed_at"], 1_700_000_654); - let order_evidence = - TradeEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); + let order_evidence = TradeEvidenceIngestRequest::new(signed_event_from_envelope(event.clone())) + .with_observed_at(created_at); let order_evidence_json = serde_json::to_value(&order_evidence).expect("order evidence json"); assert_struct_serialize_error_paths(&order_evidence, 2); - assert_eq!(order_evidence_json["event"]["id"], event.id.as_str()); + assert_eq!(order_evidence_json["event"]["id"], event.id_str()); assert_eq!(order_evidence_json["observed_at"], 1_700_000_654); } @@ -5089,9 +5120,12 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_status() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-accept", 40); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 4_000, + )) .await .expect("ingest request"); let request = TradeDecisionEnqueueRequest::new( @@ -5152,7 +5186,7 @@ async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_st .expect("outbox event") .expect("outbox event"); assert_eq!(outbox_event.state, RadrootsOutboxEventState::Signed); - assert_eq!(outbox_event.draft.kind, KIND_ORDER_DECISION); + assert_eq!(outbox_event.draft.kind_u32(), KIND_ORDER_DECISION); assert!(outbox_event.signed_event.is_some()); let status = sdk @@ -5168,7 +5202,7 @@ async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_st .request_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(request_event.id.as_str()) + Some(request_event.id_str()) ); assert_eq!( status @@ -5194,7 +5228,10 @@ async fn order_decision_enqueue_decline_stores_event_and_status_sees_declined() let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-decline", 41); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_100)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 4_100, + )) .await .expect("ingest request"); let mut decision = order_decision("order-decision-decline"); @@ -5290,7 +5327,10 @@ async fn order_decision_enqueue_returns_sanitized_signer_errors_before_decision_ let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-wrong-signer", 42); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_200)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 4_200, + )) .await .expect("ingest request"); let request = TradeDecisionEnqueueRequest::new( @@ -5332,7 +5372,7 @@ async fn order_decision_enqueue_returns_sanitized_signer_errors_before_decision_ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-conflict", 43); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event("order-decision-conflict", &request_event_id, 44); for (event, observed_at_ms) in [ @@ -5340,7 +5380,10 @@ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation( (decision_event.clone(), 4_400), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest"); } @@ -5385,7 +5428,7 @@ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation( .decision_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(decision_event.id.as_str()) + Some(decision_event.id_str()) ); } @@ -5394,9 +5437,12 @@ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation( async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-lifecycle-agreement", 50); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 5_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 5_000, + )) .await .expect("ingest request"); @@ -5544,9 +5590,12 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lifecycle() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-lifecycle-pending-revision", 55); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 5_500)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 5_500, + )) .await .expect("ingest request"); let proposal = order_revision_proposal( @@ -5662,9 +5711,12 @@ async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lif async fn order_declined_revision_finalizes_declined_negotiation() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-lifecycle-declined-revision", 56); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 5_600)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 5_600, + )) .await .expect("ingest request"); let proposal = order_revision_proposal( @@ -5784,9 +5836,12 @@ async fn order_declined_revision_finalizes_declined_negotiation() { async fn order_cancel_lifecycle_enqueue_updates_status() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-lifecycle-cancel", 60); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 6_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 6_000, + )) .await .expect("ingest request"); let cancellation_actor = buyer_actor(); @@ -5882,7 +5937,7 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-lifecycle-invalid", 70); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let missing = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( @@ -5916,7 +5971,10 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { ); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 7_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 7_000, + )) .await .expect("ingest request"); let decision_receipt = sdk @@ -6043,9 +6101,12 @@ async fn order_status_query_uses_indexed_order_id_under_background_event_noise() .await; let request_event = signed_order_request_event("order-status-noise-active", 31_000); - let request_event_id = request_event.id.clone(); + let request_event_id = request_event.id().clone(); store - .ingest_event(RadrootsEventIngest::new(request_event, 1_700_200_000_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event), + 1_700_200_000_000, + )) .await .expect("active order ingest"); @@ -6588,7 +6649,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { async fn order_status_projects_local_request_and_decision_events() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-1", 20); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event("order-1", &request_event_id, 21); for (event, observed_at_ms) in [ @@ -6596,7 +6657,10 @@ async fn order_status_projects_local_request_and_decision_events() { (decision_event.clone(), 2_100), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest"); } @@ -6618,7 +6682,7 @@ async fn order_status_projects_local_request_and_decision_events() { .iter() .map(RadrootsEventId::as_str) .collect::<Vec<_>>(), - vec![request_event.id.as_str(), decision_event.id.as_str()] + vec![request_event.id_str(), decision_event.id_str()] ); assert_eq!(receipt.status, TradeStatusKind::AgreedPendingRhi); assert_eq!( @@ -6626,18 +6690,18 @@ async fn order_status_projects_local_request_and_decision_events() { .request_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(request_event.id.as_str()) + Some(request_event.id_str()) ); assert_eq!( receipt .decision_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(decision_event.id.as_str()) + Some(decision_event.id_str()) ); assert_eq!( receipt.last_event_id.as_ref().map(RadrootsEventId::as_str), - Some(decision_event.id.as_str()) + Some(decision_event.id_str()) ); assert_eq!(receipt.listing_addr, Some(listing_address())); assert_eq!( @@ -6671,12 +6735,15 @@ async fn order_status_projects_local_request_and_decision_events() { async fn order_status_reports_limited_local_results() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-1", 25); - let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); + let request_event_id = RadrootsEventId::parse(request_event.id_str()).expect("request id"); let decision_event = signed_order_decision_event("order-1", &request_event_id, 26); for (event, observed_at_ms) in [(request_event.clone(), 2_500), (decision_event, 2_600)] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest"); } @@ -6697,19 +6764,19 @@ async fn order_status_reports_limited_local_results() { .iter() .map(RadrootsEventId::as_str) .collect::<Vec<_>>(), - vec![request_event.id.as_str()] + vec![request_event.id_str()] ); assert_eq!( receipt .request_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(request_event.id.as_str()) + Some(request_event.id_str()) ); assert!(receipt.decision_event_id.is_none()); assert_eq!( receipt.last_event_id.as_ref().map(RadrootsEventId::as_str), - Some(request_event.id.as_str()) + Some(request_event.id_str()) ); assert!(receipt.issues.is_empty()); } @@ -6725,7 +6792,10 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { (second_request_event.clone(), 2_800), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest"); } @@ -6745,10 +6815,7 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { .iter() .map(RadrootsEventId::as_str) .collect::<Vec<_>>(), - vec![ - first_request_event.id.as_str(), - second_request_event.id.as_str() - ] + vec![first_request_event.id_str(), second_request_event.id_str()] ); assert!(receipt.issues.is_empty()); let candidate_roots = receipt @@ -6765,10 +6832,7 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { .collect::<Vec<_>>(); assert_eq!( candidate_roots, - vec![ - first_request_event.id.as_str(), - second_request_event.id.as_str() - ] + vec![first_request_event.id_str(), second_request_event.id_str()] ); assert_eq!( receipt @@ -6777,15 +6841,15 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { .map(|candidate| TradeStatusRequest::locator_selector(&candidate.locator)) .collect::<Vec<_>>(), vec![ - format!("order-1@{}", first_request_event.id.as_str()), - format!("order-1@{}", second_request_event.id.as_str()) + format!("order-1@{}", first_request_event.id_str()), + format!("order-1@{}", second_request_event.id_str()) ] ); let root_specific = sdk .trades() .status( - TradeStatusRequest::parse(&format!("order-1@{}", second_request_event.id.as_str())) + TradeStatusRequest::parse(&format!("order-1@{}", second_request_event.id_str())) .expect("root status request"), ) .await @@ -6798,7 +6862,7 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { .request_event_id .as_ref() .map(RadrootsEventId::as_str), - Some(second_request_event.id.as_str()) + Some(second_request_event.id_str()) ); assert!(root_specific.ambiguity_candidates.is_empty()); } @@ -6815,7 +6879,10 @@ async fn trade_product_mutation_returns_structured_ambiguity() { (second_request_event.clone(), 2_800), ] { store - .ingest_event(RadrootsEventIngest::new(event, observed_at_ms)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(event), + observed_at_ms, + )) .await .expect("ingest"); } @@ -6860,10 +6927,7 @@ async fn trade_product_mutation_returns_structured_ambiguity() { .expect("root event id") }) .collect::<Vec<_>>(), - vec![ - first_request_event.id.as_str(), - second_request_event.id.as_str() - ] + vec![first_request_event.id_str(), second_request_event.id_str()] ); assert_eq!( error.recovery_actions(), @@ -6887,11 +6951,14 @@ async fn order_status_maps_malformed_local_data_to_sanitized_error() { let request_event = signed_order_request_event("order-1", 30); let raw_event_json = serde_json::to_string(&request_event).expect("raw event json"); store - .ingest_event(RadrootsEventIngest::new(request_event.clone(), 3_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event.clone()), + 3_000, + )) .await .expect("ingest"); sqlx::query("UPDATE event_envelopes SET tags_json = '[' WHERE event_id = ?") - .bind(request_event.id.as_str()) + .bind(request_event.id_str()) .execute(store.pool()) .await .expect("corrupt tags"); @@ -6906,7 +6973,7 @@ async fn order_status_maps_malformed_local_data_to_sanitized_error() { assert!(matches!(error, RadrootsSdkError::Projection { .. })); assert!(message.contains("contains invalid tags_json")); assert!(!message.contains(raw_event_json.as_str())); - assert!(!message.contains(request_event.sig.as_str())); + assert!(!message.contains(request_event.sig_str())); assert!(!message.contains("\"tags\"")); assert!(!message.contains("\"content\"")); } @@ -6917,7 +6984,10 @@ async fn trade_status_watch_emits_finite_refresh_window() { let order_id = "watch-finite-refresh-window"; let request_event = signed_order_request_event(order_id, 820); store - .ingest_event(RadrootsEventIngest::new(request_event, 1_700_400_000_000)) + .ingest_event(RadrootsEventIngest::new( + signed_event_from_envelope(request_event), + 1_700_400_000_000, + )) .await .expect("request ingest"); @@ -7056,7 +7126,7 @@ async fn manual_local_status_perf_gate_measures_100k_events() { let event = signed_order_request_event(&order_id, 20_000 + index as u32); store .ingest_event(RadrootsEventIngest::new( - event, + signed_event_from_envelope(event), 1_700_100_000_000 + index as i64, )) .await diff --git a/crates/sdk/tests/replica_ingest.rs b/crates/sdk/tests/replica_ingest.rs @@ -1,4 +1,4 @@ -use radroots_event::{RadrootsEventEnvelope, farm::RadrootsFarm}; +use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts, farm::RadrootsFarm}; use radroots_replica_schema::farm::IFarmFindMany; use radroots_replica_store::ReplicaSql; use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event}; @@ -17,15 +17,16 @@ fn sdk_event( content: String, tags: Vec<Vec<String>>, ) -> RadrootsEventEnvelope { - RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: format!("{id:064x}"), author: author.to_owned(), - created_at, + created_at: u64::from(created_at), kind, tags, content, sig: "f".repeat(128), - } + }) + .expect("sdk event envelope") } fn sample_farm() -> RadrootsFarm { @@ -81,5 +82,5 @@ fn sdk_farm_draft_ingests_into_replica_projection() { assert_eq!(farms.len(), 1); assert_eq!(farms[0].d_tag, sample_farm().d_tag); assert_eq!(farms[0].name, sample_farm().name); - assert_eq!(farms[0].pubkey, event.author); + assert_eq!(farms[0].pubkey, event.author_str()); } diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs @@ -1045,7 +1045,11 @@ fn sdk_examples_stay_on_product_api_boundary() { ]; for (name, example) in examples { - assert!(!example.contains("WireEventParts"), "{name}"); + assert!(!example.contains(concat!("Wire", "EventParts")), "{name}"); + assert!( + !example.contains(concat!("Radroots", "Frozen", "EventDraft")), + "{name}" + ); assert!(!example.contains("protocol::wire"), "{name}"); assert!(!example.contains("event_codec::wire"), "{name}"); assert!(!example.contains(".as_wire_parts("), "{name}"); diff --git a/crates/sdk/tests/source_boundary.rs b/crates/sdk/tests/source_boundary.rs @@ -104,6 +104,22 @@ const FORBIDDEN_SDK_README_CONCEPTS: &[ForbiddenSdkConcept] = &[ const FORBIDDEN_FOUNDATION_HARDENING_RETIRED_CONCEPTS: &[ForbiddenSdkConcept] = &[ ForbiddenSdkConcept { + pattern: "WireEventParts", + reason: "event construction must use current Radroots NIP-01 wire part names", + }, + ForbiddenSdkConcept { + pattern: "RadrootsFrozenEventDraft", + reason: "event draft surfaces must use the current RadrootsEventDraft name", + }, + ForbiddenSdkConcept { + pattern: "pub type SignedEvent", + reason: "SDK signing adapters must not expose compatibility aliases for signed events", + }, + ForbiddenSdkConcept { + pattern: "pub type NostrEventBuilder", + reason: "SDK signing adapters must not expose compatibility aliases for event builders", + }, + ForbiddenSdkConcept { pattern: "SignedNostrEvent", reason: "generic signed-event surfaces must use product-neutral signed-event names", }, diff --git a/crates/sdk/tests/support/fixture_signer.rs b/crates/sdk/tests/support/fixture_signer.rs @@ -23,29 +23,29 @@ impl RadrootsEventSigner for FixtureSigner { &self, draft: &RadrootsEventDraft, ) -> Result<RadrootsSignedEvent, RadrootsSignerError> { - if self.pubkey().as_str() != draft.expected_pubkey.as_str() { + if self.pubkey().as_str() != draft.expected_pubkey_str() { return Err(RadrootsSignerError::SigningFailed { message: "wrong fixture signer".to_owned(), }); } let sig = "f".repeat(128); let raw_json = serde_json::json!({ - "id": draft.expected_event_id, + "id": draft.expected_event_id_str(), "pubkey": self.pubkey().as_str(), - "created_at": draft.created_at, - "kind": draft.kind, - "tags": draft.tags, - "content": draft.content, + "created_at": draft.created_at_u64(), + "kind": draft.kind_u32(), + "tags": draft.tags_as_vec(), + "content": draft.content(), "sig": sig, }) .to_string(); RadrootsSignedEvent::new(RadrootsSignedEventParts { - id: draft.expected_event_id.clone(), + id: draft.expected_event_id_str().to_owned(), pubkey: self.pubkey().as_str().to_owned(), - created_at: draft.created_at, - kind: draft.kind, - tags: draft.tags.clone(), - content: draft.content.clone(), + created_at: draft.created_at_u64(), + kind: draft.kind_u32(), + tags: draft.tags_as_vec(), + content: draft.content().to_owned(), sig, raw_json, }) diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs @@ -338,7 +338,7 @@ impl RadrootsRelayPublishAdapter for RecordingPublishAdapter { self.raw_events .lock() .expect("raw event lock") - .push(request.signed_event.raw_json.clone()); + .push(request.signed_event.raw_json().to_owned()); self.request_times_ms .lock() .expect("request time lock") @@ -383,29 +383,29 @@ impl RadrootsEventSigner for FixtureSigner { &self, draft: &RadrootsEventDraft, ) -> Result<RadrootsSignedEvent, RadrootsSignerError> { - if self.pubkey().as_str() != draft.expected_pubkey.as_str() { + if self.pubkey().as_str() != draft.expected_pubkey_str() { return Err(RadrootsSignerError::SigningFailed { message: "wrong fixture signer".to_owned(), }); } let sig = "f".repeat(128); let raw_json = serde_json::json!({ - "id": draft.expected_event_id, + "id": draft.expected_event_id_str(), "pubkey": self.pubkey().as_str(), - "created_at": draft.created_at, - "kind": draft.kind, - "tags": draft.tags, - "content": draft.content, + "created_at": draft.created_at_u64(), + "kind": draft.kind_u32(), + "tags": draft.tags_as_vec(), + "content": draft.content(), "sig": sig, }) .to_string(); RadrootsSignedEvent::new(RadrootsSignedEventParts { - id: draft.expected_event_id.clone(), + id: draft.expected_event_id_str().to_owned(), pubkey: self.pubkey().as_str().to_owned(), - created_at: draft.created_at, - kind: draft.kind, - tags: draft.tags.clone(), - content: draft.content.clone(), + created_at: draft.created_at_u64(), + kind: draft.kind_u32(), + tags: draft.tags_as_vec(), + content: draft.content().to_owned(), sig, raw_json, }) diff --git a/crates/sdk/tests/unit/adapters_nostr_tests.rs b/crates/sdk/tests/unit/adapters_nostr_tests.rs @@ -5,7 +5,7 @@ use super::{ use crate::adapters::signing::sign_parts_with_identity; use crate::identity::RadrootsIdentity; use core::time::Duration; -use radroots_event_codec::wire::WireEventParts; +use radroots_event::wire::RadrootsNip01EventWireParts; use radroots_nostr::prelude::RadrootsNostrClientOptions; use tokio::runtime::Runtime; @@ -74,7 +74,7 @@ fn relay_helpers_accept_empty_relay_sets_without_network_endpoints() { let signed = sign_parts_with_identity( &identity, - WireEventParts { + RadrootsNip01EventWireParts { kind: 1, content: "hello".to_owned(), tags: Vec::new(), diff --git a/crates/sdk/tests/unit/adapters_radrootsd_tests.rs b/crates/sdk/tests/unit/adapters_radrootsd_tests.rs @@ -1,4 +1,5 @@ use super::*; +use radroots_event::wire::RadrootsNip01EventWire; use radroots_transport::RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI; use radroots_transport_publish_protocol::{ NostrPublishTargetSourcePolicy, TransportPublishDeliveryPolicy, TransportPublishEventRequest, @@ -86,7 +87,7 @@ fn spawn_http_server( } fn signed_event() -> RadrootsSignedEvent { - RadrootsSignedEvent { + let wire = RadrootsNip01EventWire { id: "a".repeat(64), pubkey: "b".repeat(64), created_at: 1_700_000_000, @@ -94,22 +95,24 @@ fn signed_event() -> RadrootsSignedEvent { tags: vec![vec!["d".to_owned(), "listing-1".to_owned()]], content: "{\"name\":\"carrots\"}".to_owned(), sig: "c".repeat(128), - raw_json: serde_json::json!({ - "id": "a".repeat(64), - "pubkey": "b".repeat(64), - "created_at": 1_700_000_000u32, - "kind": 30402u32, - "tags": [["d", "listing-1"]], - "content": "{\"name\":\"carrots\"}", - "sig": "c".repeat(128) - }) - .to_string(), - } + extra: Default::default(), + }; + let raw_json = serde_json::json!({ + "id": "a".repeat(64), + "pubkey": "b".repeat(64), + "created_at": 1_700_000_000u32, + "kind": 30402u32, + "tags": [["d", "listing-1"]], + "content": "{\"name\":\"carrots\"}", + "sig": "c".repeat(128) + }) + .to_string(); + RadrootsSignedEvent::from_wire_unchecked(wire, raw_json).expect("signed event") } fn publish_request() -> TransportPublishEventRequest { TransportPublishEventRequest { - event: signed_event_wire(&signed_event()), + raw_event_json: signed_event().raw_json().to_owned(), target_policy: TransportPublishTargetPolicy::nostr( NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault, vec!["wss://relay.example.com".to_owned()], @@ -358,9 +361,11 @@ fn proxy_config_builders_preserve_typed_runtime_options() { fn publish_event_request_json_uses_signed_event_contract() { let value = publish_event_request_json(&publish_request()).expect("request json"); - assert_eq!(value["event"]["id"], "a".repeat(64)); - assert_eq!(value["event"]["pubkey"], "b".repeat(64)); - assert_eq!(value["event"]["kind"], 30_402); + let raw_event_json = value["raw_event_json"].as_str().expect("raw event json"); + let raw_event: serde_json::Value = serde_json::from_str(raw_event_json).expect("raw event"); + assert_eq!(raw_event["id"], "a".repeat(64)); + assert_eq!(raw_event["pubkey"], "b".repeat(64)); + assert_eq!(raw_event["kind"], 30_402); assert_eq!(value["target_policy"]["kind"], "nostr"); assert_eq!( value["target_policy"]["source_policy"], @@ -462,7 +467,11 @@ async fn publish_event_posts_transport_publish_jsonrpc() { let body: serde_json::Value = serde_json::from_str(recorded.body.as_str()).expect("body"); assert_eq!(body["method"], METHOD_EVENT); assert_eq!(body["id"], SDK_RADROOTSD_PROXY_REQUEST_ID); - assert_eq!(body["params"]["event"]["content"], "{\"name\":\"carrots\"}"); + let raw_event_json = body["params"]["raw_event_json"] + .as_str() + .expect("raw event json"); + let raw_event: serde_json::Value = serde_json::from_str(raw_event_json).expect("raw event"); + assert_eq!(raw_event["content"], "{\"name\":\"carrots\"}"); assert_eq!(body["params"]["target_policy"]["kind"], "nostr"); assert_eq!( body["params"]["target_policy"]["relay_urls"][0], @@ -833,7 +842,7 @@ async fn publish_event_empty_http_error_reports_empty_body() { } #[tokio::test] -async fn publish_signed_event_rejects_invalid_protocol_requests_before_http() { +async fn publish_signed_event_rejects_invalid_target_requests_before_http() { let adapter = RadrootsdProxyPublishAdapter::new(RadrootsdProxyConfig::new("http://127.0.0.1:9/rpc")); let base = RadrootsdProxyPublishRequest { @@ -847,10 +856,6 @@ async fn publish_signed_event_rejects_invalid_protocol_requests_before_http() { timeout_ms: Some(1_000), }; - let mut invalid_event_kind = base.clone(); - invalid_event_kind.signed_event.kind = 70_000; - let mut empty_event_tag = base.clone(); - empty_event_tag.signed_event.tags = vec![Vec::new()]; let mut invalid_quorum = base.clone(); invalid_quorum.delivery_policy = TransportPublishDeliveryPolicy::Quorum { quorum: 0 }; let mut too_many_targets = base.clone(); @@ -896,8 +901,6 @@ async fn publish_signed_event_rejects_invalid_protocol_requests_before_http() { empty_idempotency.idempotency_key = Some(" ".to_owned()); for request in [ - invalid_event_kind, - empty_event_tag, invalid_quorum, too_many_targets, empty_endpoint_uri, @@ -915,7 +918,7 @@ async fn publish_signed_event_rejects_invalid_protocol_requests_before_http() { async fn adapter_rejects_invalid_request_before_transport() { let adapter = RadrootsdProxyPublishAdapter::new(RadrootsdProxyConfig::new("http://127.0.0.1:9/rpc")); - let mut request = RadrootsdProxyPublishRequest { + let request = RadrootsdProxyPublishRequest { signed_event: signed_event(), target_policy: TransportPublishTargetPolicy::nostr( NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault, @@ -925,7 +928,6 @@ async fn adapter_rejects_invalid_request_before_transport() { idempotency_key: None, timeout_ms: None, }; - request.signed_event.id = "A".repeat(64); let error = adapter .publish_signed_event(request) diff --git a/crates/sdk/tests/unit/adapters_signing_tests.rs b/crates/sdk/tests/unit/adapters_signing_tests.rs @@ -1,10 +1,10 @@ use super::{event_builder_from_parts, sign_parts_with_identity}; use crate::identity::RadrootsIdentity; -use radroots_event_codec::wire::WireEventParts; +use radroots_event::wire::RadrootsNip01EventWireParts; #[test] fn event_builder_from_parts_preserves_kind_and_content() { - let builder = event_builder_from_parts(WireEventParts { + let builder = event_builder_from_parts(RadrootsNip01EventWireParts { kind: 30402, content: "hello".into(), tags: vec![vec!["x".into(), "y".into()]], @@ -22,7 +22,7 @@ fn sign_parts_with_identity_signs_event() { let identity = RadrootsIdentity::generate(); let event = sign_parts_with_identity( &identity, - WireEventParts { + RadrootsNip01EventWireParts { kind: 30402, content: "hello".into(), tags: vec![], diff --git a/crates/sdk/tests/unit/dvm_runtime_tests.rs b/crates/sdk/tests/unit/dvm_runtime_tests.rs @@ -9,10 +9,11 @@ use crate::{ }; use radroots_authority::RadrootsActorContext; use radroots_event::{ - RadrootsEventEnvelope, contract::RadrootsActorRole, + draft::RadrootsSignedEvent, ids::{RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey}, kinds::KIND_TRADE_TRANSITION_PROOF_REQUEST, + wire::RadrootsNip01EventWire, }; use radroots_trade::validation_receipt::RadrootsValidationReceiptProofSystem; @@ -33,14 +34,17 @@ fn trade_transition_proof_plan_builds_microstandard_wire_payload() { ) .expect("plan"); let payload: serde_json::Value = - serde_json::from_str(&plan.frozen_draft.content).expect("payload json"); + serde_json::from_str(plan.frozen_draft.content()).expect("payload json"); assert_eq!( - plan.frozen_draft.contract_id, + plan.frozen_draft.contract_id(), super::DVM_TRADE_TRANSITION_PROOF_REQUEST_CONTRACT_ID ); - assert_eq!(plan.frozen_draft.kind, KIND_TRADE_TRANSITION_PROOF_REQUEST); - assert_eq!(plan.frozen_draft.expected_pubkey, SERVICE); + assert_eq!( + plan.frozen_draft.kind_u32(), + KIND_TRADE_TRANSITION_PROOF_REQUEST + ); + assert_eq!(plan.frozen_draft.expected_pubkey_str(), SERVICE); assert_eq!(plan.worker_pubkey.as_str(), WORKER); assert_eq!(plan.proof_mode, DvmProofMode::None); assert_eq!( @@ -54,12 +58,10 @@ fn trade_transition_proof_plan_builds_microstandard_wire_payload() { assert_eq!(payload["inventory_sequence"], 7); assert_eq!(payload["previous_state_root"], hash32('3')); assert_eq!(payload["listing_event_id"], event_id('1').as_str()); + let tags = plan.frozen_draft.tags_as_vec(); + assert_eq!(tags[0], vec!["a", listing_addr().as_str()]); assert_eq!( - plan.frozen_draft.tags[0], - vec!["a", listing_addr().as_str()] - ); - assert_eq!( - plan.frozen_draft.tags[1], + tags[1], vec![ "i", event_id('3').as_str(), @@ -67,7 +69,7 @@ fn trade_transition_proof_plan_builds_microstandard_wire_payload() { "radroots:order_decision_event" ] ); - assert_eq!(plan.frozen_draft.tags[2], vec!["p", WORKER]); + assert_eq!(tags[2], vec!["p", WORKER]); } #[test] @@ -371,14 +373,26 @@ fn hash32(ch: char) -> String { format!("0x{}", ch.to_string().repeat(64)) } -fn dummy_event() -> RadrootsEventEnvelope { - RadrootsEventEnvelope { +fn dummy_event() -> RadrootsSignedEvent { + let wire = RadrootsNip01EventWire { id: event_id('9').into_string(), - author: event_id('a').into_string(), + pubkey: event_id('a').into_string(), created_at: 1, kind: 3440, tags: Vec::new(), content: "{}".to_owned(), - sig: event_id('b').into_string(), - } + sig: "b".repeat(128), + extra: Default::default(), + }; + let raw_json = serde_json::json!({ + "id": wire.id.clone(), + "pubkey": wire.pubkey.clone(), + "created_at": wire.created_at, + "kind": wire.kind, + "tags": wire.tags.clone(), + "content": wire.content.clone(), + "sig": wire.sig.clone() + }) + .to_string(); + RadrootsSignedEvent::from_wire_unchecked(wire, raw_json).expect("signed event") } diff --git a/crates/sdk/tests/unit/orders_runtime_tests.rs b/crates/sdk/tests/unit/orders_runtime_tests.rs @@ -6,6 +6,7 @@ use radroots_core::{ }; use radroots_event::{ draft::RadrootsSignedEvent, + envelope::RadrootsEventEnvelopeParts, kinds::KIND_LISTING, order::{ RadrootsOrderDecisionOutcome, RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, @@ -120,7 +121,7 @@ fn ptr(id: String) -> RadrootsEventPtr { } fn nostr_event(id: String, kind: u32) -> RadrootsEventEnvelope { - RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id, author: hex_64('c'), created_at: 1_700_000_000, @@ -128,7 +129,8 @@ fn nostr_event(id: String, kind: u32) -> RadrootsEventEnvelope { tags: Vec::new(), content: "{}".to_owned(), sig: hex_128('f'), - } + }) + .expect("event envelope") } fn actor(pubkey: &RadrootsPublicKey, role: RadrootsActorRole) -> RadrootsActorContext { @@ -594,21 +596,29 @@ async fn enqueue_fixture_submit(sdk: &RadrootsClient, raw_order_id: &str) -> Tra } fn event_from_parts( - parts: WireEventParts, + parts: RadrootsNip01EventWireParts, contract_id: &str, expected_pubkey: &RadrootsPublicKey, ) -> RadrootsEventEnvelope { - let frozen = to_frozen_draft(parts, contract_id, expected_pubkey.as_str(), 1_700_000_000) - .expect("frozen draft"); - RadrootsEventEnvelope { - id: frozen.expected_event_id, + let frozen = RadrootsEventDraft::new( + contract_id, + parts.kind, + 1_700_000_000, + parts.tags, + parts.content, + expected_pubkey.as_str(), + ) + .expect("frozen draft"); + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + id: frozen.expected_event_id_str().to_owned(), author: expected_pubkey.as_str().to_owned(), - created_at: frozen.created_at, - kind: frozen.kind, - tags: frozen.tags, - content: frozen.content, + created_at: frozen.created_at_u64(), + kind: frozen.kind_u32(), + tags: frozen.tags_as_vec(), + content: frozen.content().to_owned(), sig: hex_128('f'), - } + }) + .expect("event envelope") } fn request_event() -> RadrootsEventEnvelope { @@ -623,6 +633,36 @@ fn request_event() -> RadrootsEventEnvelope { ) } +fn event_parts_from(event: &RadrootsEventEnvelope) -> RadrootsEventEnvelopeParts { + RadrootsEventEnvelopeParts { + id: event.id_str().to_owned(), + author: 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(), + } +} + +fn event_with_content(event: &RadrootsEventEnvelope, content: String) -> RadrootsEventEnvelope { + RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + content, + ..event_parts_from(event) + }) + .expect("event envelope") +} + +fn signed_event_from(event: RadrootsEventEnvelope) -> RadrootsSignedEvent { + let wire = event.to_nip01_wire(); + let raw_json = serde_json::to_string(&wire).expect("raw event json"); + RadrootsSignedEvent::from_wire_unchecked(wire, raw_json).expect("signed event") +} + +fn request_signed_event() -> RadrootsSignedEvent { + signed_event_from(request_event()) +} + fn order_request_evidence_error( result: Result<TradeRequestEvidence, RadrootsSdkError>, ) -> RadrootsSdkError { @@ -1008,7 +1048,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { 1_700_000_000, "order test", ); - assert_eq!(expected_event_id, frozen_draft.expected_event_id); + assert_eq!(expected_event_id, frozen_draft.expected_event_id_str()); } #[test] @@ -1161,7 +1201,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { ); let decision_event_id = - RadrootsEventId::parse(decision_event.id.as_str()).expect("decision event id"); + RadrootsEventId::parse(decision_event.id_str()).expect("decision event id"); let proposal = revision_proposal_payload(&root_event_id, &decision_event_id); let proposal_event = event_from_parts( order::build_order_revision_proposal_draft(&root_event_id, &decision_event_id, &proposal) @@ -1178,7 +1218,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { ); let proposal_event_id = - RadrootsEventId::parse(proposal_event.id.as_str()).expect("proposal event id"); + RadrootsEventId::parse(proposal_event.id_str()).expect("proposal event id"); let revision_decision = revision_decision_payload( &proposal, &proposal_event_id, @@ -1226,22 +1266,26 @@ fn order_request_evidence_parses_and_rejects_malformed_envelopes() { assert_eq!(evidence.buyer_pubkey, pubkey('c')); assert_eq!(evidence.seller_pubkey, pubkey('d')); - let mut invalid_id = event.clone(); - invalid_id.id = "not-hex".to_owned(); + let invalid_id = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + id: "not-hex".to_owned(), + ..event_parts_from(&event) + }) + .expect_err("invalid id"); assert!( - invalid_request_message(order_request_evidence_error(parse_order_request_evidence( - &invalid_id, - ))) - .contains("event id is invalid") + invalid_id + .to_string() + .contains("event envelope id is invalid") ); - let mut invalid_author = event.clone(); - invalid_author.author = "not-hex".to_owned(); + let invalid_author = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { + author: "not-hex".to_owned(), + ..event_parts_from(&event) + }) + .expect_err("invalid author"); assert!( - invalid_request_message(order_request_evidence_error(parse_order_request_evidence( - &invalid_author, - ))) - .contains("decode failed") + invalid_author + .to_string() + .contains("event envelope author is invalid") ); let request = order_request_payload(); @@ -1259,8 +1303,7 @@ fn order_request_evidence_parses_and_rejects_malformed_envelopes() { .contains("decode failed") ); - let mut decode_failure = event.clone(); - decode_failure.content = "{}".to_owned(); + let decode_failure = event_with_content(&event, "{}".to_owned()); assert!( invalid_request_message(order_request_evidence_error(parse_order_request_evidence( &decode_failure, @@ -1268,11 +1311,13 @@ fn order_request_evidence_parses_and_rejects_malformed_envelopes() { .contains("decode failed") ); - let mut envelope = serde_json::from_str::<serde_json::Value>(event.content.as_str()) - .expect("request envelope"); + let mut envelope = + serde_json::from_str::<serde_json::Value>(event.content()).expect("request envelope"); envelope["order_id"] = serde_json::Value::String("other-order".to_owned()); - let mut order_mismatch = event.clone(); - order_mismatch.content = serde_json::to_string(&envelope).expect("mismatched envelope"); + let order_mismatch = event_with_content( + &event, + serde_json::to_string(&envelope).expect("mismatched envelope"), + ); assert!( invalid_request_message(order_request_evidence_error(parse_order_request_evidence( &order_mismatch, @@ -1280,11 +1325,13 @@ fn order_request_evidence_parses_and_rejects_malformed_envelopes() { .contains("decode failed") ); - let mut envelope = serde_json::from_str::<serde_json::Value>(event.content.as_str()) - .expect("request envelope"); + let mut envelope = + serde_json::from_str::<serde_json::Value>(event.content()).expect("request envelope"); envelope["listing_addr"] = serde_json::Value::String(format!("30402:{}:other", hex_64('d'))); - let mut listing_mismatch = event; - listing_mismatch.content = serde_json::to_string(&envelope).expect("mismatched envelope"); + let listing_mismatch = event_with_content( + &event, + serde_json::to_string(&envelope).expect("mismatched envelope"), + ); assert!( invalid_request_message(order_request_evidence_error(parse_order_request_evidence( &listing_mismatch, @@ -3817,7 +3864,8 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .ingest_evidence( - TradeEvidenceIngestRequest::new(request_event()).with_observed_at(out_of_range,) + TradeEvidenceIngestRequest::new(request_signed_event()) + .with_observed_at(out_of_range,) ) .await, Err(RadrootsSdkError::TimestampOutOfRange { .. }) @@ -3825,7 +3873,7 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .ingest_request_evidence( - TradeRequestEvidenceIngestRequest::new(request_event()) + TradeRequestEvidenceIngestRequest::new(request_signed_event()) .with_observed_at(out_of_range,), ) .await, @@ -3945,14 +3993,16 @@ async fn order_default_timestamp_paths_report_clock_errors() { assert!(matches!( clock_error_sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(request_event())) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_signed_event())) .await, Err(RadrootsSdkError::ClockBeforeUnixEpoch) )); assert!(matches!( clock_error_sdk .trades() - .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(request_event())) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( + request_signed_event() + )) .await, Err(RadrootsSdkError::ClockBeforeUnixEpoch) )); @@ -4056,10 +4106,10 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() assert_struct_serialize_error_paths(&submit_enqueue, 6); let request_ingest = - TradeRequestEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); + TradeRequestEvidenceIngestRequest::new(request_signed_event()).with_observed_at(created_at); assert_struct_serialize_error_paths(&request_ingest, 2); let evidence_ingest = - TradeEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); + TradeEvidenceIngestRequest::new(request_signed_event()).with_observed_at(created_at); assert_struct_serialize_error_paths(&evidence_ingest, 2); let decision_prepare = TradeDecisionPrepareRequest::new( @@ -4342,7 +4392,7 @@ async fn closed_event_store_errors_are_mapped_for_ingest_and_prepared_lookup() { sdk._event_store.pool().close().await; let ingest_error = sdk .trades() - .ingest_evidence(TradeEvidenceIngestRequest::new(request_event())) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_signed_event())) .await .expect_err("closed ingest evidence"); assert!(matches!(ingest_error, RadrootsSdkError::EventStore { .. })); @@ -4351,7 +4401,9 @@ async fn closed_event_store_errors_are_mapped_for_ingest_and_prepared_lookup() { sdk._event_store.pool().close().await; let request_ingest_error = sdk .trades() - .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(request_event())) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( + request_signed_event(), + )) .await .expect_err("closed request evidence ingest"); assert!(matches!( @@ -4372,7 +4424,7 @@ async fn closed_event_store_errors_are_mapped_for_ingest_and_prepared_lookup() { #[tokio::test] async fn order_status_and_evidence_ingest_cover_source_attached_success_paths() { let sdk = prepared_order_sdk().await; - let request_event = request_event(); + let request_event = request_signed_event(); let request_receipt = sdk .trades() .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs @@ -1604,19 +1604,38 @@ async fn restore_archive_private_failures_cover_staging_and_verification_edges() .build() .await .expect("populated sdk"); + let populated_event_pubkey = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + let populated_event_tags = Vec::<Vec<String>>::new(); + let populated_event_content = "{}".to_owned(); + let populated_event_id = radroots_event::wire::compute_canonical_nip01_event_id( + populated_event_pubkey, + 1_700_000_002, + 1, + &populated_event_tags, + &populated_event_content, + ) + .expect("canonical event id"); + let populated_event_wire = radroots_event::wire::RadrootsNip01EventWire { + id: populated_event_id.into_string(), + pubkey: populated_event_pubkey.to_owned(), + created_at: 1_700_000_002, + kind: 1, + tags: populated_event_tags, + content: populated_event_content, + sig: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_owned(), + extra: Default::default(), + }; + let populated_event_raw_json = + serde_json::to_string(&populated_event_wire).expect("raw event json"); + let populated_event = radroots_event::draft::RadrootsSignedEvent::from_wire_verified_id( + populated_event_wire, + populated_event_raw_json, + ) + .expect("signed event"); populated_sdk ._event_store .ingest_event(radroots_event_store::RadrootsEventIngest::new( - radroots_event::RadrootsEventEnvelope { - id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned(), - author: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" - .to_owned(), - created_at: 1_700_000_002, - kind: 1, - tags: Vec::new(), - content: "{}".to_owned(), - sig: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_owned(), - }, + populated_event, 1_700_000_002_000, )) .await diff --git a/crates/sdk/tests/unit/signer_provider_tests.rs b/crates/sdk/tests/unit/signer_provider_tests.rs @@ -2,8 +2,8 @@ use super::*; use nostr::nips::nip44::{self, Version}; use nostr::{EventBuilder, JsonUtil, Kind, Tag}; use radroots_event::contract::RadrootsActorRole; +use radroots_event::draft::RadrootsEventDraft; use radroots_event::kinds::{KIND_COOP, KIND_FARM}; -use radroots_event_codec::wire::{WireEventParts, to_frozen_draft}; use radroots_nostr::prelude::{RadrootsNostrEvent, RadrootsNostrSecretKey}; use radroots_nostr_connect::prelude::{ RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectClientTarget, RadrootsNostrConnectError, @@ -64,15 +64,13 @@ fn frozen_draft_with( tags: Vec<Vec<String>>, content: &str, ) -> RadrootsEventDraft { - to_frozen_draft( - WireEventParts { - kind, - content: content.to_owned(), - tags, - }, + RadrootsEventDraft::new( contract_id, + kind, + u64::from(created_at), + tags, + content, pubkey, - created_at, ) .expect("frozen draft") } @@ -80,7 +78,7 @@ fn frozen_draft_with( fn sign_event(keys: &RadrootsNostrKeys, draft: &RadrootsEventDraft) -> RadrootsNostrEvent { let signed = radroots_nostr::prelude::radroots_nostr_sign_frozen_draft(keys, draft).expect("signed"); - RadrootsNostrEvent::from_json(signed.raw_json.as_str()).expect("event") + RadrootsNostrEvent::from_json(signed.raw_json()).expect("event") } fn response_event( @@ -255,7 +253,7 @@ async fn local_key_provider_signs_authorized_frozen_draft() { assert!(provider.capability().nip46_permissions.is_empty()); assert_eq!(receipt.mode, RadrootsSdkSignerMode::LocalKey); assert_eq!(receipt.signer_pubkey, USER_PUBLIC_KEY_HEX); - assert_eq!(receipt.signed_event_id, draft.expected_event_id); + assert_eq!(receipt.signed_event_id, draft.expected_event_id_str()); assert_eq!( progress, vec![ @@ -530,7 +528,7 @@ async fn myc_nip46_provider_signs_and_validates_remote_event() { let draft = frozen_draft(); let signed = radroots_nostr::prelude::radroots_nostr_sign_frozen_draft(&user_keys, &draft) .expect("signed"); - let signed_event = RadrootsNostrEvent::from_json(signed.raw_json.as_str()).expect("event"); + let signed_event = RadrootsNostrEvent::from_json(signed.raw_json()).expect("event"); let transport = Arc::new(MockNip46Transport::new( remote_keys.clone(), vec![MockNip46Response::Respond( @@ -584,14 +582,17 @@ async fn myc_nip46_provider_signs_and_validates_remote_event() { .into_iter() .map(Tag::to_vec) .collect::<Vec<_>>(); - assert_eq!(sign_event_request.pubkey.to_hex(), draft.expected_pubkey); + assert_eq!( + sign_event_request.pubkey.to_hex(), + draft.expected_pubkey_str() + ); assert_eq!( sign_event_request.created_at.as_secs(), - u64::from(draft.created_at) + draft.created_at_u64() ); - assert_eq!(sign_event_request.kind.as_u16(), draft.kind as u16); - assert_eq!(request_tags, draft.tags); - assert_eq!(sign_event_request.content, draft.content); + assert_eq!(sign_event_request.kind.as_u16(), draft.kind_u32() as u16); + assert_eq!(request_tags, draft.tags_as_vec()); + assert_eq!(sign_event_request.content, draft.content()); let request_id = request_messages[0] .id .strip_prefix("radroots-sdk-myc-nip46-sign-") @@ -610,48 +611,6 @@ async fn myc_nip46_provider_signs_and_validates_remote_event() { ); } -#[test] -fn myc_nip46_sign_event_request_reports_invalid_frozen_drafts() { - let mut invalid_pubkey = frozen_draft(); - invalid_pubkey.expected_pubkey = "not-a-public-key".to_owned(); - let error = sign_event_request_from_frozen_draft(&invalid_pubkey) - .expect_err("invalid NIP-46 pubkey error"); - assert!(matches!( - error, - RadrootsSdkError::SignerProtocol { - mode, - ref reason - } if mode == RadrootsSdkSignerMode::MycNip46.as_str() - && reason.contains("failed to parse frozen draft pubkey") - )); - - let mut oversized_kind = frozen_draft(); - oversized_kind.kind = u32::from(u16::MAX) + 1; - let error = sign_event_request_from_frozen_draft(&oversized_kind) - .expect_err("oversized NIP-46 event kind error"); - assert!(matches!( - error, - RadrootsSdkError::SignerProtocol { - mode, - ref reason - } if mode == RadrootsSdkSignerMode::MycNip46.as_str() - && reason.contains("failed to convert frozen draft kind") - )); - - let mut empty_tag = frozen_draft(); - empty_tag.tags = vec![Vec::new()]; - let error = - sign_event_request_from_frozen_draft(&empty_tag).expect_err("invalid NIP-46 tag error"); - assert!(matches!( - error, - RadrootsSdkError::SignerProtocol { - mode, - ref reason - } if mode == RadrootsSdkSignerMode::MycNip46.as_str() - && reason.contains("failed to convert frozen draft tags") - )); -} - #[tokio::test] async fn myc_nip46_provider_reports_preflight_and_progress_sink_edges() { let draft = frozen_draft(); @@ -727,7 +686,7 @@ async fn myc_nip46_provider_returns_completion_progress_errors_after_remote_sign let draft = frozen_draft(); let signed = radroots_nostr::prelude::radroots_nostr_sign_frozen_draft(&user_keys, &draft) .expect("signed"); - let signed_event = RadrootsNostrEvent::from_json(signed.raw_json.as_str()).expect("event"); + let signed_event = RadrootsNostrEvent::from_json(signed.raw_json()).expect("event"); let (signer, transport) = myc_signer_with_responses(vec![MockNip46Response::Respond( RadrootsNostrConnectResponse::SignedEvent(signed_event), )]); @@ -1086,5 +1045,5 @@ async fn sdk_builder_installs_configured_signer_provider() { )) .await .expect("receipt"); - assert_eq!(receipt.signed_event_id, draft.expected_event_id); + assert_eq!(receipt.signed_event_id, draft.expected_event_id_str()); } diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs @@ -29,8 +29,6 @@ use radroots_event::draft::{RadrootsEventDraft, RadrootsSignedEvent}; use radroots_event::ids::RadrootsEventId; #[cfg(feature = "radrootsd-proxy")] use radroots_event::kinds::KIND_FARM; -#[cfg(feature = "radrootsd-proxy")] -use radroots_event_codec::wire::{WireEventParts, to_frozen_draft}; use radroots_event_store::RadrootsEventStoreStatusSummary; #[cfg(feature = "radrootsd-proxy")] use radroots_nostr::prelude::{ @@ -134,15 +132,13 @@ fn proxy_actor() -> RadrootsActorContext { #[cfg(feature = "radrootsd-proxy")] fn proxy_frozen_draft(d_tag: &str) -> RadrootsEventDraft { - to_frozen_draft( - WireEventParts { - kind: KIND_FARM, - content: "{}".to_owned(), - tags: vec![vec!["d".to_owned(), d_tag.to_owned()]], - }, + RadrootsEventDraft::new( "radroots.farm.profile.v1", - PROXY_SIGNER_PUBLIC_KEY_HEX, + KIND_FARM, 1_700_000_000, + vec![vec!["d".to_owned(), d_tag.to_owned()]], + "{}", + PROXY_SIGNER_PUBLIC_KEY_HEX, ) .expect("frozen draft") } @@ -903,7 +899,7 @@ async fn proxy_push_empty_queue_and_private_helpers_are_deterministic() { message.clone(), ) .expect("proxy transport error receipt"); - assert_eq!(receipt.event_id, signed_event.id); + assert_eq!(receipt.event_id, signed_event.id_str()); assert_eq!(receipt.final_state, PushOutboxEventState::PublishRetryable); assert_eq!(receipt.retryable_count, 1); assert!(!receipt.quorum_met); @@ -1115,16 +1111,15 @@ async fn proxy_push_reports_missing_signed_claim_before_daemon_publish() { state: RadrootsOutboxEventState::Signed, claim_token: "claim-token".to_owned(), active_delivery_plan_id: Some(1), - draft: RadrootsEventDraft { - contract_id: "radroots.test".to_owned(), - contract_registry_version: 1, - kind: 1, - created_at: 1_700_000_000, - tags: Vec::new(), - content: "{}".to_owned(), - expected_pubkey: "a".repeat(64), - expected_event_id: "b".repeat(64), - }, + draft: RadrootsEventDraft::new( + "radroots.farm.profile.v1", + KIND_FARM, + 1_700_000_000, + vec![vec!["d".to_owned(), "missing-signed-event".to_owned()]], + "{}", + PROXY_SIGNER_PUBLIC_KEY_HEX, + ) + .expect("draft"), signed_event: None, delivery_targets: Vec::new(), }; @@ -1457,8 +1452,7 @@ async fn proxy_completion_updates_outbox_for_success_retryable_and_terminal_rece .signed_event .as_ref() .expect("signed event") - .id - .as_str(), + .id_str(), outcome_kind, ); assert_eq!( @@ -1467,16 +1461,19 @@ async fn proxy_completion_updates_outbox_for_success_retryable_and_terminal_rece .signed_event .as_ref() .expect("signed event") - .id - .clone() + .id_str() ); assert_eq!( publish.pubkey, - claimed.signed_event.as_ref().expect("signed event").pubkey + claimed + .signed_event + .as_ref() + .expect("signed event") + .pubkey_str() ); assert_eq!( publish.event_kind, - claimed.signed_event.as_ref().expect("signed event").kind + claimed.signed_event.as_ref().expect("signed event").kind() ); let proxy_receipt = push_proxy_event_receipt(claimed.outbox_event_id, publish.clone()).expect("receipt"); @@ -1564,10 +1561,7 @@ async fn proxy_completion_matches_duplicate_endpoint_targets_by_scope() { .expect("claim") .expect("claim"); assert_eq!(claimed.outbox_event_id, enqueue.outbox_event_id); - let mut publish = proxy_job( - signed_event.id.as_str(), - TransportPublishOutcomeKind::Accepted, - ); + let mut publish = proxy_job(signed_event.id_str(), TransportPublishOutcomeKind::Accepted); publish.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![ TransportPublishTarget::nostr("wss://relay.example.com") .with_scope("farm.a") @@ -1631,8 +1625,7 @@ async fn proxy_completion_rejects_duplicate_daemon_outcome_before_local_mutation .signed_event .as_ref() .expect("signed event") - .id - .as_str(), + .id_str(), TransportPublishOutcomeKind::Accepted, ); publish.targets.push(publish.targets[0].clone()); diff --git a/crates/sdk/tests/unit/workflow_runtime_tests.rs b/crates/sdk/tests/unit/workflow_runtime_tests.rs @@ -3,9 +3,8 @@ use super::*; use crate::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider}; use radroots_authority::{RadrootsSignerError, RadrootsSignerIdentity}; use radroots_event::contract::RadrootsActorRole; -use radroots_event::draft::RadrootsSignedEvent; +use radroots_event::draft::{RadrootsEventDraft, RadrootsSignedEvent, RadrootsSignedEventParts}; use radroots_event::kinds::KIND_FARM; -use radroots_event_codec::wire::{WireEventParts, to_frozen_draft}; use radroots_nostr::prelude::{ RadrootsNostrKeys, RadrootsNostrSecretKey, radroots_nostr_sign_frozen_draft, }; @@ -54,15 +53,13 @@ fn frozen_draft_for(pubkey: &str) -> RadrootsEventDraft { } fn frozen_draft_for_d_tag(pubkey: &str, d_tag: &str) -> RadrootsEventDraft { - to_frozen_draft( - WireEventParts { - kind: KIND_FARM, - content: "{}".to_owned(), - tags: vec![vec!["d".to_owned(), d_tag.to_owned()]], - }, + RadrootsEventDraft::new( "radroots.farm.profile.v1", - pubkey, + KIND_FARM, 1_700_000_000, + vec![vec!["d".to_owned(), d_tag.to_owned()]], + "{}", + pubkey, ) .expect("frozen draft") } @@ -72,16 +69,29 @@ fn frozen_draft() -> RadrootsEventDraft { } fn signed_event() -> RadrootsSignedEvent { - RadrootsSignedEvent { - id: "b".repeat(64), - pubkey: "a".repeat(64), - created_at: 1_700_000_000, - kind: 1, - tags: vec![vec!["d".to_owned(), "test".to_owned()]], - content: "{}".to_owned(), - sig: "c".repeat(128), - raw_json: "{}".to_owned(), - } + let draft = frozen_draft(); + let sig = "c".repeat(128); + let raw_json = serde_json::json!({ + "id": draft.expected_event_id_str(), + "pubkey": draft.expected_pubkey_str(), + "created_at": draft.created_at_u64(), + "kind": draft.kind_u32(), + "tags": draft.tags_as_vec(), + "content": draft.content(), + "sig": sig, + }) + .to_string(); + RadrootsSignedEvent::new(RadrootsSignedEventParts { + id: draft.expected_event_id_str().to_owned(), + pubkey: draft.expected_pubkey_str().to_owned(), + created_at: draft.created_at_u64(), + kind: draft.kind_u32(), + tags: draft.tags_as_vec(), + content: draft.content().to_owned(), + sig, + raw_json, + }) + .expect("signed event") } fn nostr_profile(relay: &'static str) -> crate::TransportProfile { @@ -121,9 +131,9 @@ fn workflow_digest_and_event_helpers_cover_error_and_input_paths() { let draft = frozen_draft(); let signed = signed_event(); - let event = event_from_signed(&signed); - assert_eq!(event.id, signed.id); - assert_eq!(event.author, signed.pubkey); + let event = signed.envelope(); + assert_eq!(event.id(), signed.id()); + assert_eq!(event.author(), signed.pubkey()); let idempotency_key = SdkIdempotencyKey::new("workflow-idempotency").expect("idempotency"); let input = signed_outbox_input( @@ -344,7 +354,7 @@ async fn enqueue_signed_workflow_stores_signed_event_and_reports_idempotency_con assert_eq!( receipt.signed_event_id.as_str(), - first_draft.expected_event_id + first_draft.expected_event_id_str() ); assert!(receipt.local_event_seq > 0); assert!(receipt.outbox_operation_id > 0); @@ -445,7 +455,10 @@ async fn enqueue_configured_signed_workflow_uses_sdk_signer_provider() { .await .expect("configured enqueue"); - assert_eq!(receipt.signed_event_id.as_str(), draft.expected_event_id); + assert_eq!( + receipt.signed_event_id.as_str(), + draft.expected_event_id_str() + ); assert_eq!(receipt.idempotency_digest_prefix.len(), 12); } diff --git a/tools/sdk_xtask_import/src/contracts.rs b/tools/sdk_xtask_import/src/contracts.rs @@ -684,7 +684,12 @@ order = 1 "profile.build_draft" = "profile.buildDraft" [shared_types] -"WireEventParts" = "WireEventParts" +"RadrootsNip01EventWireParts" = "RadrootsNip01EventWireParts" +"RadrootsNip01EventWireDto" = "RadrootsNip01EventWireDto" +"RadrootsEventDraft" = "RadrootsEventDraft" +"RadrootsSignedEventDto" = "RadrootsSignedEventDto" +"RadrootsVerifiedSignedEventDto" = "RadrootsVerifiedSignedEventDto" +"RadrootsEventEnvelopeDto" = "RadrootsEventEnvelopeDto" [artifacts] models_dir = "src/generated" diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs @@ -572,7 +572,7 @@ mod tests { "RadrootsCoopRef", "RadrootsDocument", "RadrootsDocumentSubject", - "RadrootsEventEnvelope", + "RadrootsEventEnvelopeDto", "RadrootsEventPtr", "RadrootsEventRef", "RadrootsEvidenceBounty", @@ -626,6 +626,7 @@ mod tests { "RadrootsMessageFile", "RadrootsMessageFileDimensions", "RadrootsMessageRecipient", + "RadrootsNip01EventWireDto", "RadrootsOrderCancellation", "RadrootsOrderDecision", "RadrootsOrderDecisionOutcome", @@ -659,6 +660,8 @@ mod tests { "RadrootsResourceHarvestProduct", "RadrootsRightsAssertion", "RadrootsSeal", + "RadrootsSignedEventDto", + "RadrootsSignedEventVerificationStateDto", "RadrootsSocialFarmAnchor", "RadrootsSocialLocation", "RadrootsSocialMediaDimensions", @@ -668,6 +671,8 @@ mod tests { "RadrootsTradeListingValidateRequest", "RadrootsTradeListingValidateResult", "RadrootsTradeValidationListingError", + "RadrootsVerifiedSignedEventDto", + "RadrootsVerifiedSignedEventVerificationStateDto", "RadrootsWikiArticle", "RadrootsWikiArticleVersionRef", "RadrootsWikiMergeRequest", @@ -896,6 +901,30 @@ mod tests { } #[test] + fn event_generated_types_expose_current_signed_event_dto_shapes() { + let wire = type_declaration(EVENT_BINDINGS_TYPES_TS, "RadrootsNip01EventWireDto"); + let envelope = type_declaration(EVENT_BINDINGS_TYPES_TS, "RadrootsEventEnvelopeDto"); + let signed = type_declaration(EVENT_BINDINGS_TYPES_TS, "RadrootsSignedEventDto"); + let verified = type_declaration(EVENT_BINDINGS_TYPES_TS, "RadrootsVerifiedSignedEventDto"); + + assert!(wire.contains("pubkey: string")); + assert!(wire.contains("extra: { [key: string]: unknown }")); + assert!(envelope.contains("author: string")); + assert!(!envelope.contains("pubkey")); + assert!(!envelope.contains("extra")); + assert!(signed.contains("state: RadrootsSignedEventVerificationStateDto")); + assert!(signed.contains("envelope: RadrootsEventEnvelopeDto")); + assert!(signed.contains("wire: RadrootsNip01EventWireDto")); + assert!(signed.contains("raw_json: string")); + assert!(verified.contains("state: RadrootsVerifiedSignedEventVerificationStateDto")); + assert!(verified.contains("signed_event: RadrootsSignedEventDto")); + assert!( + !EVENT_BINDINGS_TYPES_TS.contains("export type RadrootsEventEnvelope ="), + "event package must not export the retired raw envelope type name" + ); + } + + #[test] fn event_index_type_inventory_matches_current_package_surface() { let actual = type_inventory(EVENT_INDEX_BINDINGS_TYPES_TS); diff --git a/tools/sdk_xtask_import/src/smoke.rs b/tools/sdk_xtask_import/src/smoke.rs @@ -120,6 +120,7 @@ fn is_exact_crates_version(version: &str) -> bool { } const CONSUMER_MAIN: &str = r#"use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; +use radroots_event::RadrootsEventEnvelopeParts; use radroots_sdk::knowledge::prelude::*; const SECRET_KEY_HEX: &str = "0101010101010101010101010101010101010101010101010101010101010101"; @@ -141,7 +142,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { Ok(()) } -fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn std::error::Error>> { +fn sign_parts(parts: RadrootsNip01EventWireParts) -> Result<RadrootsEventEnvelope, Box<dyn std::error::Error>> { let tags = parts .tags .into_iter() @@ -152,10 +153,10 @@ fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn st .tags(tags) .custom_created_at(Timestamp::from_secs(u64::from(CREATED_AT))) .sign_with_keys(&keys)?; - Ok(RadrootsEventEnvelope { + Ok(RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts { id: event.id.to_hex(), author: event.pubkey.to_hex(), - created_at: event.created_at.as_secs() as u32, + created_at: event.created_at.as_secs(), kind: u32::from(event.kind.as_u16()), tags: event .tags @@ -165,7 +166,7 @@ fn sign_parts(parts: WireEventParts) -> Result<RadrootsEventEnvelope, Box<dyn st .collect(), content: event.content, sig: event.sig.to_string(), - }) + })?) } fn public_key_hex() -> String {