Skip to main content

kcode_k1_txn_ordering/
lib.rs

1use kcode_k1_canonical_chain::{CanonicalChain, CommitOutcome};
2pub use kcode_k1_canonical_chain::{SubmitError, TxId};
3use kcode_k1_transaction::Transaction;
4pub use kcode_k1_transaction::{GENESIS_PARENT, SubsystemId};
5use std::collections::HashMap;
6use std::panic::{AssertUnwindSafe, catch_unwind};
7use std::path::Path;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::{Arc, Condvar, Mutex, MutexGuard};
10use std::thread;
11use std::time::{Duration, Instant};
12
13pub trait Subsystem: Send + Sync + 'static {
14    fn submit_txn(&self, id: TxId, payload: &[u8]) -> Result<(), String>;
15    fn reorg(&self) -> Result<(), String>;
16}
17
18pub struct K1TxnOrdering {
19    writer: Mutex<WriterState>,
20    chain: Mutex<CanonicalChain>,
21}
22
23struct WriterState {
24    registrations: HashMap<SubsystemId, Registration>,
25}
26
27struct Registration {
28    lane: Arc<Lane>,
29    latest: Option<TxId>,
30    next_ticket: u64,
31    mode: RegistrationMode,
32}
33
34#[derive(Clone, Copy, Eq, PartialEq)]
35enum RegistrationMode {
36    Replaying,
37    Active,
38    OutOfCommission,
39}
40
41struct Lane {
42    handler: Arc<dyn Subsystem>,
43    progress: Mutex<LaneProgress>,
44    changed: Condvar,
45    running: AtomicBool,
46    faulted: AtomicBool,
47}
48
49struct LaneProgress {
50    serving: u64,
51    fault: Option<String>,
52}
53
54struct Reservation {
55    lane: Arc<Lane>,
56    ticket: u64,
57}
58
59enum CommittedWork {
60    Duplicate,
61    Extension {
62        id: TxId,
63        delivery: Option<Reservation>,
64    },
65    Reorganization {
66        id: TxId,
67        reorgs: Vec<Reservation>,
68        replacement: Option<Reservation>,
69    },
70}
71
72impl K1TxnOrdering {
73    pub fn open(root: &Path) -> Result<Self, String> {
74        let started = Instant::now();
75        let result = CanonicalChain::open(root);
76        let elapsed = started.elapsed();
77        if elapsed > Duration::from_millis(100) {
78            eprintln!(
79                "{{\"module\":\"kcode-k1-txn-ordering\",\"operation\":\"open\",\"elapsed_microseconds\":{},\"outcome\":\"{}\"}}",
80                elapsed.as_micros(),
81                if result.is_ok() { "ready" } else { "error" }
82            );
83        }
84        result.map(|chain| Self {
85            writer: Mutex::new(WriterState {
86                registrations: HashMap::new(),
87            }),
88            chain: Mutex::new(chain),
89        })
90    }
91
92    pub fn register_subsystem(
93        &self,
94        subsystem: SubsystemId,
95        after: Option<TxId>,
96        handler: Arc<dyn Subsystem>,
97    ) -> Result<(), String> {
98        let (mut cursor, lane) = {
99            let mut writer = self.lock_writer();
100            if let Some(registration) = writer.registrations.get(&subsystem) {
101                ensure_replaceable(registration)?;
102            }
103            let chain = self.lock_chain();
104            let cursor = chain.replay_cursor(subsystem, after)?;
105            let lane = Arc::new(Lane::new(handler));
106            writer.registrations.insert(
107                subsystem,
108                Registration {
109                    lane: lane.clone(),
110                    latest: after,
111                    next_ticket: 0,
112                    mode: RegistrationMode::Replaying,
113                },
114            );
115            (cursor, lane)
116        };
117        let mut latest = after;
118        loop {
119            let mut next = match self.lock_chain().replay_next(&mut cursor) {
120                Ok(next) => next,
121                Err(message) => return Err(fault_cursor(&lane, message)),
122            };
123            if next.is_none() {
124                let final_result = {
125                    let mut writer = self.lock_writer();
126                    let chain = self.lock_chain();
127                    match chain.replay_next(&mut cursor) {
128                        Ok(None) => {
129                            let registration = writer
130                                .registrations
131                                .get_mut(&subsystem)
132                                .expect("replaying registration exists");
133                            if !Arc::ptr_eq(&registration.lane, &lane) {
134                                return Err("registration changed during replay".to_owned());
135                            }
136                            registration.latest = latest;
137                            registration.mode = RegistrationMode::Active;
138                            return Ok(());
139                        }
140                        result => result,
141                    }
142                };
143                next = match final_result {
144                    Ok(next) => next,
145                    Err(message) => return Err(fault_cursor(&lane, message)),
146                };
147            }
148            let replayed = next.expect("replay result contains a transaction");
149            let transaction = match Transaction::parse(&replayed.bytes) {
150                Ok(transaction) => transaction,
151                Err(message) => {
152                    let failure = committed_error(
153                        replayed.id,
154                        vec![format!(
155                            "canonical replay transaction was invalid: {message}"
156                        )],
157                    );
158                    lane.record_fault(failure.clone());
159                    return Err(failure);
160                }
161            };
162            if let Err(message) = lane.run_replay(replayed.id, transaction.payload()) {
163                return Err(committed_error(replayed.id, vec![message]));
164            }
165            latest = Some(replayed.id);
166        }
167    }
168
169    pub fn submit_txn(&self, transaction: &[u8]) -> Result<(), SubmitError> {
170        let parsed = Transaction::parse(transaction)
171            .map_err(|message| SubmitError::Other(format!("invalid transaction: {message}")))?;
172        let payload = parsed.payload();
173        let work = {
174            let mut writer = self.lock_writer();
175            let mut chain = self.lock_chain();
176            match chain.submit_validated(transaction)? {
177                CommitOutcome::Duplicate => CommittedWork::Duplicate,
178                CommitOutcome::Extension { id, subsystem } => CommittedWork::Extension {
179                    id,
180                    delivery: reserve_active_delivery(&mut writer, subsystem, id),
181                },
182                CommitOutcome::Reorganization { id, subsystem } => {
183                    let affected: Vec<SubsystemId> = writer
184                        .registrations
185                        .iter()
186                        .filter_map(|(&registered, registration)| {
187                            (registration.is_active()
188                                && registration
189                                    .latest
190                                    .is_some_and(|latest| !chain.contains(latest)))
191                            .then_some(registered)
192                        })
193                        .collect();
194                    let mut reorgs = Vec::with_capacity(affected.len());
195                    for affected_subsystem in affected {
196                        let registration = writer
197                            .registrations
198                            .get_mut(&affected_subsystem)
199                            .expect("affected registration exists");
200                        reorgs.push(registration.reserve_reorg());
201                        registration.mode = RegistrationMode::OutOfCommission;
202                    }
203                    let replacement = reserve_active_delivery(&mut writer, subsystem, id);
204                    CommittedWork::Reorganization {
205                        id,
206                        reorgs,
207                        replacement,
208                    }
209                }
210            }
211        };
212        match work {
213            CommittedWork::Duplicate => Ok(()),
214            CommittedWork::Extension { id, delivery } => {
215                let Some(delivery) = delivery else {
216                    return Ok(());
217                };
218                delivery
219                    .run_delivery(id, payload)
220                    .map_err(|message| SubmitError::Other(committed_error(id, vec![message])))
221            }
222            CommittedWork::Reorganization {
223                id,
224                reorgs,
225                replacement,
226            } => {
227                let failures = run_reorganization(id, payload, reorgs, replacement);
228                if failures.is_empty() {
229                    Ok(())
230                } else {
231                    Err(SubmitError::Other(committed_error(id, failures)))
232                }
233            }
234        }
235    }
236
237    pub fn submit_local_txn<F, Q>(
238        &self,
239        timestamp: u64,
240        creator: [u8; 32],
241        subsystem: SubsystemId,
242        payload: &[u8],
243        signer: F,
244        queue_propagation: Q,
245    ) -> Result<Vec<u8>, String>
246    where
247        F: FnOnce(&[u8]) -> Result<[u8; 64], String>,
248        Q: FnOnce(&[u8]) -> Result<(), String>,
249    {
250        let (bytes, id, delivery) = {
251            let mut writer = self.lock_writer();
252            let lane = writer
253                .registrations
254                .get(&subsystem)
255                .filter(|registration| registration.is_active())
256                .map(|registration| registration.lane.clone())
257                .ok_or_else(|| "target subsystem is not registered and active".to_owned())?;
258            let mut chain = self.lock_chain();
259            let bytes =
260                chain.submit_local(timestamp, creator, subsystem, payload, move |prefix| {
261                    match catch_unwind(AssertUnwindSafe(|| signer(prefix))) {
262                        Ok(result) => result,
263                        Err(_) => Err("signer panicked".to_owned()),
264                    }
265                })?;
266            let id = TxId::for_transaction(&bytes);
267            let registration = writer
268                .registrations
269                .get_mut(&subsystem)
270                .expect("prechecked registration exists");
271            if !Arc::ptr_eq(&registration.lane, &lane) {
272                return Err(committed_error(
273                    id,
274                    vec!["target registration changed during local commit".to_owned()],
275                ));
276            }
277            let delivery = registration.reserve_delivery(id);
278            (bytes, id, delivery)
279        };
280        let mut failures = Vec::new();
281        match catch_unwind(AssertUnwindSafe(|| queue_propagation(&bytes))) {
282            Ok(Ok(())) => {}
283            Ok(Err(message)) => failures.push(format!("queue propagation failed: {message}")),
284            Err(_) => failures.push("queue propagation panicked".to_owned()),
285        }
286        if let Err(message) = delivery.run_delivery(id, payload) {
287            failures.push(message);
288        }
289        if failures.is_empty() {
290            Ok(bytes)
291        } else {
292            Err(committed_error(id, failures))
293        }
294    }
295
296    pub fn contains(&self, id: TxId) -> bool {
297        self.lock_chain().contains(id)
298    }
299
300    pub fn tip(&self) -> Option<TxId> {
301        self.lock_chain().tip()
302    }
303
304    pub fn between_txids(&self, older: TxId, newer: TxId) -> Result<Vec<TxId>, String> {
305        self.lock_chain().between_txids(older, newer)
306    }
307
308    pub fn get_txn(&self, id: TxId) -> Result<Option<Vec<u8>>, String> {
309        self.lock_chain().get_txn(id)
310    }
311
312    fn lock_writer(&self) -> MutexGuard<'_, WriterState> {
313        self.writer.lock().expect("KTO writer mutex poisoned")
314    }
315
316    fn lock_chain(&self) -> MutexGuard<'_, CanonicalChain> {
317        self.chain.lock().expect("KTO chain mutex poisoned")
318    }
319}
320
321impl Registration {
322    fn is_active(&self) -> bool {
323        self.mode == RegistrationMode::Active && !self.lane.faulted.load(Ordering::Acquire)
324    }
325
326    fn reserve_delivery(&mut self, id: TxId) -> Reservation {
327        let reservation = Reservation {
328            lane: self.lane.clone(),
329            ticket: self.next_ticket,
330        };
331        self.next_ticket += 1;
332        self.latest = Some(id);
333        reservation
334    }
335
336    fn reserve_reorg(&mut self) -> Reservation {
337        let reservation = Reservation {
338            lane: self.lane.clone(),
339            ticket: self.next_ticket,
340        };
341        self.next_ticket += 1;
342        reservation
343    }
344}
345
346impl Lane {
347    fn new(handler: Arc<dyn Subsystem>) -> Self {
348        Self {
349            handler,
350            progress: Mutex::new(LaneProgress {
351                serving: 0,
352                fault: None,
353            }),
354            changed: Condvar::new(),
355            running: AtomicBool::new(false),
356            faulted: AtomicBool::new(false),
357        }
358    }
359
360    fn run_replay(&self, id: TxId, payload: &[u8]) -> Result<(), String> {
361        self.running.store(true, Ordering::Release);
362        let result = invoke_callback("replay submit", || self.handler.submit_txn(id, payload));
363        if let Err(message) = &result {
364            self.record_fault(message.clone());
365        }
366        self.running.store(false, Ordering::Release);
367        result
368    }
369
370    fn run_ticket<F>(&self, ticket: u64, operation: &str, callback: F) -> Result<(), String>
371    where
372        F: FnOnce() -> Result<(), String>,
373    {
374        let mut progress = self.progress.lock().expect("subsystem lane mutex poisoned");
375        while progress.serving < ticket {
376            progress = self
377                .changed
378                .wait(progress)
379                .expect("subsystem lane mutex poisoned");
380        }
381        if progress.serving > ticket {
382            return Err(format!("{operation} ticket was already completed"));
383        }
384        if let Some(reason) = progress.fault.clone() {
385            progress.serving += 1;
386            self.changed.notify_all();
387            return Err(format!(
388                "{operation} callback was not run because the registration faulted: {reason}"
389            ));
390        }
391        self.running.store(true, Ordering::Release);
392        drop(progress);
393        let result = invoke_callback(operation, callback);
394        let mut progress = self.progress.lock().expect("subsystem lane mutex poisoned");
395        if let Err(message) = &result
396            && progress.fault.is_none()
397        {
398            progress.fault = Some(message.clone());
399            self.faulted.store(true, Ordering::Release);
400        }
401        progress.serving += 1;
402        self.running.store(false, Ordering::Release);
403        self.changed.notify_all();
404        result
405    }
406
407    fn record_fault(&self, message: String) {
408        let mut progress = self.progress.lock().expect("subsystem lane mutex poisoned");
409        if progress.fault.is_none() {
410            progress.fault = Some(message);
411            self.faulted.store(true, Ordering::Release);
412        }
413    }
414
415    fn is_quiescent(&self, next_ticket: u64) -> bool {
416        let progress = self.progress.lock().expect("subsystem lane mutex poisoned");
417        progress.serving == next_ticket && !self.running.load(Ordering::Acquire)
418    }
419}
420
421impl Reservation {
422    fn run_delivery(self, id: TxId, payload: &[u8]) -> Result<(), String> {
423        self.lane.run_ticket(self.ticket, "submit", || {
424            self.lane.handler.submit_txn(id, payload)
425        })
426    }
427
428    fn run_reorg(self) -> Result<(), String> {
429        self.lane
430            .run_ticket(self.ticket, "reorg", || self.lane.handler.reorg())
431    }
432}
433
434fn invoke_callback<F>(operation: &str, callback: F) -> Result<(), String>
435where
436    F: FnOnce() -> Result<(), String>,
437{
438    match catch_unwind(AssertUnwindSafe(callback)) {
439        Ok(Ok(())) => Ok(()),
440        Ok(Err(message)) => Err(format!("{operation} callback failed: {message}")),
441        Err(_) => Err(format!("{operation} callback panicked")),
442    }
443}
444
445fn ensure_replaceable(registration: &Registration) -> Result<(), String> {
446    if registration.mode == RegistrationMode::Replaying
447        && !registration.lane.faulted.load(Ordering::Acquire)
448    {
449        return Err("subsystem registration is replaying".to_owned());
450    }
451    if registration.mode == RegistrationMode::Active
452        && !registration.lane.faulted.load(Ordering::Acquire)
453    {
454        return Err("subsystem is already registered and active".to_owned());
455    }
456    if !registration.lane.is_quiescent(registration.next_ticket) {
457        return Err("previous subsystem lane work has not quiesced".to_owned());
458    }
459    Ok(())
460}
461
462fn reserve_active_delivery(
463    writer: &mut WriterState,
464    subsystem: SubsystemId,
465    id: TxId,
466) -> Option<Reservation> {
467    writer
468        .registrations
469        .get_mut(&subsystem)
470        .filter(|registration| registration.is_active())
471        .map(|registration| registration.reserve_delivery(id))
472}
473
474fn run_reorganization(
475    id: TxId,
476    payload: &[u8],
477    reorgs: Vec<Reservation>,
478    replacement: Option<Reservation>,
479) -> Vec<String> {
480    thread::scope(|scope| {
481        let mut jobs = Vec::with_capacity(reorgs.len() + usize::from(replacement.is_some()));
482        for reservation in reorgs {
483            jobs.push(scope.spawn(move || reservation.run_reorg()));
484        }
485        if let Some(reservation) = replacement {
486            jobs.push(scope.spawn(move || reservation.run_delivery(id, payload)));
487        }
488        let mut failures = Vec::new();
489        for job in jobs {
490            match job.join() {
491                Ok(Ok(())) => {}
492                Ok(Err(message)) => failures.push(message),
493                Err(_) => failures.push("callback lane panicked".to_owned()),
494            }
495        }
496        failures
497    })
498}
499
500fn fault_cursor(lane: &Lane, message: String) -> String {
501    let failure = format!("subsystem replay cursor failed: {message}");
502    lane.record_fault(failure.clone());
503    failure
504}
505
506fn committed_error(id: TxId, failures: Vec<String>) -> String {
507    format!("TxId {id:?} was committed; {}", failures.join("; "))
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use kcode_k1_txn_ordering_testkit::{
514        OrderingCandidate, OrderingHarness, TestQueuePropagation, TestSigner, TestSubsystem,
515        verify_callback_failure_isolation, verify_independent_subsystem_lanes,
516        verify_queue_before_callback, verify_reorganization_isolation, verify_replay_live_handoff,
517        verify_restart_duplicates_and_queries, verify_signing_commit_exclusion,
518        verify_unrelated_callback_reentry,
519    };
520    use std::path::Path;
521
522    struct Harness;
523
524    struct TestSubsystemAdapter {
525        inner: Arc<dyn TestSubsystem>,
526    }
527
528    impl Subsystem for TestSubsystemAdapter {
529        fn submit_txn(&self, id: TxId, payload: &[u8]) -> Result<(), String> {
530            self.inner.submit_txn(id, payload)
531        }
532
533        fn reorg(&self) -> Result<(), String> {
534            self.inner.reorg()
535        }
536    }
537
538    impl OrderingCandidate for K1TxnOrdering {
539        fn register_subsystem(
540            &self,
541            subsystem: SubsystemId,
542            after: Option<TxId>,
543            handler: Arc<dyn TestSubsystem>,
544        ) -> Result<(), String> {
545            K1TxnOrdering::register_subsystem(
546                self,
547                subsystem,
548                after,
549                Arc::new(TestSubsystemAdapter { inner: handler }),
550            )
551        }
552
553        fn submit_txn(&self, transaction: &[u8]) -> Result<(), SubmitError> {
554            K1TxnOrdering::submit_txn(self, transaction)
555        }
556
557        fn submit_local_txn(
558            &self,
559            timestamp: u64,
560            creator: [u8; 32],
561            subsystem: SubsystemId,
562            payload: &[u8],
563            signer: TestSigner,
564            queue_propagation: TestQueuePropagation,
565        ) -> Result<Vec<u8>, String> {
566            K1TxnOrdering::submit_local_txn(
567                self,
568                timestamp,
569                creator,
570                subsystem,
571                payload,
572                signer,
573                queue_propagation,
574            )
575        }
576
577        fn contains(&self, id: TxId) -> bool {
578            K1TxnOrdering::contains(self, id)
579        }
580
581        fn tip(&self) -> Option<TxId> {
582            K1TxnOrdering::tip(self)
583        }
584
585        fn between_txids(&self, older: TxId, newer: TxId) -> Result<Vec<TxId>, String> {
586            K1TxnOrdering::between_txids(self, older, newer)
587        }
588
589        fn get_txn(&self, id: TxId) -> Result<Option<Vec<u8>>, String> {
590            K1TxnOrdering::get_txn(self, id)
591        }
592    }
593
594    impl OrderingHarness for Harness {
595        fn open(&self, root: &Path) -> Result<Arc<dyn OrderingCandidate>, String> {
596            Ok(Arc::new(K1TxnOrdering::open(root)?))
597        }
598    }
599
600    #[test]
601    fn queue_before_callback() {
602        verify_queue_before_callback(&Harness).unwrap();
603    }
604
605    #[test]
606    fn independent_subsystem_lanes() {
607        verify_independent_subsystem_lanes(&Harness).unwrap();
608    }
609
610    #[test]
611    fn signing_commit_exclusion() {
612        verify_signing_commit_exclusion(&Harness).unwrap();
613    }
614
615    #[test]
616    fn replay_live_handoff() {
617        verify_replay_live_handoff(&Harness).unwrap();
618    }
619
620    #[test]
621    fn callback_failure_isolation() {
622        verify_callback_failure_isolation(&Harness).unwrap();
623    }
624
625    #[test]
626    fn reorganization_isolation() {
627        verify_reorganization_isolation(&Harness).unwrap();
628    }
629
630    #[test]
631    fn unrelated_callback_reentry() {
632        verify_unrelated_callback_reentry(&Harness).unwrap();
633    }
634
635    #[test]
636    fn restart_duplicates_and_queries() {
637        verify_restart_duplicates_and_queries(&Harness).unwrap();
638    }
639}