diff --git a/.cspell/holochain-words.txt b/.cspell/holochain-words.txt index af34e2afd..5b27e75da 100644 --- a/.cspell/holochain-words.txt +++ b/.cspell/holochain-words.txt @@ -10,6 +10,7 @@ Holo's Holochain Holochain's Holonix +kitsune Lightningrod memproof memproofs diff --git a/.cspell/words-that-should-exist.txt b/.cspell/words-that-should-exist.txt index c5513e3e0..5188ccfbc 100644 --- a/.cspell/words-that-should-exist.txt +++ b/.cspell/words-that-should-exist.txt @@ -2,6 +2,8 @@ affordance affordances automations +backlink +backlinks birthdate chrono composability diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 8c75d961d..a425306e0 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -38,6 +38,9 @@ { title: "Entries", url: "/build/entries/" }, { title: "Links, Paths, and Anchors", url: "/build/links-paths-and-anchors/" }, ]}, + { title: "Validation", url: "/build/validation/", children: [ + { title: "Genesis Self-Check Callback", url: "/build/genesis-self-check-callback/" }, + ]}, ] }, { title: "Resources", url: "/resources/", children: [ diff --git a/src/pages/build/callbacks-and-lifecycle-hooks.md b/src/pages/build/callbacks-and-lifecycle-hooks.md index b3b6b2803..757585853 100644 --- a/src/pages/build/callbacks-and-lifecycle-hooks.md +++ b/src/pages/build/callbacks-and-lifecycle-hooks.md @@ -21,44 +21,16 @@ The `validate` callback is called at two times: 1. When an agent tries to author an [action](/build/working-with-data/#entries-actions-and-records-primary-data), and 2. When an agent receives a [DHT operation](/concepts/4_dht/#a-cloud-of-witnesses) to store and serve as part of the shared database. -The nature of validation is out of scope for this page (we'll write a page on it soon), but here's a very basic example of a validation callback that approves everything: - -```rust -use hdi::prelude::*; - -#[hdk_extern] -pub fn validate(_: Op) -> ExternResult { - Ok(ValidateCallbackResult::Valid) -} -``` +The nature of validation is [a topic of its own](/build/validation/). Read the [`validate` callback page](/build/validate-callback/) to see examples. ### Define a `genesis_self_check` callback -If your network needs to control who can and can't join it, you can write your DNA to require a [**membrane proof**](/resources/glossary/#membrane-proof), a small chunk of bytes that the app receives from the user at installation time and stores on the source chain. You can then write validation rules for this record just like any other record. - -There's one challenge with this: validation requires access to the network (a membrane proof may reference DHT data that contains a list of public keys authorized to grant access to newcomers, for instance), but the membrane proof is written at [**genesis**](/resources/glossary/#genesis-records) time when the cell doesn't have network access yet. So Holochain can't do the usual self-validation before publishing it. - -This means someone could accidentally type or paste a malformed membrane proof and be rejected from the network. To guard against this, you can define a `genesis_self_check` function that runs at genesis time and checks the content of the membrane proof before it's written. +There's a moment in a cell's life, after it's been instantiated but before it's connected to its network, where it's published data that it can't fully validate. This data is their [**membrane proof**](/concepts/3_source_chain/#source-chain-your-own-data-store), an optional chunk of data that can serve as a joining code for the network. You can write validation rules for it just like any other record, allowing existing agents to reject newcomers with invalid credentials. -`genesis_self_check` must take a single argument of type [`GenesisSelfCheckData`](https://docs.rs/hdi/latest/hdi/prelude/type.GenesisSelfCheckData.html) and return a value of type [`ValidateCallbackResult`](https://docs.rs/hdi/latest/hdi/prelude/enum.ValidateCallbackResult.html) wrapped in an `ExternResult`. +This record can't be validated, however, because it's written to the source chain before the agent joins the network, and the `validate` callback can only be run after they've joined. -Here's an example that checks that the membrane proof exists and is the right length: - -```rust -use hdi::prelude::*; - -#[hdk_extern] -pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult { - if let Some(membrane_proof) = data.membrane_proof { - if membrane_proof.bytes().len() == 32 { - return Ok(ValidateCallbackResult::Valid); - } - return Ok(ValidateCallbackResult::Invalid("Membrane proof is not the right length. Please check it and enter it again.".into())); - } - Ok(ValidateCallbackResult::Invalid("This network needs a membrane proof to join.".into())) -} -``` +However, if you write a `genesis_self_check` callback, it can guard against some basic user entry errors that would get an honest agent banned from a network. Read the [Genesis Self-Check Callback](/build/genesis-self-check-callback/) page for more info. ## Coordinator zomes diff --git a/src/pages/build/dht-operations.md b/src/pages/build/dht-operations.md new file mode 100644 index 000000000..07b5ed766 --- /dev/null +++ b/src/pages/build/dht-operations.md @@ -0,0 +1,56 @@ +--- +title: "DHT operations" +--- + +::: intro +A DHT operation is what an agent receives when they're being asked to store and serve a piece of data. It's a request for an agent to transform [their slice of the DHT](/concepts/4_dht/#finding-peers-and-data-in-a-distributed-database) into a new state. For this reason it's the input parameter to the [`validate` callback](/build/validate-callback/): if an agent is going to change their state at someone else's request, they need to know that the new state is correct. + +::: + +!!! info Validating actions instead of DHT operations +In general, it's quite complex to validate your data using DHT operations, so we recommend letting the [scaffolding tool](/get-started/3-forum-app-tutorial/) generate a `validate` callback for you, which calls out to [stub functions](/build/validate-callback/#create-boilerplate-code-with-the-scaffolding-tool) that validate the _actions_ that create, update, or delete your application's entry and link types rather than the _operations_ that communicate those actions, and makes opinionated but sensible choices about which groups of authorities should validate what portion of the op data. The following information is presented for people who have a specific need to further reduce overall validation overhead across the network. +!!! + +An [action](/build/working-with-data/#entries-actions-and-records-primary-data) on an agent's [source chain](/concepts/3_source_chain/) yields multiple DHT operations, each of which goes to an [**authority**](/resources/glossary/#validation-authority) for that operation's [**basis address**](/resources/glossary/#basis-address) (a DHT address that the authority is responsible for). + +Here are all the DHT operations produced for all the actions: + +* All actions + * [`RegisterAgentActivity`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.RegisterAgentActivity) + * Basis address: author's public key + * Contents: action (and optionally entry, if applicable) + * Effect: Store the action. Because one agent's actions will all go to the same set of authorities, their job is to check for [source chain forks](/resources/glossary/#fork-source-chain), which is a system-level rule that you don't have to implement. + * [`StoreRecord`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.StoreRecord) + * Basis address: action hash + * Contents: action (and optionally entry, if applicable) + * Effect: Store the action, along with any entry data. +* [`Create`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.Create) + * [`StoreEntry`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.StoreEntry) + * Basis address: entry hash + * Contents: entry and action that wrote it + * Effect: Store the entry, if an identical entry hasn't been created yet, and add the action to the the list of actions associated with its creation. An entry can be created by multiple authors, and each creation action paired with its entry [can be treated as an independent piece of data](/build/entries/#entries-and-actions). **This operation isn't produced for private entries.** +* [`Update`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.Update) + * `StoreEntry` (see above) + * [`RegisterUpdate`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.RegisterUpdate) + * Basis addresses: entry and action hashes of the _old_ entry being updated + * Contents: action and entry + * Effect: Mark an entry creation action as being replaced by a new one, pointing the the entry and action that replace it. **An entry and its creation action can have multiple actions updating them.** +* [`Delete`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.Delete) + * [`RegisterDelete`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.RegisterDelete) + * Basis addresses: entry and action hashes of the entry being deleted + * Contents: action + * Effect: Mark an entry creation action as deleted, without removing the actual data. Because an entry can be created by multiple creation actions, the entry itself isn't marked as deleted until a `RegisterDelete` has been integrated for _all_ of its creation actions. +* [`CreateLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.CreateLink) + * [`RegisterCreateLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.RegisterCreateLink) + * Basis address: link's [base address](/build/links-paths-and-anchors/#define-a-link-type) + * Contents: action + * Effect: Add a link to the list of links pointing from the base to other locations +* [`DeleteLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.DeleteLink) + * [`RegisterDeleteLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html#variant.RegisterCreateLink) + * Basis addresses: old link's [base address](/build/links-paths-and-anchors/#define-a-link-type) and action hash + * Contents: action + * Effect: Mark a link as deleted, without removing the actual data. + +### Choosing who should validate what + +In practice, it's usually okay to have all groups of authorities validate all the data in an action. However, if your validation logic is highly complex and computationally costly, it can sometimes be useful to choose different validation tasks for the different DHT operations. For instance, a `RegisterAgentActivity` authority may not need to validate entry data; it could just focus on the action in the context of the entire source chain. Or a `StoreEntry` authority could focus on entry contents and leave checking of create or update privileges to `StoreRecord` authorities. \ No newline at end of file diff --git a/src/pages/build/genesis-self-check-callback.md b/src/pages/build/genesis-self-check-callback.md new file mode 100644 index 000000000..c59dc3538 --- /dev/null +++ b/src/pages/build/genesis-self-check-callback.md @@ -0,0 +1,110 @@ +--- +title: "Genesis Self-Check Callback" +--- + +::: intro +To enforce access control for a network, a DNA can require a [**membrane proof**](/concepts/3_source_chain/#source-chain-your-own-data-store), which is a piece of data that gets entered by the user and written to their [**source chain**](/concepts/3_source_chain/). The `genesis_self_check` function can guard against user entry error and help prevent them from being banned from the network accidentally. +::: + +## Membrane proof: a per-agent joining code for a network + +If your network needs to enforce membership control, your [`validate` callback](/build/validate-callback/) can check the contents of the [`AgentValidationPkg`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.AgentValidationPkg) record. This [**genesis record**](/resources/glossary/#genesis-records) contains data that's entered by the user on app installation. If validators find an invalid proof, they can ban the unauthorized agent from joining the network. + +A membrane proof can be basic, like per-agent invite codes, or it can be something complex like a [JSON Web Token (JWT)](https://jwt.io/) signed by an agent that has authority to admit members. + +!!! info Membership control isn't implemented yet +This feature is not fully implemented. Currently, validators merely record validation failure for a membrane proof and supply it on request. Our plan is to use membrane proof validation outcomes to admit or block communications from peers. +!!! + +## The need for basic pre-validation + +Most useful membrane proofs will require access to network data in order to fully validate them. But an agent can't self-validate their own membership proof at genesis time, because they haven't joined the network yet. This creates a minor problem; they may accidentally type or paste their membrane proof wrong, but won't find out until they try to join the network and get ejected. + +To reduce the risk, you can define a `genesis_self_check` function that checks the membrane proof before network communications start. This function is limited --- it naturally doesn't have access to DHT data -- but it can be a useful guard against basic data entry errors. + +## Define a `genesis_self_check` callback + +`genesis_self_check` must take a single argument of type [`GenesisSelfCheckData`](https://docs.rs/hdi/latest/hdi/prelude/type.GenesisSelfCheckData.html) and return a value of type [`ValidateCallbackResult`](https://docs.rs/hdi/latest/hdi/prelude/enum.ValidateCallbackResult.html) wrapped in an `ExternResult`. + +Here's an example that checks that the membrane proof exists and is the right length: + +```rust +use hdi::prelude::*; + +#[hdk_extern] +pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult { + if let Some(membrane_proof) = data.membrane_proof { + if membrane_proof.bytes().len() == 32 { + return Ok(ValidateCallbackResult::Valid); + } + return Ok(ValidateCallbackResult::Invalid("Membrane proof is not the right length. Please check it and enter it again.".into())); + } + Ok(ValidateCallbackResult::Invalid("This network needs a membrane proof to join.".into())) +} +``` + +This more complex example deserializes a JWT, checks that its [`sub` (subject) field](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2) matches the agent's public key, and checks that its [`iss` (issuer) field](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1) matches an authorized key hard-coded into the DNA's [`properties` block](/build/dnas/#use-dna-properties). {#jwt-membrane-proof} + +```rust +use hdi::prelude::*; + +// A type for deserializing a JWT payload from our membrane proof. +#[derive(Serialize, Deserialize)] +pub struct MembraneProofJwtPayload { + pub iss: AgentPubKeyB64, + pub sub: AgentPubKeyB64, +} + +// A type for deserializing the DNA properties that this integrity zome needs. +#[dna_properties] +pub struct DnaProperties { + membrane_proof_signing_authority: AgentPubKeyB64, +} + +#[hdk_extern] +pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult { + if let Some(membrane_proof) = data.membrane_proof { + // A JWT is three JSON objects, Base64 urlencoded, and concatenated + // with periods. Let's break it apart, decode, and deserialize it. + let parts: Vec<&str> = std::str::from_utf8(membrane_proof.bytes()) + .map_err(|e| wasm_error!(e.to_string()))? + .split(".") + .collect(); + let payload_encoded = parts[1]; + let payload_decoded = base64_url::decode(payload_encoded) + .map_err(|e| wasm_error!(e.to_string()))?; + let payload_string = std::str::from_utf8(&payload_decoded) + .map_err(|e| wasm_error!(e.to_string()))?; + let payload: MembraneProofJwtPayload = serde_json::from_str(payload_string) + .map_err(|e| wasm_error!(e.to_string()))?; + + // Now that we've got that, compare it against the public key of the + // agent submitting the membrane proof. + if payload.sub != data.agent_key.into() { + return Ok(ValidateCallbackResult::Invalid("Author's public key doesn't match membrane proof".into())); + } + + // And against the DNA's authorized membrane proof signer. + if payload.iss != DnaProperties::try_from_dna_properties()?.membrane_proof_signing_authority { + return Ok(ValidateCallbackResult::Invalid("Membrane proof issuer is unrecognized".into())); + } + } + Ok(ValidateCallbackResult::Invalid("This network needs a membrane proof to join.".into())) +} +``` + +## Fully validating a membrane proof + +Your `validate` callback also needs to validate a membrane proof in order to actually enforce network access. At the very least, it should apply the same rules as `genesis_self_check` does, and add on any checks that require DHT data. We'll explore this in full on the [`validate` callback](/build/validate-callback/#validate-agent-joining) page. + +## Reference + +* [`holochain_integrity_types::action::AgentValidationPkg`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.AgentValidationPkg) +* [`holochain_integrity_types::genesis::GenesisSelfCheckData`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/genesis/type.GenesisSelfCheckData.html) +* [`holochain_integrity_types::validate::ValidateCallbackResult`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/genesis/type.GenesisSelfCheckData.html) +* [`holochain_derive::dna_properties`](https://docs.rs/hdk_derive/latest/hdk_derive/attr.dna_properties.html) + +## Further reading + +* [Core Concepts: Genesis self-check](/concepts/7_validation/#genesis-self-check) +* [Build Guide: Validation](/build/validation/) \ No newline at end of file diff --git a/src/pages/build/index.md b/src/pages/build/index.md index ac21f0a09..6e50a9a6b 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -40,4 +40,11 @@ Now that you've got some basic concepts and the terms we use for them, it's time * [Identifiers](/build/identifiers) --- working with hashes and other unique IDs * [Entries](/build/entries/) --- defining, creating, reading, updating, and deleting data * [Links, Paths, and Anchors](/build/links-paths-and-anchors/) --- creating relationships between data -::: \ No newline at end of file +::: + +## Validation + +::: topic-list +* [Overview](/build/validation/) --- The purpose of validation in a hApp, abilities, requirements and constraints, determinism + * [Genesis Self-Check Callback](/build/genesis-self-check-callback/) --- Purpose and use cases + * [Validate Callback](/build/validate-callback/) --- Writing validation routines for various needs \ No newline at end of file diff --git a/src/pages/build/validate-callback.md b/src/pages/build/validate-callback.md new file mode 100644 index 000000000..ab350548c --- /dev/null +++ b/src/pages/build/validate-callback.md @@ -0,0 +1,226 @@ +--- +title: "Validate callback" +--- + +::: intro +The `validate` callback implements the core of your data model's logic. It receives **DHT operations**, which contain an [action](/build/working-with-data/#entries-actions-and-records-primary-data) and sometimes an [entry](/build/entries), and checks them for correctness. It can not only validate the correctness of the data itself, but can also check the correctness of the record in the context of the [source chain](/concepts/3_source_chain/) and pull in dependencies from elsewhere in the [DHT](/concepts/4_dht/). +::: + +## Define a `validate` callback + +A `validate` callback takes an input argument of [`Op`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html) and returns a [`ValidateCallbackResult`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/validate/enum.ValidateCallbackResult.html) wrapped in an `ExternResult`. + +Validating an op can have one of three outcomes: {#validation-outcomes} + +1. `ValidateCallbackResult::Valid` means that validation succeeded. +2. `ValidateCallbackResult::Invalid(String)` carries information about the validation failure for debugging. +3. ValidateCallbackResult::UnresolvedDependencies([UnresolvedDependencies](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/validate/enum.UnresolvedDependencies.html)) means that validation couldn't finish because the required dependencies couldn't be fetched from the DHT. + +Outcomes 1 and 2 are definitive and the op won't be validated again. Outcome 3, which happens automatically when a call any of the [`must_get_*` functions](https://docs.rs/hdi/latest/hdi/entry/index.html#functions) yields no data, asks Holochain to try running the op through validation later in the hope that it can retrieve the required dependencies. (It'll give up retrying after some time.) + +The simplest `validate` callback approves everything: + +```rust +use hdi::prelude::*; + +#[hdk_extern] +pub fn validate(_: Op) -> ExternResult { + Ok(ValidateCallbackResult::Valid) +} +``` + +We show it here, not because it's useful, but so you can see the function's signature. Even less useful is one that does the opposite: + +```rust +use hdi::prelude::*; + +#[hdk_extern] +pub fn validate(_: Op) -> ExternResult { + Ok(ValidateCallbackResult::Invalid("I reject everything".into())) +} +``` + +There is something worth noting here though. We return an `Ok` even though validation failed. That's because **`Err` should be reserved for true failures** such as the ones returned by host functions. + +## Create boilerplate code with the scaffolding tool + +Even after flattening, there are a lot of variants of an op, which makes for a lot of match arms in your `validate` callback. And a lot of the arms do similar but slightly different things. It's a great idea to let the scaffolding tool generate the callback for you. We won't paste a sample here --- you can scaffold an integrity zome and a few entry and link types and see what it looks like. + +Instead, it's best to fill in the stub functions that the scaffold tool creates for you. You get a well-structured `validate` callback, a `genesis_self_check` callback, along with stubs to validate actions that manipulate all the entry and link types. These stubs are called by `validate` in the right places. Filling in these stub functions is the simplest way to write validation code. + +Here are some useful examples that show you how to use the stub functions, imagining you scaffolded the `Director` and `Movie` entry types from the [Entries page](/build/entries/#define-an-entry-type) along with a collection for all `Director` entries. + +### Stub functions for entries + +These functions can be found in the file `dnas//zomes/integrity//src/.rs`. + +#### `validate_create_` + +In this function you can write rules for the contents of your entries and their dependencies. You can also write rules for the actions that write them, which means you can create rules for write privileges. (Note that it's called for both kinds of [`EntryCreationAction`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.EntryCreationAction.html) --- `Create` and `Update`.) + +This example checks that a movie is within [sensible bounds](https://en.wikipedia.org/wiki/Roundhay_Garden_Scene). + +```rust +use hdi::prelude::*; + +pub fn validate_create_movie( + _action: EntryCreationAction, + movie: Movie, +) -> ExternResult { + // Note that converting a `&str` to a `Timestamp` requires you to list + // `kitsune_p2p_timestamp` as a dependency in your `Cargo.toml`. + if movie.release_date < "1888-10-14".try_into().unwrap() { + return Ok(ValidateCallbackResult::Invalid("The movie's release date is earlier than the oldest known film.".into())); + } + Ok(ValidateCallbackResult::Valid) +} +``` + +This example checks that a director entry for a movie exists. + +```rust +use hdi::prelude::*; + +pub fn validate_create_movie( + _action: EntryCreationAction, + movie: Movie, +) -> ExternResult { + // Just call the function, and Holochain will handle the + // `UnresolvedDependencies` outcome for you. + let director_entry = must_get_entry(movie.director_hash)?; + // Try to turn it into an entry of the right type. + let _director = crate::Director::try_from(director_entry)?; + Ok(ValidateCallbackResult::Valid) +} +``` + +#### `validate_update_` + +This callback gets the original entry and its creation action along with the update. You can use it to compare differences between the two entries, or you can use it to enforce write permissions, such as only allowing the original author to update an entry: + +```rust +use hdi::prelude::*; + +pub fn validate_update_movie( + action: Update, + _movie: Movie, + original_action: EntryCreationAction, + _original_movie: Movie, +) -> ExternResult { + if action.author != original_action.author().clone() { + return Ok(ValidateCallbackResult::Invalid("Agents can only update their own Movie entries.".to_string())); + } + Ok(ValidateCallbackResult::Valid) +} +``` + +#### `validate_delete_` + +This callback gets the original entry and its creation action too. This example prevents `Director` entries from being deleted: + +```rust +use hdi::prelude::*; + +pub fn validate_delete_director( + _action: Delete, + _original_action: EntryCreationAction, + _original_director: Director, +) -> ExternResult { + Ok(ValidateCallbackResult::Invalid( + "Directors cannot be deleted".to_string(), + )) +} +``` + +And this example once again only allows people to delete their own entries: + +```rust +use hdi::prelude::*; + +pub fn validate_delete_movie( + action: Delete, + original_action: EntryCreationAction, + _original_movie: Movie, +) -> ExternResult { + if action.author != original_action.author().clone() { + return Ok(ValidateCallbackResult::Invalid("Agents can only delete their own Movie entries.".to_string())); + } + Ok(ValidateCallbackResult::Valid) +} +``` + +You can find other stub functions in that file for links that point to the most recent update (if you [chose that option](/get-started/3-forum-app-tutorial/#scaffold-most-recent-update-link) when you scaffolded the entry type), links that manage collections of entries, and backlinks from entries to entries that depend on them. + +### `validate_agent_joining` + +Use this function to validate the [**membrane proof**](/build/genesis-self-check-callback/#membrane-proof-a-per-agent-joining-code-for-a-network). Note that this is different from `genesis_self_check`, in that it's called from the `validate` function so it can access DHT data. + +This example implements a simple invite code for a network that people can invite their friends to join. All that's required is the presence of an 'invite' action on the DHT, whose hash becomes the invite code. (It's not a very secure pattern; please don't duplicate this in high-security hApps.) As a bonus, it shows a good pattern where the code for basic pre-validation is shared with `genesis_self_check`. + +```rust +use hdi::prelude::*; +use base64::prelude::*; + +pub fn validate_agent_joining( + _agent_pub_key: AgentPubKey, + membrane_proof: &Option +) -> ExternResult { + let membrane_proof: Option> = membrane_proof + .to_owned() + .map(|b| b.bytes().clone()); + validate_invite_code_format(membrane_proof.clone())?; + let invite_code_action_hash = decode_invite_code(membrane_proof.unwrap())?; + // Make sure the action exists; we don't care about the contents of the + // record. + must_get_valid_record(invite_code_action_hash)?; + // There are more checks we ought to do here, like make sure it's an + // entry creation action of a type called `invite`. + Ok(ValidateCallbackResult::Valid) +} + +#[hdk_extern] +pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult { + validate_invite_code_format(data.membrane_proof.map(|b| b.bytes().to_owned())) +} + +fn decode_invite_code(invite_code: Vec) -> ExternResult { + // Try to convert the invite code from bytes to an action hash. + let invite_code = BASE64_STANDARD.decode(invite_code) + .map_err(|e| wasm_error!(e.to_string()))?; + let invite_code = std::str::from_utf8(&invite_code) + .map_err(|e| wasm_error!(e.to_string()))?; + let invite_code_action_hash = ActionHashB64::from_b64_str(invite_code) + .map_err(|e| wasm_error!(e.to_string()))?; + Ok(invite_code_action_hash.into()) +} + +fn validate_invite_code_format(invite_code: Option>) -> ExternResult { + match invite_code { + Some(invite_code) => { + decode_invite_code(invite_code)?; + Ok(ValidateCallbackResult::Valid) + } + None => Ok(ValidateCallbackResult::Invalid("Please supply an invite code.".into())) + } +} +``` + + + +## Reference + +* [`holochain_integrity_types::op::Op`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/op/enum.Op.html) +* [`holochain_integrity_types::action::AgentValidationPkg`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.AgentValidationPkg) +* [`holochain_integrity_types::genesis::GenesisSelfCheckData`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/genesis/type.GenesisSelfCheckData.html) +* [`holochain_integrity_types::validate::ValidateCallbackResult`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/genesis/type.GenesisSelfCheckData.html) +* [HDI docs: data validation](https://docs.rs/hdi/latest/hdi/#data-validation) +* [`must_get_action`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_action.html) +* [`must_get_agent_activity`](https://docs.rs/hdi/latest/hdi/chain/fn.must_get_agent_activity.html) +* [`must_get_entry`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_entry.html) +* [`must_get_valid_record`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_valid_record.html) + +## Further reading + +* [Build Guide: Validation](/build/validation/) +* [Build Guide: `genesis_self_check` Callback](/build/genesis-self-check-callback) +* [Core Concepts: Validation](/concepts/7_validation/) \ No newline at end of file diff --git a/src/pages/build/validation.md b/src/pages/build/validation.md new file mode 100644 index 000000000..1b5cf30dd --- /dev/null +++ b/src/pages/build/validation.md @@ -0,0 +1,79 @@ +--- +title: "Validation" +--- + +::: topic-list +### In this section {data-no-toc} + +* Validation (this page) + * [`genesis_self_check` Callback](/build/genesis-self-check-callback/) --- writing a function to control access to a network + * [`validate` Callback](/build/validate-callback/) --- basic callback, examples using stub functions + * [DHT operations](/build/dht-operations/) --- further details on the underlying data structure used in DHT replication and validation +::: + +::: intro +Validation gives shape to your [DNA](/build/dnas/)'s data model. It defines the 'rules of the game' for a network --- who can create, modify, or delete data, and what that data should and shouldn't look like. It's also the basis for Holochain's peer-auditing security model. +::: + +You implement your validation logic in your application's [integrity zomes](/build/zomes/#integrity). While [entry and link types](/build/working-with-data/) enumerate the kinds of data the integrity zome defines, the data is just bytes until your validation logic gives it some shape and purpose. + +A DNA uses validation logic in two ways: + +1. By an author of data, to protect them from publishing invalid data, and +2. By an agent that's received data to store and serve, to equip them to detect invalid data and take action against the author. + +Because every peer has the DNA's validation logic on their own machine and is expected to check the data they author before they publish it, it can be assumed that invalid data is (almost) always an intentionally malicious act. + +!!! info +Currently Holochain can inform agents about invalid data when asked. In the future it'll also take automatic defensive action by putting a malicious author into an agent's network block list when they see evidence of invalid data. +!!! + +There are two callbacks that implement validation logic: + +* `validate` is the core of the zome's validation logic. It receives a [**DHT operation**](/build/dht-operations/) and returns a success/failure/indeterminate result. +* [`genesis_self_check`](/build/genesis-self-check-callback/) 'pre-validates' an agent's [**membrane proof**](/concepts/3_source_chain/#source-chain-your-own-data-store) before trying to connect to peers in the network. + +## Design considerations + +Validation is a broad topic, so we won't go into detail here. There are a few basic things to keep in mind though: + +* The structure of the `Op` type that a `validate` callback receives is complex and deeply nested, and it's best to let the [scaffolding tool](/get-started/3-forum-app-tutorial/) generate the callback for you. It generates stub functions that let you think in terms of [actions](/build/working-with-data/#entries-actions-and-records-primary-data) rather than operations, which is more natural and good enough for most needs. [Read all about DHT operations](/build/dht-operations/) if you want deep detail. +* While an entry or link can be thought of as 'things', the actions that create, update, or delete them are verbs. Validating a whole action lets you not just check the content and structure of your things, but also enforce write privileges and even throttle an agent's frequency of writes by looking at the action's place in their source chain. +* Validation rules should **always yield the same true/false outcome** for a given operation regardless of who is validating them and when. In fact Holochain prevents your validation callbacks from calling any host functions that introduce sources of non-determinism. Read more about the [available host functions](#available-host-functions). +* Data may have dependencies that affect validation outcomes, but those dependencies must be [addressable](/build/identifiers/), they must be retrievable from the same DHT, and their addresses must be constructable from data in the operation being validated. (Note that because an action references the action preceding it on an agent's source chain, this is a dependency you don't need to build into your data.) If dependencies can't be retrieved at validation time, the `validate` callback terminates early with an [indeterminate result](/build/validate-callback/#validation-outcomes), which will cause Holochain to try again later. +* When multiple actions are written in a same [atomic transaction](/build/zome-functions/#atomic-transactional-commits), actions' ops can only have dependencies on prior actions committed in the transaction, not later actions. + +### Things you don't need to worry about + +* For dependency trees that might get complex and costly to retrieve, you can use **inductive validation** rather than having to retrieve and validate all the dependencies. +* Action timestamps, sequence indices, and authorship are automatically checked for consistency against the previous action in the author's source chain. +* Data is checked against Holochain's maximum size (4 MB for entries, 1 KB for link tags). +* The entry type of `Update` actions is checked against the data they replace. +* The scaffolding tool generates a sensible default `validate` callback that does these things for you: + * Tries to deserialize an entry into the correct Rust type, and returns a validation failure if it fails. + * Checks that the original entry for an `Update` or `Delete` action exists and is a valid entry creation action. + * Checks that the original entry for an `Update` contains the same entry type. + * Checks that the original entry for a `Delete` comes from the same integrity zome. + * Checks that the [action that registers the agent's public key](/concepts/3_source_chain/#agent-id-action) is directly preceded by an [`AgentValidationPkg`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html#variant.AgentValidationPkg) action. + * Checks that [most-recent update links](/get-started/3-forum-app-tutorial/#scaffold-most-recent-update-link) and [collection links](/build/links-paths-and-anchors/#scaffold-a-simple-collection-anchor) point to valid entry creation records. + * Tries to fetch data dependencies from the DHT and make sure they're the right type. + +## Available host functions + +As mentioned, any host functions that introduce non-determinism can't be called from `genesis_self_check` or `validate` --- Holochain will return an error. That includes functions that create data or check the current time or agent info, of course, but it also includes certain functions that retrieve DHT or source chain data. That's because this data can change over time. + +These functions are available to both `validate` and `genesis_self_check`: + +* [`dna_info`](https://docs.rs/hdi/latest/hdi/info/fn.dna_info.html) +* [`zome_info`](https://docs.rs/hdi/latest/hdi/info/fn.zome_info.html) +* [`ed25519` functions](https://docs.rs/hdi/latest/hdi/ed25519/index.html) +* [`x_salsa20_poly1305` functions](https://docs.rs/hdi/latest/hdi/x_salsa20_poly1305/index.html) + +`validate` can also call these deterministic DHT retrieval functions: + +* [`must_get_action`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_action.html) tries to get an action from the DHT. (It's not guaranteed that the action will be valid.) +* [`must_get_agent_activity`](https://docs.rs/hdi/latest/hdi/chain/fn.must_get_agent_activity.html) tries to get a contiguous section of a source chain, starting from a given record and walking backwards to another spot (either the beginning of the chain, a number of records, or one of a number of given hashes). +* [`must_get_entry`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_entry.html) tries to get an entry from the DHT. (As with `must_get_action`, it's not guaranteed that the entry will be valid.) +* [`must_get_valid_record`](https://docs.rs/hdi/latest/hdi/entry/fn.must_get_valid_record.html) tries to get a record, and will fail if the record is marked invalid by any validators, even if it can be found. + +All of these functions cause a `validate` callback to terminate early with ValidateCallbackResult::UnresolvedDependencies([UnresolvedDependencies](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/validate/enum.UnresolvedDependencies.html)). \ No newline at end of file diff --git a/src/pages/concepts/3_source_chain.md b/src/pages/concepts/3_source_chain.md index 3f9024a47..641d06b26 100644 --- a/src/pages/concepts/3_source_chain.md +++ b/src/pages/concepts/3_source_chain.md @@ -62,7 +62,7 @@ This journal starts with three special system records called **genesis records** 1. The **DNA hash**. Because the DNA's executable code constitutes the 'rules of the game' for a network of participants, this record claims that your Holochain runtime has seen and is abiding by those rules. 2. The agent's **membrane proof**. When a cell tries to join a network, it shares this entry with the existing peers, who check it and determine whether the cell should be allowed to join them. Examples: an invite code, an employee ID signed by the HR department, or a proof of paid subscription fees. -3. The **agent ID**. This contains your public key as your digital identity. The signatures on all subsequent records must match this public key in order to be valid. +3. The **agent ID**. This contains your public key as your digital identity. The signatures on all subsequent records must match this public key in order to be valid. {#agent-id-action} 4. Zero or more records containing application data that was written during the init process --- that is, by the `init` [lifecycle hook](../11_lifecycle_events) of any coordinator zomes that define one. 5. The **init complete** action. This is a record meant for internal use, simply helping the conductor remind itself that it's completely activated the cell by running all the coordinator zomes' `init` callbacks. diff --git a/src/pages/concepts/4_dht.md b/src/pages/concepts/4_dht.md index d0b493aea..da52f00f2 100644 --- a/src/pages/concepts/4_dht.md +++ b/src/pages/concepts/4_dht.md @@ -159,12 +159,12 @@ The radio towers are rebuilt, the network partition heals, and new data syncs up When we [laid out the basics of Holochain](../1_the_basics/), we said that the second pillar of trust is **peer validation**. When a node is asked to store a piece of data, it doesn't _just_ store it --- it also checks it for validity. As the data is passed to more nodes in its neighborhood, it gathers more signatures attesting to its validity. -When an agent publishes a source chain record to the network, they don't actually share the record itself. Instead, one or more **DHT operations** are produced. Those operations each have a location they're sent to, a **base**. That base is the DHT address we mentioned earlier. Once an authority receives an operation, they run the application-defined validation function against the operation, either accepting it, rejecting it, or marking it for retry if not all information it depends on is available. If it passes, the authority will 'integrate' it into their slice of the DHT, which means that they'll apply the operation to transform some of the data at the base address in their little piece of the key/value store. +When an agent publishes a source chain record to the network, they don't actually share the record itself. Instead, one or more **DHT operations** are produced. Those operations each have a location they're sent to, a **basis address**. This is the DHT address we mentioned earlier. Once an authority receives an operation, they run the application-defined validation function against the operation, either accepting it, rejecting it, or marking it for retry if not all information it depends on is available. If it passes, the authority will 'integrate' it into their slice of the DHT, which means that they'll apply the operation to transform some of the data at the basis address in their little piece of the key/value store. Let's see what happens when a simple 'create entry' record is published to the DHT. -* A **store entry** operation, containing both the entry blob and the create-entry action, goes to the **entry authorities** whose arcs cover the entry hash. When it's integrated, the authorities will hold the entry data at that base, along with metadata that contains the action. -* A **store record** operation, containing the action, goes to the **record authorities** whose arcs cover the action's hash. When it's integrated, the authorities will hold the action at that base. +* A **store entry** operation, containing both the entry blob and the create-entry action, goes to the **entry authorities** whose arcs cover the entry hash. When it's integrated, the authorities will hold the entry data at that basis, along with metadata that contains the action. +* A **store record** operation, containing the action, goes to the **record authorities** whose arcs cover the action's hash. When it's integrated, the authorities will hold the action at that basis address. * An **register agent activity** operation, containing the action, goes to the **agent activity authorities**, peers whose arcs cover the author's public key. When it's integrated, the authorities will hold the action along with all prior action. No matter what operation they receive, all three authorities check the signature on it to make sure it hasn't been modified in transit and it belongs to the agent that claims to have authored it. If the signature check fails, the data is rejected. diff --git a/src/pages/get-started/3-forum-app-tutorial.md b/src/pages/get-started/3-forum-app-tutorial.md index 134785b4d..8a0a8a040 100644 --- a/src/pages/get-started/3-forum-app-tutorial.md +++ b/src/pages/get-started/3-forum-app-tutorial.md @@ -485,7 +485,7 @@ Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to co Then press Enter. -At this point you should see: +At this point you should see: {#scaffold-most-recent-update-link} ::: output-block ```text diff --git a/src/pages/resources/glossary.md b/src/pages/resources/glossary.md index 9eb0eeda7..4da0b5a1f 100644 --- a/src/pages/resources/glossary.md +++ b/src/pages/resources/glossary.md @@ -10,7 +10,7 @@ A piece of data that represents a [record](#record) on an [agent's](#agent) [sou #### Address -1. [DHT address](#dht-address), synonymous with [base](#base) +1. [DHT address](#dht-address), synonymous with [basis address](#basis-address) 2. [Transport address](#transport-address) #### Addressable content @@ -36,7 +36,7 @@ Records of all the [source chain](#source-chain) [actions](#action) that an agen #### Agent activity operation -A [DHT operation](#dht-operation) produced by the author of a [source chain](#source-chain) [record](#record), notifying the [validation authorities](#validation-authority) for the author's [agent ID entry](#agent-id-entry) that they've published something. The [base](#base) of an agent activity operation is the agent ID of the operation's [author](#author), which means that the author's [neighbors](#neighbor), as [peers](#peer) whose [agent addresses](#agent-address) are [near](#nearness) to theirs, are the [validation authorities](#validation-authority) for their agent activity data. +A [DHT operation](#dht-operation) produced by the author of a [source chain](#source-chain) [record](#record), notifying the [validation authorities](#validation-authority) for the author's [agent ID entry](#agent-id-entry) that they've published something. The [basis address](#basis-address) of an agent activity operation is the agent ID of the operation's [author](#author), which means that the author's [neighbors](#neighbor), as [peers](#peer) whose [agent addresses](#agent-address) are [near](#nearness) to theirs, are the [validation authorities](#validation-authority) for their agent activity data. #### Agent-centric @@ -56,7 +56,7 @@ The entry associated with the third of the four [genesis records](#genesis-recor #### Anchor -A Holochain application design pattern in which an easily discoverable [base](#base) is designated as a location to store a large number of [links](#link). The base's [address](#address) is typically calculated from a short string, whose value is either hard-coded into the application's code, discovered via link traversal, or entered via the UI. [Entries](#entry) and [agent IDs](#agent-id) can also serve as anchor bases. +A Holochain application design pattern in which an easily discoverable [base](#link-base) is designated as a location to store a large number of [links](#link). The base's [address](#address) is typically calculated from a short string, whose value is either hard-coded into the application's code, discovered via link traversal, or entered via the UI. [Entries](#entry) and [agent IDs](#agent-id) can also serve as anchor bases. #### Append-only @@ -93,7 +93,7 @@ A [capability grant](#capability-grant) that allows anyone possessing the [sourc When we're talking about Holochain, synonymous with one or more [DNAs](#dna) for a [hApp](#holochain-application-h-app)---that is, code that contains the base-level persistence and validation logic. -#### Base +#### Basis address The [DHT address](#dht-address) to which an [operation](#dht-operation) applies. [Validation authorities](#validation-authority) who claim responsibility for this address receive, validate, and apply operations that produce [data](#dht-data) and [metadata](#metadata) attached to this base, which is then made available to [agents](#agent) who request it. This base consequently serves as a locator that allows an agent to know which authorities to request the data from, as each base maps to a DHT address, which is handled by a number of authorities who publish their coverage of the base via their [storage arc](#storage-arc), and whose [agent addresses](#agent-address) can be mapped to their [transport addresses](#transport-address) via a [peer table](#peer-table) lookup. @@ -331,11 +331,11 @@ The unique ID of a piece of [record data](#record-data) ([entry](#entry), [actio #### DHT data -A piece of data that lives in the [DHT](#distributed-hash-table-dht). DHT data is assigned to a [neighborhood](#neighborhood) of [validation authorities](#validation-authority) based on the base [address](#address) of the [DHT operation](#dht-operation) that expresses its creation, and is [deduplicated](#deduplication). All DHT data is either [record data](#record-data) with an address of its own, or [metadata](#metadata) attached to a piece of record data. DHT data is created when [agents](#agent) [author](#author) [source chain](#source-chain) [actions](#action), which then produce [operations](#dht-operation) that are sent to the respective validation authorities for the operations' [bases](#base). Those authorities then apply the operations to their own DHT [shard](#sharding) after validating them. +A piece of data that lives in the [DHT](#distributed-hash-table-dht). DHT data is assigned to a [neighborhood](#neighborhood) of [validation authorities](#validation-authority) based on the base [address](#address) of the [DHT operation](#dht-operation) that expresses its creation, and is [deduplicated](#deduplication). All DHT data is either [record data](#record-data) with an address of its own, or [metadata](#metadata) attached to a piece of record data. DHT data is created when [agents](#agent) [author](#author) [source chain](#source-chain) [actions](#action), which then produce [operations](#dht-operation) that are sent to the respective validation authorities for the operations' [basis addresses](#basis-address). Those authorities then apply the operations to their own DHT [shard](#sharding) after validating them. #### DHT operation -A unit of [gossip](#gossip) that communicates a request to a [validation authority](#validation-authority) to transform the data they hold in some way. Each DHT operation has a [base](#base) [address](#address) and gets sent to the authorities that claim responsibility for that address by advertising that their [storage arcs](#storage-arc) include the address. For each type of [record](#record)/[action](#action), an [author](#author) produces one or more DHT operations. For example, a [create-entry action](#create-entry-action) for a [public entry](#public-entry) produces three DHT operations: +A unit of [gossip](#gossip) that communicates a request to a [validation authority](#validation-authority) to transform the data they hold in some way. Each DHT operation has a [basis address](#basis-address) and gets sent to the authorities that claim responsibility for that address by advertising that their [storage arcs](#storage-arc) include the address. For each type of [record](#record)/[action](#action), an [author](#author) produces one or more DHT operations. For example, a [create-entry action](#create-entry-action) for a [public entry](#public-entry) produces three DHT operations: * One to publish the [action](#action), whose base is the action's hash, * One to publish the entry itself, whose base is the entry's hash, and @@ -429,13 +429,21 @@ To create alternate versions of one's history in an app by basing two [source ch In Holochain terms, synonymous with [graphical user interface](#graphical-user-interface-gui) or, more generally, [client](#client). +#### Genesis + +The time period in the lifecycle of a [cell](#cell) in which [genesis records](#genesis-records) are being written. This happens before the cell has access to its [DNA](#dna)'s network and [DHT](#distributed-hash-table-dht) data. A [genesis self-check callback](#genesis-self-check-callback) can be written to pre-validate the [membrane proof](#membrane-proof) during this period. + #### Genesis records The four records at the beginning of an [agent's](#agent) [source chain](#source-chain), consisting of: -1. The [DNA hash](#dna-hash), which shows that the agent has seen the network's rules and agrees to abide by them, -2. The [membrane proof](#membrane-proof), which the agent presents as a claim that they should be allowed to join the [DHT](#distributed-hash-table-dht), -3. The [agent ID](#agent-id), which advertises the agent's [public key](#public-key-cryptography), +1. The [DNA hash](#dna-hash), which shows that the agent has seen the network's rules and agrees to abide by them +2. The [membrane proof](#membrane-proof), which the agent presents as a claim that they should be allowed to join the [DHT](#distributed-hash-table-dht) +3. The [agent ID](#agent-id), which advertises the agent's [public key](#public-key-cryptography) + +#### Genesis self-check callback + +A callback that pre-validates an agent's [membrane proof](#membrane-proof) during [genesis](#genesis) time. This callback can't access the network but it can guard against basic data entry errors. #### Global consensus @@ -463,7 +471,7 @@ A file that specifies the DNAs comprising a [hApp bundle](#h-app-bundle). #### Hash -A unique 'fingerprint' for a piece of data, calculated by running the data through a cryptographic hashing function. A hash can serve as a unique identifier for that data (such as with [addresses](#address) of [DHT data](#dht-data)) and makes it easy to verify the integrity of the data after it's been retrieved. In a Holochain DHT, the hash of an [entry](#entry) also serves as its [base](#base), allowing an agent to calculate which [authorities](#validation-authority) to request the entry from. +A unique 'fingerprint' for a piece of data, calculated by running the data through a cryptographic hashing function. A hash can serve as a unique identifier for that data (such as with [addresses](#address) of [DHT data](#dht-data)) and makes it easy to verify the integrity of the data after it's been retrieved. In a Holochain DHT, the hash of an [entry](#entry) also serves as its [basis address](#basis-address), allowing an agent to calculate which [authorities](#validation-authority) to request the entry from. #### Hash chain @@ -607,7 +615,7 @@ A [record](#record) written to an agent's [source chain](#source-chain) that pro #### Metadata -Supplementary data attached to a [base](#base) in a [DHT](#distributed-hash-table-dht). Metadata can be one of: +Supplementary data attached to a [basis address](#basis-address) in a [DHT](#distributed-hash-table-dht). Metadata can be one of: * [links](#link), * [CRUD](#crud-action) status of [record data](#record-data) that exists at the base, @@ -759,7 +767,7 @@ The data structure that holds an [action](#action) in an [agent's](#agent) [sour #### Record data -Any piece of [address](#address)able data that can (though doesn't need to) be published to the [DHT](#distributed-hash-table-dht). Record data consists of anything contained in a [record](#record) --- that is, an [action](#action) or an [entry](#entry), which are stored by separate [validation authorities](#validation-authority) on the DHT. Each [base](#base) in a DHT may only have one piece of record data associated with it. This is in contrast to [metadata](#metadata), of which there can be many attached to a base. +Any piece of [address](#address)able data that can (though doesn't need to) be published to the [DHT](#distributed-hash-table-dht). Record data consists of anything contained in a [record](#record) --- that is, an [action](#action) or an [entry](#entry), which are stored by separate [validation authorities](#validation-authority) on the DHT. Each [basis address](#basis-address) in a DHT may only have one piece of record data associated with it. This is in contrast to [metadata](#metadata), of which there can be many attached to a basis address. #### Recurring schedule @@ -831,7 +839,7 @@ A [hash chain](#hash-chain) of [records](#record) committed by an [agent](#agent #### State transition -A modification of application state. In Holochain, all state transitions are initially created as [records](#record) in an [agent's](#agent) [source chain](#source-chain) that represent the [actions](#action) of [creating, updating, and deleting](#create-read-update-delete-crud) data and metadata, as well as of system-level actions such as [capability grants](#capability-grant). A state transition further yields one or more [operations](#dht-operation) that are then [published](#publish) to the [DHT](#distributed-hash-table-dht), that is, they are sent to the appropriate [validation authorities](#validation-authority), who then apply those operations to their own DHT [shard](#sharding), which causes a state transition for the [base](#base) to which the operation applies. +A modification of application state. In Holochain, all state transitions are initially created as [records](#record) in an [agent's](#agent) [source chain](#source-chain) that represent the [actions](#action) of [creating, updating, and deleting](#create-read-update-delete-crud) data and metadata, as well as of system-level actions such as [capability grants](#capability-grant). A state transition further yields one or more [operations](#dht-operation) that are then [published](#publish) to the [DHT](#distributed-hash-table-dht), that is, they are sent to the appropriate [validation authorities](#validation-authority), who then apply those operations to their own DHT [shard](#sharding), which causes a state transition for the [basis address](#basis-address) to which the operation applies. #### Subconscious