holochain_types/chain/
chain_item.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use super::*;

use derive_more::Display;
use thiserror::Error;

/// Abstraction over an item in a chain.
// Alternate implementations are only used for testing, so this should not
// add a large monomorphization overhead
pub trait ChainItem: Clone + PartialEq + Eq + std::fmt::Debug + Send + Sync {
    /// The type used to represent a hash of this item
    type Hash: Into<ActionHash>
        + Clone
        + PartialEq
        + Eq
        + Ord
        + std::hash::Hash
        + std::fmt::Debug
        + Send
        + Sync;

    /// The sequence in the chain
    fn seq(&self) -> u32;

    /// The hash of this item
    fn get_hash(&self) -> &Self::Hash;

    /// The hash of the previous item
    fn prev_hash(&self) -> Option<&Self::Hash>;

    /// A display representation of the item
    fn to_display(&self) -> String;
}

/// Alias for getting the associated hash type of a ChainItem
pub type ChainItemHash<I> = <I as ChainItem>::Hash;

impl ChainItem for ActionHashed {
    type Hash = ActionHash;

    fn seq(&self) -> u32 {
        self.action_seq()
    }

    fn get_hash(&self) -> &Self::Hash {
        self.as_hash()
    }

    fn prev_hash(&self) -> Option<&Self::Hash> {
        self.prev_action()
    }

    fn to_display(&self) -> String {
        format!("{}", self.content)
    }
}

impl ChainItem for SignedActionHashed {
    type Hash = ActionHash;

    fn seq(&self) -> u32 {
        self.hashed.seq()
    }

    fn get_hash(&self) -> &Self::Hash {
        self.hashed.get_hash()
    }

    fn prev_hash(&self) -> Option<&Self::Hash> {
        self.hashed.prev_hash()
    }

    fn to_display(&self) -> String {
        format!("{}", self.hashed.content)
    }
}

/// Validate that a sequence of actions forms a valid hash chain via `prev_action`,
/// with an optional starting point.
pub fn validate_chain<'iter, A: 'iter + ChainItem>(
    mut actions: impl Iterator<Item = &'iter A>,
    persisted_chain_head: &Option<(A::Hash, u32)>,
) -> PrevActionResult<()> {
    // Check the chain starts in a valid way.
    let mut last_item = match actions.next() {
        Some(item) => {
            match persisted_chain_head {
                Some((prev_hash, prev_seq)) => {
                    check_prev_action_chain(prev_hash, *prev_seq, item)?;
                }
                None => {
                    // If there's no persisted chain head, then the first action
                    // must have no parent.
                    if item.prev_hash().is_some() {
                        return Err((PrevActionErrorKind::InvalidRoot, item).into());
                    }
                }
            }
            (item.get_hash(), item.seq())
        }
        None => return Ok(()),
    };

    for item in actions {
        // Check each item of the chain is valid.
        check_prev_action_chain(last_item.0, last_item.1, item)?;
        last_item = (item.get_hash(), item.seq());
    }
    Ok(())
}

// Check the action is valid for the previous action.
fn check_prev_action_chain<A: ChainItem>(
    prev_action_hash: &A::Hash,
    prev_action_seq: u32,
    action: &A,
) -> Result<(), PrevActionError> {
    // The root cannot appear later in the chain
    if action.prev_hash().is_none() {
        Err((PrevActionErrorKind::MissingPrev, action).into())
    } else if action.prev_hash().map_or(true, |p| p != prev_action_hash) {
        // Check the prev hash matches.
        Err((PrevActionErrorKind::HashMismatch(action.seq()), action).into())
    } else if action
        .seq()
        .checked_sub(1)
        .map_or(true, |s| prev_action_seq != s)
    {
        // Check the prev seq is one less.
        Err((
            PrevActionErrorKind::InvalidSeq(action.seq(), prev_action_seq),
            action,
        )
            .into())
    } else {
        Ok(())
    }
}

/// Alias
pub type PrevActionResult<T> = Result<T, PrevActionError>;

/// Context information for an invalid action to make it easier to trace in errors.
#[derive(Error, Debug, Display, PartialEq, Eq)]
#[display(
    fmt = "{} - with context seq={}, action_hash={:?}, action=[{}]",
    source,
    seq,
    action_hash,
    action_display
)]
#[allow(missing_docs)]
pub struct PrevActionError {
    #[source]
    pub source: PrevActionErrorKind,
    pub seq: u32,
    pub action_hash: ActionHash,
    pub action_display: String,
}

impl<A: ChainItem> From<(PrevActionErrorKind, &A)> for PrevActionError {
    fn from((inner, action): (PrevActionErrorKind, &A)) -> Self {
        PrevActionError {
            source: inner,
            seq: action.seq(),
            action_hash: action.get_hash().clone().into(),
            action_display: action.to_display(),
        }
    }
}

impl From<(PrevActionErrorKind, Action)> for PrevActionError {
    fn from((inner, action): (PrevActionErrorKind, Action)) -> Self {
        PrevActionError {
            source: inner,
            seq: action.action_seq(),
            action_hash: action.to_hash(),
            action_display: format!("{}", action),
        }
    }
}

#[derive(Error, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum PrevActionErrorKind {
    #[error("The previous action hash specified in an action doesn't match the actual previous action. Seq: {0}")]
    HashMismatch(u32),
    #[error("Root of source chain must be Dna")]
    InvalidRoot,
    #[error("Root of source chain must have a timestamp greater than the Dna's origin_time")]
    InvalidRootOriginTime,
    #[error("No more actions are allowed after a chain close")]
    ActionAfterChainClose,
    #[error("Previous action sequence number {1} != ({0} - 1)")]
    InvalidSeq(u32, u32),
    #[error("Action is not the first, so needs previous action")]
    MissingPrev,
    #[error("The previous action's timestamp is not before the current action's timestamp: {0:?} >= {1:?}")]
    Timestamp(Timestamp, Timestamp),
    #[error("The previous action's author does not match the current action's author: {0} != {1}")]
    Author(AgentPubKey, AgentPubKey),
    #[error("It is invalid for these two actions to be paired with each other. context: {0}, actions: {1:?}")]
    InvalidSuccessor(String, Box<(Action, Action)>),
}