diff --git a/crates/bcr-ebill-api/src/constants.rs b/crates/bcr-ebill-api/src/constants.rs index 7e745fbd..ee01a700 100644 --- a/crates/bcr-ebill-api/src/constants.rs +++ b/crates/bcr-ebill-api/src/constants.rs @@ -6,7 +6,6 @@ pub const MAX_FILE_NAME_CHARACTERS: usize = 200; pub const VALID_FILE_MIME_TYPES: [&str; 3] = ["image/jpeg", "image/png", "application/pdf"]; // When subscribing events we subtract this from the last received event time -pub const NOSTR_EVENT_TIME_SLACK: u64 = 3600 * 24 * 7; // 1 week pub const DEFAULT_INITIAL_SUBSCRIPTION_DELAY_SECONDS: u32 = 1; pub const NOSTR_MAX_RELAYS: usize = 200; diff --git a/crates/bcr-ebill-transport/src/block_transport.rs b/crates/bcr-ebill-transport/src/block_transport.rs index df818cfd..27032316 100644 --- a/crates/bcr-ebill-transport/src/block_transport.rs +++ b/crates/bcr-ebill-transport/src/block_transport.rs @@ -11,7 +11,10 @@ use bcr_ebill_core::application::ServiceTraitBounds; use bcr_ebill_core::application::company::Company; use bcr_ebill_core::protocol::blockchain::BlockchainType; use bcr_ebill_core::protocol::crypto::BcrKeys; -use bcr_ebill_core::protocol::event::{BillChainEvent, CompanyChainEvent, IdentityChainEvent}; +use bcr_ebill_core::protocol::event::{ + BillChainEvent, CompanyChainEvent, EventEnvelope, IdentityChainEvent, +}; +use bitcoin::base58; use log::{debug, error}; use bcr_ebill_api::service::transport_service::{Error, Result}; @@ -156,8 +159,20 @@ impl BlockTransportServiceApi for BlockTransportService { if let Some((recipient, invite)) = events.generate_company_invite_message() && let Some(identity) = self.nostr_transport.resolve_identity(&recipient).await { - node.send_private_event(&events.sender(), &identity, invite.try_into()?) - .await?; + let message: EventEnvelope = invite.try_into()?; + if let Err(e) = node + .send_private_event(&events.sender(), &identity, message.clone()) + .await + { + error!("Failed to send company invite, queuing for retry: {e}"); + self.nostr_transport + .queue_retry_message( + &events.sender(), + Some(&recipient), + base58::encode(&borsh::to_vec(&message)?), + ) + .await?; + } } Ok(()) @@ -216,8 +231,20 @@ impl BlockTransportServiceApi for BlockTransportService { if !invites.is_empty() { for (recipient, event) in invites { if let Some(identity) = self.nostr_transport.resolve_identity(&recipient).await { - node.send_private_event(&events.sender(), &identity, event.try_into()?) - .await?; + let message: EventEnvelope = event.try_into()?; + if let Err(e) = node + .send_private_event(&events.sender(), &identity, message.clone()) + .await + { + error!("Failed to send bill invite, queuing for retry: {e}"); + self.nostr_transport + .queue_retry_message( + &events.sender(), + Some(&recipient), + base58::encode(&borsh::to_vec(&message)?), + ) + .await?; + } } } } diff --git a/crates/bcr-ebill-transport/src/nostr.rs b/crates/bcr-ebill-transport/src/nostr.rs index 50a45467..89c8f77e 100644 --- a/crates/bcr-ebill-transport/src/nostr.rs +++ b/crates/bcr-ebill-transport/src/nostr.rs @@ -32,7 +32,7 @@ use std::{ }; use bcr_ebill_api::{ - constants::{NOSTR_EVENT_TIME_SLACK, NOSTR_MAX_RELAYS}, + constants::NOSTR_MAX_RELAYS, service::{ contact_service::ContactServiceApi, transport_service::{ @@ -1077,15 +1077,10 @@ pub async fn should_process( fn valid_time(kind: Kind, created: nostr::Timestamp, since: Option) -> bool { match since { - Some(ts) => { - let time = if matches!(kind, Kind::EncryptedDirectMessage | Kind::GiftWrap) { - add_time_slack(ts) - } else { - ts - }; + Some(time) if !matches!(kind, Kind::EncryptedDirectMessage | Kind::GiftWrap) => { created.as_u64() >= time.inner() } - None => true, + _ => true, } } @@ -1163,14 +1158,6 @@ async fn get_offset(db: &Arc, node_id: &NodeId) -> .unwrap_or(Timestamp::zero()) } -fn add_time_slack(ts: Timestamp) -> Timestamp { - if ts.inner() <= NOSTR_EVENT_TIME_SLACK { - ts - } else { - ts - NOSTR_EVENT_TIME_SLACK - } -} - pub async fn add_offset( db: &Arc, event_id: EventId, diff --git a/crates/bcr-ebill-transport/src/nostr_transport.rs b/crates/bcr-ebill-transport/src/nostr_transport.rs index 12ef8628..5480d594 100644 --- a/crates/bcr-ebill-transport/src/nostr_transport.rs +++ b/crates/bcr-ebill-transport/src/nostr_transport.rs @@ -30,6 +30,13 @@ use log::{debug, error, warn}; use bcr_ebill_api::service::transport_service::{Error, Result}; use bcr_ebill_core::protocol::PostalAddress; +#[derive(borsh::BorshDeserialize)] +struct LegacyBillEventMessage { + event_type: bcr_ebill_core::protocol::event::EventType, + version: String, + data: BillChainEventPayload, +} + /// Transport implementation for Nostr pub struct NostrTransportService { nostr_client: Arc, @@ -147,18 +154,16 @@ impl NostrTransportService { let node = self.get_node_transport(sender); for (node_id, event_to_process) in events.iter() { if let Some(identity) = self.resolve_identity(node_id).await { + let message: EventEnvelope = event_to_process.clone().try_into()?; if let Err(e) = node - .send_private_event(sender, &identity, event_to_process.clone().try_into()?) + .send_private_event(sender, &identity, message.clone()) .await { error!("Failed to send block notification, will add it to retry queue: {e}"); self.queue_retry_message( sender, Some(node_id), - base58::encode( - &borsh::to_vec(event_to_process) - .map_err(|e| Error::Message(e.to_string()))?, - ), + base58::encode(&borsh::to_vec(&message)?), ) .await?; } @@ -287,7 +292,29 @@ impl NostrTransportService { continue; } }; - match borsh::from_slice::(&decoded) { + let message = match borsh::from_slice::(&decoded) { + Ok(message) => Ok(message), + Err(envelope_err) => { + match borsh::from_slice::(&decoded) { + Ok(legacy_event) => match borsh::to_vec(&legacy_event.data) { + Ok(data) => Ok(EventEnvelope { + event_type: legacy_event.event_type, + version: legacy_event.version, + data, + }), + Err(e) => Err(Error::Message(e.to_string())), + }, + Err(legacy_err) => { + error!( + "Failed to deserialize private retry payload as envelope ({envelope_err}) or legacy event ({legacy_err})" + ); + Err(Error::Message(envelope_err.to_string())) + } + } + } + }; + + match message { Ok(message) => { self.send_retry_private_message( &queued_message.sender_id, @@ -296,10 +323,7 @@ impl NostrTransportService { ) .await } - Err(e) => { - error!("Failed to deserialize private retry payload: {e}"); - Err(Error::Message(e.to_string())) - } + Err(e) => Err(e), } } None => { diff --git a/crates/bcr-ebill-transport/src/transport_service.rs b/crates/bcr-ebill-transport/src/transport_service.rs index 12415113..6e4e6b66 100644 --- a/crates/bcr-ebill-transport/src/transport_service.rs +++ b/crates/bcr-ebill-transport/src/transport_service.rs @@ -2444,6 +2444,72 @@ mod tests { assert!(result.is_ok()); } + #[tokio::test] + async fn test_send_retry_private_message_with_legacy_payload_format() { + init_test_cfg(); + + let (service, _) = expect_service( + |mock_transport, mock_contact_store, _, mock_queue, _, _, _, _| { + let recipient = node_id_test_other(); + let message_id = "private_legacy_msg_id"; + let sender = node_id_test(); + + let legacy_event = Event::new_bill(BillChainEventPayload { + event_type: BillEventType::BillMintingRequested, + bill_id: bill_id_test(), + action_type: Some(ActionType::CheckBill), + sum: Some(Sum::new_sat(1).unwrap()), + }); + + let payload = base58::encode(&borsh::to_vec(&legacy_event).unwrap()); + + let queued_message = NostrQueuedMessage { + id: message_id.to_string(), + sender_id: sender.to_owned(), + recipient: Some(recipient.to_owned()), + payload, + }; + + let identity = get_identity_public_data( + &recipient, + &Email::new("test@example.com").unwrap(), + vec![], + ); + + mock_contact_store + .expect_get() + .with(eq(recipient.to_owned())) + .returning(move |_| Ok(Some(as_contact(&identity)))) + .once(); + + mock_transport + .expect_send_private_event() + .returning(|_, _, _| Ok(())) + .once(); + + mock_queue + .expect_get_retry_messages() + .with(eq(1)) + .returning(move |_| Ok(vec![queued_message.clone()])) + .once(); + mock_queue + .expect_get_retry_messages() + .with(eq(1)) + .returning(|_| Ok(vec![])) + .once(); + + mock_queue + .expect_succeed_retry() + .with(eq(message_id)) + .returning(|_| Ok(())) + .once(); + }, + ); + + let result = service.send_retry_messages().await; + assert!(result.is_ok()); + } + #[tokio::test] async fn test_send_retry_private_message_with_invalid_base58() { init_test_cfg(); diff --git a/crates/bcr-ebill-wasm/src/lib.rs b/crates/bcr-ebill-wasm/src/lib.rs index 6543ffc7..9a6df1d2 100644 --- a/crates/bcr-ebill-wasm/src/lib.rs +++ b/crates/bcr-ebill-wasm/src/lib.rs @@ -147,49 +147,21 @@ async fn ensure_transport_contact_data_published(ctx: &Context, default_mint_nod return; } - if let Ok(full_identity) = ctx.identity_service.get_full_identity().await { - match ctx - .transport_service - .contact_transport() - .resolve_contact(&full_identity.identity.node_id) + if let Ok(full_identity) = ctx.identity_service.get_full_identity().await + && let Err(e) = ctx + .identity_service + .publish_contact(&full_identity.identity, &full_identity.key_pair) .await - { - Ok(None) => { - if let Err(e) = ctx - .identity_service - .publish_contact(&full_identity.identity, &full_identity.key_pair) - .await - { - warn!("Could not publish identity details to Nostr: {e}") - } - } - Ok(Some(_)) => (), - Err(e) => { - warn!("Could not resolve personal identity details on Nostr: {e}") - } - } + { + warn!("Could not publish identity details to Nostr: {e}") } if let Ok(companies) = ctx.company_service.get_list_of_companies().await { for c in companies.iter() { if let Ok((company, keys)) = ctx.company_service.get_company_and_keys_by_id(&c.id).await + && let Err(e) = ctx.company_service.publish_contact(&company, &keys).await { - match ctx - .transport_service - .contact_transport() - .resolve_contact(&company.id) - .await - { - Ok(None) => { - if let Err(e) = ctx.company_service.publish_contact(&company, &keys).await { - warn!("Could not publish company details to Nostr: {e}") - } - } - Ok(Some(_)) => (), - Err(e) => { - warn!("Could not resolve company details on Nostr: {e}") - } - } + warn!("Could not publish company details to Nostr: {e}") } } }