Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions crates/bcr-ebill-api/src/service/company_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ use bitcoin::base58;
use log::{debug, error, info};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tokio_with_wasm::alias as tokio;
use uuid::Uuid;

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
Expand Down Expand Up @@ -1595,6 +1596,37 @@ impl CompanyServiceApi for CompanyService {
.add_identity(id, &company_keys)
.await?;

// Ensure all company signatories and the company itself are in our nostr contacts
// so that we can receive and decrypt messages from them.
for signatory in company.signatories.iter() {
if signatory.node_id != our_node_id {
self.transport_service
.contact_transport()
.ensure_nostr_contact(&signatory.node_id)
.await;
}
}
self.transport_service
.contact_transport()
.ensure_nostr_contact(id)
.await;

// Process any historical bill invites that were sent to this company
// before we joined, so we can construct those bills in our store.
let transport = self.transport_service.clone();
let company_id = id.clone();
tokio::spawn(async move {
if let Err(e) = transport
.process_company_historical_bill_invites(&company_id)
.await
{
error!(
"Failed to process historical bill invites for company {}: {}",
company_id, e
);
}
});

// company block
let previous_block = company_chain.get_latest_block();
let new_block = CompanyBlock::create_block_for_accept_signatory_invite(
Expand Down Expand Up @@ -2975,6 +3007,7 @@ pub mod tests {

#[tokio::test]
async fn accept_company_invite_baseline() {
crate::tests::tests::init_test_cfg();
let (
mut storage,
file_upload_store,
Expand Down Expand Up @@ -3050,6 +3083,13 @@ pub mod tests {
.expect_add_identity()
.returning(|_, _| Ok(()))
.times(1);
transport.expect_on_contact_transport(|t| {
t.expect_ensure_nostr_contact().returning(|_| ()).times(1);
});
transport
.expect_process_company_historical_bill_invites()
.returning(|_| Ok(()))
.times(1);

let service = get_service(
storage,
Expand Down
1 change: 1 addition & 0 deletions crates/bcr-ebill-api/src/service/file_reference_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ mod tests {
async fn sync_relays(&self) -> crate::service::transport_service::Result<()>;
async fn retry_failed_syncs(&self) -> crate::service::transport_service::Result<()>;
async fn add_identity(&self, node_id: &bcr_common::core::NodeId, keys: &bcr_ebill_core::protocol::crypto::BcrKeys) -> crate::service::transport_service::Result<()>;
async fn process_company_historical_bill_invites(&self, company_id: &bcr_common::core::NodeId) -> crate::service::transport_service::Result<()>;
async fn publish_file_metadata(&self, node_id: &bcr_common::core::NodeId, plaintext_hash: &str, encrypted_hash: &str, server_urls: Vec<url::Url>, mime_type: Option<String>) -> crate::service::transport_service::Result<()>;
async fn query_file_metadata_events(&self, file_hash: &str, nostr_hash: &str) -> crate::service::transport_service::Result<Vec<nostr::Event>>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub trait TransportServiceApi: ServiceTraitBounds {
/// This will also add a subscription for direct messages to this identity.
async fn add_identity(&self, node_id: &NodeId, keys: &BcrKeys) -> Result<()>;

/// Queries historical private messages for a company identity and processes any
/// bill chain invites found, constructing the bills in the local store.
async fn process_company_historical_bill_invites(&self, company_id: &NodeId) -> Result<()>;

/// Publishes file metadata (kind:1063) for the specified file.
/// This is idempotent - it will only publish if the server URLs have changed.
async fn publish_file_metadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@ pub trait TransportClientApi: ServiceTraitBounds {
) -> Result<Vec<Event>>;
/// Adds a new Nostr subscription on the primary client for an added contact
async fn add_contact_subscription(&self, contact: &NodeId) -> Result<()>;
/// Resolves all private messages matching the filter
/// Resolves private messages matching the filter for all local identities.
async fn resolve_private_events(&self, filter: Filter) -> Result<Vec<Event>>;

/// Resolves events matching the given filter. The filter is passed through as-is.
async fn resolve_events(&self, filter: Filter) -> Result<Vec<Event>>;

/// Tries to decrypt a private direct message event with any of the local signers.
/// Returns the recipient NodeId, decrypted EventEnvelope, and sender public key if successful.
async fn try_decrypt_private_event(
&self,
event: &Event,
) -> Result<Option<(NodeId, EventEnvelope, nostr::PublicKey)>>;

/// Publishes the metadata (contact info) via the Nostr client for the specified identity
async fn publish_metadata(
&self,
Expand Down
1 change: 1 addition & 0 deletions crates/bcr-ebill-transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub async fn create_transport_service(
notification_transport,
contact_transport,
block_transport,
bill_invite_handler,
)))
}

Expand Down
40 changes: 38 additions & 2 deletions crates/bcr-ebill-transport/src/nostr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub enum SortOrder {

const BLOSSOM_SERVER_LIST_KIND: Kind = Kind::Custom(10063);
const FILE_METADATA_KIND: Kind = Kind::Custom(1063);
const DM_BACKFILL_LIMIT: usize = 1000;

/// Check the output of sending an event to Nostr relays.
/// Logs warnings for individual relay failures and returns an error if no relay
Expand Down Expand Up @@ -649,6 +650,32 @@ impl TransportClientApi for NostrClient {
.await?)
}

async fn resolve_events(&self, filter: Filter) -> Result<Vec<nostr::event::Event>> {
Ok(self
.fetch_events(filter, Some(SortOrder::Asc), None)
.await?)
}

async fn try_decrypt_private_event(
&self,
event: &nostr::event::Event,
) -> Result<Option<(NodeId, EventEnvelope, nostr::PublicKey)>> {
match event.kind {
Kind::EncryptedDirectMessage | Kind::GiftWrap => {
let keys_to_try = prioritized_signers_for_event(event, &self.signers);
for (node_id, nostr_keys) in keys_to_try {
if let Some((envelope, sender, _, _)) =
unwrap_direct_message(event, &*nostr_keys).await
{
return Ok(Some((node_id, envelope, sender)));
}
}
Ok(None)
}
_ => Ok(None),
}
}

async fn publish_metadata(&self, node_id: &NodeId, data: &Metadata) -> Result<()> {
// Get the signer for this identity
let signer = self.get_signer(node_id)?;
Expand Down Expand Up @@ -850,8 +877,17 @@ impl TransportClientApi for NostrClient {
vec![Kind::GiftWrap]
};
debug!("Adding subscription for direct messages to identity: {node_id}");
self.subscribe(Filter::new().pubkey(node_id.npub()).kinds(kinds))
self.subscribe(
Filter::new()
.pubkey(node_id.npub())
.kinds(kinds)
.limit(DM_BACKFILL_LIMIT),
)
.await?;
debug!("Adding subscription for public blocks messages from identity: {node_id}");
self.subscribe(Filter::new().author(node_id.npub()).kind(Kind::TextNote))
.await?;

let relay_urls: Vec<RelayUrl> = self
.relays
.iter()
Expand Down Expand Up @@ -1042,7 +1078,7 @@ impl NostrConsumer {
Filter::new()
.pubkeys(local_pubkeys.clone())
.kinds(vec![Kind::EncryptedDirectMessage, Kind::GiftWrap])
.limit(1000),
.limit(DM_BACKFILL_LIMIT),
)
.await
.map_err(|e| {
Expand Down
2 changes: 2 additions & 0 deletions crates/bcr-ebill-transport/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ mockall::mock! {
async fn resolve_public_chain(&self, id: &str, chain_type: BlockchainType) -> Result<Vec<nostr::event::Event>>;
async fn add_contact_subscription(&self, contact: &NodeId) -> Result<()>;
async fn resolve_private_events(&self, filter: nostr::Filter) -> Result<Vec<nostr::event::Event>>;
async fn resolve_events(&self, filter: nostr::Filter) -> Result<Vec<nostr::event::Event>>;
async fn try_decrypt_private_event(&self, event: &nostr::event::Event) -> Result<Option<(NodeId, EventEnvelope, nostr::PublicKey)>>;
async fn publish_metadata(&self, node_id: &NodeId, data: &nostr::nips::nip01::Metadata) -> Result<()>;
async fn publish_relay_list(&self, node_id: &NodeId, relays: Vec<nostr::types::RelayUrl>) -> Result<()>;
async fn publish_blossom_server_list(&self, node_id: &NodeId, blossom_servers: Vec<url::Url>) -> Result<()>;
Expand Down
Loading
Loading