use std::collections::HashMap;
use std::collections::HashSet;
use crate::action::ActionType;
use crate::action::EntryType;
use crate::warrant::Warrant;
use crate::ActionHashed;
use crate::Record;
use holo_hash::ActionHash;
use holo_hash::EntryHash;
use holo_hash::HasHash;
pub use holochain_serialized_bytes::prelude::*;
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Clone, Debug)]
pub enum ChainQueryFilterRange {
Unbounded,
ActionSeqRange(u32, u32),
ActionHashRange(ActionHash, ActionHash),
ActionHashTerminated(ActionHash, u32),
}
impl Default for ChainQueryFilterRange {
fn default() -> Self {
Self::Unbounded
}
}
#[derive(
serde::Serialize, serde::Deserialize, SerializedBytes, Default, PartialEq, Clone, Debug,
)]
pub struct ChainQueryFilter {
pub sequence_range: ChainQueryFilterRange,
pub entry_type: Option<EntryType>,
pub entry_hashes: Option<HashSet<EntryHash>>,
pub action_type: Option<ActionType>,
pub include_entries: bool,
pub order_descending: bool,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, SerializedBytes)]
pub struct AgentActivity {
pub valid_activity: Vec<(u32, ActionHash)>,
pub rejected_activity: Vec<(u32, ActionHash)>,
pub status: ChainStatus,
pub highest_observed: Option<HighestObserved>,
pub warrants: Vec<Warrant>,
}
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, SerializedBytes)]
pub enum ActivityRequest {
Status,
Full,
}
#[derive(Clone, Debug, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
pub struct HighestObserved {
pub action_seq: u32,
pub hash: Vec<ActionHash>,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ChainStatus {
Empty,
Valid(ChainHead),
Forked(ChainFork),
Invalid(ChainHead),
}
impl Default for ChainStatus {
fn default() -> Self {
ChainStatus::Empty
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ChainHead {
pub action_seq: u32,
pub hash: ActionHash,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ChainFork {
pub fork_seq: u32,
pub first_action: ActionHash,
pub second_action: ActionHash,
}
impl ChainQueryFilter {
pub fn new() -> Self {
Self {
include_entries: false,
..Self::default()
}
}
pub fn sequence_range(mut self, sequence_range: ChainQueryFilterRange) -> Self {
self.sequence_range = sequence_range;
self
}
pub fn entry_type(mut self, entry_type: EntryType) -> Self {
self.entry_type = Some(entry_type);
self
}
pub fn entry_hashes(mut self, entry_hashes: HashSet<EntryHash>) -> Self {
self.entry_hashes = Some(entry_hashes);
self
}
pub fn action_type(mut self, action_type: ActionType) -> Self {
self.action_type = Some(action_type);
self
}
pub fn include_entries(mut self, include_entries: bool) -> Self {
self.include_entries = include_entries;
self
}
pub fn ascending(mut self) -> Self {
self.order_descending = false;
self
}
pub fn descending(mut self) -> Self {
self.order_descending = true;
self
}
pub fn disambiguate_forks(&self, actions: Vec<ActionHashed>) -> Vec<ActionHashed> {
match &self.sequence_range {
ChainQueryFilterRange::Unbounded => actions,
ChainQueryFilterRange::ActionSeqRange(start, end) => actions
.into_iter()
.filter(|action| *start <= action.action_seq() && action.action_seq() <= *end)
.collect(),
ChainQueryFilterRange::ActionHashRange(start, end) => {
let mut action_hashmap = actions
.into_iter()
.map(|action| (action.as_hash().clone(), action))
.collect::<HashMap<ActionHash, ActionHashed>>();
let mut filtered_actions = Vec::new();
let mut maybe_next_action = action_hashmap.remove(end);
while let Some(next_action) = maybe_next_action {
maybe_next_action = next_action
.as_content()
.prev_action()
.and_then(|prev_action| action_hashmap.remove(prev_action));
let is_start = next_action.as_hash() == start;
filtered_actions.push(next_action);
if is_start {
break;
}
}
filtered_actions
}
ChainQueryFilterRange::ActionHashTerminated(end, n) => {
let mut action_hashmap = actions
.iter()
.map(|action| (action.as_hash().clone(), action))
.collect::<HashMap<ActionHash, &ActionHashed>>();
let mut filtered_actions = Vec::new();
let mut maybe_next_action = action_hashmap.remove(end);
let mut i = 0;
while let Some(next_action) = maybe_next_action {
maybe_next_action = next_action
.as_content()
.prev_action()
.and_then(|prev_action| action_hashmap.remove(prev_action));
filtered_actions.push(next_action.clone());
if i == *n {
break;
}
i += 1;
}
filtered_actions
}
}
}
pub fn filter_actions(&self, actions: Vec<ActionHashed>) -> Vec<ActionHashed> {
self.disambiguate_forks(actions)
.into_iter()
.filter(|action| {
self.action_type
.as_ref()
.map(|action_type| action.action_type() == *action_type)
.unwrap_or(true)
&& self
.entry_type
.as_ref()
.map(|entry_type| action.entry_type() == Some(entry_type))
.unwrap_or(true)
&& self
.entry_hashes
.as_ref()
.map(|entry_hashes| match action.entry_hash() {
Some(entry_hash) => entry_hashes.contains(entry_hash),
None => false,
})
.unwrap_or(true)
})
.collect()
}
pub fn filter_records(&self, records: Vec<Record>) -> Vec<Record> {
let actions = self.filter_actions(
records
.iter()
.map(|record| record.action_hashed().clone())
.collect(),
);
let action_hashset = actions
.iter()
.map(|action| action.as_hash().clone())
.collect::<HashSet<ActionHash>>();
records
.into_iter()
.filter(|record| action_hashset.contains(record.action_address()))
.collect()
}
}
#[cfg(test)]
#[cfg(feature = "fixturators")]
mod tests {
use super::ChainQueryFilter;
use crate::action::EntryType;
use crate::fixt::AppEntryDefFixturator;
use crate::fixt::*;
use crate::ActionHashed;
use crate::ChainQueryFilterRange;
use ::fixt::prelude::*;
use holo_hash::HasHash;
fn fixtures() -> [ActionHashed; 7] {
let entry_type_1 = EntryType::App(fixt!(AppEntryDef));
let entry_type_2 = EntryType::AgentPubKey;
let entry_hash_0 = fixt!(EntryHash);
let mut h0 = fixt!(Create);
h0.entry_type = entry_type_1.clone();
h0.action_seq = 0;
h0.entry_hash = entry_hash_0.clone();
let hh0 = ActionHashed::from_content_sync(h0.into());
let mut h1 = fixt!(Update);
h1.entry_type = entry_type_2.clone();
h1.action_seq = 1;
h1.prev_action = hh0.as_hash().clone();
let hh1 = ActionHashed::from_content_sync(h1.into());
let mut h2 = fixt!(CreateLink);
h2.action_seq = 2;
h2.prev_action = hh1.as_hash().clone();
let hh2 = ActionHashed::from_content_sync(h2.into());
let mut h3 = fixt!(Create);
h3.entry_type = entry_type_2.clone();
h3.action_seq = 3;
h3.prev_action = hh2.as_hash().clone();
let hh3 = ActionHashed::from_content_sync(h3.into());
let mut h3a = fixt!(Create);
h3a.entry_type = entry_type_1.clone();
h3a.action_seq = 3;
h3a.prev_action = hh2.as_hash().clone();
let hh3a = ActionHashed::from_content_sync(h3a.into());
let mut h4 = fixt!(Update);
h4.entry_type = entry_type_1.clone();
h4.entry_hash = entry_hash_0;
h4.action_seq = 4;
h4.prev_action = hh3.as_hash().clone();
let hh4 = ActionHashed::from_content_sync(h4.into());
let mut h5 = fixt!(CreateLink);
h5.action_seq = 5;
h5.prev_action = hh4.as_hash().clone();
let hh5 = ActionHashed::from_content_sync(h5.into());
let actions = [hh0, hh1, hh2, hh3, hh3a, hh4, hh5];
actions
}
fn map_query(query: &ChainQueryFilter, actions: &[ActionHashed]) -> Vec<bool> {
let filtered = query.filter_actions(actions.to_vec());
actions
.iter()
.map(|h| filtered.contains(h))
.collect::<Vec<_>>()
}
#[test]
fn filter_by_entry_type() {
let actions = fixtures();
let query_1 =
ChainQueryFilter::new().entry_type(actions[0].entry_type().unwrap().to_owned());
let query_2 =
ChainQueryFilter::new().entry_type(actions[1].entry_type().unwrap().to_owned());
assert_eq!(
map_query(&query_1, &actions),
[true, false, false, false, true, true, false].to_vec()
);
assert_eq!(
map_query(&query_2, &actions),
[false, true, false, true, false, false, false].to_vec()
);
}
#[test]
fn filter_by_entry_hash() {
let actions = fixtures();
let query = ChainQueryFilter::new().entry_hashes(
vec![
actions[3].entry_hash().unwrap().clone(),
actions[5].entry_hash().unwrap().clone(),
]
.into_iter()
.collect(),
);
assert_eq!(
map_query(&query, &actions),
vec![true, false, false, true, false, true, false]
);
}
#[test]
fn filter_by_action_type() {
let actions = fixtures();
let query_1 = ChainQueryFilter::new().action_type(actions[0].action_type());
let query_2 = ChainQueryFilter::new().action_type(actions[1].action_type());
let query_3 = ChainQueryFilter::new().action_type(actions[2].action_type());
assert_eq!(
map_query(&query_1, &actions),
[true, false, false, true, true, false, false].to_vec()
);
assert_eq!(
map_query(&query_2, &actions),
[false, true, false, false, false, true, false].to_vec()
);
assert_eq!(
map_query(&query_3, &actions),
[false, false, true, false, false, false, true].to_vec()
);
}
#[test]
fn filter_by_chain_sequence() {
let actions = fixtures();
for (sequence_range, expected, name) in vec![
(
ChainQueryFilterRange::Unbounded,
vec![true, true, true, true, true, true, true],
"unbounded",
),
(
ChainQueryFilterRange::ActionSeqRange(0, 0),
vec![true, false, false, false, false, false, false],
"first only",
),
(
ChainQueryFilterRange::ActionSeqRange(0, 1),
vec![true, true, false, false, false, false, false],
"several from start",
),
(
ChainQueryFilterRange::ActionSeqRange(1, 2),
vec![false, true, true, false, false, false, false],
"several not start",
),
(
ChainQueryFilterRange::ActionSeqRange(2, 999),
vec![false, false, true, true, true, true, true],
"exceeds chain length, not start",
),
(
ChainQueryFilterRange::ActionHashRange(
actions[2].as_hash().clone(),
actions[6].as_hash().clone(),
),
vec![false, false, true, true, false, true, true],
"hash bounded not 3a",
),
(
ChainQueryFilterRange::ActionHashRange(
actions[2].as_hash().clone(),
actions[4].as_hash().clone(),
),
vec![false, false, true, false, true, false, false],
"hash bounded 3a",
),
(
ChainQueryFilterRange::ActionHashTerminated(actions[2].as_hash().clone(), 1),
vec![false, true, true, false, false, false, false],
"hash terminated not start",
),
(
ChainQueryFilterRange::ActionHashTerminated(actions[2].as_hash().clone(), 0),
vec![false, false, true, false, false, false, false],
"hash terminated not start 0 prior",
),
(
ChainQueryFilterRange::ActionHashTerminated(actions[5].as_hash().clone(), 7),
vec![true, true, true, true, false, true, false],
"hash terminated main chain before chain start",
),
(
ChainQueryFilterRange::ActionHashTerminated(actions[4].as_hash().clone(), 7),
vec![true, true, true, false, true, false, false],
"hash terminated 3a chain before chain start",
),
] {
assert_eq!(
(
map_query(
&ChainQueryFilter::new().sequence_range(sequence_range),
&actions,
),
name
),
(expected, name),
);
}
}
#[test]
fn filter_by_multi() {
let actions = fixtures();
assert_eq!(
map_query(
&ChainQueryFilter::new()
.action_type(actions[0].action_type())
.entry_type(actions[0].entry_type().unwrap().clone())
.sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 0)),
&actions
),
[true, false, false, false, false, false, false].to_vec()
);
assert_eq!(
map_query(
&ChainQueryFilter::new()
.action_type(actions[1].action_type())
.entry_type(actions[0].entry_type().unwrap().clone())
.sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 999)),
&actions
),
[false, false, false, false, false, true, false].to_vec()
);
assert_eq!(
map_query(
&ChainQueryFilter::new()
.entry_type(actions[0].entry_type().unwrap().clone())
.sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 999)),
&actions
),
[true, false, false, false, true, true, false].to_vec()
);
}
}