hdi/
entry.rs

1use crate::prelude::*;
2
3pub use hdk_derive::hdk_entry_helper;
4pub use hdk_derive::hdk_entry_types;
5
6#[cfg(doc)]
7pub mod examples;
8
9/// MUST get an EntryHashed at a given EntryHash.
10///
11/// The EntryHashed is NOT guaranteed to be associated with a valid (or even validated) Action/Record.
12/// For example, an invalid [`Record`] could be published and [`must_get_entry`] would return the EntryHashed.
13///
14/// This may be useful during validation callbacks where the validity and relevance of some content can be
15/// asserted by the CURRENT validation callback independent of a Record. This behaviour avoids the potential for
16/// eclipse attacks to lie about the validity of some data and cause problems for a hApp.
17/// If you NEED to know that a dependency is valid in order for the current validation logic
18/// (e.g. inductive validation of a tree) then [`must_get_valid_record`] is likely what you need.
19///
20/// [`must_get_entry`] is available in contexts such as validation where both determinism and network access is desirable.
21///
22/// An EntryHashed will NOT be returned if:
23// - @TODO It is PURGED (community redacted entry)
24// - @TODO ALL actions pointing to it are WITHDRAWN by the authors
25/// - ALL actions pointing to it are ABANDONED by ALL authorities due to validation failure
26/// - Nobody knows about it on the currently visible network
27///
28/// If an [`EntryHashed`] fails to be returned:
29///
30/// - Callbacks will return early with [`UnresolvedDependencies`]
31/// - Zome calls will receive a [`WasmError`] from the host
32pub fn must_get_entry(entry_hash: EntryHash) -> ExternResult<EntryHashed> {
33    HDI.with(|h| {
34        h.borrow()
35            .must_get_entry(MustGetEntryInput::new(entry_hash))
36    })
37}
38
39/// MUST get a [`SignedActionHashed`] at a given [`ActionHash`].
40///
41/// The [`SignedActionHashed`] is NOT guaranteed to be a valid (or even validated) Record.
42/// For example, an invalid [`Action`] could be published and [`must_get_action`] would return the [`SignedActionHashed`].
43///
44/// This may be useful during validation callbacks where the validity depends on an action existing
45/// regardless of its associated [`Entry`].
46/// For example, we may simply need to check that the author is the same for two referenced [`Action`]'s.
47///
48/// [`must_get_action`] is available in contexts such as validation where both determinism and network access is desirable.
49///
50/// A [`SignedActionHashed`] will NOT be returned if:
51///
52// - @TODO The action is WITHDRAWN by the author
53// - @TODO The action is ABANDONED by ALL authorities
54/// - Nobody knows about it on the currently visible network
55///
56/// If a [`SignedActionHashed`] fails to be returned:
57///
58/// - Callbacks will return early with [`UnresolvedDependencies`]
59/// - Zome calls will receive a [`WasmError`] from the host
60pub fn must_get_action(action_hash: ActionHash) -> ExternResult<SignedActionHashed> {
61    HDI.with(|h| {
62        h.borrow()
63            .must_get_action(MustGetActionInput::new(action_hash))
64    })
65}
66
67/// MUST get a VALID [`Record`] at a given [`ActionHash`].
68///
69/// The [`Record`] is guaranteed to be valid.
70/// More accurately the [`Record`] is guarantee to be consistently reported as valid by the visible network.
71///
72/// The validity requirement makes this more complex but notably enables inductive validation of arbitrary graph structures.
73/// For example "If this [`Record`] is valid, and its parent is valid, up to the root, then the whole tree of Records is valid".
74///
75/// If at least one authority (1 of N trust) claims the [`Record`] is invalid then a conflict resolution/warranting round will be triggered.
76///
77/// In the case of a total eclipse (every visible authority is lying) then we cannot immediately detect an invalid Record.
78/// Unlike [`must_get_entry`] and [`must_get_action`] we cannot simply inspect the cryptographic integrity to know this.
79///
80/// In theory we can run validation of the returned [`Record`] ourselves, which itself may be based on `must_get_X` calls.
81/// If there is a large nested graph of [`must_get_valid_record`] calls this could be extremely heavy.
82/// Note though that each "hop" in recursive validation is routed to a completely different set of authorities.
83/// It does not take many hops to reach the point where an attacker needs to eclipse the entire network to lie about [`Record`] validity.
84///
85// @TODO We keep signed receipts from authorities serving up "valid records".
86// - If we ever discover a record we were told is valid is invalid we can retroactively look to warrant authorities
87// - We can async (e.g. in a background task) be recursively validating [`Record`] dependencies ourselves, following hops until there is no room for lies
88// - We can with small probability recursively validate to several hops inline to discourage potential eclipse attacks with a credible immediate threat
89///
90/// If you do not care about validity and simply want a pair of [`Action`]+[`Entry`] data, then use both [`must_get_action`] and [`must_get_entry`] together.
91///
92/// [`must_get_valid_record`] is available in contexts such as validation where both determinism and network access is desirable.
93///
94/// An [`Record`] will not be returned if:
95///
96// - @TODO It is WITHDRAWN by the author
97// - @TODO The Entry is PURGED by the community
98/// - It is ABANDONED by ALL authorities due to failed validation
99/// - If ANY authority (1 of N trust) OR ourselves (0 of N trust) believes it INVALID
100/// - Nobody knows about it on the visible network
101///
102/// If an [`Record`] fails to be returned:
103///
104/// - Callbacks will return early with [`UnresolvedDependencies`]
105/// - Zome calls will receive a [`WasmError`] from the host
106pub fn must_get_valid_record(action_hash: ActionHash) -> ExternResult<Record> {
107    HDI.with(|h| {
108        h.borrow()
109            .must_get_valid_record(MustGetValidRecordInput::new(action_hash))
110    })
111}
112
113/// Implements conversion traits to allow a struct to be handled as an app entry.
114/// If you have some need to implement custom serialization logic or metadata injection
115/// you can do so by implementing these traits manually instead.
116///
117/// This requires that [`TryFrom<SerializedBytes>`] and [`TryInto<SerializedBytes>`]
118/// [`derive@SerializedBytes`] is implemented for the entry type, which implies that
119/// [`serde::Serialize`] and [`serde::Deserialize`] is also implemented.
120/// These can all be derived and there is an attribute macro that both does the default defines.
121#[macro_export]
122macro_rules! app_entry {
123    ( $t:ident ) => {
124        impl TryFrom<&$crate::prelude::Entry> for $t {
125            type Error = $crate::prelude::WasmError;
126            fn try_from(entry: &$crate::prelude::Entry) -> Result<Self, Self::Error> {
127                match entry {
128                    $crate::prelude::Entry::App(eb) => Ok(Self::try_from(
129                        $crate::prelude::SerializedBytes::from(eb.to_owned()),
130                    ).map_err(|e| $crate::prelude::wasm_error!(e))?),
131                    $crate::prelude::Entry::CounterSign(_, eb) => Ok(Self::try_from(
132                        $crate::prelude::SerializedBytes::from(eb.to_owned()),
133                    ).map_err(|e| $crate::prelude::wasm_error!(e))?),
134                    _ => Err($crate::prelude::wasm_error!(
135                        "{:?} is not an Entry::App or Entry::CounterSign so has no serialized bytes",
136                        entry
137                    )),
138                }
139            }
140        }
141
142        impl TryFrom<$crate::prelude::Entry> for $t {
143            type Error = $crate::prelude::WasmError;
144            fn try_from(entry: $crate::prelude::Entry) -> Result<Self, Self::Error> {
145                Self::try_from(&entry)
146            }
147        }
148
149        impl TryFrom<$crate::prelude::EntryHashed> for $t {
150            type Error = $crate::prelude::WasmError;
151            fn try_from(entry_hashed: $crate::prelude::EntryHashed) -> Result<Self, Self::Error> {
152                Self::try_from(entry_hashed.as_content())
153            }
154        }
155
156        impl TryFrom<&$crate::prelude::Record> for $t {
157            type Error = $crate::prelude::WasmError;
158            fn try_from(record: &$crate::prelude::Record) -> Result<Self, Self::Error> {
159                Ok(match &record.entry {
160                    RecordEntry::Present(entry) => Self::try_from(entry)?,
161                    _ => return Err(
162                        $crate::prelude::wasm_error!(
163                        $crate::prelude::WasmErrorInner::Guest(format!("Tried to deserialize a record, expecting it to contain entry data, but there was none. Record ActionHash: {}", record.signed_action.hashed.hash))),
164                    )
165                })
166            }
167        }
168
169        impl TryFrom<$crate::prelude::Record> for $t {
170            type Error = $crate::prelude::WasmError;
171            fn try_from(record: $crate::prelude::Record) -> Result<Self, Self::Error> {
172                (&record).try_into()
173            }
174        }
175
176        impl TryFrom<&$t> for $crate::prelude::AppEntryBytes {
177            type Error = $crate::prelude::WasmError;
178            fn try_from(t: &$t) -> Result<Self, Self::Error> {
179                AppEntryBytes::try_from(SerializedBytes::try_from(t).map_err(|e| wasm_error!(e))?).map_err(|entry_error| match entry_error {
180                    EntryError::SerializedBytes(serialized_bytes_error) => {
181                        wasm_error!(WasmErrorInner::Serialize(serialized_bytes_error))
182                    }
183                    EntryError::EntryTooLarge(_) => {
184                        wasm_error!(WasmErrorInner::Guest(entry_error.to_string()))
185                    }
186                })
187            }
188        }
189
190        impl TryFrom<$t> for $crate::prelude::AppEntryBytes {
191            type Error = $crate::prelude::WasmError;
192            fn try_from(t: $t) -> Result<Self, Self::Error> {
193                Self::try_from(&t)
194            }
195        }
196
197        impl TryFrom<&$t> for $crate::prelude::Entry {
198            type Error = $crate::prelude::WasmError;
199            fn try_from(t: &$t) -> Result<Self, Self::Error> {
200                Ok(Self::App($crate::prelude::AppEntryBytes::try_from(t)?))
201            }
202        }
203
204        impl TryFrom<$t> for $crate::prelude::Entry {
205            type Error = $crate::prelude::WasmError;
206            fn try_from(t: $t) -> Result<Self, Self::Error> {
207                Self::try_from(&t)
208            }
209        }
210    };
211}
212
213/// Shorthand to implement the entry defs callback similar to the vec![ .. ] macro but for entries.
214///
215/// e.g. the following are the same
216///
217/// ```ignore
218/// entry_defs![ Foo::entry_type() ];
219/// ```
220///
221/// ```ignore
222/// #[hdk_extern]
223/// fn entry_defs(_: ()) -> ExternResult<EntryDefsCallbackResult> {
224///   Ok(vec![ Foo::entry_type() ].into())
225/// }
226/// ```
227#[doc(hidden)]
228#[macro_export]
229macro_rules! entry_types {
230    [ $( $def:expr ),* ] => {
231        #[hdk_extern]
232        pub fn entry_defs(_: ()) -> $crate::prelude::ExternResult<$crate::prelude::EntryDefsCallbackResult> {
233            Ok($crate::prelude::EntryDefsCallbackResult::from(vec![ $( $def ),* ]))
234        }
235    };
236}