holochain_zome_types 0.0.15

Holochain zome types
Documentation
use crate::entry_def::EntryVisibility;
use crate::link::LinkTag;
use crate::timestamp::Timestamp;
pub use builder::HeaderBuilder;
pub use builder::HeaderBuilderCommon;
use conversions::WrongHeaderError;
use holo_hash::impl_hashable_content;
use holo_hash::AgentPubKey;
use holo_hash::DnaHash;
use holo_hash::EntryHash;
use holo_hash::HashableContent;
use holo_hash::HeaderHash;
use holo_hash::HoloHashed;
use holochain_serialized_bytes::prelude::*;
use thiserror::Error;

#[cfg(feature = "rusqlite")]
use crate::impl_to_sql_via_display;

pub mod builder;
pub mod conversions;
#[cfg(any(test, feature = "test_utils"))]
pub mod facts;

/// Any header with a header_seq less than this value is part of an element
/// created during genesis. Anything with this seq or higher was created
/// after genesis.
pub const POST_GENESIS_SEQ_THRESHOLD: u32 = 3;

#[derive(Error, Debug)]
pub enum HeaderError {
    #[error("Tried to create a NewEntryHeader with a type that isn't a Create or Update")]
    NotNewEntry,
    #[error(transparent)]
    WrongHeaderError(#[from] WrongHeaderError),
    #[error("{0}")]
    Rebase(String),
}

#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ChainTopOrdering {
    /// Relaxed chain top ordering REWRITES HEADERS INLINE during a flush of
    /// the source chain to sit on top of the current chain top. The "as at"
    /// of the zome call initial state is completely ignored.
    /// This may be significantly more efficient if you are CERTAIN that none
    /// of your zome or validation logic is order dependent. Examples include
    /// simple chat messages or tweets. Note however that even chat messages
    /// and tweets may have subtle order dependencies, such as if a cap grant
    /// was written or revoked that would have invalidated the zome call that
    /// wrote data after the revocation, etc.
    /// The efficiency of relaxed ordering comes from simply rehashing and
    /// signing headers on the new chain top during flush, avoiding the
    /// overhead of the client, websockets, zome call instance, wasm execution,
    /// validation, etc. that would result from handling a `HeadMoved` error
    /// via an external driver.
    Relaxed,
    /// The default `Strict` ordering is the default for a very good reason.
    /// Writes normally compare the chain head from the start of a zome call
    /// against the time a write transaction is flushed from the source chain.
    /// This is REQUIRED for data integrity if any zome or validation logic
    /// depends on the ordering of data in a chain.
    /// This order dependence could be obvious such as an explicit reference or
    /// dependency. It could be very subtle such as checking for the existence
    /// or absence of some data.
    /// If you are unsure whether your data is order dependent you should err
    /// on the side of caution and handle `HeadMoved` errors on the client of
    /// the zome call and restart the zome call from the start.
    Strict,
}

impl Default for ChainTopOrdering {
    fn default() -> Self {
        Self::Strict
    }
}

/// Header contains variants for each type of header.
///
/// This struct really defines a local source chain, in the sense that it
/// implements the pointers between hashes that a hash chain relies on, which
/// are then used to check the integrity of data using cryptographic hash
/// functions.
#[allow(missing_docs)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[serde(tag = "type")]
pub enum Header {
    // The first header in a chain (for the DNA) doesn't have a previous header
    Dna(Dna),
    AgentValidationPkg(AgentValidationPkg),
    InitZomesComplete(InitZomesComplete),
    CreateLink(CreateLink),
    DeleteLink(DeleteLink),
    OpenChain(OpenChain),
    CloseChain(CloseChain),
    Create(Create),
    Update(Update),
    Delete(Delete),
}

pub type HeaderHashed = HoloHashed<Header>;

/// a utility wrapper to write intos for our data types
macro_rules! write_into_header {
    ($($n:ident),*,) => {
        $(
            impl HeaderInner for $n {
                fn into_header(self) -> Header {
                    Header::$n(self)
                }
            }
        )*

        /// A unit enum which just maps onto the different Header variants,
        /// without containing any extra data
        #[derive(serde::Serialize, serde::Deserialize, SerializedBytes, PartialEq, Eq, Clone, Debug)]
        #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
        pub enum HeaderType {
            $($n,)*
        }

        impl std::fmt::Display for HeaderType {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                writeln!(
                    f,
                    "{}",
                    match self {
                        $( HeaderType::$n => stringify!($n), )*
                    }
                )
            }
        }

        impl From<&Header> for HeaderType {
            fn from(header: &Header) -> HeaderType {
                match header {
                    $(
                        Header::$n(_) => HeaderType::$n,
                    )*
                }
            }
        }
    };
}

/// A trait to specify the common parts of a Header
pub trait HeaderInner {
    /// Get a full header from the subset
    fn into_header(self) -> Header;
}

impl<I: HeaderInner> From<I> for Header {
    fn from(i: I) -> Self {
        i.into_header()
    }
}

write_into_header! {
    Dna,
    AgentValidationPkg,
    InitZomesComplete,
    CreateLink,
    DeleteLink,
    OpenChain,
    CloseChain,
    Create,
    Update,
    Delete,
}

#[cfg(feature = "rusqlite")]
impl_to_sql_via_display!(HeaderType);

/// a utility macro just to not have to type in the match statement everywhere.
macro_rules! match_header {
    ($h:ident => |$i:ident| { $($t:tt)* }) => {
        match $h {
            Header::Dna($i) => { $($t)* }
            Header::AgentValidationPkg($i) => { $($t)* }
            Header::InitZomesComplete($i) => { $($t)* }
            Header::CreateLink($i) => { $($t)* }
            Header::DeleteLink($i) => { $($t)* }
            Header::OpenChain($i) => { $($t)* }
            Header::CloseChain($i) => { $($t)* }
            Header::Create($i) => { $($t)* }
            Header::Update($i) => { $($t)* }
            Header::Delete($i) => { $($t)* }
        }
    };
}

impl Header {
    /// Returns the address and entry type of the Entry, if applicable.
    // TODO: DRY: possibly create an `EntryData` struct which is used by both
    // Create and Update
    pub fn entry_data(&self) -> Option<(&EntryHash, &EntryType)> {
        match self {
            Self::Create(Create {
                entry_hash,
                entry_type,
                ..
            }) => Some((entry_hash, entry_type)),
            Self::Update(Update {
                entry_hash,
                entry_type,
                ..
            }) => Some((entry_hash, entry_type)),
            _ => None,
        }
    }

    pub fn entry_hash(&self) -> Option<&EntryHash> {
        self.entry_data().map(|d| d.0)
    }

    pub fn entry_type(&self) -> Option<&EntryType> {
        self.entry_data().map(|d| d.1)
    }

    pub fn header_type(&self) -> HeaderType {
        self.into()
    }

    /// returns the public key of the agent who signed this header.
    pub fn author(&self) -> &AgentPubKey {
        match_header!(self => |i| { &i.author })
    }

    /// returns the timestamp of when the header was created
    pub fn timestamp(&self) -> Timestamp {
        match_header!(self => |i| { i.timestamp })
    }

    pub fn rebase_on(
        &mut self,
        new_prev_header: HeaderHash,
        new_prev_seq: u32,
        new_prev_timestamp: Timestamp,
    ) -> Result<(), HeaderError> {
        let new_seq = new_prev_seq + 1;
        let new_timestamp = self.timestamp().max(
            (new_prev_timestamp + std::time::Duration::from_nanos(1))
                .map_err(|e| HeaderError::Rebase(e.to_string()))?,
        );
        match self {
            Self::Dna(_) => return Err(HeaderError::Rebase("Rebased a DNA Header".to_string())),
            Self::AgentValidationPkg(AgentValidationPkg {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::InitZomesComplete(InitZomesComplete {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::CreateLink(CreateLink {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::DeleteLink(DeleteLink {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::Delete(Delete {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::CloseChain(CloseChain {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::OpenChain(OpenChain {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::Create(Create {
                timestamp,
                header_seq,
                prev_header,
                ..
            })
            | Self::Update(Update {
                timestamp,
                header_seq,
                prev_header,
                ..
            }) => {
                *timestamp = new_timestamp;
                *header_seq = new_seq;
                *prev_header = new_prev_header;
            }
        };
        Ok(())
    }

    /// returns the sequence ordinal of this header
    pub fn header_seq(&self) -> u32 {
        match self {
            // Dna is always 0
            Self::Dna(Dna { .. }) => 0,
            Self::AgentValidationPkg(AgentValidationPkg { header_seq, .. })
            | Self::InitZomesComplete(InitZomesComplete { header_seq, .. })
            | Self::CreateLink(CreateLink { header_seq, .. })
            | Self::DeleteLink(DeleteLink { header_seq, .. })
            | Self::Delete(Delete { header_seq, .. })
            | Self::CloseChain(CloseChain { header_seq, .. })
            | Self::OpenChain(OpenChain { header_seq, .. })
            | Self::Create(Create { header_seq, .. })
            | Self::Update(Update { header_seq, .. }) => *header_seq,
        }
    }

    /// returns the previous header except for the DNA header which doesn't have a previous
    pub fn prev_header(&self) -> Option<&HeaderHash> {
        Some(match self {
            Self::Dna(Dna { .. }) => return None,
            Self::AgentValidationPkg(AgentValidationPkg { prev_header, .. }) => prev_header,
            Self::InitZomesComplete(InitZomesComplete { prev_header, .. }) => prev_header,
            Self::CreateLink(CreateLink { prev_header, .. }) => prev_header,
            Self::DeleteLink(DeleteLink { prev_header, .. }) => prev_header,
            Self::Delete(Delete { prev_header, .. }) => prev_header,
            Self::CloseChain(CloseChain { prev_header, .. }) => prev_header,
            Self::OpenChain(OpenChain { prev_header, .. }) => prev_header,
            Self::Create(Create { prev_header, .. }) => prev_header,
            Self::Update(Update { prev_header, .. }) => prev_header,
        })
    }

    pub fn is_genesis(&self) -> bool {
        self.header_seq() < POST_GENESIS_SEQ_THRESHOLD
    }
}

impl_hashable_content!(Header, Header);

/// this id is an internal reference, which also serves as a canonical ordering
/// for zome initialization.  The value should be auto-generated from the Zome Bundle def
// TODO: Check this can never be written to > 255
#[derive(
    Debug,
    Copy,
    Clone,
    Hash,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    SerializedBytes,
)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct ZomeId(u8);

#[derive(
    Debug,
    Copy,
    Clone,
    Hash,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    SerializedBytes,
)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct EntryDefIndex(pub u8);

/// The Dna Header is always the first header in a source chain
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Dna {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    // No previous header, because DNA is always first chain entry
    pub hash: DnaHash,
}

/// Header for an agent validation package, used to determine whether an agent
/// is allowed to participate in this DNA
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct AgentValidationPkg {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub membrane_proof: Option<SerializedBytes>,
}

/// A header which declares that all zome init functions have successfully
/// completed, and the chain is ready for commits. Contains no explicit data.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct InitZomesComplete {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,
}

/// Declares that a metadata Link should be made between two EntryHashes
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CreateLink {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub base_address: EntryHash,
    pub target_address: EntryHash,
    pub zome_id: ZomeId,
    pub tag: LinkTag,
}

/// Declares that a previously made Link should be nullified and considered removed.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct DeleteLink {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    /// this is redundant with the `CreateLink` header but needs to be included to facilitate DHT ops
    /// this is NOT exposed to wasm developers and is validated by the subconscious to ensure that
    /// it always matches the `base_address` of the `CreateLink`
    pub base_address: EntryHash,
    /// The address of the `CreateLink` being reversed
    pub link_add_address: HeaderHash,
}

/// When migrating to a new version of a DNA, this header is committed to the
/// new chain to declare the migration path taken. **Currently unused**
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct OpenChain {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub prev_dna_hash: DnaHash,
}

/// When migrating to a new version of a DNA, this header is committed to the
/// old chain to declare the migration path taken. **Currently unused**
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CloseChain {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub new_dna_hash: DnaHash,
}

/// A header which "speaks" Entry content into being. The same content can be
/// referenced by multiple such headers.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Create {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub entry_type: EntryType,
    pub entry_hash: EntryHash,
}

/// A header which specifies that some new Entry content is intended to be an
/// update to some old Entry.
///
/// This header semantically updates an entry to a new entry.
/// It has the following effects:
/// - Create a new Entry
/// - This is the header of that new entry
/// - Create a metadata relationship between the original entry and this new header
///
/// The original header is required to prevent update loops:
/// If you update A to B and B back to A, and then you don't know which one came first,
/// or how to break the loop.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Update {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub original_header_address: HeaderHash,
    pub original_entry_address: EntryHash,

    pub entry_type: EntryType,
    pub entry_hash: EntryHash,
}

/// Declare that a previously published Header should be nullified and
/// considered deleted.
///
/// Via the associated [DhtOp], this also has an effect on Entries: namely,
/// that a previously published Entry will become inaccessible if all of its
/// Headers are marked deleted.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Delete {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    /// Address of the Element being deleted
    pub deletes_address: HeaderHash,
    pub deletes_entry_address: EntryHash,
}

/// Placeholder for future when we want to have updates on headers
/// Not currently in use.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct UpdateHeader {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    pub original_header_address: HeaderHash,
}

/// Placeholder for future when we want to have deletes on headers
/// Not currently in use.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct DeleteHeader {
    pub author: AgentPubKey,
    pub timestamp: Timestamp,
    pub header_seq: u32,
    pub prev_header: HeaderHash,

    /// Address of the header being deleted
    pub deletes_address: HeaderHash,
}

/// Allows Headers which reference Entries to know what type of Entry it is
/// referencing. Useful for examining Headers without needing to fetch the
/// corresponding Entries.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum EntryType {
    /// An AgentPubKey
    AgentPubKey,
    /// An app-provided entry, along with its app-provided AppEntryType
    App(AppEntryType),
    /// A Capability claim
    CapClaim,
    /// A Capability grant.
    CapGrant,
}

impl EntryType {
    pub fn visibility(&self) -> &EntryVisibility {
        match self {
            EntryType::AgentPubKey => &EntryVisibility::Public,
            EntryType::App(t) => t.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 => writeln!(f, "AgentPubKey"),
            EntryType::App(aet) => writeln!(
                f,
                "App({:?}, {}, {:?})",
                aet.id(),
                aet.zome_id(),
                aet.visibility()
            ),
            EntryType::CapClaim => writeln!(f, "CapClaim"),
            EntryType::CapGrant => writeln!(f, "CapGrant"),
        }
    }
}

#[cfg(feature = "rusqlite")]
impl_to_sql_via_display!(EntryType);

/// Information about a class of Entries provided by the DNA
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct AppEntryType {
    /// u8 identifier of what entry type this is
    /// this needs to match the position of the entry type returned by entry defs
    pub(crate) id: EntryDefIndex,
    /// u8 identifier of what zome this is for
    /// this needs to be shared across the dna
    /// comes from the numeric index position of a zome in dna config
    pub(crate) zome_id: ZomeId,
    // @todo don't do this, use entry defs instead
    pub(crate) visibility: EntryVisibility,
}

impl AppEntryType {
    pub fn new(id: EntryDefIndex, zome_id: ZomeId, visibility: EntryVisibility) -> Self {
        Self {
            id,
            zome_id,
            visibility,
        }
    }

    pub fn id(&self) -> EntryDefIndex {
        self.id
    }
    pub fn zome_id(&self) -> ZomeId {
        self.zome_id
    }
    pub fn visibility(&self) -> &EntryVisibility {
        &self.visibility
    }
}

impl From<EntryDefIndex> for u8 {
    fn from(ei: EntryDefIndex) -> Self {
        ei.0
    }
}

impl EntryDefIndex {
    /// Use as an index into a slice
    pub fn index(&self) -> usize {
        self.0 as usize
    }
}

impl ZomeId {
    /// Use as an index into a slice
    pub fn index(&self) -> usize {
        self.0 as usize
    }
}

#[cfg(feature = "full")]
impl rusqlite::ToSql for ZomeId {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
        Ok(self.0.into())
    }
}