hdi/
entry.rs

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