use std::iter::Peekable;
use std::ops::RangeInclusive;
use crate::activity::AgentActivityResponse;
use crate::activity::ChainItems;
use holo_hash::ActionHash;
use holo_hash::AgentPubKey;
use holo_hash::HasHash;
use holochain_serialized_bytes::prelude::*;
use holochain_zome_types::prelude::ChainStatus;
use holochain_zome_types::ActionHashed;
use holochain_zome_types::ChainFilter;
use holochain_zome_types::ChainFilters;
use holochain_zome_types::RegisterAgentActivity;
use holochain_zome_types::SignedActionHashed;
#[cfg(all(test, feature = "test_utils"))]
pub mod test;
pub trait AgentActivityExt {
fn empty<T>(agent: &AgentPubKey) -> AgentActivityResponse<T> {
AgentActivityResponse {
agent: agent.clone(),
valid_activity: ChainItems::NotRequested,
rejected_activity: ChainItems::NotRequested,
status: ChainStatus::Empty,
highest_observed: None,
}
}
}
impl AgentActivityExt for AgentActivityResponse {}
pub trait ChainItem: Clone + PartialEq + Eq + std::fmt::Debug + Send + Sync {
type Hash: Into<ActionHash>
+ Clone
+ PartialEq
+ Eq
+ Ord
+ std::hash::Hash
+ std::fmt::Debug
+ Send
+ Sync;
fn seq(&self) -> u32;
fn get_hash(&self) -> &Self::Hash;
fn prev_hash(&self) -> Option<&Self::Hash>;
fn to_display(&self) -> String;
}
pub type ChainItemHash<I> = <I as ChainItem>::Hash;
impl ChainItem for ActionHashed {
type Hash = ActionHash;
fn seq(&self) -> u32 {
self.action_seq()
}
fn get_hash(&self) -> &Self::Hash {
self.as_hash()
}
fn prev_hash(&self) -> Option<&Self::Hash> {
self.prev_action()
}
fn to_display(&self) -> String {
format!("{}", self.content)
}
}
impl ChainItem for SignedActionHashed {
type Hash = ActionHash;
fn seq(&self) -> u32 {
self.hashed.seq()
}
fn get_hash(&self) -> &Self::Hash {
self.hashed.get_hash()
}
fn prev_hash(&self) -> Option<&Self::Hash> {
self.hashed.prev_hash()
}
fn to_display(&self) -> String {
format!("{}", self.hashed.content)
}
}
#[must_use = "Iterator doesn't do anything unless consumed."]
#[derive(Debug)]
pub struct ChainFilterIter<I: AsRef<A>, A: ChainItem = SignedActionHashed> {
filter: ChainFilter<A::Hash>,
iter: Peekable<std::vec::IntoIter<I>>,
end: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainFilterRange {
filter: ChainFilter,
range: RangeInclusive<u32>,
chain_bottom_type: ChainBottomType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ChainBottomType {
Genesis,
Take,
Until,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sequences {
Found(ChainFilterRange),
ChainTopNotFound(ActionHash),
EmptyRange,
}
#[derive(Debug, Clone, PartialEq, Eq, SerializedBytes, Serialize, Deserialize)]
pub enum MustGetAgentActivityResponse {
Activity(Vec<RegisterAgentActivity>),
IncompleteChain,
ChainTopNotFound(ActionHash),
EmptyRange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BoundedMustGetAgentActivityResponse {
Activity(Vec<RegisterAgentActivity>, ChainFilterRange),
IncompleteChain,
ChainTopNotFound(ActionHash),
EmptyRange,
}
impl BoundedMustGetAgentActivityResponse {
pub fn normalize(&mut self) {
if let Self::Activity(activity, _) = self {
activity.sort_unstable_by_key(|a| a.action.action().action_seq());
activity.dedup_by_key(|a| a.action.as_hash().clone());
}
}
}
pub fn merge_bounded_agent_activity_responses(
acc: BoundedMustGetAgentActivityResponse,
next: &BoundedMustGetAgentActivityResponse,
) -> BoundedMustGetAgentActivityResponse {
match (&acc, next) {
(
BoundedMustGetAgentActivityResponse::Activity(responses, chain_filter),
BoundedMustGetAgentActivityResponse::Activity(more_responses, other_chain_filter),
) => {
if chain_filter == other_chain_filter {
let mut merged_responses = responses.clone();
merged_responses.extend(more_responses.to_owned());
let mut merged_activity = BoundedMustGetAgentActivityResponse::Activity(
merged_responses,
chain_filter.clone(),
);
merged_activity.normalize();
merged_activity
}
else {
BoundedMustGetAgentActivityResponse::IncompleteChain
}
}
(BoundedMustGetAgentActivityResponse::Activity(_, _), _) => acc,
(_, BoundedMustGetAgentActivityResponse::Activity(_, _)) => next.clone(),
_ => acc,
}
}
impl<I: AsRef<A>, A: ChainItem> ChainFilterIter<I, A> {
pub fn new(filter: ChainFilter<A::Hash>, mut chain: Vec<I>) -> Self {
chain.sort_unstable_by_key(|a| u32::MAX - a.as_ref().seq());
let mut iter = chain.into_iter().peekable();
let i = iter.by_ref();
while let Some(op) = i.peek() {
if *op.as_ref().get_hash() == filter.chain_top {
break;
}
i.next();
}
Self {
filter,
iter,
end: false,
}
}
}
impl<I: AsRef<A>, A: ChainItem> Iterator for ChainFilterIter<I, A> {
type Item = I;
fn next(&mut self) -> Option<Self::Item> {
if self.end {
return None;
}
let op = self.iter.next()?;
let op = loop {
let parent = self.iter.peek();
match parent {
Some(parent) => {
let child_seq = op.as_ref().seq();
let parent_seq = parent.as_ref().seq();
match (child_seq.cmp(&parent_seq), op.as_ref().prev_hash()) {
(std::cmp::Ordering::Less, _) => {
self.end = true;
break op;
}
(std::cmp::Ordering::Equal, _) => {
self.iter.next();
continue;
}
(std::cmp::Ordering::Greater, None) => {
return None;
}
(std::cmp::Ordering::Greater, _)
if parent_seq.checked_add(1)? != child_seq =>
{
self.end = true;
break op;
}
(std::cmp::Ordering::Greater, Some(prev_hash))
if prev_hash != parent.as_ref().get_hash() =>
{
self.iter.next();
continue;
}
(std::cmp::Ordering::Greater, Some(_)) => {
break op;
}
}
}
None => break op,
}
};
match &mut self.filter.filters {
ChainFilters::Take(n) => *n = n.checked_sub(1)?,
ChainFilters::Until(until_hashes) => {
if until_hashes.contains(op.as_ref().get_hash()) {
self.end = true;
}
}
ChainFilters::ToGenesis => (),
ChainFilters::Both(n, until_hashes) => {
*n = n.checked_sub(1)?;
if until_hashes.contains(op.as_ref().get_hash()) {
self.end = true;
}
}
}
Some(op)
}
}
impl Sequences {
pub fn find_sequences<F, E>(filter: ChainFilter, mut get_seq: F) -> Result<Self, E>
where
F: FnMut(&ActionHash) -> Result<Option<u32>, E>,
{
let chain_top = match get_seq(&filter.chain_top)? {
Some(seq) => seq,
None => return Ok(Self::ChainTopNotFound(filter.chain_top)),
};
let mut chain_bottom_type = ChainBottomType::Genesis;
let distance = match filter.get_until() {
Some(until_hashes) => {
let max = until_hashes
.iter()
.filter_map(|hash| {
match get_seq(hash) {
Ok(seq) => {
let seq = seq?;
(seq <= chain_top).then(|| Ok(seq))
}
Err(e) => Some(Err(e)),
}
})
.try_fold(0, |max, result| {
let seq = result?;
Ok(max.max(seq))
})?;
if max != 0 {
chain_bottom_type = ChainBottomType::Until;
}
chain_top - max
}
None => chain_top,
};
let start = match filter.get_take() {
Some(take) => {
if take == 0 {
return Ok(Self::EmptyRange);
} else if take <= distance {
chain_bottom_type = ChainBottomType::Take;
chain_top.saturating_sub(take).saturating_add(1)
} else {
chain_top - distance
}
}
None => chain_top - distance,
};
Ok(Self::Found(ChainFilterRange {
filter,
range: start..=chain_top,
chain_bottom_type,
}))
}
}
impl ChainFilterRange {
pub fn range(&self) -> &RangeInclusive<u32> {
&self.range
}
pub fn filter_then_check(
self,
chain: Vec<RegisterAgentActivity>,
) -> MustGetAgentActivityResponse {
let until_hashes = self.filter.get_until().cloned();
let out: Vec<_> = ChainFilterIter::new(self.filter, chain).collect();
match out.last().zip(out.first()) {
Some((lowest, highest))
if (lowest.action.action().action_seq()..=highest.action.action().action_seq())
== self.range =>
{
if let Some(hashes) = until_hashes {
if matches!(self.chain_bottom_type, ChainBottomType::Until)
&& !hashes.contains(lowest.action.action_address())
{
return MustGetAgentActivityResponse::IncompleteChain;
}
}
MustGetAgentActivityResponse::Activity(out)
}
_ => MustGetAgentActivityResponse::IncompleteChain,
}
}
}
#[cfg(test)]
pub mod tests {
use super::BoundedMustGetAgentActivityResponse;
use super::ChainBottomType;
use super::ChainFilter;
use super::ChainFilterRange;
use holochain_types::prelude::Action;
use holochain_types::prelude::ActionHash;
use holochain_types::prelude::ActionHashed;
use holochain_types::prelude::AgentPubKey;
use holochain_types::prelude::DnaHash;
use holochain_types::prelude::RegisterAgentActivity;
use holochain_types::prelude::Signature;
use holochain_types::prelude::SignedActionHashed;
use holochain_types::prelude::Timestamp;
use holochain_types::prelude::SIGNATURE_BYTES;
use holochain_zome_types::Dna;
use test_case::test_case;
#[test_case(
BoundedMustGetAgentActivityResponse::EmptyRange,
BoundedMustGetAgentActivityResponse::EmptyRange
=> BoundedMustGetAgentActivityResponse::EmptyRange
)]
#[test_case(
BoundedMustGetAgentActivityResponse::EmptyRange,
BoundedMustGetAgentActivityResponse::IncompleteChain
=> BoundedMustGetAgentActivityResponse::EmptyRange
)]
#[test_case(
BoundedMustGetAgentActivityResponse::EmptyRange,
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
=> BoundedMustGetAgentActivityResponse::EmptyRange
)]
#[test_case(
BoundedMustGetAgentActivityResponse::IncompleteChain,
BoundedMustGetAgentActivityResponse::IncompleteChain
=> BoundedMustGetAgentActivityResponse::IncompleteChain
)]
#[test_case(
BoundedMustGetAgentActivityResponse::IncompleteChain,
BoundedMustGetAgentActivityResponse::EmptyRange
=> BoundedMustGetAgentActivityResponse::IncompleteChain
)]
#[test_case(
BoundedMustGetAgentActivityResponse::IncompleteChain,
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
=> BoundedMustGetAgentActivityResponse::IncompleteChain
)]
#[test_case(
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36])),
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![1; 36]))
=> BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
)]
#[test_case(
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36])),
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
=> BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
)]
#[test_case(
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36])),
BoundedMustGetAgentActivityResponse::EmptyRange
=> BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
)]
#[test_case(
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36])),
BoundedMustGetAgentActivityResponse::IncompleteChain
=> BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
)]
#[test_case(
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
}),
BoundedMustGetAgentActivityResponse::EmptyRange
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
}),
BoundedMustGetAgentActivityResponse::IncompleteChain
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
}),
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36]))
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::EmptyRange,
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::IncompleteChain,
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::ChainTopNotFound(ActionHash::from_raw_36(vec![0; 36])),
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
}),
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
=> BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
#[test_case(
BoundedMustGetAgentActivityResponse::Activity(vec![], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
}),
BoundedMustGetAgentActivityResponse::Activity(vec![RegisterAgentActivity{
action: SignedActionHashed::with_presigned(
ActionHashed::from_content_sync(Action::Dna(Dna {
author: AgentPubKey::from_raw_36(vec![0; 36]),
timestamp: Timestamp(0),
hash: DnaHash::from_raw_36(vec![0; 36]),
})),
Signature([0; SIGNATURE_BYTES]),
),
cached_entry: None,
}], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
=> BoundedMustGetAgentActivityResponse::Activity(vec![RegisterAgentActivity{
action: SignedActionHashed::with_presigned(
ActionHashed::from_content_sync(Action::Dna(Dna {
author: AgentPubKey::from_raw_36(vec![0; 36]),
timestamp: Timestamp(0),
hash: DnaHash::from_raw_36(vec![0; 36]),
})),
Signature([0; SIGNATURE_BYTES]),
),
cached_entry: None
}], ChainFilterRange {
filter: ChainFilter::new(ActionHash::from_raw_36(vec![0; 36])),
range: 0..=0,
chain_bottom_type: ChainBottomType::Genesis,
})
)]
fn test_merge_bounded_agent_activity_responses(
acc: BoundedMustGetAgentActivityResponse,
next: BoundedMustGetAgentActivityResponse,
) -> BoundedMustGetAgentActivityResponse {
super::merge_bounded_agent_activity_responses(acc, &next)
}
}