holochain_types/chain/
chain_item.rs1use super::*;
2use derive_more::Display;
3use thiserror::Error;
4
5pub trait ChainItem: Clone + PartialEq + Eq + std::fmt::Debug + Send + Sync {
9 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 fn seq(&self) -> u32;
22
23 fn get_timestamp(&self) -> Timestamp;
25
26 fn get_hash(&self) -> &Self::Hash;
28
29 fn prev_hash(&self) -> Option<&Self::Hash>;
31
32 fn to_display(&self) -> String;
34}
35
36pub 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
87pub 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 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 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_prev_action_chain(last_item.0, last_item.1, item)?;
116 last_item = (item.get_hash(), item.seq());
117 }
118 Ok(())
119}
120
121fn check_prev_action_chain<A: ChainItem>(
123 prev_action_hash: &A::Hash,
124 prev_action_seq: u32,
125 action: &A,
126) -> Result<(), PrevActionError> {
127 if action.prev_hash().is_none() {
129 Err((PrevActionErrorKind::MissingPrev, action).into())
130 } else if action.prev_hash() != Some(prev_action_hash) {
131 Err((PrevActionErrorKind::HashMismatch(action.seq()), action).into())
133 } else if action.seq().checked_sub(1) != Some(prev_action_seq) {
134 Err((
136 PrevActionErrorKind::InvalidSeq(action.seq(), prev_action_seq),
137 action,
138 )
139 .into())
140 } else {
141 Ok(())
142 }
143}
144
145pub type PrevActionResult<T> = Result<T, PrevActionError>;
147
148#[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}