hdi/
lib.rs

1//! Holochain Deterministic Integrity (HDI) is Holochain's data model and integrity toolset for
2//! writing zomes.
3//!
4//! The logic of a Holochain DNA can be divided into two parts: integrity and coordination.
5//! Integrity is the part of the hApp that defines the data types and validates data
6//! manipulations. Coordination encompasses the domain logic and implements the functions
7//! that manipulate data.
8//!
9//! # Examples
10//!
11//! An example of an integrity zome with data definition and data validation can be found in the
12//! wasm workspace of the Holochain repository:
13//! <https://github.com/holochain/holochain/blob/develop/crates/test_utils/wasm/wasm_workspace/integrity_zome/src/lib.rs>.
14//!
15//! # Data definition
16//!
17//! The DNA's data model is defined in integrity zomes. They comprise all data type definitions
18//! as well as relationships between those types. Integrity zomes are purely definitions and do
19//! not contain functions to manipulate the data. Therefore a hApp's data model is encapsulated
20//! and completely independent of the domain logic, which is encoded in coordinator zomes.
21//!
22//! The MVC (model, view, controller) design pattern can be used as an analogy. **The
23//! application’s integrity zomes comprise its model layer** — everything that defines the shape
24//! of the data. In practice, this means three things:
25//! - entry type definitions
26//! - link type definitions
27//! - a validation callback that constrains the kinds of data that can validly be called entries
28//!   and links of those types (see also [`Op`](crate::prelude::holochain_integrity_types::Op)).
29//!
30//! **The coordination zomes comprise the application's controller layer** — the code that actually
31//! writes and retrieves data, handles countersigning sessions and sends and receives messages
32//! between peers or between a cell and its UI. In other words, all the zome functions, `init`
33//! functions, remote signal receivers, and scheduler callbacks will all live in coordinator zomes.
34//!
35//! Advantages of this approach are:
36//! * The DNA hash is constant as long as the integrity zomes remain the same. The peer network of
37//!   a DNA is tied to its hash. Changes to the DNA hash result in a new peer network. Changes to the
38//!   domain logic enclosed in coordinator zomes, however, do not affect the DNA hash. Hence the DNAs
39//!   and therefore hApps can be modified without creating a new peer network on every
40//!   deployment.
41//! * Integrity zomes can be shared among DNAs. Any coordinator zome can import an integrity
42//!   zome's data types and implement functions for data manipulation. This composability of
43//!   integrity and coordinator zomes allows for a multitude of permutations with shared integrity
44//!   zomes, i. e. a shared data model.
45//!
46//! # Data validation
47//!
48//! The second fundamental part of integrity zomes is data validation. For every
49//! [operation](crate::prelude::holochain_integrity_types::Op)
50//! that is produced by an [action](crate::prelude::holochain_integrity_types::Action), a
51//! validation rule can be specified. Both data types and data values can be
52//! validated.
53//!
54//! All of these validation rules are declared in the `validate` callback. It
55//! is executed for a new action by each validation authority.
56//!
57//! There's a helper type called [`FlatOp`](crate::flat_op::FlatOp) available for easy access to
58//! all link and entry variants when validating an operation. In many cases, this type can be
59//! easier to work with than the bare [`Op`](crate::prelude::holochain_integrity_types::Op).
60//! [`FlatOp`](crate::flat_op::FlatOp) contains the same information as
61//! [`Op`](crate::prelude::holochain_integrity_types::Op) but with a flatter, more accessible data
62//! structure than [`Op`](crate::prelude::holochain_integrity_types::Op)'s deeply nested and
63//! concise structure.
64//!
65//! ```
66//! # #[cfg(not(feature = "test_utils"))]
67//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
68//! # Ok(())
69//! # }
70//! # #[cfg(feature = "test_utils")]
71//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
72//! # use hdi::prelude::*;
73//! # #[hdk_entry_helper]
74//! # pub struct A;
75//! # #[hdk_entry_helper]
76//! # pub struct B;
77//! # #[hdk_entry_types(skip_hdk_extern = true)]
78//! # #[unit_enum(UnitEntryTypes)]
79//! # pub enum EntryTypes {
80//! #     A(A),
81//! #     B(B),
82//! # }
83//! # #[hdk_link_types(skip_no_mangle = true)]
84//! # pub enum LinkTypes {
85//! #   A,
86//! #   B,
87//! # }
88//! # let op = holochain_integrity_types::Op::RegisterCreateLink(
89//! # holochain_integrity_types::RegisterCreateLink {
90//! #     create_link: holochain_integrity_types::SignedHashed {
91//! #         hashed: holo_hash::HoloHashed {
92//! #             content: holochain_integrity_types::CreateLink {
93//! #                 author: AgentPubKey::from_raw_36(vec![0u8; 36]),
94//! #                 timestamp: Timestamp(0),
95//! #                 action_seq: 1,
96//! #                 prev_action: ActionHash::from_raw_36(vec![0u8; 36]),
97//! #                 base_address: EntryHash::from_raw_36(vec![0u8; 36]).into(),
98//! #                 target_address: EntryHash::from_raw_36(vec![0u8; 36]).into(),
99//! #                 zome_index: 0.into(),
100//! #                 link_type: 0.into(),
101//! #                 tag: ().into(),
102//! #                 weight: Default::default(),
103//! #             },
104//! #             hash: ActionHash::from_raw_36(vec![0u8; 36]),
105//! #         },
106//! #         signature: Signature([0u8; 64]),
107//! #     },
108//! # },
109//! # );
110//! # #[cfg(feature = "test_utils")]
111//! # hdi::test_utils::set_zome_types(&[(0, 2)], &[(0, 2)]);
112//! # let result: Result<hdi::prelude::ValidateCallbackResult, Box<dyn std::error::Error>> =
113//! match op.flattened()? {
114//!     FlatOp::StoreEntry(OpEntry::CreateEntry { app_entry, .. }) => match app_entry {
115//!         EntryTypes::A(_) => Ok(ValidateCallbackResult::Valid),
116//!         EntryTypes::B(_) => Ok(ValidateCallbackResult::Invalid(
117//!             "No Bs allowed in this app".to_string(),
118//!         )),
119//!     },
120//!     FlatOp::RegisterCreateLink {
121//!         base_address: _,
122//!         target_address: _,
123//!         tag: _,
124//!         link_type,
125//!         action: _,
126//!     } => match link_type {
127//!         LinkTypes::A => Ok(ValidateCallbackResult::Valid),
128//!         LinkTypes::B => Ok(ValidateCallbackResult::Invalid(
129//!             "No Bs allowed in this app".to_string(),
130//!         )),
131//!     },
132//!     _ => Ok(ValidateCallbackResult::Valid),
133//! };
134//! # Ok(())
135//! # }
136//! ```
137//! See an example of the `validate` callback in an integrity zome in the WASM workspace:
138//! <https://github.com/holochain/holochain/blob/develop/crates/test_utils/wasm/wasm_workspace/validate/src/integrity.rs>.
139//! Many more validation examples can be browsed in that very workspace.
140
141/// Current HDI rust crate version.
142pub const HDI_VERSION: &str = env!("CARGO_PKG_VERSION");
143
144pub use hdk_derive::hdk_entry_helper;
145pub use hdk_derive::hdk_entry_types;
146pub use hdk_derive::hdk_extern;
147pub use hdk_derive::hdk_link_types;
148
149/// Working with app and system entries.
150///
151/// Most Holochain applications will define their own app entry types.
152///
153/// App entries are all entries that are not system entries.
154/// Definitions of entry types belong in the integrity zomes of a DNA. In contrast, operations
155/// for manipulating entries go into coordinator zomes.
156///
157/// # Examples
158///
159/// Refer to the WASM workspace in the Holochain repository for examples.
160/// Here's a simple example of an entry definition:
161/// <https://github.com/holochain/holochain/blob/develop/crates/test_utils/wasm/wasm_workspace/entry_defs/src/integrity.rs>
162///
163/// An example of a coordinator zome with functions to manipulate entries:
164/// <https://github.com/holochain/holochain/blob/develop/crates/test_utils/wasm/wasm_workspace/coordinator_zome/src/lib.rs>
165///
166/// CRUD in Holochain is represented as a graph/tree of Records referencing each other (via Action hashes) representing new states of a shared identity.
167/// Because the network is always subject to the possibility of partitions, there is no way to assert an objective truth about the 'current' or 'real' value that all participants will agree on.
168/// This is a key difference between Holochain and blockchains.
169/// Where blockchains define a consensus algorithm that brings all participants as close as possible to a single value while Holochain lets each participant discover their own truth.
170///
171/// The practical implication of this is that agents fetch as much information as they can from the network then follow an algorithm to 'walk' or 'reduce' the revisions and discover 'current' for themselves.
172///
173/// In Holochain terms, blockchain consensus is walking all the known 'updates' (blocks) that pass validation then walking/reducing down them to disover the 'chain with the most work' or similar.
174/// For example, to implement a blockchain in Holochain, attach a proof of work to each update and then follow the updates with the most work to the end.
175///
176/// There are many other ways to discover the correct path through updates, for example a friendly game of chess between two players could involve consensual re-orgs or 'undos' of moves by countersigning a different update higher up the tree, to branch out a new revision history.
177///
178/// Two agents with the same information may even disagree on the 'correct' path through updates and this may be valid for a particular application.
179/// For example, an agent could choose to 'block' another agent and ignore all their updates.
180pub mod entry;
181
182pub mod hash;
183
184/// Distributed Hash Tables (DHTs) are fundamentally all key/value stores (content addressable).
185///
186/// This has lots of benefits but can make discoverability difficult.
187///
188/// When agents have the hash for some content they can directly fetch it but they need a way to discover the hash.
189/// For example, Alice can create new usernames or chat messages while Bob is offline.
190/// Unless there is a registry at a known location for Bob to lookup new usernames and chat messages he will never discover them.
191///
192/// The most basic solution is to create a single entry with constant content, e.g. "chat-messages" and link all messages from this.
193///
194/// The basic solution has two main issues:
195///
196/// - Fetching _all_ chat messages may be something like fetching _all_ tweets (impossible, too much data)
197/// - Holochain neighbourhoods (who needs to hold the data) center around the content address so the poor nodes closest to "chat-messages" will be forced to hold _all_ messages (DHT hotspots)
198///
199/// To address this problem we can introduce a tree structure.
200/// Ideally the tree structure embeds some domain specific _granularity_ into each "hop".
201/// For example the root level for chat messages could link to years, each year can link to months, then days and minutes.
202/// The "minutes" level will link to all chat messages in that exact minute.
203/// Any minutes with no chat messages will simply never be linked to.
204/// A GUI can poll from as deep in the tree as makes sense, for example it could start at the current day when the application first loads and then poll the past 5 minutes in parallel every 2 minutes (just a conceptual example).
205///
206/// If the tree embeds granularity then it can replace the need for 'pagination' which is a problematic concept in a partitioned p2p network.
207/// If the tree cannot embed meaningful granularity, for example maybe the only option is to build a tree based on the binary representation of the hash of the content, then we solve DHT hotspots but our applications will have no way to narrow down polling, other than to brute force the tree.
208///
209/// Examples of granularity include:
210///
211/// - Latitude/longitude for geo data
212/// - Timestamps
213/// - Lexical (alphabetical) ordering
214/// - Orders of magnitude
215/// - File system paths
216/// - Etc.
217///
218/// When modelling your data into open sets/collections that need to be looked up, try to find a way to create buckets of granularity that don't need to be brute forced.
219///
220/// In the case that granularity can be defined the tree structure solves both our main issues:
221///
222/// - We never need to fetch _all_ messages because we can start as deeply down the tree as is appropriate and
223/// - We avoid DHT hotspots because each branch of the tree has its own hash and set of links, therefore a different neighbourhood of agents
224///
225/// The [`hash_path`] module includes 3 submodules to help build and navigate these tree structures efficiently:
226///
227/// - [`hash_path::path`] is the basic general purpose implementation of tree structures as `Vec<Vec<u8>>`
228/// - [`hash_path::shard`] is a string based DSL for creating lexical shards out of strings as utf-32 (e.g. usernames)
229/// - [`hash_path::anchor`] implements the "anchor" pattern (two level string based tree, "type" and "text") in terms of paths
230pub mod hash_path;
231
232/// Maps a Rust function to an extern that WASM can expose to the Holochain host.
233///
234/// Annotate any compatible function with `#[hdk_extern]` to expose it to Holochain as a WASM extern.
235/// The [`map_extern!`] macro is used internally by the `#[hdk_extern]` attribute.
236///
237/// Compatible functions:
238///
239/// - Have a globally unique name
240/// - Accept `serde::Serialize + std::fmt::Debug` input
241/// - Return [`Result<O, WasmError>`] ([`map_extern::ExternResult`]) output where `O: serde::Serialize + std::fmt::Debug`
242///
243/// This module only defines macros so check the HDI crate root to see more documentation.
244///
245/// A _new_ extern function is created with the same name as the function with the `#[hdk_extern]` attribute.
246/// The new extern is placed in a child module of the current scope.
247/// This new extern is hoisted by WASM to share a global namespace with all other externs so names must be globally unique even if the base function is scoped.
248///
249/// The new extern handles:
250///
251/// - Extern syntax for Rust
252/// - Receiving the serialized bytes from the host at a memory pointer specified by the guest
253/// - Setting the HDI WASM tracing subscriber as the global default
254/// - Deserializing the input from the host
255/// - Calling the function annotated with `#[hdk_extern]`
256/// - Serializing the result
257/// - Converting the serialized result to a memory pointer for the host
258/// - Error handling for all the above
259///
260/// If you want to do something different to the default you will need to understand and reimplement all the above.
261pub mod map_extern;
262
263/// Exports common types and functions according to the Rust prelude pattern.
264pub mod prelude;
265
266/// Encryption and decryption using the (secret)box algorithms popularised by Libsodium.
267///
268/// Libsodium defines and implements two encryption functions `secretbox` and `box`.
269/// The former implements shared secret encryption and the latter does the same but with a DH key exchange to generate the shared secret.
270/// This has the effect of being able to encrypt data so that only the intended recipient can read it.
271/// This is also repudiable so both participants know the data must have been encrypted by the other (because they didn't encrypt it themselves) but cannot prove this to anybody else (because they _could have_ encrypted it themselves).
272/// If repudiability is not something you want, you need to use a different approach.
273///
274/// Note that the secrets are located within the secure lair keystore and never touch WASM memory.
275// @todo actually secretbox puts the secret in WASM, but this will be fixed soon
276/// The WASM must provide either the public key for box or an opaque _reference_ to the secret key so that lair can encrypt or decrypt as required.
277///
278// @todo implement a way to export/send an encrypted shared secret for a peer from lair
279///
280/// Note that even though the elliptic curve is the same as is used by ed25519, the keypairs cannot be shared because the curve is mathematically translated in the signing vs. encryption algorithms.
281/// In theory the keypairs could also be translated to move between the two algorithms but Holochain doesn't offer a way to do this (yet?).
282/// Create new keypairs for encryption and save the associated public key to your local source chain, and send it to peers you want to interact with.
283pub mod x_salsa20_poly1305;
284
285/// Rexporting the paste macro as it is used internally and may help structure downstream code.
286pub use paste;
287
288/// Create and verify signatures for serializable Rust structures and raw binary data.
289///
290/// The signatures are always created with the [Ed25519](https://en.wikipedia.org/wiki/EdDSA) algorithm by the secure keystore (lair).
291///
292/// Agent public keys that identify agents are the public half of a signing keypair.
293/// The private half of the signing keypair never leaves the secure keystore and certainly never touches WASM.
294///
295/// If a signature is requested for a public key that has no corresponding private key in lair, the signing will fail.
296///
297/// Signatures can always be verified with the public key alone so can be done remotely (by other agents) and offline, etc.
298///
299/// The elliptic curve used by the signing algorithm is the same as the curve used by the encryption algorithms but is _not_ constant time (because signature verification doesn't need to be).
300///
301/// In general it is __not a good idea to reuse signing keys for encryption__ even if the curve is the same, without mathematically translating the keypair, and even then it's dubious to do so.
302pub mod ed25519;
303
304/// Request contextual information from the Holochain host.
305///
306/// The Holochain host has additional runtime context that the WASM may find useful and cannot produce for itself including:
307///
308/// - The calling agent
309/// - The current app (bundle of DNAs)
310/// - The current DNA
311/// - The current Zome
312/// - The function call itself
313pub mod info;
314
315#[cfg(feature = "trace")]
316/// Integrates HDI with the Rust tracing crate.
317///
318/// The functions and structs in this module do _not_ need to be used directly.
319/// The `#[hdk_extern]` attribute on functions exposed externally all set the
320/// [`trace::WasmSubscriber`] as the global default.
321///
322/// This module defines a [`trace::WasmSubscriber`] that forwards all tracing macro calls to
323/// another subscriber on the host.
324/// The logging level can be changed for the host at runtime using the `WASM_LOG` environment
325/// variable that works exactly as `RUST_LOG` for other tracing.
326pub mod trace;
327
328/// The interface between the host and guest is implemented as an [`hdi::HdiT`] trait.
329///
330/// The [`hdi::set_hdi`] function globally sets a [`std::cell::RefCell`] to track the current HDI implementation.
331/// When the `mock` feature is set then this will default to an HDI that always errors, else a WASM host is assumed to exist.
332/// The `mockall` crate (in prelude with `mock` feature) can be used to generate compatible mocks for unit testing.
333/// See mocking examples in the test WASMs crate, such as `agent_info`.
334pub mod hdi;
335
336pub mod agent;
337
338pub mod link;
339
340pub mod chain;
341
342#[deny(missing_docs)]
343pub mod op;
344
345#[deny(missing_docs)]
346pub mod flat_op;
347
348#[cfg(any(feature = "test_utils", test))]
349pub mod test_utils;