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