Querying Source Chains

An agent can query their source chain for a history of the records they’ve written, including link and public and private entry data, which includes capability grants and claims. They can also query the public portion of another agent’s source chain in a zome function or validate callback.

An agent’s source chain is their record of local state changes. It’s a multi-purpose data structure, and can be interpreted in different ways, including as:

Filtering a query

Whether an agent is querying their own source chain or another agent’s, you build a query with the ChainQueryFilter struct, which has a few filter types:

After retrieving the filtered records, you can then further filter them in memory using Rust’s standard Iterator trait.

Use the builder interface

Rather than building a struct and having to specify fields you don’t need, you can use the builder interface on ChainQueryFilter:

use hdk::prelude::*;
use movies::prelude::*;

let filter_only_movie_updates = ChainQueryFilter::new()
    .entry_type(EntryType::App(UnitEntryTypes::Movie.into()))
    .action_type(ActionType::Update)
    .include_entries(true);

let filter_only_cap_grants_and_claims_newest_first = ChainQueryFilter::new()
    .entry_type(EntryType::CapGrant)
    .entry_type(EntryType::CapClaim)
    .include_entries(true)
    .descending();

let filter_first_ten_records = ChainQueryFilter::new()
    .sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 9));

Use ChainQueryFilter to query a vector of actions or records

If you already have a vector of Actions or Records in memory, you can apply a ChainQueryFilter to them as if you were querying a source chain.

include hdk::prelude::*;

let actions: Vec<Action> = /* get some actions somehow */;
let movie_update_actions = filter_only_movie_updates.filter_actions(actions);

Query an agent’s own source chain

An agent can query their own source chain with the query host function, which takes a ChainQueryFilter and returns a Vec<Record> wrapped in an ExternResult.

use hdk::prelude::*;

#[hdk_extern]
pub fn get_all_movies_i_authored() -> Vec<Record> {
    query(ChainQueryFilter::new()
        .entry_type(EntryType::App(UnitEntryTypes::Movie.into()))
        .include_entries(true)
    )
}

Query another agent’s source chain for validation

When validating an agent’s activity, you can query their existing source chain records with must_get_agent_activity. Validation logic must be deterministic, so this function’s filter struct and return value remove non-determinism.

must_get_agent_activity only allows you to select a contiguous, bounded slice of a source chain, and doesn’t return any information about the validity of the actions in that slice or the chain as a whole. It needs to get the entire slice from an authority, so it’s best to use it only when validating a RegisterAgentActivity operation, where the validating authority already has that data.

This example fills out a validate_update_movie stub function, generated by the scaffolding tool, to enforce that an agent may only edit a movie listing three times.

use hdi::prelude::*;

pub fn validate_update_movie(
    action: Update,
    _movie: Movie,
    original_action: EntryCreationAction,
    _original_movie: Movie,
) -> ExternResult<ValidateCallbackResult> {
    let result = must_get_agent_activity(
        // Get the agent hash from the action itself.
        action.author,
        // You can only validate an action based on the source chain records
        // that precede it, so use the previous action hash as the `chain_top`
        // argument in this query.
        // We don't specify a range here, so it defaults to
        // `ChainFilters::ToGenesis`. We also don't specify whether to include
        // entries, so it defaults to false.
        ChainFilter::new(action.prev_action)
    )?;

    // The result is a vector of
    // holochain_integrity_types::op::RegisterAgentActivity DHT ops, from
    // which we can get the actions.
    let result_as_actions = result
        .iter()
        .map(|o| o.action.hashed.content)
        .collect();

    // Now we build and use a filter to only get updates for `Movie` entries.
    let filter_movie_update_actions = ChainQueryFilter::new()
        .action_type(ActionType::Update)
        .entry_type(EntryType::App(EntryTypes::Movie.into()));
    let movie_update_actions: Vec<Update> = filter_movie_update_actions
        .filter_actions(result_as_actions)
        .iter()
        .map(|a| a.try_into().unwrap())
        .collect();

    // Now find out how many times we've tried to update the same `Movie` as
    // we're trying to update now.
    let times_I_updated_this_movie = movie_update_actions
        .fold(1, |c, a| c + if a.original_action_address == action.original_action_address { 1 } else { 0 });

    if times_I_updated_this_movie > 3 {
        Ok(ValidateCallbackResult::Invalid("Already tried to update the same movie action 3 times"))
    } else {
        Ok(ValidateCallbackResult::Valid)
    }
}

Reference

Further reading

It looks like you are using Internet Explorer. While the basic content is available, this is no longer a supported browser by the manufacturer, and no attention is being given to having IE work well here.