1#[cfg(any(test, feature = "test-hooks"))]
2use crate::commit_lock::GroupCommitLock;
3#[cfg(any(test, feature = "test-hooks", target_arch = "wasm32"))]
4pub mod message_clock;
5#[cfg(any(test, feature = "test-utils"))]
6#[doc(hidden)]
7pub mod test_support;
8use crate::change_callbacks::UnstableChangeCallbacks;
9use crate::config::{DeviceSync, DeviceSyncMode, ForkRecoveryOpts};
10use crate::events::InternalEvent;
11use crate::metrics::SyncMetric;
12use crate::policy::StreamPolicy;
13use crate::task_channels::TaskWorkerChannels;
14use crate::version::VersionInfo;
15use crate::worker::metrics::WorkerMetrics;
16use crate::worker::{SyncMetricsSlot, WorkerConfig, WorkerKind};
17use crate::{
18 identity::{Identity, IdentityError},
19 mutex_registry::MutexRegistry,
20};
21use parking_lot::Mutex;
22use std::sync::Arc;
23use std::sync::atomic::{AtomicBool, Ordering};
24use tokio_util::sync::CancellationToken;
25use xmtp_api::{ApiClientWrapper, XmtpApi};
26use xmtp_common::{MaybeSend, MaybeSync};
27use xmtp_db::XmtpDb;
28use xmtp_db::XmtpMlsStorageProvider;
29use xmtp_db::xmtp_openmls_provider::XmtpOpenMlsProviderRef;
30use xmtp_events::EventBus;
31use xmtp_events::EventWriter;
32use xmtp_id::scw_verifier::SmartContractSignatureVerifier;
33use xmtp_id::{InboxIdRef, associations::builder::SignatureRequest};
34use xmtp_mls_server_config::server_configuration::ServerConfigurationHandle;
35use xmtp_proto::types::InstallationId;
36
37#[cfg(any(test, feature = "test-hooks"))]
39#[doc(hidden)]
40pub mod test_hooks {
41 use parking_lot::Mutex;
42
43 static FAIL_NEXT_DELIVERY_RELEASE: Mutex<Option<Vec<u8>>> = Mutex::new(None);
44
45 pub fn fail_next_delivery_release(installation_id: Vec<u8>) {
48 *FAIL_NEXT_DELIVERY_RELEASE.lock() = Some(installation_id);
49 }
50
51 pub(super) fn take_delivery_release_failure(installation_id: &[u8]) -> bool {
52 let mut fail = FAIL_NEXT_DELIVERY_RELEASE.lock();
53 if fail.as_deref() == Some(installation_id) {
54 *fail = None;
55 true
56 } else {
57 false
58 }
59 }
60}
61
62#[derive(Default)]
72pub struct ForegroundCalls {
73 state: Mutex<ForegroundCallState>,
74 idle: tokio::sync::Notify,
75}
76
77#[derive(Default)]
78struct ForegroundCallState {
79 closed: bool,
80 running: usize,
81}
82
83#[must_use = "the call leaves the gate when this guard drops"]
85pub struct ForegroundCall {
86 calls: Arc<ForegroundCalls>,
87}
88
89impl ForegroundCalls {
90 pub fn enter(self: &Arc<Self>) -> Option<ForegroundCall> {
92 let mut state = self.state.lock();
93 if state.closed {
94 return None;
95 }
96 state.running += 1;
97 Some(ForegroundCall {
98 calls: self.clone(),
99 })
100 }
101
102 pub async fn close_and_wait(&self) {
104 self.state.lock().closed = true;
105 loop {
106 let idle = self.idle.notified();
107 futures::pin_mut!(idle);
108 idle.as_mut().enable();
109 if self.state.lock().running == 0 {
110 return;
111 }
112 idle.await;
113 }
114 }
115}
116
117impl Drop for ForegroundCall {
118 fn drop(&mut self) {
119 let mut state = self.calls.state.lock();
120 state.running -= 1;
121 if state.running == 0 {
122 self.calls.idle.notify_waiters();
123 }
124 }
125}
126
127#[derive(Clone, Default)]
132pub struct HookState {
133 #[cfg(any(test, feature = "test-hooks"))]
135 pub mls_commit_lock: Arc<GroupCommitLock>,
136 #[cfg(any(test, feature = "test-hooks", target_arch = "wasm32"))]
138 pub message_clock: Arc<message_clock::MessageClock>,
139}
140
141pub struct LocalContext<ApiClient, Db, S, F> {
147 pub identity: Identity,
149 pub api_client: ApiClientWrapper<ApiClient>,
151 pub store: Db,
153 pub mls_storage: S,
155 pub mutexes: MutexRegistry,
157 pub hooks: HookState,
159 pub version_info: VersionInfo,
161 pub server_configuration: ServerConfigurationHandle,
164 pub events: EventBus<InternalEvent>,
166 pub public_event_writer: Arc<dyn EventWriter<()>>,
168 pub registration_event_pending: Arc<AtomicBool>,
170 pub delivery_owner: Arc<Mutex<Option<xmtp_db::delivery::DeliveryOwner>>>,
172 pub retired_delivery_owner: Arc<Mutex<Option<xmtp_db::delivery::DeliveryOwner>>>,
175 pub scw_verifier: Arc<Box<dyn SmartContractSignatureVerifier>>,
177 pub device_sync: DeviceSync,
179 pub fork_recovery_opts: ForkRecoveryOpts,
181 pub change_callbacks: UnstableChangeCallbacks,
184 pub stream_policy: StreamPolicy,
186 pub worker_config: WorkerConfig,
188 pub worker_metrics: SyncMetricsSlot,
191 pub task_channels: TaskWorkerChannels,
193 pub cancellation_token: CancellationToken,
195 pub shutdown_complete: Arc<AtomicBool>,
202 pub foreground_calls: Arc<ForegroundCalls>,
204 pub features: F,
206}
207
208impl<ApiClient, Db, S, F> LocalContext<ApiClient, Db, S, F>
209where
210 Db: XmtpDb + 'static,
211 ApiClient: XmtpApi + 'static,
212 S: XmtpMlsStorageProvider + 'static,
213{
214 pub fn db(&self) -> Db::DbQuery {
217 self.store.db()
218 }
219
220 pub fn mls_provider(&'_ self) -> XmtpOpenMlsProviderRef<'_, S> {
222 XmtpOpenMlsProviderRef::new(&self.mls_storage)
223 }
224
225 pub fn store(&self) -> &Db {
226 &self.store
227 }
228
229 pub fn scw_verifier(&self) -> &Arc<Box<dyn SmartContractSignatureVerifier>> {
230 &self.scw_verifier
231 }
232
233 pub fn device_sync_worker_enabled(&self) -> bool {
234 !matches!(self.device_sync.mode, DeviceSyncMode::Disabled)
235 }
236}
237
238impl<ApiClient, Db, S, F> LocalContext<ApiClient, Db, S, F> {
239 pub fn replace_mls_store<S2>(self, mls_store: S2) -> LocalContext<ApiClient, Db, S2, F> {
240 LocalContext::<ApiClient, Db, S2, F> {
241 identity: self.identity,
242 api_client: self.api_client,
243 store: self.store,
244 mls_storage: mls_store,
245 mutexes: self.mutexes,
246 hooks: self.hooks,
247 version_info: self.version_info,
248 server_configuration: self.server_configuration,
249 events: self.events,
250 public_event_writer: self.public_event_writer,
251 registration_event_pending: self.registration_event_pending,
252 delivery_owner: self.delivery_owner,
253 retired_delivery_owner: self.retired_delivery_owner,
254 scw_verifier: self.scw_verifier,
255 device_sync: self.device_sync,
256 fork_recovery_opts: self.fork_recovery_opts,
257 change_callbacks: self.change_callbacks,
258 stream_policy: self.stream_policy,
259 worker_config: self.worker_config,
260 worker_metrics: self.worker_metrics,
261 task_channels: self.task_channels,
262 cancellation_token: self.cancellation_token,
263 shutdown_complete: self.shutdown_complete,
264 foreground_calls: self.foreground_calls,
265 features: self.features,
266 }
267 }
268}
269
270impl<ApiClient, Db, S, F> LocalContext<ApiClient, Db, S, F> {
271 pub fn installation_public_key(&self) -> InstallationId {
273 (*self.identity.installation_keys.public_bytes()).into()
274 }
275
276 pub fn installation_id(&self) -> InstallationId {
278 self.identity.installation_id()
279 }
280
281 pub fn inbox_id(&self) -> InboxIdRef<'_> {
283 self.identity.inbox_id()
284 }
285
286 pub fn signature_request(&self) -> Option<SignatureRequest> {
289 self.identity.signature_request()
290 }
291
292 pub fn sign_with_public_context(
293 &self,
294 text: impl AsRef<str>,
295 ) -> Result<Vec<u8>, IdentityError> {
296 self.identity.sign_with_public_context(text)
297 }
298
299 #[cfg(any(test, feature = "test-hooks"))]
300 pub fn mls_commit_lock(&self) -> &Arc<GroupCommitLock> {
301 &self.hooks.mls_commit_lock
302 }
303
304 #[cfg(any(test, feature = "test-hooks"))]
307 pub fn freeze_message_clock(&self, wall_ns: i64) {
308 self.hooks.message_clock.freeze(wall_ns);
309 }
310
311 pub fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>> {
312 self.worker_metrics.lock().clone()
313 }
314
315 pub fn cancellation_token(&self) -> &CancellationToken {
316 &self.cancellation_token
317 }
318
319 pub fn shutdown_complete(&self) -> bool {
320 self.shutdown_complete.load(Ordering::Acquire)
321 }
322
323 pub fn mark_shutdown_complete(&self) {
324 self.shutdown_complete.store(true, Ordering::Release);
325 }
326}
327
328pub trait CoreContext
332where
333 Self: MaybeSend + MaybeSync + Sized + Clone,
334{
335 type Db: XmtpDb;
336 type ApiClient: XmtpApi;
337 type MlsStorage: XmtpMlsStorageProvider;
338 type Features: MaybeSend + MaybeSync;
339 type ContextReference: CoreContext<
341 Db = Self::Db,
342 ApiClient = Self::ApiClient,
343 MlsStorage = Self::MlsStorage,
344 Features = Self::Features,
345 > + 'static;
346
347 fn context_ref(&self) -> &Self::ContextReference;
349 fn features(&self) -> &Self::Features;
350 fn db(&self) -> <Self::Db as XmtpDb>::DbQuery;
351 fn api(&self) -> &ApiClientWrapper<Self::ApiClient>;
352 fn scw_verifier(&self) -> Arc<Box<dyn SmartContractSignatureVerifier>>;
353
354 fn device_sync(&self) -> &DeviceSync;
355
356 fn device_sync_worker_enabled(&self) -> bool {
357 !matches!(self.device_sync().mode, DeviceSyncMode::Disabled)
358 }
359
360 fn fork_recovery_opts(&self) -> &ForkRecoveryOpts;
361
362 fn worker_config(&self) -> &WorkerConfig;
363
364 fn worker_interval(
367 &self,
368 kind: WorkerKind,
369 const_default: std::time::Duration,
370 ) -> (std::time::Duration, std::time::Duration) {
371 self.worker_config().interval(kind, const_default)
372 }
373
374 fn mls_provider(&'_ self) -> XmtpOpenMlsProviderRef<'_, Self::MlsStorage> {
376 XmtpOpenMlsProviderRef::new(self.mls_storage())
377 }
378
379 fn mls_storage(&self) -> &Self::MlsStorage;
380 fn identity(&self) -> &Identity;
381
382 fn signature_request(&self) -> Option<SignatureRequest> {
383 self.identity().signature_request()
384 }
385
386 fn inbox_id(&self) -> InboxIdRef<'_> {
387 self.identity().inbox_id()
388 }
389
390 fn installation_id(&self) -> InstallationId {
391 (*self.identity().installation_keys.public_bytes()).into()
392 }
393
394 fn version_info(&self) -> &VersionInfo;
395 fn server_configuration(&self) -> &ServerConfigurationHandle;
397 fn events(&self) -> &EventBus<InternalEvent>;
398 fn registration_event_pending(&self) -> &AtomicBool;
399 fn public_event_writer(&self) -> &Arc<dyn EventWriter<()>>;
400 fn delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>>;
402 fn retired_delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>>;
406
407 fn close_message_delivery(&self) -> Result<(), xmtp_db::StorageError> {
409 use xmtp_db::delivery::QueryDelivery;
410 #[cfg(any(test, feature = "test-hooks"))]
411 if test_hooks::take_delivery_release_failure(&self.installation_id().to_vec()) {
412 return Err(xmtp_db::StorageError::DbDeserialize);
413 }
414 let mut registered = self.delivery_owner().lock();
415 if let Some(owner) = *registered {
416 self.db().release_delivery_owner(owner)?;
417 *registered = None;
418 }
419 Ok(())
420 }
421 fn task_channels(&self) -> &TaskWorkerChannels;
422 fn change_callbacks(&self) -> &UnstableChangeCallbacks;
424 fn stream_policy(&self) -> &StreamPolicy;
426 fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>>;
427 #[cfg(any(test, feature = "test-hooks"))]
428 fn mls_commit_lock(&self) -> &Arc<GroupCommitLock>;
429 fn mutexes(&self) -> &MutexRegistry;
430 fn cancellation_token(&self) -> &CancellationToken;
431
432 fn is_closed(&self) -> bool {
434 self.cancellation_token().is_cancelled()
435 }
436
437 fn shutdown_complete(&self) -> bool;
442
443 fn mark_shutdown_complete(&self);
444
445 fn foreground_calls(&self) -> &Arc<ForegroundCalls>;
447
448 fn message_timestamp_ns(&self) -> i64;
450}
451
452impl<XApiClient, XDb, XMls, F> CoreContext for Arc<LocalContext<XApiClient, XDb, XMls, F>>
453where
454 XApiClient: XmtpApi + 'static,
455 XDb: XmtpDb + 'static,
456 XMls: XmtpMlsStorageProvider + 'static,
457 F: MaybeSend + MaybeSync + 'static,
458{
459 type Db = XDb;
460 type ApiClient = XApiClient;
461 type MlsStorage = XMls;
462 type Features = F;
463 type ContextReference = Self;
464
465 fn context_ref(&self) -> &Self::ContextReference {
466 self
467 }
468
469 fn features(&self) -> &F {
470 &self.features
471 }
472
473 fn db(&self) -> <Self::Db as XmtpDb>::DbQuery {
474 self.store.db()
475 }
476
477 fn api(&self) -> &ApiClientWrapper<Self::ApiClient> {
478 &self.api_client
479 }
480
481 fn scw_verifier(&self) -> Arc<Box<dyn SmartContractSignatureVerifier>> {
482 self.scw_verifier.clone()
483 }
484
485 fn device_sync(&self) -> &DeviceSync {
486 &self.device_sync
487 }
488
489 fn fork_recovery_opts(&self) -> &ForkRecoveryOpts {
490 &self.fork_recovery_opts
491 }
492
493 fn worker_config(&self) -> &WorkerConfig {
494 &self.worker_config
495 }
496
497 fn mls_storage(&self) -> &Self::MlsStorage {
500 &self.mls_storage
501 }
502
503 fn identity(&self) -> &Identity {
504 &self.identity
505 }
506
507 fn version_info(&self) -> &VersionInfo {
508 &self.version_info
509 }
510
511 fn server_configuration(&self) -> &ServerConfigurationHandle {
512 &self.server_configuration
513 }
514
515 fn events(&self) -> &EventBus<InternalEvent> {
516 &self.events
517 }
518
519 fn registration_event_pending(&self) -> &AtomicBool {
520 &self.registration_event_pending
521 }
522
523 fn public_event_writer(&self) -> &Arc<dyn EventWriter<()>> {
524 &self.public_event_writer
525 }
526
527 fn delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>> {
528 &self.delivery_owner
529 }
530
531 fn retired_delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>> {
532 &self.retired_delivery_owner
533 }
534
535 #[cfg(any(test, feature = "test-hooks"))]
536 fn mls_commit_lock(&self) -> &Arc<GroupCommitLock> {
537 &self.hooks.mls_commit_lock
538 }
539
540 fn task_channels(&self) -> &TaskWorkerChannels {
541 &self.task_channels
542 }
543
544 fn change_callbacks(&self) -> &UnstableChangeCallbacks {
545 &self.change_callbacks
546 }
547
548 fn stream_policy(&self) -> &StreamPolicy {
549 &self.stream_policy
550 }
551
552 fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>> {
553 self.worker_metrics.lock().clone()
554 }
555
556 fn mutexes(&self) -> &MutexRegistry {
557 &self.mutexes
558 }
559
560 fn cancellation_token(&self) -> &CancellationToken {
561 &self.cancellation_token
562 }
563
564 fn shutdown_complete(&self) -> bool {
565 self.shutdown_complete.load(Ordering::Acquire)
566 }
567
568 fn mark_shutdown_complete(&self) {
569 self.shutdown_complete.store(true, Ordering::Release);
570 }
571
572 fn foreground_calls(&self) -> &Arc<ForegroundCalls> {
573 &self.foreground_calls
574 }
575
576 fn message_timestamp_ns(&self) -> i64 {
577 #[cfg(target_arch = "wasm32")]
578 {
579 self.hooks.message_clock.now_ns()
580 }
581
582 #[cfg(not(target_arch = "wasm32"))]
583 {
584 #[cfg(any(test, feature = "test-hooks"))]
585 if self.hooks.message_clock.is_frozen() {
586 return self.hooks.message_clock.now_ns();
587 }
588 xmtp_common::time::now_ns()
589 }
590 }
591}
592
593impl<T> CoreContext for &T
594where
595 T: CoreContext,
596{
597 type Db = <T as CoreContext>::Db;
598 type ApiClient = <T as CoreContext>::ApiClient;
599 type MlsStorage = <T as CoreContext>::MlsStorage;
600 type Features = <T as CoreContext>::Features;
601 type ContextReference = <T as CoreContext>::ContextReference;
602
603 fn context_ref(&self) -> &Self::ContextReference {
604 <T as CoreContext>::context_ref(self)
605 }
606
607 fn features(&self) -> &Self::Features {
608 <T as CoreContext>::features(self)
609 }
610
611 fn db(&self) -> <Self::Db as XmtpDb>::DbQuery {
612 <T as CoreContext>::db(self)
613 }
614
615 fn api(&self) -> &ApiClientWrapper<Self::ApiClient> {
616 <T as CoreContext>::api(self)
617 }
618
619 fn scw_verifier(&self) -> Arc<Box<dyn SmartContractSignatureVerifier>> {
620 <T as CoreContext>::scw_verifier(self)
621 }
622
623 fn device_sync(&self) -> &DeviceSync {
624 <T as CoreContext>::device_sync(self)
625 }
626
627 fn device_sync_worker_enabled(&self) -> bool {
628 <T as CoreContext>::device_sync_worker_enabled(self)
629 }
630
631 fn fork_recovery_opts(&self) -> &ForkRecoveryOpts {
632 <T as CoreContext>::fork_recovery_opts(self)
633 }
634
635 fn worker_config(&self) -> &WorkerConfig {
636 <T as CoreContext>::worker_config(self)
637 }
638
639 fn mls_storage(&self) -> &Self::MlsStorage {
640 <T as CoreContext>::mls_storage(self)
641 }
642
643 fn identity(&self) -> &Identity {
644 <T as CoreContext>::identity(self)
645 }
646
647 fn version_info(&self) -> &VersionInfo {
648 <T as CoreContext>::version_info(self)
649 }
650
651 fn server_configuration(&self) -> &ServerConfigurationHandle {
652 <T as CoreContext>::server_configuration(self)
653 }
654
655 fn events(&self) -> &EventBus<InternalEvent> {
656 <T as CoreContext>::events(self)
657 }
658
659 fn registration_event_pending(&self) -> &AtomicBool {
660 <T as CoreContext>::registration_event_pending(self)
661 }
662
663 fn public_event_writer(&self) -> &Arc<dyn EventWriter<()>> {
664 <T as CoreContext>::public_event_writer(self)
665 }
666
667 fn delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>> {
668 <T as CoreContext>::delivery_owner(self)
669 }
670
671 fn retired_delivery_owner(&self) -> &Mutex<Option<xmtp_db::delivery::DeliveryOwner>> {
672 <T as CoreContext>::retired_delivery_owner(self)
673 }
674
675 #[cfg(any(test, feature = "test-hooks"))]
676 fn mls_commit_lock(&self) -> &Arc<GroupCommitLock> {
677 <T as CoreContext>::mls_commit_lock(self)
678 }
679
680 fn task_channels(&self) -> &TaskWorkerChannels {
681 <T as CoreContext>::task_channels(self)
682 }
683
684 fn change_callbacks(&self) -> &UnstableChangeCallbacks {
685 <T as CoreContext>::change_callbacks(self)
686 }
687
688 fn stream_policy(&self) -> &StreamPolicy {
689 <T as CoreContext>::stream_policy(self)
690 }
691
692 fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>> {
693 <T as CoreContext>::sync_metrics(self)
694 }
695
696 fn mutexes(&self) -> &MutexRegistry {
697 <T as CoreContext>::mutexes(self)
698 }
699
700 fn cancellation_token(&self) -> &CancellationToken {
701 <T as CoreContext>::cancellation_token(self)
702 }
703
704 fn shutdown_complete(&self) -> bool {
705 <T as CoreContext>::shutdown_complete(self)
706 }
707
708 fn mark_shutdown_complete(&self) {
709 <T as CoreContext>::mark_shutdown_complete(self)
710 }
711
712 fn foreground_calls(&self) -> &Arc<ForegroundCalls> {
713 <T as CoreContext>::foreground_calls(self)
714 }
715
716 fn message_timestamp_ns(&self) -> i64 {
717 <T as CoreContext>::message_timestamp_ns(self)
718 }
719}