holochain_types/chain/
chain_item.rs

1use super::*;
2use derive_more::Display;
3use thiserror::Error;
4
5/// Abstraction over an item in a chain.
6// Alternate implementations are only used for testing, so this should not
7// add a large monomorphization overhead
8pub trait ChainItem: Clone + PartialEq + Eq + std::fmt::Debug + Send + Sync {
9    /// The type used to represent a hash of this item
10    type Hash: Into<ActionHash>
11        + Clone
12        + PartialEq
13        + Eq
14        + Ord
15        + std::hash::Hash
16        + std::fmt::Debug
17        + Send
18        + Sync;
19
20    /// The sequence in the chain
21    fn seq(&self) -> u32;
22
23    /// The hash of this item
24    fn get_timestamp(&self) -> Timestamp;
25
26    /// The hash of this item
27    fn get_hash(&self) -> &Self::Hash;
28
29    /// The hash of the previous item
30    fn prev_hash(&self) -> Option<&Self::Hash>;
31
32    /// A display representation of the item
33    fn to_display(&self) -> String;
34}
35
36/// Alias for getting the associated hash type of a ChainItem
37pub type ChainItemHash<I> = <I as ChainItem>::Hash;
38
39impl ChainItem for ActionHashed {
40    type Hash = ActionHash;
41
42    fn seq(&self) -> u32 {
43        self.action_seq()
44    }
45
46    fn get_timestamp(&self) -> Timestamp {
47        self.timestamp()
48    }
49
50    fn get_hash(&self) -> &Self::Hash {
51        self.as_hash()
52    }
53
54    fn prev_hash(&self) -> Option<&Self::Hash> {
55        self.prev_action()
56    }
57
58    fn to_display(&self) -> String {
59        format!("{}", self.content)
60    }
61}
62
63impl ChainItem for SignedActionHashed {
64    type Hash = ActionHash;
65
66    fn seq(&self) -> u32 {
67        self.hashed.seq()
68    }
69
70    fn get_timestamp(&self) -> Timestamp {
71        self.hashed.timestamp()
72    }
73
74    fn get_hash(&self) -> &Self::Hash {
75        self.hashed.get_hash()
76    }
77
78    fn prev_hash(&self) -> Option<&Self::Hash> {
79        self.hashed.prev_hash()
80    }
81
82    fn to_display(&self) -> String {
83        format!("{}", self.hashed.content)
84    }
85}
86
87/// Validate that a sequence of actions forms a valid hash chain via `prev_action`,
88/// with an optional starting point.
89pub fn validate_chain<'iter, A: 'iter + ChainItem>(
90    mut actions: impl Iterator<Item = &'iter A>,
91    persisted_chain_head: &Option<(A::Hash, u32)>,
92) -> PrevActionResult<()> {
93    // Check the chain starts in a valid way.
94    let mut last_item = match actions.next() {
95        Some(item) => {
96            match persisted_chain_head {
97                Some((prev_hash, prev_seq)) => {
98                    check_prev_action_chain(prev_hash, *prev_seq, item)?;
99                }
100                None => {
101                    // If there's no persisted chain head, then the first action
102                    // must have no parent.
103                    if item.prev_hash().is_some() {
104                        return Err((PrevActionErrorKind::InvalidRoot, item).into());
105                    }
106                }
107            }
108            (item.get_hash(), item.seq())
109        }
110        None => return Ok(()),
111    };
112
113    for item in actions {
114        // Check each item of the chain is valid.
115        check_prev_action_chain(last_item.0, last_item.1, item)?;
116        last_item = (item.get_hash(), item.seq());
117    }
118    Ok(())
119}
120
121// Check the action is valid for the previous action.
122fn check_prev_action_chain<A: ChainItem>(
123    prev_action_hash: &A::Hash,
124    prev_action_seq: u32,
125    action: &A,
126) -> Result<(), PrevActionError> {
127    // The root cannot appear later in the chain
128    if action.prev_hash().is_none() {
129        Err((PrevActionErrorKind::MissingPrev, action).into())
130    } else if action.prev_hash() != Some(prev_action_hash) {
131        // Check the prev hash matches.
132        Err((PrevActionErrorKind::HashMismatch(action.seq()), action).into())
133    } else if action.seq().checked_sub(1) != Some(prev_action_seq) {
134        // Check the prev seq is one less.
135        Err((
136            PrevActionErrorKind::InvalidSeq(action.seq(), prev_action_seq),
137            action,
138        )
139            .into())
140    } else {
141        Ok(())
142    }
143}
144
145/// Alias
146pub type PrevActionResult<T> = Result<T, PrevActionError>;
147
148/// Context information for an invalid action to make it easier to trace in errors.
149#[derive(Error, Debug, Display, PartialEq, Eq)]
150#[display(
151    "{} - with context seq={}, action_hash={:?}, action=[{}]",
152    source,
153    seq,
154    action_hash,
155    action_display
156)]
157#[allow(missing_docs)]
158pub struct PrevActionError {
159    #[source]
160    pub source: PrevActionErrorKind,
161    pub seq: u32,
162    pub action_hash: ActionHash,
163    pub action_display: String,
164}
165
166impl<A: ChainItem> From<(PrevActionErrorKind, &A)> for PrevActionError {
167    fn from((inner, action): (PrevActionErrorKind, &A)) -> Self {
168        PrevActionError {
169            source: inner,
170            seq: action.seq(),
171            action_hash: action.get_hash().clone().into(),
172            action_display: action.to_display(),
173        }
174    }
175}
176
177impl From<(PrevActionErrorKind, Action)> for PrevActionError {
178    fn from((inner, action): (PrevActionErrorKind, Action)) -> Self {
179        PrevActionError {
180            source: inner,
181            seq: action.action_seq(),
182            action_hash: action.to_hash(),
183            action_display: format!("{action}"),
184        }
185    }
186}
187
188#[derive(Error, Debug, PartialEq, Eq)]
189#[allow(missing_docs)]
190pub enum PrevActionErrorKind {
191    #[error("The previous action hash specified in an action doesn't match the actual previous action. Seq: {0}")]
192    HashMismatch(u32),
193    #[error("Root of source chain must be Dna")]
194    InvalidRoot,
195    #[error("No more actions are allowed after a chain close")]
196    ActionAfterChainClose,
197    #[error("Previous action sequence number {1} != ({0} - 1)")]
198    InvalidSeq(u32, u32),
199    #[error("Action is not the first, so needs previous action")]
200    MissingPrev,
201    #[error("The previous action's timestamp is not before the current action's timestamp: {0:?} >= {1:?}")]
202    Timestamp(Timestamp, Timestamp),
203    #[error("The previous action's author does not match the current action's author: {0} != {1}")]
204    Author(AgentPubKey, AgentPubKey),
205    #[error("It is invalid for these two actions to be paired with each other. context: {0}, actions: {1:?}")]
206    InvalidSuccessor(String, Box<(Action, Action)>),
207}