//! atproto commit operations. //! //! fast, synchronous implementation of step 3 of sync 1.1 commit validation, //! operation inversion. //! //! ```no_run //! # use std::collections::HashMap; //! # use hubble_commit::{Cid, Key, Op, OpOrder, invert_ops}; //! # fn example(blocks: HashMap>, root: Cid, claimed_prev: Cid) //! # -> Result<(), Box> { //! let ops = vec![Op::Create { //! key: Key::new("app.bsky.feed.post/3lrbpi5ot35kpjb")?, //! cid: root, //! }]; //! let prev = invert_ops(&blocks, root, &ops, OpOrder::AsGiven)?; //! assert_eq!(prev, claimed_prev); //! # Ok(()) //! # } //! ``` //! //! (in the future maybe we'll add commit coalescing too) mod block; mod blocks; pub mod car; mod error; mod key; mod node; mod op; mod tree; // VENDORED from dasl until it releases the `Hash` derive we need to key // blocks by CID. Re-exported by hubble-sync as `DaslCid`. pub mod cid; pub use block::{BlockError, verify_block}; pub use blocks::Blocks; pub use car::{CarError, CarReader}; pub use cid::{Cid, CidParseError}; pub use error::{InvertError, NodeFault, OpKind}; pub use key::{ Key, KeyError, MAX_COLLECTION_LEN, MAX_KEY_LEN, MAX_LAYER, MAX_RKEY_LEN, layer_of, shared_prefix_len, }; pub use op::Op; pub use tree::Tree; /// How to order operations when inverting more than one. /// /// In general, just use `AsGiven` (pass `Default::default()` to not even think /// about it). #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum OpOrder { /// Invert in the order given, reordering nothing (default) /// /// Makes no attempt to optimize ops inversion order, can fail if a producer /// emits a technically-provable but spec-non-compliant minimal block set. /// /// It's good to reject stuff that's not spec compliant. /// /// [spec]: https://www.ietf.org/archive/id/draft-holmgren-at-synchronization-00.html #[default] AsGiven, /// Invert highest key first: fewest blocks to prove. /// /// Tolerates a producer that sends only the blocks inversion strictly /// needs rather than the ones the spec requires. Out of mst-test-suite's /// 16,384 exhaustive cases this, ordering affects 447. /// /// If there is some PDS emitting commits that don't meet the spec's /// adjacent keys requirement but are technically provable, this order /// should succeed at proving them. /// /// (but please ask the pds implementor to emit spec compliant commits) HighestKeyFirst, } /// Invert `ops` against the tree at `root`, returning the root it had before. pub fn invert_ops( blocks: &(impl Blocks + Sync), root: Cid, ops: &[Op], order: OpOrder, ) -> Result { let mut tree = Tree::new(blocks, root); let mut ordered: Vec<&Op> = ops.iter().collect(); if order == OpOrder::HighestKeyFirst { ordered.sort_unstable_by(|a, b| b.key().cmp(a.key())); } for op in ordered { tree.invert(op)?; } tree.root() }