use crate::entry_def::EntryVisibility;
use crate::genesis::MembraneProof;
use crate::link::{LinkTag, LinkType};
use holo_hash::{
ActionHash, AgentPubKey, AnyLinkableHash, DnaHash, EntryHash, HashableContent,
HashableContentBytes, HoloHashed,
};
use holochain_serialized_bytes::prelude::*;
use holochain_timestamp::Timestamp;
use std::borrow::Borrow;
pub mod conversions;
pub const POST_GENESIS_SEQ_THRESHOLD: u32 = 3;
#[derive(
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
SerializedBytes,
)]
pub struct ZomeIndex(pub u8);
impl ZomeIndex {
pub fn new(u: u8) -> Self {
Self(u)
}
}
#[derive(
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
SerializedBytes,
)]
pub struct EntryDefIndex(pub u8);
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
pub enum MigrationTarget {
Dna(DnaHash),
Agent(AgentPubKey),
}
impl From<DnaHash> for MigrationTarget {
fn from(dna: DnaHash) -> Self {
MigrationTarget::Dna(dna)
}
}
impl From<AgentPubKey> for MigrationTarget {
fn from(agent: AgentPubKey) -> Self {
MigrationTarget::Agent(agent)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
pub enum EntryType {
AgentPubKey,
App(AppEntryDef),
CapClaim,
CapGrant,
}
impl EntryType {
pub fn visibility(&self) -> &EntryVisibility {
match self {
EntryType::AgentPubKey => &EntryVisibility::Public,
EntryType::App(app_entry_def) => app_entry_def.visibility(),
EntryType::CapClaim => &EntryVisibility::Private,
EntryType::CapGrant => &EntryVisibility::Private,
}
}
}
impl std::fmt::Display for EntryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EntryType::AgentPubKey => write!(f, "AgentPubKey"),
EntryType::App(app_entry_def) => write!(
f,
"App({:?}, {:?})",
app_entry_def.entry_index(),
app_entry_def.visibility()
),
EntryType::CapClaim => write!(f, "CapClaim"),
EntryType::CapGrant => write!(f, "CapGrant"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
pub struct AppEntryDef {
pub entry_index: EntryDefIndex,
pub zome_index: ZomeIndex,
pub visibility: EntryVisibility,
}
impl AppEntryDef {
pub fn new(
entry_index: EntryDefIndex,
zome_index: ZomeIndex,
visibility: EntryVisibility,
) -> Self {
Self {
entry_index,
zome_index,
visibility,
}
}
pub fn entry_index(&self) -> EntryDefIndex {
self.entry_index
}
pub fn zome_index(&self) -> ZomeIndex {
self.zome_index
}
pub fn visibility(&self) -> &EntryVisibility {
&self.visibility
}
}
impl From<EntryDefIndex> for u8 {
fn from(ei: EntryDefIndex) -> Self {
ei.0
}
}
impl ZomeIndex {
pub fn index(&self) -> usize {
self.0 as usize
}
}
impl std::ops::Deref for ZomeIndex {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<u8> for ZomeIndex {
fn borrow(&self) -> &u8 {
&self.0
}
}
pub trait ActionHashedContainer: ActionSequenceAndHash {
fn action(&self) -> &Action;
fn action_hash(&self) -> &ActionHash;
}
pub trait ActionSequenceAndHash {
fn action_seq(&self) -> u32;
fn address(&self) -> &ActionHash;
}
impl ActionSequenceAndHash for (u32, ActionHash) {
fn action_seq(&self) -> u32 {
self.0
}
fn address(&self) -> &ActionHash {
&self.1
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(i64)]
pub enum RecordValidity {
Accepted = 1,
Rejected = 2,
}
pub type OpValidity = RecordValidity;
impl From<RecordValidity> for i64 {
fn from(v: RecordValidity) -> Self {
v as i64
}
}
impl TryFrom<i64> for RecordValidity {
type Error = i64;
fn try_from(v: i64) -> Result<Self, Self::Error> {
match v {
1 => Ok(RecordValidity::Accepted),
2 => Ok(RecordValidity::Rejected),
other => Err(other),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
#[repr(i64)]
pub enum ActionType {
Dna = 1,
AgentValidationPkg = 2,
InitZomesComplete = 3,
Create = 4,
Update = 5,
Delete = 6,
CreateLink = 7,
DeleteLink = 8,
CloseChain = 9,
OpenChain = 10,
}
impl From<ActionType> for i64 {
fn from(t: ActionType) -> Self {
t as i64
}
}
impl TryFrom<i64> for ActionType {
type Error = i64;
fn try_from(v: i64) -> Result<Self, Self::Error> {
use ActionType::*;
match v {
1 => Ok(Dna),
2 => Ok(AgentValidationPkg),
3 => Ok(InitZomesComplete),
4 => Ok(Create),
5 => Ok(Update),
6 => Ok(Delete),
7 => Ok(CreateLink),
8 => Ok(DeleteLink),
9 => Ok(CloseChain),
10 => Ok(OpenChain),
other => Err(other),
}
}
}
impl core::fmt::Display for ActionType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
ActionType::Dna => "Dna",
ActionType::AgentValidationPkg => "AgentValidationPkg",
ActionType::InitZomesComplete => "InitZomesComplete",
ActionType::Create => "Create",
ActionType::Update => "Update",
ActionType::Delete => "Delete",
ActionType::CreateLink => "CreateLink",
ActionType::DeleteLink => "DeleteLink",
ActionType::CloseChain => "CloseChain",
ActionType::OpenChain => "OpenChain",
};
f.write_str(name)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(i64)]
pub enum CapAccessType {
Unrestricted = 0,
Transferable = 1,
Assigned = 2,
}
impl From<CapAccessType> for i64 {
fn from(a: CapAccessType) -> Self {
a as i64
}
}
impl TryFrom<i64> for CapAccessType {
type Error = i64;
fn try_from(v: i64) -> Result<Self, Self::Error> {
match v {
0 => Ok(CapAccessType::Unrestricted),
1 => Ok(CapAccessType::Transferable),
2 => Ok(CapAccessType::Assigned),
other => Err(other),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct ActionHeader {
pub author: AgentPubKey,
pub timestamp: Timestamp,
pub action_seq: u32,
pub prev_action: Option<ActionHash>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct DnaData {
pub dna_hash: DnaHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct AgentValidationPkgData {
pub membrane_proof: Option<MembraneProof>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct InitZomesCompleteData {}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct CreateData {
pub entry_type: EntryType,
pub entry_hash: EntryHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct UpdateData {
pub original_action_address: ActionHash,
pub original_entry_address: EntryHash,
pub entry_type: EntryType,
pub entry_hash: EntryHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct DeleteData {
pub deletes_address: ActionHash,
pub deletes_entry_address: EntryHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct CreateLinkData {
pub base_address: AnyLinkableHash,
pub target_address: AnyLinkableHash,
pub zome_index: ZomeIndex,
pub link_type: LinkType,
pub tag: LinkTag,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct DeleteLinkData {
pub base_address: AnyLinkableHash,
pub link_add_address: ActionHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct CloseChainData {
pub new_target: Option<crate::action::MigrationTarget>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct OpenChainData {
pub prev_target: crate::action::MigrationTarget,
pub close_hash: ActionHash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
#[serde(tag = "type")]
pub enum ActionData {
Dna(DnaData),
AgentValidationPkg(AgentValidationPkgData),
InitZomesComplete(InitZomesCompleteData),
Create(CreateData),
Update(UpdateData),
Delete(DeleteData),
CreateLink(CreateLinkData),
DeleteLink(DeleteLinkData),
CloseChain(CloseChainData),
OpenChain(OpenChainData),
}
impl ActionData {
pub fn action_type(&self) -> ActionType {
match self {
ActionData::Dna(_) => ActionType::Dna,
ActionData::AgentValidationPkg(_) => ActionType::AgentValidationPkg,
ActionData::InitZomesComplete(_) => ActionType::InitZomesComplete,
ActionData::Create(_) => ActionType::Create,
ActionData::Update(_) => ActionType::Update,
ActionData::Delete(_) => ActionType::Delete,
ActionData::CreateLink(_) => ActionType::CreateLink,
ActionData::DeleteLink(_) => ActionType::DeleteLink,
ActionData::CloseChain(_) => ActionType::CloseChain,
ActionData::OpenChain(_) => ActionType::OpenChain,
}
}
pub fn entry_hash(&self) -> Option<&EntryHash> {
match self {
ActionData::Create(d) => Some(&d.entry_hash),
ActionData::Update(d) => Some(&d.entry_hash),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, SerializedBytes)]
pub struct Action {
pub header: ActionHeader,
pub data: ActionData,
}
impl Action {
pub fn author(&self) -> &AgentPubKey {
&self.header.author
}
pub fn signer(&self) -> &AgentPubKey {
match &self.data {
ActionData::CloseChain(CloseChainData {
new_target: Some(crate::action::MigrationTarget::Agent(agent)),
}) => agent,
_ => self.author(),
}
}
pub fn timestamp(&self) -> Timestamp {
self.header.timestamp
}
pub fn action_seq(&self) -> u32 {
self.header.action_seq
}
pub fn prev_action(&self) -> Option<&ActionHash> {
self.header.prev_action.as_ref()
}
pub fn prev_action_mut(&mut self) -> Option<&mut ActionHash> {
self.header.prev_action.as_mut()
}
pub fn is_genesis(&self) -> bool {
self.action_seq() < crate::action::POST_GENESIS_SEQ_THRESHOLD
}
pub fn action_type(&self) -> ActionType {
self.data.action_type()
}
pub fn entry_hash(&self) -> Option<&EntryHash> {
self.data.entry_hash()
}
pub fn entry_type(&self) -> Option<&EntryType> {
match &self.data {
ActionData::Create(d) => Some(&d.entry_type),
ActionData::Update(d) => Some(&d.entry_type),
_ => None,
}
}
pub fn entry_type_mut(&mut self) -> Option<&mut EntryType> {
match &mut self.data {
ActionData::Create(d) => Some(&mut d.entry_type),
ActionData::Update(d) => Some(&mut d.entry_type),
_ => None,
}
}
pub fn entry_hash_mut(&mut self) -> Option<&mut EntryHash> {
match &mut self.data {
ActionData::Create(d) => Some(&mut d.entry_hash),
ActionData::Update(d) => Some(&mut d.entry_hash),
_ => None,
}
}
pub fn app_entry_def(&self) -> Option<&AppEntryDef> {
match self.entry_type()? {
EntryType::App(app_entry_def) => Some(app_entry_def),
_ => None,
}
}
pub fn entry_data(&self) -> Option<(&EntryHash, &EntryType)> {
match &self.data {
ActionData::Create(d) => Some((&d.entry_hash, &d.entry_type)),
ActionData::Update(d) => Some((&d.entry_hash, &d.entry_type)),
_ => None,
}
}
pub fn into_entry_data(self) -> Option<(EntryHash, EntryType)> {
match self.data {
ActionData::Create(d) => Some((d.entry_hash, d.entry_type)),
ActionData::Update(d) => Some((d.entry_hash, d.entry_type)),
_ => None,
}
}
pub fn entry_visibility(&self) -> Option<&EntryVisibility> {
self.entry_type().map(|entry_type| entry_type.visibility())
}
}
impl HashableContent for Action {
type HashType = holo_hash::hash_type::Action;
fn hash_type(&self) -> Self::HashType {
use holo_hash::PrimitiveHashType;
Self::HashType::new()
}
fn hashable_content(&self) -> HashableContentBytes {
HashableContentBytes::Content(
SerializedBytes::try_from(self).expect("Could not serialize Action"),
)
}
}
pub type ActionHashed = HoloHashed<Action>;
pub type SignedActionHashed = crate::record::SignedHashed<Action>;
impl SignedActionHashed {
pub fn action(&self) -> &Action {
&self.hashed.content
}
pub fn action_address(&self) -> &ActionHash {
&self.hashed.hash
}
}
impl crate::action::ActionSequenceAndHash for ActionHashed {
fn action_seq(&self) -> u32 {
self.content.action_seq()
}
fn address(&self) -> &ActionHash {
&self.hash
}
}
impl crate::action::ActionHashedContainer for ActionHashed {
fn action(&self) -> &Action {
&self.content
}
fn action_hash(&self) -> &ActionHash {
&self.hash
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_validity_i64_roundtrip() {
for v in [RecordValidity::Accepted, RecordValidity::Rejected] {
let n: i64 = v.into();
assert_eq!(RecordValidity::try_from(n).unwrap(), v);
}
assert!(RecordValidity::try_from(0).is_err());
assert!(RecordValidity::try_from(3).is_err());
}
#[test]
fn action_type_i64_roundtrip() {
use ActionType::*;
for v in [
Dna,
AgentValidationPkg,
InitZomesComplete,
Create,
Update,
Delete,
CreateLink,
DeleteLink,
CloseChain,
OpenChain,
] {
let n: i64 = v.into();
assert_eq!(ActionType::try_from(n).unwrap(), v);
}
assert!(ActionType::try_from(0).is_err());
assert!(ActionType::try_from(11).is_err());
}
#[test]
fn cap_access_i64_roundtrip() {
for v in [
CapAccessType::Unrestricted,
CapAccessType::Transferable,
CapAccessType::Assigned,
] {
let n: i64 = v.into();
assert_eq!(CapAccessType::try_from(n).unwrap(), v);
}
assert!(CapAccessType::try_from(-1).is_err());
assert!(CapAccessType::try_from(3).is_err());
}
#[test]
fn data_structs_construct() {
let _ = DnaData {
dna_hash: DnaHash::from_raw_36(vec![0u8; 36]),
};
let _ = InitZomesCompleteData {};
}
#[test]
fn action_data_serde_roundtrip() {
let cases: Vec<ActionData> = vec![
ActionData::Dna(DnaData {
dna_hash: DnaHash::from_raw_36(vec![1u8; 36]),
}),
ActionData::InitZomesComplete(InitZomesCompleteData {}),
ActionData::Create(CreateData {
entry_type: EntryType::AgentPubKey,
entry_hash: EntryHash::from_raw_36(vec![2u8; 36]),
}),
];
for data in cases {
let bytes = holochain_serialized_bytes::encode(&data).unwrap();
let decoded: ActionData = holochain_serialized_bytes::decode(&bytes).unwrap();
assert_eq!(decoded.action_type(), data.action_type());
}
}
fn sample_action(data: ActionData) -> Action {
Action {
header: ActionHeader {
author: AgentPubKey::from_raw_36(vec![1u8; 36]),
timestamp: Timestamp::from_micros(42),
action_seq: 5,
prev_action: Some(ActionHash::from_raw_36(vec![2u8; 36])),
},
data,
}
}
fn sample_create_data() -> ActionData {
ActionData::Create(CreateData {
entry_type: EntryType::AgentPubKey,
entry_hash: EntryHash::from_raw_36(vec![3u8; 36]),
})
}
#[test]
fn action_accessors_read_header_fields() {
let a = sample_action(sample_create_data());
assert_eq!(a.author(), &AgentPubKey::from_raw_36(vec![1u8; 36]));
assert_eq!(a.timestamp(), Timestamp::from_micros(42));
assert_eq!(a.action_seq(), 5);
assert_eq!(
a.prev_action(),
Some(&ActionHash::from_raw_36(vec![2u8; 36]))
);
assert_eq!(a.action_type(), ActionType::Create);
}
#[test]
fn action_prev_action_mut_writes_through_the_header() {
let mut a = sample_action(sample_create_data());
let new_prev = ActionHash::from_raw_36(vec![9u8; 36]);
*a.prev_action_mut().expect("has a prev action") = new_prev.clone();
assert_eq!(a.prev_action(), Some(&new_prev));
}
#[test]
fn action_entry_type_and_data_some_for_create_and_update() {
let create = sample_action(sample_create_data());
assert_eq!(create.entry_type(), Some(&EntryType::AgentPubKey));
assert_eq!(
create.entry_data(),
Some((
&EntryHash::from_raw_36(vec![3u8; 36]),
&EntryType::AgentPubKey
))
);
assert_eq!(
create.entry_hash(),
Some(&EntryHash::from_raw_36(vec![3u8; 36]))
);
let update = sample_action(ActionData::Update(UpdateData {
original_action_address: ActionHash::from_raw_36(vec![6u8; 36]),
original_entry_address: EntryHash::from_raw_36(vec![7u8; 36]),
entry_type: EntryType::CapClaim,
entry_hash: EntryHash::from_raw_36(vec![8u8; 36]),
}));
assert_eq!(update.entry_type(), Some(&EntryType::CapClaim));
assert_eq!(
update.entry_data(),
Some((&EntryHash::from_raw_36(vec![8u8; 36]), &EntryType::CapClaim))
);
}
#[test]
fn action_entry_type_and_data_none_for_non_entry_actions() {
let dna = sample_action(ActionData::Dna(DnaData {
dna_hash: DnaHash::from_raw_36(vec![5u8; 36]),
}));
assert_eq!(dna.entry_type(), None);
assert_eq!(dna.entry_data(), None);
assert_eq!(dna.entry_hash(), None);
assert_eq!(dna.entry_visibility(), None);
let delete = sample_action(ActionData::Delete(DeleteData {
deletes_address: ActionHash::from_raw_36(vec![9u8; 36]),
deletes_entry_address: EntryHash::from_raw_36(vec![10u8; 36]),
}));
assert!(delete.entry_data().is_none());
}
#[test]
fn action_app_entry_def_some_for_app_entry_type() {
let app_entry_def = AppEntryDef::new(
crate::action::EntryDefIndex(1),
crate::action::ZomeIndex(2),
EntryVisibility::Public,
);
let create = sample_action(ActionData::Create(CreateData {
entry_type: EntryType::App(app_entry_def.clone()),
entry_hash: EntryHash::from_raw_36(vec![3u8; 36]),
}));
assert_eq!(create.app_entry_def(), Some(&app_entry_def));
}
#[test]
fn action_app_entry_def_none_for_non_app_entry_type() {
let create = sample_action(sample_create_data());
assert_eq!(create.app_entry_def(), None);
let dna = sample_action(ActionData::Dna(DnaData {
dna_hash: DnaHash::from_raw_36(vec![5u8; 36]),
}));
assert_eq!(dna.app_entry_def(), None);
}
#[test]
fn action_into_entry_data_moves_the_fields_out() {
let create = sample_action(sample_create_data());
let (hash, ty) = create.into_entry_data().expect("create has entry data");
assert_eq!(hash, EntryHash::from_raw_36(vec![3u8; 36]));
assert_eq!(ty, EntryType::AgentPubKey);
let dna = sample_action(ActionData::Dna(DnaData {
dna_hash: DnaHash::from_raw_36(vec![5u8; 36]),
}));
assert!(dna.into_entry_data().is_none());
}
#[test]
fn action_entry_visibility_reads_through_entry_type() {
let create = sample_action(sample_create_data());
assert_eq!(create.entry_visibility(), Some(&EntryVisibility::Public));
let cap_claim = sample_action(ActionData::Create(CreateData {
entry_type: EntryType::CapClaim,
entry_hash: EntryHash::from_raw_36(vec![3u8; 36]),
}));
assert_eq!(
cap_claim.entry_visibility(),
Some(&EntryVisibility::Private)
);
}
#[test]
fn action_is_genesis_below_threshold() {
let mut a = sample_action(sample_create_data());
a.header.action_seq = 0;
assert!(a.is_genesis());
a.header.action_seq = crate::action::POST_GENESIS_SEQ_THRESHOLD;
assert!(!a.is_genesis());
}
#[test]
fn action_signer_defaults_to_author() {
let a = sample_action(sample_create_data());
assert_eq!(a.signer(), a.author());
}
#[test]
fn action_signer_uses_the_migration_agent_for_close_chain() {
let new_agent = AgentPubKey::from_raw_36(vec![7u8; 36]);
let a = sample_action(ActionData::CloseChain(CloseChainData {
new_target: Some(crate::action::MigrationTarget::Agent(new_agent.clone())),
}));
assert_eq!(a.signer(), &new_agent);
assert_ne!(a.signer(), a.author());
}
#[test]
fn action_signer_uses_author_for_close_chain_without_agent_target() {
let a = sample_action(ActionData::CloseChain(CloseChainData { new_target: None }));
assert_eq!(a.signer(), a.author());
}
}