Validation Rules

### Validation Workflow - Validation needs to be deterministic: same result no matter who runs it and when - After an action is committed, it is transformed into different DHT Operations (DHT Ops) - Then, those DHT Ops are published to the different neighborhoods in the DHT - When those nodes receive that publish, they add it to their validation queue - If the validation fails, those nodes produce warrants that get gossiped throughout the network - All other nodes can verify that the bad agent is behaving badly - They then disconnect from the bad agent - If the validation succeeds, the node starts to serve that record to DHT queries (get, get_links, etc.) ### Validation callback function context - The function `validate` is called in two contexts 1. The author validates their own recods before publishing them 2. All prospective hosts of the DHT Op validate
## DHT Ops
Chain Action DhtOp Name Targeted hash basis Payload Metadata
create_entry(entry)
RegisterAgentActivity
Author's public key
Action
Hash of the action
StoreRecord
Hash of the action
Record
-
StoreEntry
Hash of the entry
Record
-
update_entry(original_action_hash, new_entry)
RegisterAgentActivity
Author's public key
New Action
Hash of the action
StoreRecord
Hash of the action
New Record
-
StoreEntry
Hash of the entry
New Record
-
RegisterEntryUpdate
Hash of the original entry
New action
Old entry is updated to new entry
RegisterRecordUpdate
Hash of the original action
New action
Old action is updated to new action
delete_entry(action_hash)
RegisterAgentActivity
Author's public key
New Action
Hash of the action
StoreRecord
Hash of the new action
New Record
-
RegisterEntryDelete
Hash of the deleted entry
New Action
Old entry deleted by new action
RegisterRecordDelete
Hash of the deleted action
New Action
Old action deleted by new action
create_link(base, target, tag)
RegisterAgentActivity
Author's public key
Action
Hash of the action
StoreRecord
Hash of the new action
Record
-
RegisterCreateLink
"Base" hash
Action
Link from the base to the target hash
delete_link(create_link_action_hash)
RegisterAgentActivity
Author's public key
Action
Hash of the action
StoreRecord
Hash of the new action
Record
-
RegisterDeleteLink
Hash of the deleted create link action hash
Action
Deleted link sent to tombstone
### System Validation - Does not depend on your application code - Holochain enforces some system level validations across all apps - Actions - Source chains can't be forked - All actions must be signed by their author - Timestamp and sequence number for the actions must be incremental - If the action is accompanied by an entry, the entry hash in the action is correct - Entries - The entry byte array can be deserialized to the appropriate struct
### App Validation (I) - Validation rules for a given entry or link type must go in the same zome as the type definition - Private entries are only validated by its author, so you can't trust the private entries to be valid - Callback in your integrity zome: `validate(dht_op: Op)` - Three results possible: 1. Valid 2. Invalid: it specifies the reason why it failed 3. Unresolved dependencies: not enough data to complete the validation
### App Validation (II)
              
#[hdk_extern]
pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
  match op.flattened::<EntryTypes, LinkTypes>()? { // "flattened" converts the complex "Op"
                                                   // structure into a much simpler "FlatOp"
    FlatOp::StoreEntry(store_entry) => match store_entry {
      OpEntry::CreateEntry { app_entry, action } => match app_entry { // "app_entry" is your own 
                                                                      // app defined EntryTypes
        EntryTypes::Post(post) => match post.content.len() <= 180 { // Only posts of 180 
                                                                    // characters or less 
                                                                    // are valid  
          true => Ok(ValidateCallbackResult::Valid),
          false => Ok(ValidateCallbackResult::Invalid(String::from("Post is too long")))
        },
        EntryTypes::Comment(comment) => Ok(ValidateCallbackResult::Valid), 
      },
      OpEntry::UpdateEntry { app_entry, action, original_action_hash, original_entry_hash } => 
        Ok(ValidateCallbackResult::Valid), // app_entry will be exactly the same as above
      _ => Ok(ValidateCallbackResult::Valid),
    },
    FlatOp::StoreRecord(store_record) => match store_record {
      OpRecord::CreateEntry { app_entry, action, } => Ok(ValidateCallbackResult::Valid), 
      OpRecord::UpdateEntry { app_entry, action, original_action_hash, original_entry_hash } =>
        Ok(ValidateCallbackResult::Valid), // app_entry will be exactly the same as above
      _ => Ok(ValidateCallbackResult::Valid),
    },
    FlatOp::RegisterUpdate(update_entry) |
    FlatOp::RegisterDelete(delete_entry) |
    FlatOp::RegisterCreateLink { .. } | 
    FlatOp::RegisterDeleteLink { .. } |
    FlatOp::RegisterAgentActivity(agent_activity) => Ok(ValidateCallbackResult::Valid)
  }
}
              
            
### Validation challenges - Non-deterministic queries can't be considered during validation - `get` and `get_details` are forbidden - They attempt to return a current list of updates and deletes - `get_links` is forbidden - `get_agent_activity` is forbidden unless bounded by action hashes - `agent_info()` is forbidden, as its result depends on which cell it runs - Deterministic alternatives: - `must_get_entry` - Only returns the entry, not its action - Never returns `None`, rather the validation has unresolved dependencies - `must_get_action` - Only returns the action - Never returns `None`, rather the validation has unresolved dependencies - `must_get_valid_record` - Returns the record - Never returns `None`, rather the validation has unresolved dependencies - `must_get_agent_activity` - Returns the source chain actions stored in the DHT for a given agent - Needs the chain_top action ID for the given agent - Never returns `None`, rather the validation has unresolved dependencies
### Genesis Self-Check - Callback executed when the cell is instantiated - If it fails, the app for that cell will fail to install - It does not have access to any network calls (get, get_details, etc.) because it's run before joining other peers in the network - This is not attacker-proof - A bad actor can just not run this callback and try to join the network either way - The bad actor will be identified when they propagate their genesis actions (`Dna`, `AgentValidationPkg` and `Create` of the `AgentPubKey`) and the other peers run the normal app validation `validate(op: Op)` and see that the `Create` of the `AgentPubKey` is invalid - So genesis self-check is to prevent good actor to unintentionally join a network when they actually didn't have the permission to do so
            
#[hdk_extern]
pub fn genesis_self_check(data: GenesisSelfCheckData) -> ExternResult<ValidateCallbackResult> {
    // Check data.agent_key, data.membrane_proof
}
            
          
### Preventing unwanted agents from joining the network (I) - Any request to create a cell receives a membrane proof parameter - When any cell is instantiated, it generates three records: - Dna - AgentValidationPkg - Includes the membrane proof that the agent provided when instantiating the cell - Create - The `AgentPubKey` of the agent as the entry - If the third record fails validation, the network doesn't allow that agent to join it
### Preventing unwanted agents from joining the network (II)
            
fn is_membrane_proof_valid(
  for_agent: AgentPubKey,
  membrane_proof: Option<MembraneProof>,
) -> ExternResult<ValidateCallbackResult> {
  // Check that the agent is allowed to join the network
}

#[hdk_extern]
pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> { // Normal validate callback
  if let FlatOp::RegisterAgentActivity(
    OpActivity::CreateAgent { agent, action }
  ) = op.flattened::<EntryTypes, LinkTypes>()? {
    let previous_action = must_get_action(action.prev_action)?; // Getting the previous record,
                                                                // which must be the 
                                                                // `AgentValidationPkg`
    match previous_action.action() {
      Action::AgentValidationPkg(pkg) => is_membrane_proof_valid(agent, pkg.membrane_proof),
      _ => Ok(ValidateCallbackResult::Invalid(
            "The previous action for a `CreateAgent` action must be an `AgentValidationPkg`"
            .to_string(),
      ))
    }
  }
  // ... rest of the validation code
}

            
          

That's it!