@@ -21,7 +21,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
2121use std:: time:: Duration ;
2222use std:: { fmt, ops} ;
2323
24- use fedimint_core:: core:: { ModuleInstanceId , ModuleKind } ;
24+ use fedimint_core:: core:: { ModuleInstanceId , ModuleKind , OperationId } ;
2525use fedimint_core:: db:: {
2626 Database , DatabaseTransaction , IDatabaseTransactionOpsCoreTyped , NonCommittable ,
2727} ;
@@ -45,6 +45,16 @@ use tracing::{debug, trace};
4545pub const DB_KEY_PREFIX_UNORDERED_EVENT_LOG : u8 = 0x3a ;
4646pub const DB_KEY_PREFIX_EVENT_LOG : u8 = 0x39 ;
4747pub const DB_KEY_PREFIX_EVENT_LOG_TRIMABLE : u8 = 0x41 ;
48+ /// Transient bridge, written in the same dbtx as an operation-tagged unordered
49+ /// event: maps the (write-time) [`UnordedEventLogId`] to its `OperationId`.
50+ /// The ordering task consumes it to build
51+ /// [`DB_KEY_PREFIX_EVENT_LOG_BY_OPERATION`] once the final [`EventLogId`] is
52+ /// known, then deletes it.
53+ pub const DB_KEY_PREFIX_UNORDERED_EVENT_OPERATION : u8 = 0x3d ;
54+ /// Secondary index for operation-scoped tailing: `(OperationId, EventLogId)` ->
55+ /// the full [`EventLogEntry`], so `subscribe_operation_events` is a cheap range
56+ /// scan without dereferencing back into the main ordered log.
57+ pub const DB_KEY_PREFIX_EVENT_LOG_BY_OPERATION : u8 = 0x3e ;
4858
4959/// Minimum age in ID count for trimable events to be deleted
5060const TRIMABLE_EVENTLOG_MIN_ID_AGE : u64 = 10_000 ;
@@ -103,7 +113,7 @@ static UNORDEREDED_EVENT_LOG_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
103113/// conflicts due the ID allocation. Instead they are picked based on
104114/// a time and a counter, so they are mostly but not strictly ordered and
105115/// monotonic, and even more importantly: not contiguous.
106- #[ derive( Debug , Encodable , Decodable ) ]
116+ #[ derive( Debug , Clone , Copy , Encodable , Decodable ) ]
107117pub struct UnordedEventLogId {
108118 ts_usecs : u64 ,
109119 counter : u64 ,
@@ -446,6 +456,40 @@ impl_db_lookup!(
446456 query_prefix = UnorderedEventLogIdPrefixAll
447457) ;
448458
459+ /// Bridge key: the write-time [`UnordedEventLogId`] of an operation-tagged
460+ /// event -> its [`OperationId`]. Written by the operation-aware `log_event`,
461+ /// consumed and deleted by the ordering task.
462+ #[ derive( Clone , Debug , Encodable , Decodable ) ]
463+ pub struct UnorderedEventOperationKey ( pub UnordedEventLogId ) ;
464+
465+ impl_db_record ! (
466+ key = UnorderedEventOperationKey ,
467+ value = OperationId ,
468+ db_prefix = DB_KEY_PREFIX_UNORDERED_EVENT_OPERATION ,
469+ ) ;
470+
471+ /// Secondary per-operation index key. Ordered by `(operation, event_id)`, so a
472+ /// range scan over one operation yields its events in event-log order.
473+ #[ derive( Clone , Debug , Encodable , Decodable ) ]
474+ pub struct EventLogByOperationKey {
475+ pub operation : OperationId ,
476+ pub event_id : EventLogId ,
477+ }
478+
479+ #[ derive( Clone , Debug , Encodable , Decodable ) ]
480+ pub struct EventLogByOperationKeyPrefix ( pub OperationId ) ;
481+
482+ impl_db_record ! (
483+ key = EventLogByOperationKey ,
484+ value = EventLogEntry ,
485+ db_prefix = DB_KEY_PREFIX_EVENT_LOG_BY_OPERATION ,
486+ ) ;
487+
488+ impl_db_lookup ! (
489+ key = EventLogByOperationKey ,
490+ query_prefix = EventLogByOperationKeyPrefix
491+ ) ;
492+
449493#[ derive( Clone , Debug , Encodable , Decodable ) ]
450494pub struct EventLogIdPrefixAll ;
451495
@@ -525,6 +569,7 @@ pub trait DBTransactionEventLogExt {
525569 kind : EventKind ,
526570 module_kind : Option < ModuleKind > ,
527571 module_id : Option < ModuleInstanceId > ,
572+ operation : Option < OperationId > ,
528573 payload : Vec < u8 > ,
529574 persist : EventPersistence ,
530575 ) ;
@@ -546,12 +591,18 @@ pub trait DBTransactionEventLogExt {
546591 E :: KIND ,
547592 E :: MODULE ,
548593 module_id,
594+ None ,
549595 serde_json:: to_vec ( & event) . expect ( "Serialization can't fail" ) ,
550596 <E as Event >:: PERSISTENCE ,
551597 )
552598 . await ;
553599 }
554600
601+ /// All events logged for `operation`, in event-log order, read from the
602+ /// per-operation secondary index. Each returned [`PersistedLogEntry`] is
603+ /// keyed by the entry's ordered [`EventLogId`].
604+ async fn get_operation_event_log ( & mut self , operation : OperationId ) -> Vec < PersistedLogEntry > ;
605+
555606 /// Next [`EventLogId`] to use for new ordered events.
556607 ///
557608 /// Used by ordering task, though might be
@@ -586,6 +637,7 @@ where
586637 kind : EventKind ,
587638 module_kind : Option < ModuleKind > ,
588639 module_id : Option < ModuleInstanceId > ,
640+ operation : Option < OperationId > ,
589641 payload : Vec < u8 > ,
590642 persist : EventPersistence ,
591643 ) {
@@ -623,11 +675,30 @@ where
623675 {
624676 panic ! ( "Trying to overwrite event in the client event log" ) ;
625677 }
678+
679+ // Record the operation association so the ordering task can build the
680+ // per-operation secondary index once it assigns the final `EventLogId`.
681+ if let Some ( operation) = operation {
682+ self . insert_entry ( & UnorderedEventOperationKey ( unordered_id) , & operation)
683+ . await ;
684+ }
685+
626686 self . on_commit ( move || {
627687 log_ordering_wakeup_tx. send_replace ( ( ) ) ;
628688 } ) ;
629689 }
630690
691+ async fn get_operation_event_log ( & mut self , operation : OperationId ) -> Vec < PersistedLogEntry > {
692+ self . find_by_prefix ( & EventLogByOperationKeyPrefix ( operation) )
693+ . await
694+ . map ( |( k, v) | PersistedLogEntry {
695+ id : k. event_id ,
696+ inner : v,
697+ } )
698+ . collect ( )
699+ . await
700+ }
701+
631702 async fn get_next_event_log_id ( & mut self ) -> EventLogId {
632703 self . find_by_prefix_sorted_descending ( & EventLogIdPrefixAll )
633704 . await
@@ -747,6 +818,14 @@ pub async fn run_event_log_ordering_task(
747818 dbtx. remove_entry( unordered_id) . await . is_some( ) ,
748819 "Must never fail to remove entry"
749820 ) ;
821+
822+ // Consume the operation bridge (if any) written alongside this event.
823+ // The per-operation index is keyed by the default log's `EventLogId`,
824+ // so it is only built for non-trimable persisted events below.
825+ let operation = dbtx
826+ . remove_entry ( & UnorderedEventOperationKey ( * unordered_id) )
827+ . await ;
828+
750829 if entry. persist ( ) {
751830 // Non-trimable events get persisted in both the default event log
752831 // and trimable event log
@@ -757,6 +836,20 @@ pub async fn run_event_log_ordering_task(
757836 . is_none( ) ,
758837 "Must never overwrite existing event"
759838 ) ;
839+ if let Some ( operation) = operation {
840+ assert ! (
841+ dbtx. insert_entry(
842+ & EventLogByOperationKey {
843+ operation,
844+ event_id: next_entry_id,
845+ } ,
846+ & entry. inner,
847+ )
848+ . await
849+ . is_none( ) ,
850+ "Must never overwrite existing per-operation event"
851+ ) ;
852+ }
760853 trace ! ( target: LOG_CLIENT_EVENT_LOG , ?unordered_id, id=?next_entry_id, "Ordered event log event" ) ;
761854 next_entry_id = next_entry_id. next ( ) ;
762855 }
0 commit comments