holochain_core 0.0.52-alpha2

holochain core
use crate::{
    agent::state::AgentState,
    dht::{
        actions::remove_queued_holding_workflow::HoldingWorkflowQueueing,
        dht_store::HoldAspectAttemptId, pending_validations::PendingValidation,
    },
    network::{
        direct_message::DirectMessage,
        entry_aspect::EntryAspect,
        entry_with_header::EntryWithHeader,
        query::{GetLinksNetworkQuery, NetworkQueryResult},
        state::NetworkState,
    },
    nucleus::{
        actions::{call_zome_function::ExecuteZomeFnResponse, initialize::Initialization},
        state::NucleusState,
        HdkFnCall, HdkFnCallResult, ZomeFnCall,
    },
    state::State,
};

use holochain_core_types::{
    chain_header::ChainHeader, crud_status::CrudStatus, dna::Dna, entry::Entry,
    signature::Provenance, validation::ValidationPackage,
};
use holochain_net::{connection::net_connection::NetHandler, p2p_config::P2pConfig};
use holochain_persistence_api::cas::content::Address;
use lib3h_protocol::data_types::{EntryListData, FetchEntryData, QueryEntryData};
use std::{
    hash::{Hash, Hasher},
    time::{Duration, SystemTime},
    vec::Vec,
};

/// Wrapper for actions that provides a unique ID
/// The unique ID is needed for state tracking to ensure that we can differentiate between two
/// Action dispatches containing the same value when doing "time travel debug".
/// The standard approach is to drop the ActionWrapper into the key of a state history HashMap and
/// use the convenience unwrap_to! macro to extract the action data in a reducer.
/// All reducer functions must accept an ActionWrapper so all dispatchers take an ActionWrapper.
#[derive(Clone, Debug, Serialize)]
pub struct ActionWrapper {
    action: Action,
    id: String,
}

impl ActionWrapper {
    /// constructor from &Action
    /// internal unique ID is automatically set
    pub fn new(a: Action) -> Self {
        ActionWrapper {
            action: a,
            // auto generate id
            id: nanoid::simple(),
        }
    }

    /// read only access to action
    pub fn action(&self) -> &Action {
        &self.action
    }

    /// read only access to id
    pub fn id(&self) -> &String {
        &self.id
    }
}

impl PartialEq for ActionWrapper {
    fn eq(&self, other: &ActionWrapper) -> bool {
        self.id == other.id
    }
}

impl Eq for ActionWrapper {}

impl Hash for ActionWrapper {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

///This describes a key for the actions
#[derive(Clone, PartialEq, Debug, Serialize, Eq, Hash)]
pub enum QueryKey {
    Entry(GetEntryKey),
    Links(GetLinksKey),
}

///This is a payload for the Get Method
#[derive(Clone, PartialEq, Debug, Serialize)]
pub enum QueryPayload {
    Entry,
    Links((Option<CrudStatus>, GetLinksNetworkQuery)),
}

/// All Actions for the Holochain Instance Store, according to Redux pattern.
#[derive(Clone, PartialEq, Debug, Serialize)]
#[serde(tag = "action_type", content = "data")]
#[allow(clippy::large_enum_variant)]
pub enum Action {
    /// Get rid of stale information that we should drop to not have the state grow infinitely.
    Prune,
    ClearActionResponse(String),

    // ----------------
    // Agent actions:
    // ----------------
    /// Writes an entry to the source chain.
    /// Does not validate, assumes entry is valid.
    Commit((Entry, Option<Address>, Vec<Provenance>)),

    // -------------
    // DHT actions:
    // -------------
    /// Adds a holding workflow (=PendingValidation) to the queue.
    /// With optional delay where the SystemTime is the time when the action got dispatched
    /// and the Duration is the delay added to that time.
    QueueHoldingWorkflow((PendingValidation, Option<(SystemTime, Duration)>)),

    /// Removes the given item from the holding queue.
    RemoveQueuedHoldingWorkflow((HoldingWorkflowQueueing, PendingValidation)),

    /// Adds an entry aspect to the local DHT shard.
    /// Does not validate, assumes referenced entry is valid.
    HoldAspect((EntryAspect, HoldAspectAttemptId)),

    //action for updating crudstatus
    CrudStatus((EntryWithHeader, CrudStatus)),

    // ----------------
    // Network actions:
    // ----------------
    /// Create a network proxy instance from the given [NetworkSettings](struct.NetworkSettings.html)
    InitNetwork(NetworkSettings),

    /// Shut down network by sending JsonProtocoll::UntrackDna, stopping network thread and dropping P2pNetwork instance
    ShutdownNetwork,

    /// Makes the network PUT the given entry to the DHT.
    /// Distinguishes between different entry types and does
    /// the right thing respectively.
    /// (only publish for AppEntryType, publish and publish_meta for links etc)
    Publish(Address),

    /// Publish to the network the header entry for the entry at the given address.
    /// Note that the given address is that of the entry NOT the address of the header itself
    PublishHeaderEntry(Address),

    /// Performs a Network Query Action based on the key and payload, used for links and Entries.
    /// Includes the timeout information: system time of dispatch and duration until it timeouts.
    Query((QueryKey, QueryPayload, Option<(SystemTime, Duration)>)),

    ///Performs a Query Timeout Action which times out the query given by the key.
    QueryTimeout(QueryKey),

    /// Lets the network module respond to a Query request.
    /// Triggered from the corresponding workflow after retrieving the
    /// requested object from the DHT
    RespondQuery((QueryEntryData, NetworkQueryResult)),

    /// We got a response for our get request which needs to be added to the state.
    /// Triggered from the network handler.
    HandleQuery((NetworkQueryResult, QueryKey)),

    /// Clean up the query result so the state doesn't grow indefinitely.
    ClearQueryResult(QueryKey),

    RespondFetch((FetchEntryData, Vec<EntryAspect>)),

    /// Makes the network module send a direct (node-to-node) message
    /// to the address given in [DirectMessageData](struct.DirectMessageData.html)
    /// Includes the timeout information: system time of dispatch and duration until it timeouts.
    SendDirectMessage((DirectMessageData, Option<(SystemTime, Duration)>)),

    /// Makes the direct message connection with the given ID timeout by adding an
    /// Err(HolochainError::Timeout) to NetworkState::custom_direct_message_replys.
    SendDirectMessageTimeout(String),

    /// Makes the network module forget about the direct message
    /// connection with the given ID.
    /// Triggered when we got an answer to our initial DM.
    ResolveDirectConnection(String),

    /// Makes the network module DM the source of the given entry
    /// and prepare for receiveing an answer
    GetValidationPackage((ValidationKey, ChainHeader)),

    /// Makes the get validation request with the given ID timeout by adding an
    /// Err(HolochainError::Timeout) to NetworkState::get_validation_package_results.
    GetValidationPackageTimeout(ValidationKey),

    /// Updates the state to hold the response that we got for
    /// our previous request for a validation package.
    /// Triggered from the network handler when we get the response.
    HandleGetValidationPackage((Address, ValidationKey, Option<ValidationPackage>)),

    /// Clean up the validation package result so the state doesn't grow indefinitely.
    ClearValidationPackageResult(ValidationKey),

    /// Updates the state to hold the response that we got for
    /// our previous custom direct message.
    /// Triggered from the network handler when we get the response.
    HandleCustomSendResponse((String, Result<String, String>)),

    /// Clean up the custom send response result so the state doesn't grow indefinitely.
    ClearCustomSendResponse(String),

    /// Sends the given data as JsonProtocol::HandleGetAuthoringEntryListResult
    RespondAuthoringList(EntryListData),

    /// Sends the given data as JsonProtocol::HandleGetGossipEntryListResult
    RespondGossipList(EntryListData),

    // ----------------
    // Nucleus actions:
    // ----------------
    /// initialize a chain from Dna
    /// not the same as init
    /// may call init internally
    InitializeChain(Dna),
    /// return the result of an InitializeChain action
    /// the result is an initialization structure which include the generated public token if any
    ReturnInitializationResult(Result<Initialization, String>),

    /// Gets dispatched when a zome function call starts.
    QueueZomeFunctionCall(ZomeFnCall),

    /// return the result of a zome WASM function call
    ReturnZomeFunctionResult(ExecuteZomeFnResponse),

    /// Let the State track that a zome call has called an HDK function
    TraceInvokeHdkFunction((ZomeFnCall, HdkFnCall)),

    /// Let the State track that an HDK function called by a zome call has returned
    TraceReturnHdkFunction((ZomeFnCall, HdkFnCall, HdkFnCallResult)),

    /// Remove all traces of the given call from state (mainly the result)
    ClearZomeFunctionCall(ZomeFnCall),

    /// No-op, used to check if an action channel is still open
    Ping,
}

/// function signature for action handler functions
// @TODO merge these into a single signature
// @see https://github.com/holochain/holochain-rust/issues/194
pub type AgentReduceFn = ReduceFn<AgentState>;
pub type NetworkReduceFn = ReduceFn<NetworkState>;
pub type NucleusReduceFn = ReduceFn<NucleusState>;
pub type ReduceFn<S> = fn(&mut S, &State, &ActionWrapper);

/// The unique key that represents a GetLinks request, used to associate the eventual
/// response with this GetLinks request
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
pub struct GetLinksKey {
    /// The address of the Link base
    pub base_address: Address,

    /// The link type
    pub link_type: Option<String>,

    /// The link tag, None means get all the tags for a given type
    pub tag: Option<String>,

    /// A unique ID that is used to pair the eventual result to this request
    pub id: String,
}

/// The unique key that represents a Get request, used to associate the eventual
/// response with this Get request
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
pub struct GetEntryKey {
    /// The address of the entry to get
    pub address: Address,

    /// A unique ID that is used to pair the eventual result to this request
    pub id: String,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct ValidationKey {
    /// The address of the entry to get the package for
    pub address: Address,

    /// A unique ID that is used to pair the eventual result to this request
    pub id: String,
}

/// Everything the network module needs to know in order to send a
/// direct message.
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct DirectMessageData {
    /// The address of the node to send a message to
    pub address: Address,

    /// The message itself
    pub message: DirectMessage,

    /// A unique message ID that is used to identify the response and attribute
    /// it to the right context
    pub msg_id: String,

    /// Should be true if we are responding to a previous message with this message.
    /// msg_id should then be the same as the in the message that we received.
    pub is_response: bool,
}

/// Everything the network needs to initialize
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct NetworkSettings {
    /// P2pConfig that gets passed to [P2pNetwork](struct.P2pNetwork.html)
    /// determines how to connect to the network module.
    pub p2p_config: P2pConfig,

    /// DNA address is needed so the network module knows which network to
    /// connect us to.
    pub dna_address: Address,

    /// The network module needs to know who we are.
    /// This is this agent's address.
    pub agent_id: String,

    /// This is a closure of the code that gets called by the network
    /// module to have us process incoming messages
    pub handler: NetHandler,
}

#[cfg(test)]
pub mod tests {

    use crate::{
        action::{Action, ActionWrapper, GetEntryKey, QueryKey, QueryPayload},
        nucleus::tests::test_call_response,
    };
    use holochain_core_types::entry::{expected_entry_address, test_entry};
    use test_utils::calculate_hash;

    /// dummy action
    pub fn test_action() -> Action {
        Action::Query((
            QueryKey::Entry(GetEntryKey {
                address: expected_entry_address(),
                id: String::from("test-id"),
            }),
            QueryPayload::Entry,
            None,
        ))
    }

    /// dummy action wrapper with test_action()
    pub fn test_action_wrapper() -> ht::SpanWrap<ActionWrapper> {
        ht::noop("test-noop".into()).wrap(ActionWrapper::new(test_action()))
    }

    /// dummy action wrapper with commit of test_entry()
    pub fn test_action_wrapper_commit() -> ht::SpanWrap<ActionWrapper> {
        ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::Commit((
            test_entry(),
            None,
            vec![],
        ))))
    }

    /// dummy action for a get of test_hash()
    pub fn test_action_wrapper_get() -> ht::SpanWrap<ActionWrapper> {
        ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::Query((
            QueryKey::Entry(GetEntryKey {
                address: expected_entry_address(),
                id: nanoid::simple(),
            }),
            QueryPayload::Entry,
            None,
        ))))
    }

    pub fn test_action_wrapper_rzfr() -> ht::SpanWrap<ActionWrapper> {
        ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::ReturnZomeFunctionResult(
            test_call_response(),
        )))
    }

    #[test]
    /// smoke test actions
    fn new_action() {
        let a1 = test_action();
        let a2 = test_action();

        // unlike actions and wrappers, signals are equal to themselves
        assert_eq!(a1, a2);
    }

    #[test]
    /// tests that new action wrappers take an action and ensure uniqueness
    fn new_action_wrapper() {
        let aw1 = test_action_wrapper();
        let aw2 = test_action_wrapper();

        assert_eq!(aw1.data, aw1.data);
        assert_ne!(aw1.data, aw2.data);
    }

    #[test]
    /// tests read access to actions
    fn action_wrapper_action() {
        let aw1 = test_action_wrapper();
        let aw2 = test_action_wrapper();

        assert_eq!(aw1.action(), aw2.action());
        assert_eq!(aw1.action(), &test_action());
    }

    #[test]
    /// tests read access to action wrapper ids
    fn action_wrapper_id() {
        // can't set the ID directly (by design)
        // at least test that IDs are unique, and that hitting the id() method doesn't error
        let aw1 = test_action_wrapper();
        let aw2 = test_action_wrapper();

        assert_ne!(aw1.id(), aw2.id());
    }

    #[test]
    /// tests that action wrapper hashes are unique
    fn action_wrapper_hash() {
        let aw1 = test_action_wrapper();
        let aw2 = test_action_wrapper();

        assert_ne!(calculate_hash(&aw1.data), calculate_hash(&aw2.data));
    }
}