holochain_types/
dht_op.rs

1//! Data structures representing the operations that can be performed within a Holochain DHT.
2//!
3//! See the [item-level documentation for `DhtOp`][DhtOp] for more details.
4//!
5//! [DhtOp]: enum.DhtOp.html
6
7use std::str::FromStr;
8
9use crate::action::NewEntryAction;
10use crate::prelude::*;
11use crate::record::RecordGroup;
12use crate::warrant::WarrantOp;
13use holo_hash::*;
14use holochain_sqlite::rusqlite::types::FromSql;
15use holochain_sqlite::rusqlite::ToSql;
16use holochain_zome_types::action;
17use holochain_zome_types::prelude::*;
18use serde::Deserialize;
19use serde::Serialize;
20
21mod error;
22pub use error::*;
23
24#[cfg(test)]
25mod tests;
26
27/// A unit of DHT gossip. Used to notify an authority of new (meta)data to hold
28/// as well as changes to the status of already held data.
29#[derive(
30    Clone, Debug, Serialize, Deserialize, SerializedBytes, Eq, PartialEq, Hash, derive_more::From,
31)]
32pub enum DhtOp {
33    /// An op representing storage of some record information.
34    ChainOp(Box<ChainOp>),
35    /// A op representing storage of a claim that a ChainOp was invalid
36    WarrantOp(Box<WarrantOp>),
37}
38
39/// A unit of DHT gossip concerning source chain data.
40#[derive(
41    Clone, Debug, Serialize, Deserialize, SerializedBytes, Eq, PartialEq, Hash, derive_more::Display,
42)]
43pub enum ChainOp {
44    #[display(fmt = "StoreRecord")]
45    /// Used to notify the authority for an action that it has been created.
46    ///
47    /// Conceptually, authorities receiving this `ChainOp` do three things:
48    ///
49    /// - Ensure that the record passes validation.
50    /// - Store the action into their DHT shard.
51    /// - Store the entry into their CAS.
52    ///   - Note: they do not become responsible for keeping the set of
53    ///     references from that entry up-to-date.
54    StoreRecord(Signature, Action, RecordEntry),
55
56    #[display(fmt = "StoreEntry")]
57    /// Used to notify the authority for an entry that it has been created
58    /// anew. (The same entry can be created more than once.)
59    ///
60    /// Conceptually, authorities receiving this `ChainOp` do four things:
61    ///
62    /// - Ensure that the record passes validation.
63    /// - Store the entry into their DHT shard.
64    /// - Store the action into their CAS.
65    ///   - Note: they do not become responsible for keeping the set of
66    ///     references from that action up-to-date.
67    /// - Add a "created-by" reference from the entry to the hash of the action.
68    ///
69    // TODO: document how those "created-by" references are stored in
70    // reality.
71    StoreEntry(Signature, NewEntryAction, Entry),
72
73    #[display(fmt = "RegisterAgentActivity")]
74    /// Used to notify the authority for an agent's public key that that agent
75    /// has committed a new action.
76    ///
77    /// Conceptually, authorities receiving this `ChainOp` do three things:
78    ///
79    /// - Ensure that *the action alone* passes surface-level validation.
80    /// - Store the action into their DHT shard.
81    //   - FIXME: @artbrock, do they?
82    /// - Add an "agent-activity" reference from the public key to the hash
83    ///   of the action.
84    ///
85    // TODO: document how those "agent-activity" references are stored in
86    // reality.
87    RegisterAgentActivity(Signature, Action),
88
89    #[display(fmt = "RegisterUpdatedContent")]
90    /// Op for updating an entry.
91    /// This is sent to the entry authority.
92    // TODO: This entry is here for validation by the entry update action holder
93    // link's don't do this. The entry is validated by store entry. Maybe we either
94    // need to remove the Entry here or add it to link.
95    RegisterUpdatedContent(Signature, action::Update, RecordEntry),
96
97    #[display(fmt = "RegisterUpdatedRecord")]
98    /// Op for updating a record.
99    /// This is sent to the record authority.
100    RegisterUpdatedRecord(Signature, action::Update, RecordEntry),
101
102    #[display(fmt = "RegisterDeletedBy")]
103    /// Op for registering an action deletion with the Action authority
104    RegisterDeletedBy(Signature, action::Delete),
105
106    #[display(fmt = "RegisterDeletedEntryAction")]
107    /// Op for registering an action deletion with the Entry authority, so that
108    /// the Entry can be marked Dead if all of its Actions have been deleted
109    RegisterDeletedEntryAction(Signature, action::Delete),
110
111    #[display(fmt = "RegisterAddLink")]
112    /// Op for adding a link
113    RegisterAddLink(Signature, action::CreateLink),
114
115    #[display(fmt = "RegisterRemoveLink")]
116    /// Op for removing a link
117    RegisterRemoveLink(Signature, action::DeleteLink),
118}
119
120impl From<ChainOp> for DhtOp {
121    fn from(op: ChainOp) -> Self {
122        DhtOp::ChainOp(Box::new(op))
123    }
124}
125
126impl From<WarrantOp> for DhtOp {
127    fn from(op: WarrantOp) -> Self {
128        DhtOp::WarrantOp(Box::new(op))
129    }
130}
131
132impl From<SignedWarrant> for DhtOp {
133    fn from(op: SignedWarrant) -> Self {
134        DhtOp::WarrantOp(Box::new(WarrantOp::from(op)))
135    }
136}
137
138/// A type for storing in databases that doesn't need the actual
139/// data. Everything is a hash of the type except the signatures.
140#[allow(missing_docs)]
141#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, derive_more::From)]
142pub enum DhtOpLite {
143    Chain(Box<ChainOpLite>),
144    /// Note: WarrantOps are already "lite", as they only contain hashes
145    Warrant(Box<WarrantOp>),
146}
147
148/// A type for storing in databases that doesn't need the actual
149/// data. Everything is a hash of the type except the signatures.
150#[allow(missing_docs)]
151#[derive(Clone, Debug, Serialize, Deserialize, derive_more::Display)]
152pub enum ChainOpLite {
153    #[display(fmt = "StoreRecord")]
154    StoreRecord(ActionHash, Option<EntryHash>, OpBasis),
155    #[display(fmt = "StoreEntry")]
156    StoreEntry(ActionHash, EntryHash, OpBasis),
157    #[display(fmt = "RegisterAgentActivity")]
158    RegisterAgentActivity(ActionHash, OpBasis),
159    #[display(fmt = "RegisterUpdatedContent")]
160    RegisterUpdatedContent(ActionHash, EntryHash, OpBasis),
161    #[display(fmt = "RegisterUpdatedRecord")]
162    RegisterUpdatedRecord(ActionHash, EntryHash, OpBasis),
163    #[display(fmt = "RegisterDeletedBy")]
164    RegisterDeletedBy(ActionHash, OpBasis),
165    #[display(fmt = "RegisterDeletedEntryAction")]
166    RegisterDeletedEntryAction(ActionHash, OpBasis),
167    #[display(fmt = "RegisterAddLink")]
168    RegisterAddLink(ActionHash, OpBasis),
169    #[display(fmt = "RegisterRemoveLink")]
170    RegisterRemoveLink(ActionHash, OpBasis),
171}
172
173impl From<ChainOpLite> for DhtOpLite {
174    fn from(op: ChainOpLite) -> Self {
175        DhtOpLite::Chain(Box::new(op))
176    }
177}
178
179impl From<WarrantOp> for DhtOpLite {
180    fn from(op: WarrantOp) -> Self {
181        DhtOpLite::Warrant(Box::new(op))
182    }
183}
184
185impl From<SignedWarrant> for DhtOpLite {
186    fn from(warrant: SignedWarrant) -> Self {
187        DhtOpLite::Warrant(Box::new(warrant.into()))
188    }
189}
190
191impl PartialEq for ChainOpLite {
192    fn eq(&self, other: &Self) -> bool {
193        // The ops are the same if they are the same type on the same action hash.
194        // We can't derive eq because `Option<EntryHash>` doesn't make the op different.
195        // We can ignore the basis because the basis is derived from the action and op type.
196        self.get_type() == other.get_type() && self.action_hash() == other.action_hash()
197    }
198}
199
200impl Eq for ChainOpLite {}
201
202impl std::hash::Hash for ChainOpLite {
203    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
204        self.get_type().hash(state);
205        self.action_hash().hash(state);
206    }
207}
208
209/// Unit enum type corresponding to the different types of DhtOp
210#[allow(missing_docs)]
211#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, derive_more::From)]
212pub enum DhtOpType {
213    Chain(ChainOpType),
214    Warrant(WarrantOpType),
215}
216
217impl ToSql for DhtOpType {
218    fn to_sql(
219        &self,
220    ) -> holochain_sqlite::rusqlite::Result<holochain_sqlite::rusqlite::types::ToSqlOutput> {
221        match self {
222            DhtOpType::Chain(op) => op.to_sql(),
223            DhtOpType::Warrant(op) => op.to_sql(),
224        }
225    }
226}
227
228impl FromSql for DhtOpType {
229    fn column_result(
230        value: holochain_sqlite::rusqlite::types::ValueRef<'_>,
231    ) -> holochain_sqlite::rusqlite::types::FromSqlResult<Self> {
232        String::column_result(value)
233            .and_then(|string| {
234                ChainOpType::from_str(&string)
235                    .map(DhtOpType::from)
236                    .or_else(|_| WarrantOpType::from_str(&string).map(DhtOpType::from))
237                    .map_err(|_| holochain_sqlite::rusqlite::types::FromSqlError::InvalidType)
238            })
239            .map(Into::into)
240    }
241}
242
243/// A sys validation dependency
244pub type SysValDeps = Vec<ActionHash>;
245
246/// This enum is used to encode just the enum variant of ChainOp
247#[allow(missing_docs)]
248#[derive(
249    Clone,
250    Copy,
251    Debug,
252    Serialize,
253    Deserialize,
254    Eq,
255    PartialEq,
256    Hash,
257    derive_more::Display,
258    strum_macros::EnumString,
259)]
260pub enum ChainOpType {
261    #[display(fmt = "StoreRecord")]
262    StoreRecord,
263    #[display(fmt = "StoreEntry")]
264    StoreEntry,
265    #[display(fmt = "RegisterAgentActivity")]
266    RegisterAgentActivity,
267    #[display(fmt = "RegisterUpdatedContent")]
268    RegisterUpdatedContent,
269    #[display(fmt = "RegisterUpdatedRecord")]
270    RegisterUpdatedRecord,
271    #[display(fmt = "RegisterDeletedBy")]
272    RegisterDeletedBy,
273    #[display(fmt = "RegisterDeletedEntryAction")]
274    RegisterDeletedEntryAction,
275    #[display(fmt = "RegisterAddLink")]
276    RegisterAddLink,
277    #[display(fmt = "RegisterRemoveLink")]
278    RegisterRemoveLink,
279}
280impl ChainOpType {
281    /// Calculate the op's sys validation dependencies (action hashes)
282    pub fn sys_validation_dependencies(&self, action: &Action) -> SysValDeps {
283        match self {
284            ChainOpType::StoreRecord | ChainOpType::StoreEntry => vec![],
285            ChainOpType::RegisterAgentActivity => action
286                .prev_action()
287                .map(|p| vec![p.clone()])
288                .unwrap_or_default(),
289            ChainOpType::RegisterUpdatedContent | ChainOpType::RegisterUpdatedRecord => {
290                match action {
291                    Action::Update(update) => vec![update.original_action_address.clone()],
292                    _ => vec![],
293                }
294            }
295            ChainOpType::RegisterDeletedBy | ChainOpType::RegisterDeletedEntryAction => {
296                match action {
297                    Action::Delete(delete) => vec![delete.deletes_address.clone()],
298                    _ => vec![],
299                }
300            }
301            ChainOpType::RegisterAddLink => vec![],
302            ChainOpType::RegisterRemoveLink => match action {
303                Action::DeleteLink(delete_link) => vec![delete_link.link_add_address.clone()],
304                _ => vec![],
305            },
306        }
307    }
308}
309
310impl rusqlite::ToSql for ChainOpType {
311    fn to_sql(
312        &self,
313    ) -> holochain_sqlite::rusqlite::Result<holochain_sqlite::rusqlite::types::ToSqlOutput> {
314        Ok(holochain_sqlite::rusqlite::types::ToSqlOutput::Owned(
315            format!("{}", self).into(),
316        ))
317    }
318}
319
320impl rusqlite::types::FromSql for ChainOpType {
321    fn column_result(
322        value: holochain_sqlite::rusqlite::types::ValueRef<'_>,
323    ) -> holochain_sqlite::rusqlite::types::FromSqlResult<Self> {
324        String::column_result(value).and_then(|string| {
325            ChainOpType::from_str(&string)
326                .map_err(|_| holochain_sqlite::rusqlite::types::FromSqlError::InvalidType)
327        })
328    }
329}
330
331impl DhtOp {
332    /// If this is a chain op, return that
333    pub fn as_chain_op(&self) -> Option<&ChainOp> {
334        match self {
335            Self::ChainOp(op) => Some(op),
336            _ => None,
337        }
338    }
339
340    /// Get the type as a unit enum, for Display purposes
341    pub fn get_type(&self) -> DhtOpType {
342        match self {
343            Self::ChainOp(op) => DhtOpType::Chain(op.get_type()),
344            Self::WarrantOp(op) => DhtOpType::Warrant(op.get_type()),
345        }
346    }
347
348    /// Returns the basis hash which determines which agents will receive this DhtOp
349    pub fn dht_basis(&self) -> OpBasis {
350        match self {
351            Self::ChainOp(op) => op.as_unique_form().basis(),
352            Self::WarrantOp(op) => op.dht_basis(),
353        }
354    }
355
356    /// Get the signature for this op
357    pub fn signature(&self) -> &Signature {
358        match self {
359            Self::ChainOp(op) => op.signature(),
360            Self::WarrantOp(op) => op.signature(),
361        }
362    }
363
364    fn to_order(&self) -> OpOrder {
365        match self {
366            Self::ChainOp(op) => OpOrder::new(op.get_type(), op.timestamp()),
367            Self::WarrantOp(op) => OpOrder::new(op.get_type(), op.timestamp()),
368        }
369    }
370
371    /// Access to the Timestamp
372    pub fn author(&self) -> AgentPubKey {
373        match self {
374            Self::ChainOp(op) => op.action().author().clone(),
375            Self::WarrantOp(op) => op.author.clone(),
376        }
377    }
378
379    /// Access to the Timestamp
380    pub fn timestamp(&self) -> Timestamp {
381        match self {
382            Self::ChainOp(op) => op.timestamp(),
383            Self::WarrantOp(op) => op.timestamp(),
384        }
385    }
386
387    /// Convert a [DhtOp] to a [DhtOpLite] and basis
388    pub fn to_lite(&self) -> DhtOpLite {
389        match self {
390            Self::ChainOp(op) => DhtOpLite::Chain(op.to_lite().into()),
391            Self::WarrantOp(op) => DhtOpLite::Warrant(op.clone()),
392        }
393    }
394
395    /// Calculate the op's sys validation dependency action hash
396    pub fn sys_validation_dependencies(&self) -> SysValDeps {
397        match self {
398            Self::ChainOp(op) => op.get_type().sys_validation_dependencies(&op.action()),
399            Self::WarrantOp(op) => match &op.proof {
400                WarrantProof::ChainIntegrity(w) => match w {
401                    ChainIntegrityWarrant::InvalidChainOp {
402                        action: action_hash,
403                        ..
404                    } => vec![action_hash.0.clone()],
405                    ChainIntegrityWarrant::ChainFork { action_pair, .. } => {
406                        vec![action_pair.0 .0.clone()]
407                    }
408                },
409            },
410        }
411    }
412}
413
414impl PartialOrd for DhtOp {
415    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
416        Some(self.cmp(other))
417    }
418}
419
420impl Ord for DhtOp {
421    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
422        match self.to_order().cmp(&other.to_order()) {
423            // Use signature as a tiebreaker
424            std::cmp::Ordering::Equal => self.signature().cmp(other.signature()),
425            ordering => ordering,
426        }
427    }
428}
429
430impl ChainOp {
431    fn as_unique_form(&self) -> ChainOpUniqueForm<'_> {
432        match self {
433            Self::StoreRecord(_, action, _) => ChainOpUniqueForm::StoreRecord(action),
434            Self::StoreEntry(_, action, _) => ChainOpUniqueForm::StoreEntry(action),
435            Self::RegisterAgentActivity(_, action) => {
436                ChainOpUniqueForm::RegisterAgentActivity(action)
437            }
438            Self::RegisterUpdatedContent(_, action, _) => {
439                ChainOpUniqueForm::RegisterUpdatedContent(action)
440            }
441            Self::RegisterUpdatedRecord(_, action, _) => {
442                ChainOpUniqueForm::RegisterUpdatedRecord(action)
443            }
444            Self::RegisterDeletedBy(_, action) => ChainOpUniqueForm::RegisterDeletedBy(action),
445            Self::RegisterDeletedEntryAction(_, action) => {
446                ChainOpUniqueForm::RegisterDeletedEntryAction(action)
447            }
448            Self::RegisterAddLink(_, action) => ChainOpUniqueForm::RegisterAddLink(action),
449            Self::RegisterRemoveLink(_, action) => ChainOpUniqueForm::RegisterRemoveLink(action),
450        }
451    }
452
453    /// Returns the basis hash which determines which agents will receive this DhtOp
454    pub fn dht_basis(&self) -> OpBasis {
455        self.as_unique_form().basis()
456    }
457
458    /// Get the signature for this op
459    pub fn signature(&self) -> &Signature {
460        match self {
461            Self::StoreRecord(s, _, _)
462            | Self::StoreEntry(s, _, _)
463            | Self::RegisterAgentActivity(s, _)
464            | Self::RegisterUpdatedContent(s, _, _)
465            | Self::RegisterUpdatedRecord(s, _, _)
466            | Self::RegisterDeletedBy(s, _)
467            | Self::RegisterDeletedEntryAction(s, _)
468            | Self::RegisterAddLink(s, _)
469            | Self::RegisterRemoveLink(s, _) => s,
470        }
471    }
472
473    /// Convert a [ChainOp] to a [ChainOpLite] and basis
474    pub fn to_lite(&self) -> ChainOpLite {
475        let basis = self.dht_basis();
476        match self {
477            Self::StoreRecord(_, a, _) => {
478                let e = a.entry_data().map(|(e, _)| e.clone());
479                let h = ActionHash::with_data_sync(a);
480                ChainOpLite::StoreRecord(h, e, basis)
481            }
482            Self::StoreEntry(_, a, _) => {
483                let e = a.entry().clone();
484                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
485                ChainOpLite::StoreEntry(h, e, basis)
486            }
487            Self::RegisterAgentActivity(_, a) => {
488                let h = ActionHash::with_data_sync(a);
489                ChainOpLite::RegisterAgentActivity(h, basis)
490            }
491            Self::RegisterUpdatedContent(_, a, _) => {
492                let e = a.entry_hash.clone();
493                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
494                ChainOpLite::RegisterUpdatedContent(h, e, basis)
495            }
496            Self::RegisterUpdatedRecord(_, a, _) => {
497                let e = a.entry_hash.clone();
498                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
499                ChainOpLite::RegisterUpdatedRecord(h, e, basis)
500            }
501            Self::RegisterDeletedBy(_, a) => {
502                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
503                ChainOpLite::RegisterDeletedBy(h, basis)
504            }
505            Self::RegisterDeletedEntryAction(_, a) => {
506                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
507                ChainOpLite::RegisterDeletedEntryAction(h, basis)
508            }
509            Self::RegisterAddLink(_, a) => {
510                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
511                ChainOpLite::RegisterAddLink(h, basis)
512            }
513            Self::RegisterRemoveLink(_, a) => {
514                let h = ActionHash::with_data_sync(&Action::from(a.clone()));
515                ChainOpLite::RegisterRemoveLink(h, basis)
516            }
517        }
518    }
519
520    /// Get the action from this op
521    /// This requires cloning and converting the action
522    /// as some ops don't hold the Action type
523    pub fn action(&self) -> Action {
524        match self {
525            Self::StoreRecord(_, a, _) => a.clone(),
526            Self::StoreEntry(_, a, _) => a.clone().into(),
527            Self::RegisterAgentActivity(_, a) => a.clone(),
528            Self::RegisterUpdatedContent(_, a, _) => a.clone().into(),
529            Self::RegisterUpdatedRecord(_, a, _) => a.clone().into(),
530            Self::RegisterDeletedBy(_, a) => a.clone().into(),
531            Self::RegisterDeletedEntryAction(_, a) => a.clone().into(),
532            Self::RegisterAddLink(_, a) => a.clone().into(),
533            Self::RegisterRemoveLink(_, a) => a.clone().into(),
534        }
535    }
536
537    /// Get the signed action from this op
538    pub fn signed_action(&self) -> SignedAction {
539        match self {
540            Self::StoreRecord(s, a, _) => SignedAction::new(a.clone(), s.clone()),
541            Self::StoreEntry(s, a, _) => SignedAction::new(a.clone().into(), s.clone()),
542            Self::RegisterAgentActivity(s, a) => SignedAction::new(a.clone(), s.clone()),
543            Self::RegisterUpdatedContent(s, a, _) => SignedAction::new(a.clone().into(), s.clone()),
544            Self::RegisterUpdatedRecord(s, a, _) => SignedAction::new(a.clone().into(), s.clone()),
545            Self::RegisterDeletedBy(s, a) => SignedAction::new(a.clone().into(), s.clone()),
546            Self::RegisterDeletedEntryAction(s, a) => {
547                SignedAction::new(a.clone().into(), s.clone())
548            }
549            Self::RegisterAddLink(s, a) => SignedAction::new(a.clone().into(), s.clone()),
550            Self::RegisterRemoveLink(s, a) => SignedAction::new(a.clone().into(), s.clone()),
551        }
552    }
553
554    /// Check if this represents a genesis op.
555    pub fn is_genesis(&self) -> bool {
556        // XXX: Not great encapsulation here, but hey, at least it's using
557        // a const value for the comparison.
558        match self {
559            ChainOp::StoreRecord(_, a, _) => a.is_genesis(),
560            ChainOp::StoreEntry(_, a, _) => a.action_seq() < POST_GENESIS_SEQ_THRESHOLD,
561            ChainOp::RegisterAgentActivity(_, a) => a.is_genesis(),
562            ChainOp::RegisterUpdatedContent(_, a, _) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
563            ChainOp::RegisterUpdatedRecord(_, a, _) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
564            ChainOp::RegisterDeletedBy(_, a) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
565            ChainOp::RegisterDeletedEntryAction(_, a) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
566            ChainOp::RegisterAddLink(_, a) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
567            ChainOp::RegisterRemoveLink(_, a) => a.action_seq < POST_GENESIS_SEQ_THRESHOLD,
568        }
569    }
570
571    /// Get the entry from this op, if one exists
572    pub fn entry(&self) -> RecordEntryRef {
573        match self {
574            Self::StoreRecord(_, _, e) => e.as_ref(),
575            Self::StoreEntry(_, _, e) => RecordEntry::Present(e),
576            Self::RegisterUpdatedContent(_, _, e) => e.as_ref(),
577            Self::RegisterUpdatedRecord(_, _, e) => e.as_ref(),
578            Self::RegisterAgentActivity(_, a) => RecordEntry::new(a.entry_visibility(), None),
579            Self::RegisterDeletedBy(_, _) => RecordEntry::NA,
580            Self::RegisterDeletedEntryAction(_, _) => RecordEntry::NA,
581            Self::RegisterAddLink(_, _) => RecordEntry::NA,
582            Self::RegisterRemoveLink(_, _) => RecordEntry::NA,
583        }
584    }
585
586    /// Get the type as a unit enum, for Display purposes
587    pub fn get_type(&self) -> ChainOpType {
588        match self {
589            Self::StoreRecord(_, _, _) => ChainOpType::StoreRecord,
590            Self::StoreEntry(_, _, _) => ChainOpType::StoreEntry,
591            Self::RegisterUpdatedContent(_, _, _) => ChainOpType::RegisterUpdatedContent,
592            Self::RegisterUpdatedRecord(_, _, _) => ChainOpType::RegisterUpdatedRecord,
593            Self::RegisterAgentActivity(_, _) => ChainOpType::RegisterAgentActivity,
594            Self::RegisterDeletedBy(_, _) => ChainOpType::RegisterDeletedBy,
595            Self::RegisterDeletedEntryAction(_, _) => ChainOpType::RegisterDeletedEntryAction,
596            Self::RegisterAddLink(_, _) => ChainOpType::RegisterAddLink,
597            Self::RegisterRemoveLink(_, _) => ChainOpType::RegisterRemoveLink,
598        }
599    }
600
601    /// From a type, action and an entry (if there is one)
602    pub fn from_type(
603        op_type: ChainOpType,
604        action: SignedAction,
605        entry: Option<Entry>,
606    ) -> DhtOpResult<Self> {
607        let (action, signature) = action.into();
608        let entry = RecordEntry::new(action.entry_visibility(), entry);
609        let r = match op_type {
610            ChainOpType::StoreRecord => Self::StoreRecord(signature, action, entry),
611            ChainOpType::StoreEntry => {
612                let entry = entry
613                    .into_option()
614                    .ok_or_else(|| DhtOpError::ActionWithoutEntry(action.clone()))?;
615                let action = match action {
616                    Action::Create(c) => NewEntryAction::Create(c),
617                    Action::Update(c) => NewEntryAction::Update(c),
618                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
619                };
620                Self::StoreEntry(signature, action, entry)
621            }
622            ChainOpType::RegisterAgentActivity => Self::RegisterAgentActivity(signature, action),
623            ChainOpType::RegisterUpdatedContent => {
624                Self::RegisterUpdatedContent(signature, action.try_into()?, entry)
625            }
626            ChainOpType::RegisterUpdatedRecord => {
627                Self::RegisterUpdatedRecord(signature, action.try_into()?, entry)
628            }
629            ChainOpType::RegisterDeletedBy => {
630                Self::RegisterDeletedBy(signature, action.try_into()?)
631            }
632            ChainOpType::RegisterDeletedEntryAction => {
633                Self::RegisterDeletedEntryAction(signature, action.try_into()?)
634            }
635            ChainOpType::RegisterAddLink => Self::RegisterAddLink(signature, action.try_into()?),
636            ChainOpType::RegisterRemoveLink => {
637                Self::RegisterRemoveLink(signature, action.try_into()?)
638            }
639        };
640        Ok(r)
641    }
642
643    /// "Normalize" the op by running it through `from_type` to give a sensible
644    /// interpretation of the potential absence of an entry.
645    ///
646    /// If the action contains no entry but an entry is provided anyway, ignore
647    /// the provided entry.
648    ///
649    /// This is useful when generating arbitrary ops.
650    #[cfg(feature = "test_utils")]
651    pub fn normalized(self) -> DhtOpResult<Self> {
652        let action = self.signed_action();
653        let entry = if action.entry_hash().is_none() {
654            None
655        } else {
656            self.entry().into_option().cloned()
657        };
658        Self::from_type(self.get_type(), action, entry)
659    }
660
661    /// Enzymatic countersigning session ops need special handling so that they
662    /// arrive at the enzyme and not elsewhere. If this isn't an enzymatic
663    /// countersigning session then the return will be None so can be used as
664    /// a boolean for filtering with is_some().
665    pub fn enzymatic_countersigning_enzyme(&self) -> Option<&AgentPubKey> {
666        if let Some(Entry::CounterSign(session_data, _)) = self.entry().into_option() {
667            if session_data.preflight_request().enzymatic {
668                session_data
669                    .preflight_request()
670                    .signing_agents
671                    .first()
672                    .map(|(pubkey, _)| pubkey)
673            } else {
674                None
675            }
676        } else {
677            None
678        }
679    }
680
681    /// Access to the Timestamp
682    pub fn timestamp(&self) -> Timestamp {
683        match self {
684            ChainOp::StoreRecord(_, a, _) => a.timestamp(),
685            ChainOp::StoreEntry(_, a, _) => a.timestamp(),
686            ChainOp::RegisterAgentActivity(_, a) => a.timestamp(),
687            ChainOp::RegisterUpdatedContent(_, a, _) => a.timestamp,
688            ChainOp::RegisterUpdatedRecord(_, a, _) => a.timestamp,
689            ChainOp::RegisterDeletedBy(_, a) => a.timestamp,
690            ChainOp::RegisterDeletedEntryAction(_, a) => a.timestamp,
691            ChainOp::RegisterAddLink(_, a) => a.timestamp,
692            ChainOp::RegisterRemoveLink(_, a) => a.timestamp,
693        }
694    }
695
696    /// returns a reference to the action author
697    pub fn author(&self) -> &AgentPubKey {
698        match self {
699            ChainOp::StoreRecord(_, a, _) => a.author(),
700            ChainOp::StoreEntry(_, a, _) => a.author(),
701            ChainOp::RegisterAgentActivity(_, a) => a.author(),
702            ChainOp::RegisterUpdatedContent(_, a, _) => &a.author,
703            ChainOp::RegisterUpdatedRecord(_, a, _) => &a.author,
704            ChainOp::RegisterDeletedBy(_, a) => &a.author,
705            ChainOp::RegisterDeletedEntryAction(_, a) => &a.author,
706            ChainOp::RegisterAddLink(_, a) => &a.author,
707            ChainOp::RegisterRemoveLink(_, a) => &a.author,
708        }
709    }
710
711    /// Calculate the op's sys validation dependency action hash
712    pub fn sys_validation_dependencies(&self) -> SysValDeps {
713        self.get_type().sys_validation_dependencies(&self.action())
714    }
715
716    /// Map the RecordEntry in the op using a function
717    pub fn map_entry(self, f: impl FnOnce(RecordEntry) -> RecordEntry) -> Self {
718        match self {
719            Self::StoreRecord(signature, action, entry) => {
720                Self::StoreRecord(signature, action, f(entry))
721            }
722            Self::RegisterUpdatedContent(signature, action, entry) => {
723                Self::RegisterUpdatedContent(signature, action, f(entry))
724            }
725            Self::RegisterUpdatedRecord(signature, action, entry) => {
726                Self::RegisterUpdatedRecord(signature, action, f(entry))
727            }
728            _ => self,
729        }
730    }
731}
732
733impl DhtOpLite {
734    /// Get the dht basis for where to send this op
735    pub fn dht_basis(&self) -> OpBasis {
736        match self {
737            Self::Chain(op) => op.dht_basis().clone(),
738            Self::Warrant(op) => op.dht_basis(),
739        }
740    }
741
742    /// If this is a chain op, return it
743    pub fn as_chain_op(&self) -> Option<&ChainOpLite> {
744        match self {
745            Self::Chain(op) => Some(op),
746            _ => None,
747        }
748    }
749
750    /// Get the type as a unit enum, for Display purposes
751    pub fn get_type(&self) -> DhtOpType {
752        match self {
753            Self::Chain(op) => op.get_type().into(),
754            Self::Warrant(op) => op.get_type().into(),
755        }
756    }
757
758    /// Get the AnyDhtHash which would be used in a `must_get_*` context.
759    ///
760    /// For instance, `must_get_entry` will use an EntryHash, and requires a
761    /// StoreEntry record to be integrated to succeed. All other must_gets take
762    /// an ActionHash.
763    pub fn fetch_dependency_hashes(&self) -> Vec<AnyDhtHash> {
764        match self {
765            Self::Chain(op) => match &**op {
766                ChainOpLite::StoreEntry(_, entry_hash, _) => vec![entry_hash.clone().into()],
767                other => vec![other.action_hash().clone().into()],
768            },
769            Self::Warrant(op) => match &op.proof {
770                WarrantProof::ChainIntegrity(w) => match w {
771                    ChainIntegrityWarrant::InvalidChainOp {
772                        action: action_hash,
773                        ..
774                    } => vec![action_hash.0.clone().into()],
775                    ChainIntegrityWarrant::ChainFork { action_pair, .. } => {
776                        vec![
777                            action_pair.0 .0.clone().into(),
778                            action_pair.1 .0.clone().into(),
779                        ]
780                    }
781                },
782            },
783        }
784    }
785}
786
787impl ChainOpLite {
788    /// Get the dht basis for where to send this op
789    pub fn dht_basis(&self) -> &OpBasis {
790        match self {
791            ChainOpLite::StoreRecord(_, _, b)
792            | ChainOpLite::StoreEntry(_, _, b)
793            | ChainOpLite::RegisterAgentActivity(_, b)
794            | ChainOpLite::RegisterUpdatedContent(_, _, b)
795            | ChainOpLite::RegisterUpdatedRecord(_, _, b)
796            | ChainOpLite::RegisterDeletedBy(_, b)
797            | ChainOpLite::RegisterDeletedEntryAction(_, b)
798            | ChainOpLite::RegisterAddLink(_, b)
799            | ChainOpLite::RegisterRemoveLink(_, b) => b,
800        }
801    }
802
803    /// Get the action hash from this op
804    pub fn action_hash(&self) -> &ActionHash {
805        match self {
806            Self::StoreRecord(h, _, _)
807            | Self::StoreEntry(h, _, _)
808            | Self::RegisterAgentActivity(h, _)
809            | Self::RegisterUpdatedContent(h, _, _)
810            | Self::RegisterUpdatedRecord(h, _, _)
811            | Self::RegisterDeletedBy(h, _)
812            | Self::RegisterDeletedEntryAction(h, _)
813            | Self::RegisterAddLink(h, _)
814            | Self::RegisterRemoveLink(h, _) => h,
815        }
816    }
817
818    /// Get the type as a unit enum, for Display purposes
819    pub fn get_type(&self) -> ChainOpType {
820        match self {
821            Self::StoreRecord(_, _, _) => ChainOpType::StoreRecord,
822            Self::StoreEntry(_, _, _) => ChainOpType::StoreEntry,
823            Self::RegisterUpdatedContent(_, _, _) => ChainOpType::RegisterUpdatedContent,
824            Self::RegisterUpdatedRecord(_, _, _) => ChainOpType::RegisterUpdatedRecord,
825            Self::RegisterAgentActivity(_, _) => ChainOpType::RegisterAgentActivity,
826            Self::RegisterDeletedBy(_, _) => ChainOpType::RegisterDeletedBy,
827            Self::RegisterDeletedEntryAction(_, _) => ChainOpType::RegisterDeletedEntryAction,
828            Self::RegisterAddLink(_, _) => ChainOpType::RegisterAddLink,
829            Self::RegisterRemoveLink(_, _) => ChainOpType::RegisterRemoveLink,
830        }
831    }
832
833    /// From a type with the hashes.
834    pub fn from_type(
835        op_type: ChainOpType,
836        action_hash: ActionHash,
837        action: &Action,
838    ) -> DhtOpResult<Self> {
839        let op = match op_type {
840            ChainOpType::StoreRecord => {
841                let entry_hash = action.entry_hash().cloned();
842                Self::StoreRecord(action_hash.clone(), entry_hash, action_hash.into())
843            }
844            ChainOpType::StoreEntry => {
845                let entry_hash = action
846                    .entry_hash()
847                    .cloned()
848                    .ok_or_else(|| DhtOpError::ActionWithoutEntry(action.clone()))?;
849                Self::StoreEntry(action_hash, entry_hash.clone(), entry_hash.into())
850            }
851            ChainOpType::RegisterAgentActivity => {
852                Self::RegisterAgentActivity(action_hash, action.author().clone().into())
853            }
854            ChainOpType::RegisterUpdatedContent => {
855                let entry_hash = action
856                    .entry_hash()
857                    .cloned()
858                    .ok_or_else(|| DhtOpError::ActionWithoutEntry(action.clone()))?;
859                let basis = match action {
860                    Action::Update(update) => update.original_entry_address.clone(),
861                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
862                };
863                Self::RegisterUpdatedContent(action_hash, entry_hash, basis.into())
864            }
865            ChainOpType::RegisterUpdatedRecord => {
866                let entry_hash = action
867                    .entry_hash()
868                    .cloned()
869                    .ok_or_else(|| DhtOpError::ActionWithoutEntry(action.clone()))?;
870                let basis = match action {
871                    Action::Update(update) => update.original_entry_address.clone(),
872                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
873                };
874                Self::RegisterUpdatedRecord(action_hash, entry_hash, basis.into())
875            }
876            ChainOpType::RegisterDeletedBy => {
877                let basis = match action {
878                    Action::Delete(delete) => delete.deletes_address.clone(),
879                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
880                };
881                Self::RegisterDeletedBy(action_hash, basis.into())
882            }
883            ChainOpType::RegisterDeletedEntryAction => {
884                let basis = match action {
885                    Action::Delete(delete) => delete.deletes_entry_address.clone(),
886                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
887                };
888                Self::RegisterDeletedEntryAction(action_hash, basis.into())
889            }
890            ChainOpType::RegisterAddLink => {
891                let basis = match action {
892                    Action::CreateLink(create_link) => create_link.base_address.clone(),
893                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
894                };
895                Self::RegisterAddLink(action_hash, basis)
896            }
897            ChainOpType::RegisterRemoveLink => {
898                let basis = match action {
899                    Action::DeleteLink(delete_link) => delete_link.base_address.clone(),
900                    _ => return Err(DhtOpError::OpActionMismatch(op_type, action.action_type())),
901                };
902                Self::RegisterRemoveLink(action_hash, basis)
903            }
904        };
905        Ok(op)
906    }
907}
908
909#[allow(missing_docs)]
910#[derive(Serialize, Debug)]
911pub enum ChainOpUniqueForm<'a> {
912    // As an optimization, we don't include signatures. They would be redundant
913    // with actions and therefore would waste hash/comparison time to include.
914    StoreRecord(&'a Action),
915    StoreEntry(&'a NewEntryAction),
916    RegisterAgentActivity(&'a Action),
917    RegisterUpdatedContent(&'a action::Update),
918    RegisterUpdatedRecord(&'a action::Update),
919    RegisterDeletedBy(&'a action::Delete),
920    RegisterDeletedEntryAction(&'a action::Delete),
921    RegisterAddLink(&'a action::CreateLink),
922    RegisterRemoveLink(&'a action::DeleteLink),
923}
924
925impl<'a> ChainOpUniqueForm<'a> {
926    fn basis(&'a self) -> OpBasis {
927        match self {
928            ChainOpUniqueForm::StoreRecord(action) => ActionHash::with_data_sync(*action).into(),
929            ChainOpUniqueForm::StoreEntry(action) => action.entry().clone().into(),
930            ChainOpUniqueForm::RegisterAgentActivity(action) => action.author().clone().into(),
931            ChainOpUniqueForm::RegisterUpdatedContent(action) => {
932                action.original_entry_address.clone().into()
933            }
934            ChainOpUniqueForm::RegisterUpdatedRecord(action) => {
935                action.original_action_address.clone().into()
936            }
937            ChainOpUniqueForm::RegisterDeletedBy(action) => action.deletes_address.clone().into(),
938            ChainOpUniqueForm::RegisterDeletedEntryAction(action) => {
939                action.deletes_entry_address.clone().into()
940            }
941            ChainOpUniqueForm::RegisterAddLink(action) => action.base_address.clone(),
942            ChainOpUniqueForm::RegisterRemoveLink(action) => action.base_address.clone(),
943        }
944    }
945
946    /// Get the dht op hash without cloning the action.
947    pub fn op_hash(op_type: ChainOpType, action: Action) -> DhtOpResult<(Action, DhtOpHash)> {
948        match op_type {
949            ChainOpType::StoreRecord => {
950                let hash = DhtOpHash::with_data_sync(&ChainOpUniqueForm::StoreRecord(&action));
951                Ok((action, hash))
952            }
953            ChainOpType::StoreEntry => {
954                let action = action.try_into()?;
955                let hash = DhtOpHash::with_data_sync(&ChainOpUniqueForm::StoreEntry(&action));
956                Ok((action.into(), hash))
957            }
958            ChainOpType::RegisterAgentActivity => {
959                let hash =
960                    DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterAgentActivity(&action));
961                Ok((action, hash))
962            }
963            ChainOpType::RegisterUpdatedContent => {
964                let action = action.try_into()?;
965                let hash =
966                    DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterUpdatedContent(&action));
967                Ok((action.into(), hash))
968            }
969            ChainOpType::RegisterUpdatedRecord => {
970                let action = action.try_into()?;
971                let hash =
972                    DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterUpdatedRecord(&action));
973                Ok((action.into(), hash))
974            }
975            ChainOpType::RegisterDeletedBy => {
976                let action = action.try_into()?;
977                let hash =
978                    DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterDeletedBy(&action));
979                Ok((action.into(), hash))
980            }
981            ChainOpType::RegisterDeletedEntryAction => {
982                let action = action.try_into()?;
983                let hash = DhtOpHash::with_data_sync(
984                    &ChainOpUniqueForm::RegisterDeletedEntryAction(&action),
985                );
986                Ok((action.into(), hash))
987            }
988            ChainOpType::RegisterAddLink => {
989                let action = action.try_into()?;
990                let hash = DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterAddLink(&action));
991                Ok((action.into(), hash))
992            }
993            ChainOpType::RegisterRemoveLink => {
994                let action = action.try_into()?;
995                let hash =
996                    DhtOpHash::with_data_sync(&ChainOpUniqueForm::RegisterRemoveLink(&action));
997                Ok((action.into(), hash))
998            }
999        }
1000    }
1001}
1002
1003/// Produce all DhtOps for a Record
1004pub fn produce_ops_from_record(record: &Record) -> DhtOpResult<Vec<ChainOp>> {
1005    let op_lites = produce_op_lites_from_records(vec![record])?;
1006    let (shh, entry) = record.clone().into_inner();
1007    let SignedActionHashed {
1008        hashed: ActionHashed {
1009            content: action, ..
1010        },
1011        signature,
1012    } = shh;
1013
1014    let mut ops = Vec::with_capacity(op_lites.len());
1015
1016    for op_light in op_lites {
1017        let signature = signature.clone();
1018        let action = action.clone();
1019        let op = match op_light {
1020            ChainOpLite::StoreRecord(_, _, _) => {
1021                ChainOp::StoreRecord(signature, action, entry.clone())
1022            }
1023            ChainOpLite::StoreEntry(_, _, _) => {
1024                let new_entry_action = action.clone().try_into()?;
1025                let e = match entry.clone().into_option() {
1026                    Some(e) => e,
1027                    None => {
1028                        // Entry is private so continue
1029                        continue;
1030                    }
1031                };
1032                ChainOp::StoreEntry(signature, new_entry_action, e)
1033            }
1034            ChainOpLite::RegisterAgentActivity(_, _) => {
1035                ChainOp::RegisterAgentActivity(signature, action)
1036            }
1037            ChainOpLite::RegisterUpdatedContent(_, _, _) => {
1038                let entry_update = action.try_into()?;
1039                ChainOp::RegisterUpdatedContent(signature, entry_update, entry.clone())
1040            }
1041            ChainOpLite::RegisterUpdatedRecord(_, _, _) => {
1042                let entry_update = action.try_into()?;
1043                ChainOp::RegisterUpdatedRecord(signature, entry_update, entry.clone())
1044            }
1045            ChainOpLite::RegisterDeletedEntryAction(_, _) => {
1046                let record_delete = action.try_into()?;
1047                ChainOp::RegisterDeletedEntryAction(signature, record_delete)
1048            }
1049            ChainOpLite::RegisterDeletedBy(_, _) => {
1050                let record_delete = action.try_into()?;
1051                ChainOp::RegisterDeletedBy(signature, record_delete)
1052            }
1053            ChainOpLite::RegisterAddLink(_, _) => {
1054                let link_add = action.try_into()?;
1055                ChainOp::RegisterAddLink(signature, link_add)
1056            }
1057            ChainOpLite::RegisterRemoveLink(_, _) => {
1058                let link_remove = action.try_into()?;
1059                ChainOp::RegisterRemoveLink(signature, link_remove)
1060            }
1061        };
1062        ops.push(op);
1063    }
1064    Ok(ops)
1065}
1066
1067/// Produce all the op lites for these records
1068pub fn produce_op_lites_from_records(actions: Vec<&Record>) -> DhtOpResult<Vec<ChainOpLite>> {
1069    let actions_and_hashes = actions.into_iter().map(|e| {
1070        (
1071            e.action_address(),
1072            e.action(),
1073            e.action().entry_data().map(|(h, _)| h.clone()),
1074        )
1075    });
1076    produce_op_lites_from_iter(actions_and_hashes)
1077}
1078
1079/// Produce all the op lites from this record group
1080/// with a shared entry
1081pub fn produce_op_lites_from_record_group(
1082    records: &RecordGroup<'_>,
1083) -> DhtOpResult<Vec<ChainOpLite>> {
1084    let actions_and_hashes = records.actions_and_hashes();
1085    let maybe_entry_hash = Some(records.entry_hash());
1086    produce_op_lites_from_parts(actions_and_hashes, maybe_entry_hash)
1087}
1088
1089/// Data minimal clone (no cloning entries) cheap &Record to DhtOpLite conversion
1090fn produce_op_lites_from_parts<'a>(
1091    actions_and_hashes: impl Iterator<Item = (&'a ActionHash, &'a Action)>,
1092    maybe_entry_hash: Option<&EntryHash>,
1093) -> DhtOpResult<Vec<ChainOpLite>> {
1094    let iter = actions_and_hashes.map(|(head, hash)| (head, hash, maybe_entry_hash.cloned()));
1095    produce_op_lites_from_iter(iter)
1096}
1097
1098/// Produce op lites from iter of (action hash, action, maybe entry).
1099pub fn produce_op_lites_from_iter<'a>(
1100    iter: impl Iterator<Item = (&'a ActionHash, &'a Action, Option<EntryHash>)>,
1101) -> DhtOpResult<Vec<ChainOpLite>> {
1102    let mut ops = Vec::new();
1103
1104    for (action_hash, action, maybe_entry_hash) in iter {
1105        let op_lites = action_to_op_types(action)
1106            .into_iter()
1107            .filter_map(|op_type| {
1108                let op_light = match (op_type, action) {
1109                    (ChainOpType::StoreRecord, _) => {
1110                        let store_record_basis = ChainOpUniqueForm::StoreRecord(action).basis();
1111                        ChainOpLite::StoreRecord(
1112                            action_hash.clone(),
1113                            maybe_entry_hash.clone(),
1114                            store_record_basis,
1115                        )
1116                    }
1117                    (ChainOpType::RegisterAgentActivity, _) => {
1118                        let register_activity_basis =
1119                            ChainOpUniqueForm::RegisterAgentActivity(action).basis();
1120                        ChainOpLite::RegisterAgentActivity(
1121                            action_hash.clone(),
1122                            register_activity_basis,
1123                        )
1124                    }
1125                    (ChainOpType::StoreEntry, Action::Create(create)) => ChainOpLite::StoreEntry(
1126                        action_hash.clone(),
1127                        maybe_entry_hash.clone()?,
1128                        ChainOpUniqueForm::StoreEntry(&NewEntryAction::Create(create.clone()))
1129                            .basis(),
1130                    ),
1131                    (ChainOpType::StoreEntry, Action::Update(update)) => ChainOpLite::StoreEntry(
1132                        action_hash.clone(),
1133                        maybe_entry_hash.clone()?,
1134                        ChainOpUniqueForm::StoreEntry(&NewEntryAction::Update(update.clone()))
1135                            .basis(),
1136                    ),
1137                    (ChainOpType::RegisterUpdatedContent, Action::Update(update)) => {
1138                        ChainOpLite::RegisterUpdatedContent(
1139                            action_hash.clone(),
1140                            maybe_entry_hash.clone()?,
1141                            ChainOpUniqueForm::RegisterUpdatedContent(update).basis(),
1142                        )
1143                    }
1144                    (ChainOpType::RegisterUpdatedRecord, Action::Update(update)) => {
1145                        ChainOpLite::RegisterUpdatedRecord(
1146                            action_hash.clone(),
1147                            maybe_entry_hash.clone()?,
1148                            ChainOpUniqueForm::RegisterUpdatedRecord(update).basis(),
1149                        )
1150                    }
1151                    (ChainOpType::RegisterDeletedBy, Action::Delete(delete)) => {
1152                        ChainOpLite::RegisterDeletedBy(
1153                            action_hash.clone(),
1154                            ChainOpUniqueForm::RegisterDeletedBy(delete).basis(),
1155                        )
1156                    }
1157                    (ChainOpType::RegisterDeletedEntryAction, Action::Delete(delete)) => {
1158                        ChainOpLite::RegisterDeletedEntryAction(
1159                            action_hash.clone(),
1160                            ChainOpUniqueForm::RegisterDeletedEntryAction(delete).basis(),
1161                        )
1162                    }
1163                    (ChainOpType::RegisterAddLink, Action::CreateLink(create_link)) => {
1164                        ChainOpLite::RegisterAddLink(
1165                            action_hash.clone(),
1166                            ChainOpUniqueForm::RegisterAddLink(create_link).basis(),
1167                        )
1168                    }
1169                    (ChainOpType::RegisterRemoveLink, Action::DeleteLink(delete_link)) => {
1170                        ChainOpLite::RegisterRemoveLink(
1171                            action_hash.clone(),
1172                            ChainOpUniqueForm::RegisterRemoveLink(delete_link).basis(),
1173                        )
1174                    }
1175                    _ => return None,
1176                };
1177                Some(op_light)
1178            });
1179        ops.extend(op_lites);
1180    }
1181    Ok(ops)
1182}
1183
1184/// Produce op types from a given [`Action`].
1185pub fn action_to_op_types(action: &Action) -> Vec<ChainOpType> {
1186    match action {
1187        Action::Dna(_)
1188        | Action::OpenChain(_)
1189        | Action::CloseChain(_)
1190        | Action::AgentValidationPkg(_)
1191        | Action::InitZomesComplete(_) => {
1192            vec![ChainOpType::StoreRecord, ChainOpType::RegisterAgentActivity]
1193        }
1194        Action::CreateLink(_) => vec![
1195            ChainOpType::StoreRecord,
1196            ChainOpType::RegisterAgentActivity,
1197            ChainOpType::RegisterAddLink,
1198        ],
1199
1200        Action::DeleteLink(_) => vec![
1201            ChainOpType::StoreRecord,
1202            ChainOpType::RegisterAgentActivity,
1203            ChainOpType::RegisterRemoveLink,
1204        ],
1205        Action::Create(_) => vec![
1206            ChainOpType::StoreRecord,
1207            ChainOpType::RegisterAgentActivity,
1208            ChainOpType::StoreEntry,
1209        ],
1210        Action::Update(_) => vec![
1211            ChainOpType::StoreRecord,
1212            ChainOpType::RegisterAgentActivity,
1213            ChainOpType::StoreEntry,
1214            ChainOpType::RegisterUpdatedContent,
1215            ChainOpType::RegisterUpdatedRecord,
1216        ],
1217        Action::Delete(_) => vec![
1218            ChainOpType::StoreRecord,
1219            ChainOpType::RegisterAgentActivity,
1220            ChainOpType::RegisterDeletedBy,
1221            ChainOpType::RegisterDeletedEntryAction,
1222        ],
1223    }
1224}
1225
1226// This has to be done manually because the macro
1227// implements both directions and that isn't possible with references
1228// TODO: Maybe add a one-way version to holochain_serialized_bytes?
1229impl<'a> TryFrom<&ChainOpUniqueForm<'a>> for SerializedBytes {
1230    type Error = SerializedBytesError;
1231    fn try_from(u: &ChainOpUniqueForm<'a>) -> Result<Self, Self::Error> {
1232        match holochain_serialized_bytes::encode(u) {
1233            Ok(v) => Ok(SerializedBytes::from(
1234                holochain_serialized_bytes::UnsafeBytes::from(v),
1235            )),
1236            Err(e) => Err(SerializedBytesError::Serialize(e.to_string())),
1237        }
1238    }
1239}
1240
1241/// A DhtOp paired with its DhtOpHash
1242pub type DhtOpHashed = HoloHashed<DhtOp>;
1243
1244/// A ChainOp paired with its ChainOpHash
1245pub type ChainOpHashed = HoloHashed<ChainOp>;
1246
1247impl HashableContent for DhtOp {
1248    type HashType = hash_type::DhtOp;
1249
1250    fn hash_type(&self) -> Self::HashType {
1251        hash_type::DhtOp
1252    }
1253
1254    fn hashable_content(&self) -> HashableContentBytes {
1255        match self {
1256            DhtOp::ChainOp(op) => op.hashable_content(),
1257            DhtOp::WarrantOp(op) => op.hashable_content(),
1258        }
1259    }
1260}
1261
1262impl HashableContent for ChainOp {
1263    type HashType = hash_type::DhtOp;
1264
1265    fn hash_type(&self) -> Self::HashType {
1266        hash_type::DhtOp
1267    }
1268
1269    fn hashable_content(&self) -> HashableContentBytes {
1270        HashableContentBytes::Content(
1271            (&self.as_unique_form())
1272                .try_into()
1273                .expect("Could not serialize HashableContent"),
1274        )
1275    }
1276}
1277
1278impl HashableContent for ChainOpUniqueForm<'_> {
1279    type HashType = hash_type::DhtOp;
1280
1281    fn hash_type(&self) -> Self::HashType {
1282        hash_type::DhtOp
1283    }
1284
1285    fn hashable_content(&self) -> HashableContentBytes {
1286        HashableContentBytes::Content(
1287            UnsafeBytes::from(
1288                holochain_serialized_bytes::encode(self)
1289                    .expect("Could not serialize HashableContent"),
1290            )
1291            .into(),
1292        )
1293    }
1294}
1295
1296#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SerializedBytes)]
1297/// Condensed version of ops for sending across the wire.
1298pub enum WireOps {
1299    /// Response for get entry.
1300    Entry(WireEntryOps),
1301    /// Response for get record.
1302    Record(WireRecordOps),
1303    /// A warrant in place of data in the case that the data is invalid.
1304    /// There is no "wire" version because this is about as compact as it gets.
1305    Warrant(Box<WarrantOp>),
1306}
1307
1308impl WireOps {
1309    /// Render the wire ops to DhtOps.
1310    pub fn render(self) -> DhtOpResult<RenderedOps> {
1311        match self {
1312            WireOps::Entry(o) => o.render(),
1313            WireOps::Record(o) => o.render(),
1314            WireOps::Warrant(warrant) => Ok(RenderedOps {
1315                entry: Default::default(),
1316                ops: Default::default(),
1317                warrant: Some(*warrant),
1318            }),
1319        }
1320    }
1321}
1322
1323#[derive(Debug, PartialEq, Eq, Clone)]
1324/// The data rendered from a wire op to place in the database.
1325pub struct RenderedOp {
1326    /// The action to insert into the database.
1327    pub action: SignedActionHashed,
1328    /// The action to insert into the database.
1329    pub op_light: DhtOpLite,
1330    /// The hash of the [`DhtOp`]
1331    pub op_hash: DhtOpHash,
1332    /// The validation status of the action.
1333    pub validation_status: Option<ValidationStatus>,
1334}
1335
1336impl RenderedOp {
1337    /// Try to create a new rendered op from wire data.
1338    /// This function computes all the hashes and
1339    /// reconstructs the full actions.
1340    pub fn new(
1341        action: Action,
1342        signature: Signature,
1343        validation_status: Option<ValidationStatus>,
1344        op_type: ChainOpType,
1345    ) -> DhtOpResult<Self> {
1346        let (action, op_hash) = ChainOpUniqueForm::op_hash(op_type, action)?;
1347        let action_hashed = ActionHashed::from_content_sync(action);
1348        // TODO: Verify signature?
1349        let action = SignedActionHashed::with_presigned(action_hashed, signature);
1350        let op_light =
1351            ChainOpLite::from_type(op_type, action.as_hash().clone(), action.action())?.into();
1352        Ok(Self {
1353            action,
1354            op_light,
1355            op_hash,
1356            validation_status,
1357        })
1358    }
1359}
1360
1361#[derive(Debug, PartialEq, Eq, Clone, Default)]
1362/// The full data for insertion into the database.
1363/// The reason we don't use [`DhtOp`] is because we don't
1364/// want to clone the entry for every action.
1365pub struct RenderedOps {
1366    /// Entry for the ops if there is one.
1367    pub entry: Option<EntryHashed>,
1368    /// Op data to insert.
1369    pub ops: Vec<RenderedOp>,
1370    /// Warrant, if the data is invalid.
1371    /// If this is Some, all other fields should be empty, and vice versa.
1372    // TODO: RenderedOps really should be an enum, for the valid and invalid cases.
1373    pub warrant: Option<WarrantOp>,
1374}
1375
1376/// Type for deriving ordering of DhtOps
1377/// Don't change the order of this enum unless
1378/// you mean to change the order we process ops
1379#[allow(missing_docs)]
1380#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
1381pub enum OpNumericalOrder {
1382    RegisterAgentActivity = 0,
1383    StoreEntry,
1384    StoreRecord,
1385    RegisterUpdatedContent,
1386    RegisterUpdatedRecord,
1387    RegisterDeletedBy,
1388    RegisterDeletedEntryAction,
1389    RegisterAddLink,
1390    RegisterRemoveLink,
1391    ChainIntegrityWarrant,
1392}
1393
1394/// This is used as an index for ordering ops in our database.
1395/// It gives the most likely ordering where dependencies will come
1396/// first.
1397#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
1398pub struct OpOrder {
1399    order: OpNumericalOrder,
1400    timestamp: holochain_zome_types::timestamp::Timestamp,
1401}
1402
1403impl std::fmt::Display for OpOrder {
1404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1405        write!(
1406            f,
1407            "{}{:019}",
1408            self.order as u8,
1409            // clamp unrealistic negative timestamps to 0
1410            i64::max(0, self.timestamp.as_micros())
1411        )
1412    }
1413}
1414
1415impl OpOrder {
1416    /// Create a new ordering from a op type and timestamp.
1417    pub fn new(
1418        op_type: impl Into<DhtOpType>,
1419        timestamp: holochain_zome_types::timestamp::Timestamp,
1420    ) -> Self {
1421        let order = match op_type.into() {
1422            DhtOpType::Chain(ChainOpType::StoreRecord) => OpNumericalOrder::StoreRecord,
1423            DhtOpType::Chain(ChainOpType::StoreEntry) => OpNumericalOrder::StoreEntry,
1424            DhtOpType::Chain(ChainOpType::RegisterAgentActivity) => {
1425                OpNumericalOrder::RegisterAgentActivity
1426            }
1427            DhtOpType::Chain(ChainOpType::RegisterUpdatedContent) => {
1428                OpNumericalOrder::RegisterUpdatedContent
1429            }
1430            DhtOpType::Chain(ChainOpType::RegisterUpdatedRecord) => {
1431                OpNumericalOrder::RegisterUpdatedRecord
1432            }
1433            DhtOpType::Chain(ChainOpType::RegisterDeletedBy) => OpNumericalOrder::RegisterDeletedBy,
1434            DhtOpType::Chain(ChainOpType::RegisterDeletedEntryAction) => {
1435                OpNumericalOrder::RegisterDeletedEntryAction
1436            }
1437            DhtOpType::Chain(ChainOpType::RegisterAddLink) => OpNumericalOrder::RegisterAddLink,
1438            DhtOpType::Chain(ChainOpType::RegisterRemoveLink) => {
1439                OpNumericalOrder::RegisterRemoveLink
1440            }
1441            DhtOpType::Warrant(WarrantOpType::ChainIntegrityWarrant) => {
1442                OpNumericalOrder::ChainIntegrityWarrant
1443            }
1444        };
1445        Self { order, timestamp }
1446    }
1447}
1448
1449impl holochain_sqlite::rusqlite::ToSql for OpOrder {
1450    fn to_sql(
1451        &self,
1452    ) -> holochain_sqlite::rusqlite::Result<holochain_sqlite::rusqlite::types::ToSqlOutput> {
1453        Ok(holochain_sqlite::rusqlite::types::ToSqlOutput::Owned(
1454            self.to_string().into(),
1455        ))
1456    }
1457}