use std::iter::FromIterator;
use std::time::Duration;
use crate::prelude::*;
use holo_hash::ActionHash;
use holo_hash::AgentPubKey;
use holo_hash::EntryHash;
use holochain_serialized_bytes::SerializedBytesError;
pub const SESSION_ACTION_TIME_OFFSET: Duration = Duration::from_millis(1000);
pub const SESSION_TIME_FUTURE_MAX: Duration =
Duration::from_millis(5000 + SESSION_ACTION_TIME_OFFSET.as_millis() as u64);
pub const MIN_COUNTERSIGNING_AGENTS: usize = 2;
pub const MAX_COUNTERSIGNING_AGENTS: usize = 8;
pub use error::CounterSigningError;
mod error;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningSessionTimes {
pub start: Timestamp,
pub end: Timestamp,
}
impl CounterSigningSessionTimes {
pub fn try_new(start: Timestamp, end: Timestamp) -> Result<Self, CounterSigningError> {
let session_times = Self { start, end };
session_times.check_integrity()?;
Ok(session_times)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
let times_are_valid = &Timestamp::from_micros(0) < self.start()
&& self.start()
<= &(self.end() - SESSION_ACTION_TIME_OFFSET).map_err(|_| {
CounterSigningError::CounterSigningSessionTimes((*self).clone())
})?;
if times_are_valid {
Ok(())
} else {
Err(CounterSigningError::CounterSigningSessionTimes(
(*self).clone(),
))
}
}
pub fn start(&self) -> &Timestamp {
&self.start
}
#[cfg(feature = "test_utils")]
pub fn start_mut(&mut self) -> &mut Timestamp {
&mut self.start
}
pub fn end(&self) -> &Timestamp {
&self.end
}
#[cfg(feature = "test_utils")]
pub fn end_mut(&mut self) -> &mut Timestamp {
&mut self.end
}
}
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightBytes(#[serde(with = "serde_bytes")] pub Vec<u8>);
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Role(pub u8);
impl Role {
pub fn new(role: u8) -> Self {
Self(role)
}
}
pub type CounterSigningAgents = Vec<(AgentPubKey, Vec<Role>)>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightRequest {
pub app_entry_hash: EntryHash,
pub signing_agents: CounterSigningAgents,
pub optional_signing_agents: CounterSigningAgents,
pub minimum_optional_signing_agents: u8,
pub enzymatic: bool,
pub session_times: CounterSigningSessionTimes,
pub action_base: ActionBase,
pub preflight_bytes: PreflightBytes,
}
impl PreflightRequest {
#[allow(clippy::too_many_arguments)]
pub fn try_new(
app_entry_hash: EntryHash,
signing_agents: CounterSigningAgents,
optional_signing_agents: CounterSigningAgents,
minimum_optional_signing_agents: u8,
enzymatic: bool,
session_times: CounterSigningSessionTimes,
action_base: ActionBase,
preflight_bytes: PreflightBytes,
) -> Result<Self, CounterSigningError> {
let preflight_request = Self {
app_entry_hash,
signing_agents,
optional_signing_agents,
minimum_optional_signing_agents,
enzymatic,
session_times,
action_base,
preflight_bytes,
};
preflight_request.check_integrity()?;
Ok(preflight_request)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.check_enzyme()?;
self.session_times.check_integrity()?;
self.check_agents()?;
Ok(())
}
pub fn check_agents_dupes(&self) -> Result<(), CounterSigningError> {
let v: Vec<AgentPubKey> = self
.signing_agents
.iter()
.map(|(agent, _roles)| agent.clone())
.collect();
if std::collections::HashSet::<AgentPubKey>::from_iter(v.clone()).len()
== self.signing_agents.len()
{
Ok(())
} else {
Err(CounterSigningError::AgentsDupes(v))
}
}
pub fn check_agents_len(&self) -> Result<(), CounterSigningError> {
if MIN_COUNTERSIGNING_AGENTS <= self.signing_agents.len()
&& self.signing_agents.len() <= MAX_COUNTERSIGNING_AGENTS
{
Ok(())
} else {
Err(CounterSigningError::AgentsLength(self.signing_agents.len()))
}
}
pub fn check_agents_optional(&self) -> Result<(), CounterSigningError> {
if self.minimum_optional_signing_agents as usize > self.optional_signing_agents.len() {
return Err(CounterSigningError::OptionalAgentsLength(
self.minimum_optional_signing_agents,
self.optional_signing_agents.len(),
));
}
if ((self.minimum_optional_signing_agents * 2) as usize)
< self.optional_signing_agents.len()
&& !self.optional_signing_agents.is_empty()
{
return Err(CounterSigningError::MinOptionalAgents(
self.minimum_optional_signing_agents,
self.optional_signing_agents.len(),
));
}
Ok(())
}
pub fn check_agents(&self) -> Result<(), CounterSigningError> {
self.check_agents_dupes()?;
self.check_agents_len()?;
self.check_agents_optional()?;
Ok(())
}
pub fn check_enzyme(&self) -> Result<(), CounterSigningError> {
if self.enzymatic
&& !self.optional_signing_agents.is_empty()
&& self.signing_agents.get(0) != self.optional_signing_agents.get(0)
{
return Err(CounterSigningError::EnzymeMismatch(
self.signing_agents.get(0).cloned(),
self.optional_signing_agents.get(0).cloned(),
));
}
if !self.enzymatic && !self.optional_signing_agents.is_empty() {
return Err(CounterSigningError::NonEnzymaticOptionalSigners);
}
Ok(())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightResponse {
pub request: PreflightRequest,
pub agent_state: CounterSigningAgentState,
pub signature: Signature,
}
impl PreflightResponse {
pub fn try_new(
request: PreflightRequest,
agent_state: CounterSigningAgentState,
signature: Signature,
) -> Result<Self, CounterSigningError> {
let preflight_response = Self {
request,
agent_state,
signature,
};
preflight_response.check_integrity()?;
Ok(preflight_response)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.request().check_integrity()
}
pub fn encode_fields_for_signature(
request: &PreflightRequest,
agent_state: &CounterSigningAgentState,
) -> Result<Vec<u8>, SerializedBytesError> {
holochain_serialized_bytes::encode(&(request, agent_state))
}
pub fn encode_for_signature(&self) -> Result<Vec<u8>, SerializedBytesError> {
Self::encode_fields_for_signature(&self.request, &self.agent_state)
}
pub fn request(&self) -> &PreflightRequest {
&self.request
}
#[cfg(feature = "test_utils")]
pub fn request_mut(&mut self) -> &mut PreflightRequest {
&mut self.request
}
pub fn agent_state(&self) -> &CounterSigningAgentState {
&self.agent_state
}
#[cfg(feature = "test_utils")]
pub fn agent_state_mut(&mut self) -> &mut CounterSigningAgentState {
&mut self.agent_state
}
pub fn signature(&self) -> &Signature {
&self.signature
}
#[cfg(feature = "test_utils")]
pub fn signature_mut(&mut self) -> &mut Signature {
&mut self.signature
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[allow(clippy::large_enum_variant)]
pub enum PreflightRequestAcceptance {
Accepted(PreflightResponse),
UnacceptableFutureStart,
UnacceptableAgentNotFound,
Invalid(String),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningAgentState {
agent_index: u8,
chain_top: ActionHash,
action_seq: u32,
}
impl CounterSigningAgentState {
pub fn new(agent_index: u8, chain_top: ActionHash, action_seq: u32) -> Self {
Self {
agent_index,
chain_top,
action_seq,
}
}
pub fn agent_index(&self) -> &u8 {
&self.agent_index
}
#[cfg(feature = "test_utils")]
pub fn agent_index_mut(&mut self) -> &mut u8 {
&mut self.agent_index
}
pub fn chain_top(&self) -> &ActionHash {
&self.chain_top
}
#[cfg(feature = "test_utils")]
pub fn chain_top_mut(&mut self) -> &mut ActionHash {
&mut self.chain_top
}
pub fn action_seq(&self) -> &u32 {
&self.action_seq
}
#[cfg(feature = "test_utils")]
pub fn action_seq_mut(&mut self) -> &mut u32 {
&mut self.action_seq
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum ActionBase {
Create(CreateBase),
Update(UpdateBase),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CreateBase {
entry_type: EntryType,
}
impl CreateBase {
pub fn new(entry_type: EntryType) -> Self {
Self { entry_type }
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct UpdateBase {
pub original_action_address: ActionHash,
pub original_entry_address: EntryHash,
pub entry_type: EntryType,
}
impl Action {
pub fn from_countersigning_data(
entry_hash: EntryHash,
session_data: &CounterSigningSessionData,
author: AgentPubKey,
weight: EntryRateWeight,
) -> Result<Self, CounterSigningError> {
let agent_state = session_data.agent_state_for_agent(&author)?;
Ok(match &session_data.preflight_request().action_base {
ActionBase::Create(base) => Action::Create(Create {
author,
timestamp: session_data.to_timestamp(),
action_seq: agent_state.action_seq + 1,
prev_action: agent_state.chain_top.clone(),
entry_type: base.entry_type.clone(),
weight,
entry_hash,
}),
ActionBase::Update(base) => Action::Update(Update {
author,
timestamp: session_data.to_timestamp(),
action_seq: agent_state.action_seq + 1,
prev_action: agent_state.chain_top.clone(),
original_action_address: base.original_action_address.clone(),
original_entry_address: base.original_entry_address.clone(),
entry_type: base.entry_type.clone(),
weight,
entry_hash,
}),
})
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningSessionData {
pub preflight_request: PreflightRequest,
pub responses: Vec<(CounterSigningAgentState, Signature)>,
pub optional_responses: Vec<(CounterSigningAgentState, Signature)>,
}
impl CounterSigningSessionData {
pub fn try_from_responses(
responses: Vec<PreflightResponse>,
optional_responses: Vec<PreflightResponse>,
) -> Result<Self, CounterSigningError> {
let preflight_request = responses
.get(0)
.ok_or(CounterSigningError::MissingResponse)?
.to_owned()
.request;
let convert_responses =
|rs: Vec<PreflightResponse>| -> Vec<(CounterSigningAgentState, Signature)> {
rs.into_iter()
.map(|response| (response.agent_state.clone(), response.signature))
.collect()
};
let responses = convert_responses(responses);
let optional_responses = convert_responses(optional_responses);
Ok(Self {
preflight_request,
responses,
optional_responses,
})
}
pub fn agent_state_for_agent(
&self,
agent: &AgentPubKey,
) -> Result<&CounterSigningAgentState, CounterSigningError> {
match self
.preflight_request
.signing_agents
.iter()
.position(|(pubkey, _)| pubkey == agent)
{
Some(agent_index) => match self.responses.get(agent_index) {
Some((agent_state, _)) => Ok(agent_state),
None => Err(CounterSigningError::AgentIndexOutOfBounds),
},
None => Err(CounterSigningError::AgentIndexOutOfBounds),
}
}
pub fn build_action_set(
&self,
entry_hash: EntryHash,
weight: EntryRateWeight,
) -> Result<Vec<Action>, CounterSigningError> {
let mut actions = vec![];
let mut build_actions = |countersigning_agents: &CounterSigningAgents| -> Result<(), _> {
for (agent, _role) in countersigning_agents.iter() {
actions.push(Action::from_countersigning_data(
entry_hash.clone(),
self,
agent.clone(),
weight.clone(),
)?);
}
Ok(())
};
build_actions(&self.preflight_request.signing_agents)?;
build_actions(&self.preflight_request.optional_signing_agents)?;
Ok(actions)
}
pub fn try_new(
preflight_request: PreflightRequest,
responses: Vec<(CounterSigningAgentState, Signature)>,
optional_responses: Vec<(CounterSigningAgentState, Signature)>,
) -> Result<Self, CounterSigningError> {
let session_data = Self {
preflight_request,
responses,
optional_responses,
};
session_data.check_integrity()?;
Ok(session_data)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.check_responses_indexes()
}
pub fn check_responses_indexes(&self) -> Result<(), CounterSigningError> {
if self.preflight_request().signing_agents.len() != self.responses().len() {
Err(CounterSigningError::CounterSigningSessionResponsesLength(
self.responses().len(),
self.preflight_request().signing_agents.len(),
))
} else {
for (i, (response, _response_signature)) in self.responses().iter().enumerate() {
if *response.agent_index() as usize != i {
return Err(CounterSigningError::CounterSigningSessionResponsesOrder(
*response.agent_index(),
i,
));
}
}
Ok(())
}
}
pub fn to_timestamp(&self) -> Timestamp {
(self.preflight_request().session_times.start() + SESSION_ACTION_TIME_OFFSET)
.unwrap_or(Timestamp::MAX)
}
pub fn preflight_request(&self) -> &PreflightRequest {
&self.preflight_request
}
#[cfg(feature = "test_utils")]
pub fn preflight_request_mut(&mut self) -> &mut PreflightRequest {
&mut self.preflight_request
}
pub fn signing_agents(&self) -> impl Iterator<Item = &AgentPubKey> {
self.preflight_request.signing_agents.iter().map(|(a, _)| a)
}
pub fn responses(&self) -> &Vec<(CounterSigningAgentState, Signature)> {
&self.responses
}
#[cfg(feature = "test_utils")]
pub fn responses_mut(&mut self) -> &mut Vec<(CounterSigningAgentState, Signature)> {
&mut self.responses
}
}
#[cfg(test)]
pub mod test {
use crate::CounterSigningAgentState;
use crate::CounterSigningSessionData;
use crate::Signature;
use holo_hash::AgentPubKey;
use super::CounterSigningError;
use super::CounterSigningSessionTimes;
use super::PreflightRequest;
use super::SESSION_ACTION_TIME_OFFSET;
use crate::Role;
use arbitrary::Arbitrary;
#[test]
pub fn test_check_countersigning_session_times() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut session_times = CounterSigningSessionTimes::arbitrary(&mut u).unwrap();
assert!(matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
));
*session_times.end_mut() =
(session_times.end() + core::time::Duration::from_millis(1)).unwrap();
assert!(matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
));
*session_times.end_mut() = (session_times.end() + SESSION_ACTION_TIME_OFFSET).unwrap();
assert!(matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
));
*session_times.start_mut() =
(session_times.start() + core::time::Duration::from_millis(1)).unwrap();
assert_eq!(session_times.check_integrity().unwrap(), (),);
*session_times.start_mut() =
(session_times.start() + core::time::Duration::from_millis(1)).unwrap();
assert!(matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
));
}
#[test]
pub fn test_check_countersigning_preflight_request_optional_agents() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
assert_eq!(preflight_request.check_agents_optional().unwrap(), ());
let data: Vec<_> = (0u8..255).cycle().take(100000).collect();
let mut uk = arbitrary::Unstructured::new(&data);
let alice = AgentPubKey::arbitrary(&mut uk).unwrap();
preflight_request
.optional_signing_agents
.push((alice.clone(), vec![]));
assert!(matches!(
preflight_request.check_agents_optional(),
Err(CounterSigningError::MinOptionalAgents(0, 1))
));
preflight_request.minimum_optional_signing_agents = 1;
assert_eq!(preflight_request.check_agents_optional().unwrap(), ());
preflight_request
.optional_signing_agents
.push((alice.clone(), vec![]));
assert_eq!(preflight_request.check_agents_optional().unwrap(), ());
preflight_request
.optional_signing_agents
.push((alice.clone(), vec![]));
assert!(matches!(
preflight_request.check_agents_optional(),
Err(CounterSigningError::MinOptionalAgents(1, 3))
));
preflight_request.minimum_optional_signing_agents = 2;
assert_eq!(preflight_request.check_agents_optional().unwrap(), ());
preflight_request.minimum_optional_signing_agents = 4;
assert!(matches!(
preflight_request.check_agents_optional(),
Err(CounterSigningError::OptionalAgentsLength(4, 3))
));
}
#[test]
pub fn test_check_countersigning_preflight_request_enzyme() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
assert_eq!(preflight_request.check_enzyme().unwrap(), ());
let data: Vec<_> = (0u8..255).cycle().take(100000).collect();
let mut uk = arbitrary::Unstructured::new(&data);
let alice = AgentPubKey::arbitrary(&mut uk).unwrap();
let bob = AgentPubKey::arbitrary(&mut uk).unwrap();
preflight_request
.signing_agents
.push((alice.clone(), vec![]));
assert_eq!(preflight_request.check_enzyme().unwrap(), (),);
preflight_request
.optional_signing_agents
.push((alice.clone(), vec![]));
assert!(matches!(
preflight_request.check_enzyme(),
Err(CounterSigningError::NonEnzymaticOptionalSigners),
));
preflight_request.optional_signing_agents = vec![];
preflight_request.enzymatic = true;
assert_eq!(preflight_request.check_enzyme().unwrap(), ());
preflight_request.optional_signing_agents = vec![(alice.clone(), vec![])];
assert_eq!(preflight_request.check_enzyme().unwrap(), ());
preflight_request.optional_signing_agents = vec![(bob.clone(), vec![])];
assert!(matches!(
preflight_request.check_enzyme(),
Err(CounterSigningError::EnzymeMismatch(_, _)),
));
}
#[test]
pub fn test_check_countersigning_preflight_request_agents_len() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
assert!(matches!(
preflight_request.check_agents_len(),
Err(CounterSigningError::AgentsLength(_))
));
let alice = AgentPubKey::arbitrary(&mut u).unwrap();
preflight_request
.signing_agents
.push((alice.clone(), vec![]));
assert!(matches!(
preflight_request.check_agents_len(),
Err(CounterSigningError::AgentsLength(_))
));
let bob = AgentPubKey::arbitrary(&mut u).unwrap();
preflight_request.signing_agents.push((bob.clone(), vec![]));
assert_eq!(preflight_request.check_agents_len().unwrap(), (),);
}
#[test]
pub fn test_check_countersigning_preflight_request_agents_dupes() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
let data: Vec<_> = (0u8..255).cycle().take(100000).collect();
let mut uk = arbitrary::Unstructured::new(&data);
let alice = AgentPubKey::arbitrary(&mut uk).unwrap();
let bob = AgentPubKey::arbitrary(&mut uk).unwrap();
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
preflight_request
.signing_agents
.push((alice.clone(), vec![]));
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
preflight_request.signing_agents.push((bob.clone(), vec![]));
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
preflight_request
.signing_agents
.push((alice.clone(), vec![Role::new(0_u8)]));
assert!(matches!(
preflight_request.check_agents_dupes(),
Err(CounterSigningError::AgentsDupes(_))
));
}
#[test]
pub fn test_check_countersigning_session_data_responses_indexes() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut session_data = CounterSigningSessionData::arbitrary(&mut u).unwrap();
let alice = AgentPubKey::arbitrary(&mut u).unwrap();
let bob = AgentPubKey::arbitrary(&mut u).unwrap();
assert_eq!(session_data.check_responses_indexes().unwrap(), ());
session_data
.preflight_request_mut()
.signing_agents
.push((alice.clone(), vec![]));
assert!(matches!(
session_data.check_responses_indexes(),
Err(CounterSigningError::CounterSigningSessionResponsesLength(
_,
_
))
));
session_data
.preflight_request_mut()
.signing_agents
.push((bob.clone(), vec![]));
let alice_state = CounterSigningAgentState::arbitrary(&mut u).unwrap();
let alice_signature = Signature::arbitrary(&mut u).unwrap();
let mut bob_state = CounterSigningAgentState::arbitrary(&mut u).unwrap();
let bob_signature = Signature::arbitrary(&mut u).unwrap();
(*session_data.responses_mut()).push((alice_state, alice_signature));
(*session_data.responses_mut()).push((bob_state.clone(), bob_signature.clone()));
assert!(matches!(
session_data.check_responses_indexes(),
Err(CounterSigningError::CounterSigningSessionResponsesOrder(
_,
_
))
));
*bob_state.agent_index_mut() = 1;
(*session_data.responses_mut()).pop();
(*session_data.responses_mut()).push((bob_state, bob_signature));
assert_eq!(session_data.check_responses_indexes().unwrap(), (),);
}
}