From 5bf604a63bdc42f376c5ba4ba7bc03deb51440ab Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 13:05:15 -0700 Subject: [PATCH 01/18] ensure dna/agent entries are only published on first startup, also add the chain header entries to the authoring list --- core/src/network/handler/lists.rs | 15 +++++++++++ core/src/workflows/application.rs | 41 ++++++++++++++++++------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 2859371bb5..9f0c44a5cf 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -27,6 +27,14 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc) -> Vec
{ .collect() } +fn get_all_chain_header_entries(context: Arc) -> Vec
{ + let chain = context.state().unwrap().agent().iter_chain(); + chain + .map(|chain_header| chain_header.address()) + .collect() +} + fn get_all_aspect_addresses(entry: &Address, context: Arc) -> HcResult> { let mut address_list: Vec
= get_meta_aspects(entry, context.clone())? .iter() diff --git a/core/src/workflows/application.rs b/core/src/workflows/application.rs index a7c0bdca84..2bdcb9117d 100644 --- a/core/src/workflows/application.rs +++ b/core/src/workflows/application.rs @@ -26,27 +26,34 @@ pub async fn initialize( let instance_context = instance.initialize_context(context.clone()); let dna = dna.ok_or(HolochainError::DnaMissing)?; - // 2. Initialize the local chain - if let Err(err) = await!(get_dna_and_agent(&instance_context)) { - context.log(format!( - "dna/initialize: Couldn't get DNA and agent from chain: {:?}", - err - )); - context.log("dna/initialize: Initializing new chain from given DNA..."); - await!(initialize_chain(dna.clone(), &instance_context))?; - } + // 2. Initialize the local chain if not already + let first_initialization = match await!(get_dna_and_agent(&instance_context)) { + Ok(_) => false, + Err(err) => { + context.log( + format!("dna/initialize: Couldn't get DNA and agent from chain: {:?}", + err) + ); + await!(initialize_chain(dna.clone(), &instance_context))?; + context.log("dna/initialize: Initializing new chain from given DNA..."); + true + } + }; // 3. Initialize the network await!(initialize_network(&instance_context))?; - // 4. Call publish on the agent and DNA entries. - // This is to trigger the publishing of their headers not the entries themselves - let dna_entry = Entry::Dna(Box::new(dna.clone())); - await!(publish(dna_entry.address(), &context))?; - let agent_id_entry = Entry::AgentId(context.agent_id.clone()); - await!(publish(agent_id_entry.address(), &context))?; + if first_initialization { + // 4. (first initialization only) Call publish on the agent and DNA entries. + // This is to trigger the fast publishing of their headers not the entries themselves + let dna_entry = Entry::Dna(Box::new(dna.clone())); + await!(publish(dna_entry.address(), &context))?; + let agent_id_entry = Entry::AgentId(context.agent_id.clone()); + await!(publish(agent_id_entry.address(), &context))?; + + // 5. (first initialization only) Call the init callbacks in the zomes + await!(call_init(dna, &instance_context))?; + } - // 5. Call the init callbacks in the zomes - await!(call_init(dna, &instance_context))?; Ok(instance_context) } From 21898f743dc1953092acd1c621d5fe3796db874b Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 13:09:28 -0700 Subject: [PATCH 02/18] use iter_chain in get_all_public_chain_entries --- core/src/network/handler/lists.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 9f0c44a5cf..54acdfad8c 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -47,10 +47,8 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc) -> Vec
{ - let chain = context.state().unwrap().agent().chain_store(); - let top_header = context.state().unwrap().agent().top_chain_header(); + let chain = context.state().unwrap().agent().iter_chain(); chain - .iter(&top_header) .filter(|ref chain_header| chain_header.entry_type().can_publish(&context)) .map(|chain_header| chain_header.entry_address().clone()) .collect() From 602e83913af51bea38d780b1d892b9fcfee67276 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 14:13:38 -0700 Subject: [PATCH 03/18] split out new action for PublishHeaderEntry. Tests passing with this refactor --- core/src/action.rs | 4 + core/src/network/actions/mod.rs | 2 + .../network/actions/publish_header_entry.rs | 54 +++++++++ core/src/network/handler/lists.rs | 30 ++--- core/src/network/reducers/mod.rs | 3 + core/src/network/reducers/publish.rs | 35 ------ .../network/reducers/publish_header_entry.rs | 109 ++++++++++++++++++ core/src/workflows/application.rs | 11 +- core/src/workflows/author_entry.rs | 18 ++- 9 files changed, 208 insertions(+), 58 deletions(-) create mode 100644 core/src/network/actions/publish_header_entry.rs create mode 100644 core/src/network/reducers/publish_header_entry.rs diff --git a/core/src/action.rs b/core/src/action.rs index a7e72d9331..6c275d799c 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -130,6 +130,10 @@ pub enum Action { /// (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), + /// Get an Entry on the network by address GetEntry(GetEntryKey), diff --git a/core/src/network/actions/mod.rs b/core/src/network/actions/mod.rs index 918c99c63a..181aaa9cd8 100644 --- a/core/src/network/actions/mod.rs +++ b/core/src/network/actions/mod.rs @@ -5,6 +5,7 @@ pub mod get_validation_package; pub mod initialize_network; pub mod publish; pub mod shutdown; +pub mod publish_header_entry; use holochain_core_types::error::HcResult; use holochain_persistence_api::cas::content::Address; @@ -12,6 +13,7 @@ use holochain_persistence_api::cas::content::Address; #[derive(Clone, Debug)] pub enum ActionResponse { Publish(HcResult
), + PublishHeaderEntry(HcResult
), RespondGet(HcResult<()>), RespondFetch(HcResult<()>), RespondGetLinks(HcResult<()>), diff --git a/core/src/network/actions/publish_header_entry.rs b/core/src/network/actions/publish_header_entry.rs new file mode 100644 index 0000000000..388f826714 --- /dev/null +++ b/core/src/network/actions/publish_header_entry.rs @@ -0,0 +1,54 @@ +use crate::{ + action::{Action, ActionWrapper}, + context::Context, + instance::dispatch_action, + network::actions::ActionResponse, +}; +use futures::{future::Future, task::Poll}; +use holochain_core_types::error::HcResult; +use holochain_persistence_api::cas::content::Address; +use std::{pin::Pin, sync::Arc}; + +/// Publish Header Entry Action Creator +/// Returns a future that resolves to an ActionResponse. +pub async fn publish_header_entry(address: Address, context: &Arc) -> HcResult
{ + let action_wrapper = ActionWrapper::new(Action::PublishHeaderEntry(address)); + dispatch_action(context.action_channel(), action_wrapper.clone()); + await!(PublishHeaderEntryFuture { + context: context.clone(), + action: action_wrapper, + }) +} + +/// PublishFuture resolves to ActionResponse +/// Tracks the state for a response to its ActionWrapper +pub struct PublishHeaderEntryFuture { + context: Arc, + action: ActionWrapper, +} + +impl Future for PublishHeaderEntryFuture { + type Output = HcResult
; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { + if let Some(err) = self.context.action_channel_error("PublishHeaderEntryFuture") { + return Poll::Ready(Err(err)); + } + let state = self.context.state().unwrap().network(); + if let Err(error) = state.initialized() { + return Poll::Ready(Err(error)); + } + // + // TODO: connect the waker to state updates for performance reasons + // See: https://github.com/holochain/holochain-rust/issues/314 + // + cx.waker().clone().wake(); + match state.actions().get(&self.action) { + Some(ActionResponse::PublishHeaderEntry(result)) => match result { + Ok(address) => Poll::Ready(Ok(address.to_owned())), + Err(error) => Poll::Ready(Err(error.clone())), + }, + _ => Poll::Pending, + } + } +} diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 54acdfad8c..9209ff85a8 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -5,7 +5,9 @@ use crate::{ instance::dispatch_action, network::handler::{get_content_aspect, get_meta_aspects}, }; -use holochain_core_types::error::HcResult; +use holochain_core_types::{ + error::HcResult, +}; use holochain_persistence_api::cas::content::{Address, AddressableContent}; use lib3h_protocol::data_types::{EntryListData, GetListData}; use snowflake::ProcessUniqueId; @@ -27,13 +29,13 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc) -> Vec
{ .collect() } -fn get_all_chain_header_entries(context: Arc) -> Vec
{ - let chain = context.state().unwrap().agent().iter_chain(); - chain - .map(|chain_header| chain_header.address()) - .collect() -} +// fn get_all_chain_header_entries(context: Arc) -> Vec
{ +// let chain = context.state().unwrap().agent().iter_chain(); +// chain +// .map(|chain_header| Entry::ChainHeader(chain_header).address()) +// .collect() +// } fn get_all_aspect_addresses(entry: &Address, context: Arc) -> HcResult> { let mut address_list: Vec
= get_meta_aspects(entry, context.clone())? diff --git a/core/src/network/reducers/mod.rs b/core/src/network/reducers/mod.rs index 0556436f5b..4a0f25ba92 100644 --- a/core/src/network/reducers/mod.rs +++ b/core/src/network/reducers/mod.rs @@ -7,6 +7,7 @@ pub mod handle_get_result; pub mod handle_get_validation_package; pub mod init; pub mod publish; +pub mod publish_header_entry; pub mod resolve_direct_connection; pub mod respond_authoring_list; pub mod respond_fetch; @@ -30,6 +31,7 @@ use crate::{ handle_get_validation_package::reduce_handle_get_validation_package, init::reduce_init, publish::reduce_publish, + publish_header_entry::reduce_publish_header_entry, resolve_direct_connection::reduce_resolve_direct_connection, respond_authoring_list::reduce_respond_authoring_list, respond_fetch::reduce_respond_fetch_data, @@ -67,6 +69,7 @@ fn resolve_reducer(action_wrapper: &ActionWrapper) -> Option { Action::HandleGetValidationPackage(_) => Some(reduce_handle_get_validation_package), Action::InitNetwork(_) => Some(reduce_init), Action::Publish(_) => Some(reduce_publish), + Action::PublishHeaderEntry(_) => Some(reduce_publish_header_entry), Action::ResolveDirectConnection(_) => Some(reduce_resolve_direct_connection), Action::RespondAuthoringList(_) => Some(reduce_respond_authoring_list), Action::RespondGossipList(_) => Some(reduce_respond_gossip_list), diff --git a/core/src/network/reducers/publish.rs b/core/src/network/reducers/publish.rs index 8591c587ee..463bdee1ec 100644 --- a/core/src/network/reducers/publish.rs +++ b/core/src/network/reducers/publish.rs @@ -9,13 +9,11 @@ use crate::{ }, state::State, entry::CanPublish, - agent::state::create_new_chain_header, }; use holochain_core_types::{ crud_status::CrudStatus, entry::{entry_type::EntryType, Entry}, error::HolochainError, - chain_header::ChainHeader, }; use lib3h_protocol::{ data_types::{EntryData, ProvidedEntryData}, @@ -46,38 +44,6 @@ fn publish_entry( ) } -/// Send to network a request to publish a header entry alone -/// This is similar to publishing a regular entry but it has its own special dummy header. -fn publish_header( - network_state: &mut NetworkState, - root_state: &State, - chain_header: &ChainHeader, -) -> Result<(), HolochainError> { - let header_entry = Entry::ChainHeader(chain_header.clone()); - let header_entry_header = create_new_chain_header( - &header_entry, - &root_state.agent(), - root_state, - &None, - &Vec::new(), - )?; - send( - network_state, - Lib3hClientProtocol::PublishEntry(ProvidedEntryData { - space_address: network_state.dna_address.clone().unwrap().into(), - provider_agent_id: network_state.agent_id.clone().unwrap().into(), - entry: EntryData { - entry_address: header_entry.address().clone(), - aspect_list: vec![EntryAspect::Content( - header_entry.clone(), - header_entry_header, - ) - .into()], - }, - }), - ) -} - /// Send to network a publish request for either delete or update aspect information fn publish_update_delete_meta( network_state: &mut NetworkState, @@ -162,7 +128,6 @@ fn reduce_publish_inner( network_state.initialized()?; let entry_with_header = fetch_entry_with_header(&address, root_state)?; - publish_header(network_state, root_state, &entry_with_header.header)?; // for non-publishing entries early return Ok if ! entry_with_header.entry.entry_type().can_publish_from_state(root_state) { return Ok(()); } diff --git a/core/src/network/reducers/publish_header_entry.rs b/core/src/network/reducers/publish_header_entry.rs new file mode 100644 index 0000000000..6a00b4cf6e --- /dev/null +++ b/core/src/network/reducers/publish_header_entry.rs @@ -0,0 +1,109 @@ +use crate::{ + action::ActionWrapper, + network::{ + actions::ActionResponse, + entry_aspect::EntryAspect, + entry_with_header::{fetch_entry_with_header}, + reducers::send, + state::NetworkState, + }, + state::State, + agent::state::create_new_chain_header, +}; +use holochain_core_types::{ + entry::{Entry}, + error::HolochainError, + chain_header::ChainHeader, +}; +use lib3h_protocol::{ + data_types::{EntryData, ProvidedEntryData}, + protocol_client::Lib3hClientProtocol, +}; + +use holochain_persistence_api::cas::content::{Address, AddressableContent}; + + +/// Send to network a request to publish a header entry alone +/// This is similar to publishing a regular entry but it has its own special dummy header. +fn publish_header( + network_state: &mut NetworkState, + root_state: &State, + chain_header: &ChainHeader, +) -> Result<(), HolochainError> { + let header_entry = Entry::ChainHeader(chain_header.clone()); + let header_entry_header = create_new_chain_header( + &header_entry, + &root_state.agent(), + root_state, + &None, + &Vec::new(), + )?; + send( + network_state, + Lib3hClientProtocol::PublishEntry(ProvidedEntryData { + space_address: network_state.dna_address.clone().unwrap().into(), + provider_agent_id: network_state.agent_id.clone().unwrap().into(), + entry: EntryData { + entry_address: header_entry.address().clone(), + aspect_list: vec![EntryAspect::Content( + header_entry.clone(), + header_entry_header, + ) + .into()], + }, + }), + ) +} + + +fn reduce_publish_header_entry_inner( + network_state: &mut NetworkState, + root_state: &State, + address: &Address, +) -> Result<(), HolochainError> { + network_state.initialized()?; + let entry_with_header = fetch_entry_with_header(&address, root_state)?; + publish_header(network_state, root_state, &entry_with_header.header) +} + +pub fn reduce_publish_header_entry( + network_state: &mut NetworkState, + root_state: &State, + action_wrapper: &ActionWrapper, +) { + let action = action_wrapper.action(); + let address = unwrap_to!(action => crate::action::Action::PublishHeaderEntry); + + let result = reduce_publish_header_entry_inner(network_state, root_state, &address); + network_state.actions.insert( + action_wrapper.clone(), + ActionResponse::PublishHeaderEntry(match result { + Ok(_) => Ok(address.clone()), + Err(e) => Err(HolochainError::ErrorGeneric(e.to_string())), + }), + ); +} + +#[cfg(test)] +mod tests { + + use crate::{ + action::{Action, ActionWrapper}, + instance::tests::test_context, + state::test_store, + }; + use holochain_core_types::entry::test_entry; + use holochain_persistence_api::cas::content::AddressableContent; + + #[test] + pub fn reduce_publish_header_entry_test() { + let context = test_context("alice", None); + let store = test_store(context.clone()); + + let entry = test_entry(); + let action_wrapper = ActionWrapper::new(Action::PublishHeaderEntry(entry.address())); + + store.reduce(action_wrapper); + } + +} diff --git a/core/src/workflows/application.rs b/core/src/workflows/application.rs index 2bdcb9117d..5bc6eb2833 100644 --- a/core/src/workflows/application.rs +++ b/core/src/workflows/application.rs @@ -2,7 +2,7 @@ use crate::{ context::{get_dna_and_agent, Context}, instance::Instance, network::actions::{ - publish::publish, + publish_header_entry::publish_header_entry, initialize_network::initialize_network, }, nucleus::actions::{call_init::call_init, initialize::initialize_chain}, @@ -13,8 +13,6 @@ use holochain_core_types::{ entry::Entry, }; use holochain_persistence_api::cas::content::AddressableContent; - - use std::sync::Arc; pub async fn initialize( @@ -44,12 +42,11 @@ pub async fn initialize( await!(initialize_network(&instance_context))?; if first_initialization { - // 4. (first initialization only) Call publish on the agent and DNA entries. - // This is to trigger the fast publishing of their headers not the entries themselves + // 4. (first initialization only) Publish the headers of the agent and DNA entries. let dna_entry = Entry::Dna(Box::new(dna.clone())); - await!(publish(dna_entry.address(), &context))?; + await!(publish_header_entry(dna_entry.address(), &context))?; let agent_id_entry = Entry::AgentId(context.agent_id.clone()); - await!(publish(agent_id_entry.address(), &context))?; + await!(publish_header_entry(agent_id_entry.address(), &context))?; // 5. (first initialization only) Call the init callbacks in the zomes await!(call_init(dna, &instance_context))?; diff --git a/core/src/workflows/author_entry.rs b/core/src/workflows/author_entry.rs index f71b8b4b4e..c67c00c89d 100644 --- a/core/src/workflows/author_entry.rs +++ b/core/src/workflows/author_entry.rs @@ -1,7 +1,10 @@ use crate::{ agent::actions::commit::commit_entry, context::Context, - network::actions::publish::publish, + network::actions::{ + publish::publish, + // publish_header_entry::publish_header_entry, + }, nucleus::{ actions::build_validation_package::build_validation_package, validation::validate_entry, }, @@ -80,7 +83,7 @@ pub async fn author_entry<'a>( address )); - // 4. Publish the valid entry to DHT. + // 4. Publish the valid entry and its header (will be optional in the future) to DHT. // For publishable entires this will publish the entry and the header // For non-publishable entries this will only publish the header context.log(format!( @@ -92,6 +95,17 @@ pub async fn author_entry<'a>( "debug/workflow/authoring_entry/{}: published!", address )); + + // context.log(format!( + // "debug/workflow/authoring_entry/{}: publishing header...", + // address + // )); + // await!(publish_header_entry(entry.address(), &context))?; + // context.log(format!( + // "debug/workflow/authoring_entry/{}: header published!", + // address + // )); + Ok(CommitEntryResult::new(addr)) } From e475d0345a9cd4566f3e0c7c073d27ac5d84db18 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 15:17:08 -0700 Subject: [PATCH 04/18] header publishing implemented again. Tests seem to have been dropped when merging develop --- core/src/workflows/author_entry.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/core/src/workflows/author_entry.rs b/core/src/workflows/author_entry.rs index 5020cc9b57..fb8d8535e6 100644 --- a/core/src/workflows/author_entry.rs +++ b/core/src/workflows/author_entry.rs @@ -8,6 +8,7 @@ use crate::{ nucleus::{ actions::build_validation_package::build_validation_package, validation::validate_entry, }, + entry::CanPublish, }; use holochain_core_types::{ @@ -80,13 +81,25 @@ pub async fn author_entry<'a>( ))?; log_debug!(context, "workflow/authoring_entry/{}: committed", address); - // 4. Publish the valid entry and its header (will be optional in the future) to DHT. - // For publishable entires this will publish the entry and the header - // For non-publishable entries this will only publish the header - log_debug!(context, "debug/workflow/authoring_entry/{}: publishing...", address); - await!(publish(entry.address(), &context))?; - log_debug!(context, "debug/workflow/authoring_entry/{}: published!", address); + // 4. Publish the valid entry to DHT. This will call Hold to itself + if entry.entry_type().can_publish(context) { + log_debug!(context, + "workflow/authoring_entry/{}: publishing...", + address + ); + await!(publish(entry.address(), &context))?; + log_debug!(context, + "workflow/authoring_entry/{}: published!", + address + ); + } else { + log_debug!(context, + "workflow/authoring_entry/{}: entry is private, no publishing", + address + ); + } + // 5. Publish the header for all types (including private entries) log_debug!(context, "debug/workflow/authoring_entry/{}: publishing header...", address); await!(publish_header_entry(entry.address(), &context))?; log_debug!(context, "debug/workflow/authoring_entry/{}: header published!", address); From 1d944eb93982fa191239f95c4c6f1831b41009cd Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 15:22:19 -0700 Subject: [PATCH 05/18] adds tests back for author_entry --- core/src/workflows/author_entry.rs | 164 +++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 19 deletions(-) diff --git a/core/src/workflows/author_entry.rs b/core/src/workflows/author_entry.rs index fb8d8535e6..a2ff9afbf4 100644 --- a/core/src/workflows/author_entry.rs +++ b/core/src/workflows/author_entry.rs @@ -110,9 +110,13 @@ pub async fn author_entry<'a>( #[cfg(test)] pub mod tests { use super::author_entry; + use crate::nucleus::actions::get_entry::get_entry_from_dht; use crate::nucleus::actions::tests::*; - use holochain_core_types::entry::test_entry_with_value; - use holochain_json_api::json::JsonString; + use holochain_core_types::{ + entry::{test_entry_with_value, Entry}, + chain_header::ChainHeader, + }; + use holochain_persistence_api::cas::content::AddressableContent; use std::{thread, time}; #[test] @@ -135,31 +139,153 @@ pub mod tests { .address(); thread::sleep(time::Duration::from_millis(500)); - let mut json: Option = None; + let mut entry: Option = None; let mut tries = 0; - while json.is_none() && tries < 120 { + while entry.is_none() && tries < 120 { tries = tries + 1; { - let state = &context2.state().unwrap(); - json = state - .dht() - .content_storage() - .read() - .unwrap() - .fetch(&entry_address) - .expect("could not fetch from CAS"); + entry = get_entry_from_dht(&context2, &entry_address).expect("Could not retrieve entry from DHT"); } - println!("Try {}: {:?}", tries, json); - if json.is_none() { + println!("Try {}: {:?}", tries, entry); + if entry.is_none() { thread::sleep(time::Duration::from_millis(1000)); } } + assert_eq!( + entry, + Some(test_entry_with_value("{\"stuff\":\"test entry value\"}")) + ); + } + + #[test] + /// test that the header of an entry can be retrieved directly by its hash by another agent connected + /// via the in-memory network + fn test_commit_with_dht_publish_header_is_published() { + let mut dna = test_dna(); + dna.uuid = "test_commit_with_dht_publish_header_is_published".to_string(); + let netname = Some("test_commit_with_dht_publish_header_is_published, the network"); + let (_instance1, context1) = instance_by_name("jill", dna.clone(), netname); + let (_instance2, context2) = instance_by_name("jack", dna, netname); + + let entry_address = context1 + .block_on(author_entry( + &test_entry_with_value("{\"stuff\":\"test entry value\"}"), + None, + &context1, + &vec![], + )) + .unwrap() + .address(); + + thread::sleep(time::Duration::from_millis(500)); + + // get the header from the top of Jill's chain + let state = &context1.state().unwrap(); + let header = state.get_headers(entry_address) + .expect("Could not retrieve headers from authors chain") + .into_iter() + .next() + .expect("No headers were found for this entry in the authors chain"); + let header_entry = Entry::ChainHeader(header); - let x: String = json.unwrap().to_string(); + // try and load it by its address as Jack. This means it has been communicated over the mock network + let mut entry: Option = None; + let mut tries = 0; + while entry.is_none() && tries < 10 { + tries = tries + 1; + { + entry = get_entry_from_dht(&context2, &header_entry.address()).expect("Could not retrieve entry from DHT"); + } + println!("Try {}: {:?}", tries, entry); + if entry.is_none() { + thread::sleep(time::Duration::from_millis(1000)); + } + } assert_eq!( - x, - "{\"App\":[\"testEntryType\",\"{\\\"stuff\\\":\\\"test entry value\\\"}\"]}" - .to_string(), + entry, + Some(header_entry), ); } -} + + + #[test] + /// test that all headers are published so an agents local chain can be reconstructed by another agent + fn test_reconstruct_chain_via_published_headers() { + let mut dna = test_dna(); + dna.uuid = "test_reconstruct_chain_via_published_headers".to_string(); + let netname = Some("test_reconstruct_chain_via_published_headers, the network"); + // the ordering of these is important. Jack will get Jills DNA and AgentId headers but not visa-versa + let (_instance2, context2) = instance_by_name("jack", dna.clone(), netname); + let (_instance1, context1) = instance_by_name("jill", dna.clone(), netname); + + // Jill publishes an entry + context1 + .block_on(author_entry( + &test_entry_with_value("{\"stuff\":\"test entry value number 1\"}"), + None, + &context1, + &vec![], + )) + .unwrap() + .address(); + thread::sleep(time::Duration::from_millis(500)); + + // Jill publishes another entry + context1 + .block_on(author_entry( + &test_entry_with_value("{\"stuff\":\"test entry value number 2\"}"), + None, + &context1, + &vec![], + )) + .unwrap() + .address(); + thread::sleep(time::Duration::from_millis(500)); + + // collect Jills local chain + let state = &context1.state().unwrap(); + let jill_headers: Vec = state + .agent() + .iter_chain() + .collect(); + let header = jill_headers.first().expect("Must be at least one header in chain"); + + // jack retrieves the top header addresss and reconstructs the Jills local chain by following the header back-links + let mut jack_headers: Vec = Vec::new(); + let mut next_header_addr = header.address(); + loop { + let mut entry: Option = None; + let mut tries = 0; + while entry.is_none() && tries < 10 { + tries = tries + 1; + { + entry = get_entry_from_dht(&context2, &next_header_addr).expect("Could not retrieve entry from DHT"); + } + println!("Try {}: {:?}", tries, entry); + if entry.is_none() { + thread::sleep(time::Duration::from_millis(1000)); + } + } + if let Some(Entry::ChainHeader(header)) = entry { + jack_headers.push(header.clone()); + if let Some(next_addr) = header.link() { + next_header_addr = next_addr + } else { + break // chain has been followed to the genesis entry + } + } else { + panic!(format!("Could not retrieve header at address: {}", next_header_addr)) + } + } + + assert_eq!( + jack_headers.len(), + 4, + ); + + assert_eq!( + jack_headers, + jill_headers, + ); + } +} \ No newline at end of file From 3db0e65109d6fa6c92c4fd17dc4f3c1679d61702 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 18:21:19 -0700 Subject: [PATCH 06/18] add special validation case and AddressableContent for ChainHeader --- core/src/nucleus/validation/mod.rs | 3 +++ core_types/src/entry/mod.rs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/nucleus/validation/mod.rs b/core/src/nucleus/validation/mod.rs index db09521f77..491c32bab7 100644 --- a/core/src/nucleus/validation/mod.rs +++ b/core/src/nucleus/validation/mod.rs @@ -123,6 +123,9 @@ pub async fn validate_entry( context, )), + // chain headers always pass for now. In future this should check that the entry is valid + EntryType::ChainHeader => Ok(()), + _ => Err(ValidationError::NotImplemented), } } diff --git a/core_types/src/entry/mod.rs b/core_types/src/entry/mod.rs index b8ff85a04b..474c87efde 100644 --- a/core_types/src/entry/mod.rs +++ b/core_types/src/entry/mod.rs @@ -107,16 +107,21 @@ impl AddressableContent for Entry { fn address(&self) -> Address { match &self { Entry::AgentId(agent_id) => agent_id.address(), + Entry::ChainHeader(chain_header) => chain_header.address(), _ => Address::encode_from_str(&String::from(self.content()), Hash::SHA2256), } } fn content(&self) -> Content { - self.into() + match &self { + Entry::ChainHeader(chain_header) => chain_header.into(), + _ => self.into(), + } } fn try_from_content(content: &Content) -> JsonResult { Entry::try_from(content.to_owned()) + .or_else(|_| ChainHeader::try_from(content).map(|header| Entry::ChainHeader(header))) } } From a5a8f760389e92eee35d559aa655b6791f8ff8ad Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 6 Aug 2019 18:33:46 -0700 Subject: [PATCH 07/18] add validation package for ChainHeader entry --- core/src/nucleus/actions/get_entry.rs | 15 +++++++-------- .../ribosome/callback/validation_package.rs | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/nucleus/actions/get_entry.rs b/core/src/nucleus/actions/get_entry.rs index 1d1f1d4840..db7ef57c60 100644 --- a/core/src/nucleus/actions/get_entry.rs +++ b/core/src/nucleus/actions/get_entry.rs @@ -7,13 +7,12 @@ use holochain_core_types::{ }; use holochain_persistence_api::{ - cas::{content::Address, storage::ContentAddressableStorage}, + cas::{content::{Address, AddressableContent}, storage::ContentAddressableStorage}, eav::IndexFilter, }; use std::{ collections::BTreeSet, - convert::TryInto, str::FromStr, sync::{Arc, RwLock}, }; @@ -22,12 +21,12 @@ pub(crate) fn get_entry_from_cas( storage: &Arc>, address: &Address, ) -> Result, HolochainError> { - let json = (*storage.read().unwrap()).fetch(&address)?; - - let entry: Option = json - .and_then(|js| js.try_into().ok()) - .map(|s: Entry| s.into()); - Ok(entry) + if let Some(json) = (*storage.read().unwrap()).fetch(&address)? { + let entry = Entry::try_from_content(&json)?; + Ok(Some(entry)) + } else { + Ok(None) // no errors but entry is not in CAS + } } pub fn get_entry_from_agent_chain( diff --git a/core/src/nucleus/ribosome/callback/validation_package.rs b/core/src/nucleus/ribosome/callback/validation_package.rs index 2a85137d58..68bf2cae92 100644 --- a/core/src/nucleus/ribosome/callback/validation_package.rs +++ b/core/src/nucleus/ribosome/callback/validation_package.rs @@ -109,6 +109,7 @@ pub fn get_validation_package_definition( EntryType::Deletion => JsonString::from(ValidationPackageDefinition::ChainFull), EntryType::CapTokenGrant => JsonString::from(ValidationPackageDefinition::Entry), EntryType::AgentId => JsonString::from(ValidationPackageDefinition::Entry), + EntryType::ChainHeader => JsonString::from(ValidationPackageDefinition::Entry), _ => Err(HolochainError::NotImplemented( "get_validation_package_definition/3".into(), ))?, From 041779d15462e1095d9add5312a6d25c1fd612c6 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 7 Aug 2019 10:04:47 -0700 Subject: [PATCH 08/18] adds chain headers to author list --- core/src/network/handler/lists.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 9209ff85a8..85d0c5e7a1 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -7,6 +7,7 @@ use crate::{ }; use holochain_core_types::{ error::HcResult, + entry::Entry, }; use holochain_persistence_api::cas::content::{Address, AddressableContent}; use lib3h_protocol::data_types::{EntryListData, GetListData}; @@ -29,13 +30,15 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc) -> Vec
{ .collect() } -// fn get_all_chain_header_entries(context: Arc) -> Vec
{ -// let chain = context.state().unwrap().agent().iter_chain(); -// chain -// .map(|chain_header| Entry::ChainHeader(chain_header).address()) -// .collect() -// } +fn get_all_chain_header_entries(context: Arc) -> Vec
{ + let chain = context.state().unwrap().agent().iter_chain(); + chain + .map(|chain_header| Entry::ChainHeader(chain_header).address()) + .collect() +} fn get_all_aspect_addresses(entry: &Address, context: Arc) -> HcResult> { let mut address_list: Vec
= get_meta_aspects(entry, context.clone())? From 9a22ac4f252e95967d229d64e1b416d79646089f Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 7 Aug 2019 10:49:59 -0700 Subject: [PATCH 09/18] fmt --- core/src/action.rs | 13 ++++--------- core/src/agent/state.rs | 10 ++-------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/core/src/action.rs b/core/src/action.rs index 94d9ebad31..6e7e3acd4e 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -4,7 +4,7 @@ use crate::{ direct_message::DirectMessage, entry_aspect::EntryAspect, entry_with_header::EntryWithHeader, - query::{GetLinksNetworkQuery,NetworkQueryResult}, + query::{GetLinksNetworkQuery, NetworkQueryResult}, state::NetworkState, }, nucleus::{ @@ -18,13 +18,8 @@ use crate::{ }; use holochain_core_types::{ - chain_header::ChainHeader, - crud_status::CrudStatus, - dna::Dna, - entry::Entry, - error::HolochainError, - link::link_data::LinkData, - signature::Provenance, + chain_header::ChainHeader, crud_status::CrudStatus, dna::Dna, entry::Entry, + error::HolochainError, link::link_data::LinkData, signature::Provenance, validation::ValidationPackage, }; use holochain_net::{connection::net_connection::NetHandler, p2p_config::P2pConfig}; @@ -146,7 +141,7 @@ pub enum Action { /// 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), + PublishHeaderEntry(Address), ///Performs a Network Query Action based on the key and payload, used for links and Entries Query((QueryKey, QueryPayload)), diff --git a/core/src/agent/state.rs b/core/src/agent/state.rs index ce82f4b8ac..ee18654774 100644 --- a/core/src/agent/state.rs +++ b/core/src/agent/state.rs @@ -420,14 +420,8 @@ pub mod tests { let agent_state = test_agent_state(Some(context.agent_id.address())); let state = State::new_with_agent(context.clone(), agent_state.clone()); - let header = create_new_chain_header( - &test_entry(), - &agent_state, - &state, - &None, - &vec![], - ) - .unwrap(); + let header = + create_new_chain_header(&test_entry(), &agent_state, &state, &None, &vec![]).unwrap(); let agent_id = context.agent_id.clone(); assert_eq!( header, From 58b6d565e5b4919badc108257aad9ed3781c896f Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 7 Aug 2019 10:53:00 -0700 Subject: [PATCH 10/18] changelog --- CHANGELOG-UNRELEASED.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG-UNRELEASED.md b/CHANGELOG-UNRELEASED.md index cf1a64471a..7b9478c91a 100644 --- a/CHANGELOG-UNRELEASED.md +++ b/CHANGELOG-UNRELEASED.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added * New logging implementation added as a subcrate : a fast logger with a filtering capability using regex expressions, please so [logging](logging) for more details. +* Adds publishing of headers again after rollback. Header publishing is now its own action rather than part of the `Publish` action that plays nicely with the testing framework. It also adds header entries to the author list so they are gossiped properly. [#1640](https://github.com/holochain/holochain-rust/pull/1640). ### Changed From b9d7d6af452cff8b2841d16a8ae8470f3d0fd9f9 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 7 Aug 2019 17:12:22 -0700 Subject: [PATCH 11/18] adds tests for retrieving header aspects in author_list --- core/src/network/handler/lists.rs | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 85d0c5e7a1..68786adb50 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -108,3 +108,67 @@ pub fn handle_get_gossip_list(get_list_data: GetListData, context: Arc) }) .expect("Could not spawn thread for creating of gossip list"); } + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::workflows::author_entry::author_entry; + use crate::nucleus::actions::tests::*; + use holochain_core_types::{ + entry::{Entry, test_entry_with_value}, + }; + use holochain_persistence_api::cas::content::{AddressableContent, Address}; + use std::{thread, time}; + + #[test] + fn test_can_get_chain_header_list() { + let mut dna = test_dna(); + dna.uuid = "test_can_get_chain_header_list".to_string(); + let (_instance, context) = instance_by_name("jill", dna, None); + + context + .block_on(author_entry( + &test_entry_with_value("{\"stuff\":\"test entry value\"}"), + None, + &context, + &vec![], + )) + .unwrap() + .address(); + + thread::sleep(time::Duration::from_millis(500)); + + let chain = context.state().unwrap().agent().iter_chain(); + let header_entry_addrs: Vec
= chain.map(|header| Entry::ChainHeader(header).address()).collect(); + + assert_eq!( + get_all_chain_header_entries(context), + header_entry_addrs, + ) + + } + + #[test] + fn test_can_get_all_aspect_addr_for_headers() { + let mut dna = test_dna(); + dna.uuid = "test_can_get_chain_header_list".to_string(); + let (_instance, context) = instance_by_name("jill", dna, None); + + context + .block_on(author_entry( + &test_entry_with_value("{\"stuff\":\"test entry value\"}"), + None, + &context, + &vec![], + )) + .unwrap() + .address(); + + thread::sleep(time::Duration::from_millis(500)); + + assert!(get_all_chain_header_entries(context.clone()).iter().all(|chain_header| { + get_all_aspect_addresses(&chain_header, context.clone()).is_ok() + })); + } + +} From e5865f47b7f9b0b3e3c83c727bddbbc38f9e6847 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 7 Aug 2019 17:26:41 -0700 Subject: [PATCH 12/18] logs errors getting aspects rather than panic --- core/src/network/handler/lists.rs | 34 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index 68786adb50..b5cb6c3c1b 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -23,21 +23,35 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc { + address_map.insert( + entry.clone(), + aspects, + ); + }, + Err(_err) => log_debug!(context, + "handler/get_authoring_list: Error getting entry aspects of authoring list for entry with address: {}", + entry + ), + }; } // chain header entries also should be communicated on the authoring list // In future make this depend if header publishing is enabled for chain_header_entry in get_all_chain_header_entries(context.clone()) { - address_map.insert( - chain_header_entry.clone(), - get_all_aspect_addresses(&chain_header_entry, context.clone()) - .expect("Error getting entry aspects of authoring list for chain headers"), - ); + match get_all_aspect_addresses(&chain_header_entry, context.clone()) { + Ok(aspects) => { + address_map.insert( + chain_header_entry.clone(), + aspects, + ); + }, + Err(_err) => log_debug!(context, + "handler/get_authoring_list: Error getting entry aspects of authoring list for chain header with address: {}", + chain_header_entry) + , + }; } let action = Action::RespondAuthoringList(EntryListData { From cbd139bc42b548d25869c78482dd7d8a21683973 Mon Sep 17 00:00:00 2001 From: Nicolas Luck Date: Fri, 16 Aug 2019 16:38:46 +0200 Subject: [PATCH 13/18] Fix construction content aspects for authoring list Have get_content_aspect() try the source chain first. Construct content aspects for headers on the fly. --- core/src/agent/state.rs | 14 +++- core/src/network/handler/lists.rs | 59 +++++++------- core/src/network/handler/mod.rs | 77 +++++++++++++------ .../network/reducers/publish_header_entry.rs | 3 +- .../actions/build_validation_package.rs | 3 +- 5 files changed, 97 insertions(+), 59 deletions(-) diff --git a/core/src/agent/state.rs b/core/src/agent/state.rs index ee18654774..510827f3cb 100644 --- a/core/src/agent/state.rs +++ b/core/src/agent/state.rs @@ -176,14 +176,14 @@ pub enum ActionResponse { pub fn create_new_chain_header( entry: &Entry, agent_state: &AgentState, - root_state: &State, + root_state: &StateWrapper, crud_link: &Option
, provenances: &Vec, ) -> Result { let agent_address = agent_state.get_agent_address()?; let signature = Signature::from( root_state - .conductor_api + .conductor_api() .execute(entry.address().to_string(), CryptoMethod::Sign)?, // Temporarily replaced by error handling for Holo hack signing. // TODO: pull in the expect below after removing the Holo signing hack again @@ -229,7 +229,7 @@ fn reduce_commit_entry( let result = create_new_chain_header( &entry, agent_state, - root_state, + &StateWrapper::from(root_state.clone()), &maybe_link_update_delete, provenances, ) @@ -421,7 +421,13 @@ pub mod tests { let state = State::new_with_agent(context.clone(), agent_state.clone()); let header = - create_new_chain_header(&test_entry(), &agent_state, &state, &None, &vec![]).unwrap(); + create_new_chain_header( + &test_entry(), + &agent_state, + &StateWrapper::from(state), + &None, + &vec![] + ).unwrap(); let agent_id = context.agent_id.clone(); assert_eq!( header, diff --git a/core/src/network/handler/lists.rs b/core/src/network/handler/lists.rs index b5cb6c3c1b..972a24e837 100644 --- a/core/src/network/handler/lists.rs +++ b/core/src/network/handler/lists.rs @@ -13,6 +13,8 @@ use holochain_persistence_api::cas::content::{Address, AddressableContent}; use lib3h_protocol::data_types::{EntryListData, GetListData}; use snowflake::ProcessUniqueId; use std::{collections::HashMap, sync::Arc, thread}; +use crate::network::entry_aspect::EntryAspect; +use crate::agent::state::create_new_chain_header; pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc) { thread::Builder::new() @@ -23,35 +25,32 @@ pub fn handle_get_authoring_list(get_list_data: GetListData, context: Arc { - address_map.insert( - entry.clone(), - aspects, - ); - }, - Err(_err) => log_debug!(context, - "handler/get_authoring_list: Error getting entry aspects of authoring list for entry with address: {}", - entry - ), - }; + let content_aspect = get_content_aspect(&entry, context.clone()) + .expect("Must be able to get content aspect of entry that is in our source chain"); + address_map.insert( + entry.clone(), + vec![content_aspect.address()] + ); } // chain header entries also should be communicated on the authoring list // In future make this depend if header publishing is enabled + let state = context.state() + .expect("There must be a state in context when we are responding to a HandleGetAuthoringEntryList"); for chain_header_entry in get_all_chain_header_entries(context.clone()) { - match get_all_aspect_addresses(&chain_header_entry, context.clone()) { - Ok(aspects) => { - address_map.insert( - chain_header_entry.clone(), - aspects, - ); - }, - Err(_err) => log_debug!(context, - "handler/get_authoring_list: Error getting entry aspects of authoring list for chain header with address: {}", - chain_header_entry) - , - }; + let address = chain_header_entry.address(); + let header_entry_header = create_new_chain_header( + &chain_header_entry, + &state.agent(), + &*state, + &None, + &Vec::new(), + ).expect("Must be able to create dummy header header when responding to HandleGetAuthoringEntryList"); + let content_aspect = EntryAspect::Content( + chain_header_entry, + header_entry_header, + ); + address_map.insert(address, vec![content_aspect.address()]); } let action = Action::RespondAuthoringList(EntryListData { @@ -73,10 +72,10 @@ fn get_all_public_chain_entries(context: Arc) -> Vec
{ .collect() } -fn get_all_chain_header_entries(context: Arc) -> Vec
{ +fn get_all_chain_header_entries(context: Arc) -> Vec { let chain = context.state().unwrap().agent().iter_chain(); chain - .map(|chain_header| Entry::ChainHeader(chain_header).address()) + .map(|chain_header| Entry::ChainHeader(chain_header)) .collect() } @@ -131,7 +130,7 @@ pub mod tests { use holochain_core_types::{ entry::{Entry, test_entry_with_value}, }; - use holochain_persistence_api::cas::content::{AddressableContent, Address}; + use holochain_persistence_api::cas::content::AddressableContent; use std::{thread, time}; #[test] @@ -153,11 +152,11 @@ pub mod tests { thread::sleep(time::Duration::from_millis(500)); let chain = context.state().unwrap().agent().iter_chain(); - let header_entry_addrs: Vec
= chain.map(|header| Entry::ChainHeader(header).address()).collect(); + let header_entries: Vec = chain.map(|header| Entry::ChainHeader(header)).collect(); assert_eq!( get_all_chain_header_entries(context), - header_entry_addrs, + header_entries, ) } @@ -181,7 +180,7 @@ pub mod tests { thread::sleep(time::Duration::from_millis(500)); assert!(get_all_chain_header_entries(context.clone()).iter().all(|chain_header| { - get_all_aspect_addresses(&chain_header, context.clone()).is_ok() + get_all_aspect_addresses(&chain_header.address(), context.clone()).is_ok() })); } diff --git a/core/src/network/handler/mod.rs b/core/src/network/handler/mod.rs index e9c7fb6068..c85e299070 100644 --- a/core/src/network/handler/mod.rs +++ b/core/src/network/handler/mod.rs @@ -18,7 +18,6 @@ use crate::{ store::*, }, }, - nucleus, workflows::get_entry_result::get_entry_with_meta_workflow, }; use boolinator::*; @@ -31,6 +30,8 @@ use lib3h_protocol::{ protocol_server::Lib3hServerProtocol, }; use std::{convert::TryFrom, sync::Arc}; +use crate::nucleus::actions::get_entry::get_entry_from_cas; +use crate::network::entry_with_header::EntryWithHeader; // FIXME: Temporary hack to ignore messages incorrectly sent to us by the networking // module that aren't really meant for us @@ -252,35 +253,65 @@ fn get_content_aspect( entry_address: &Address, context: Arc, ) -> Result { - let entry_with_meta = - nucleus::actions::get_entry::get_entry_with_meta(&context, entry_address.clone())? - .ok_or(HolochainError::EntryNotFoundLocally)?; + let state = context.state() + .ok_or(HolochainError::InitializationFailed( + String::from("In get_content_aspect: no state found") + ))?; - let _ = entry_with_meta + // Optimistically look for entry in chain... + let maybe_chain_header = state.agent() + .iter_chain() + .find(|ref chain_header| chain_header.entry_address() == entry_address); + + // If we have found a header for the requested entry in the chain... + let maybe_entry_with_header = if let Some(header) = maybe_chain_header { + // ... we can just get the content from the chain CAS + Some(EntryWithHeader { + entry: get_entry_from_cas(&state.agent().chain_store().content_storage(), header.entry_address())? + .expect("Could not find entry in chain CAS, but header is chain"), + header + }) + } else { + // ... but if we didn't author that entry, let's see if we have it in the DHT cas: + if let Some(entry) = get_entry_from_cas(&state.dht().content_storage(), entry_address)? { + // If we have it in the DHT cas that's good, + // but then we have to get the header like this: + let headers = context + .state() + .expect("Could not get state for handle_fetch_entry") + .get_headers(entry_address.clone()) + .map_err(|error| { + let err_message = format!( + "net/fetch/get_content_aspect: Error trying to get headers {:?}", + error + ); + log_error!(context, "{}", err_message.clone()); + HolochainError::ErrorGeneric(err_message) + })?; + if headers.len() > 0 { + // TODO: this is just taking the first header.. + // We should actually transform all headers into EntryAspect::Headers and just the first one + // into an EntryAspect content (What about ordering? Using the headers timestamp?) + Some(EntryWithHeader{entry, header: headers[0].clone()}) + } else { + None + } + } else { + None + } + }; + + let entry_with_header = maybe_entry_with_header.ok_or(HolochainError::EntryNotFoundLocally)?; + + let _ = entry_with_header .entry .entry_type() .can_publish(&context) .ok_or(HolochainError::EntryIsPrivate)?; - let headers = context - .state() - .expect("Could not get state for handle_fetch_entry") - .get_headers(entry_address.clone()) - .map_err(|error| { - let err_message = format!( - "net/fetch/get_content_aspect: Error trying to get headers {:?}", - error - ); - log_error!(context, "{}", err_message.clone()); - HolochainError::ErrorGeneric(err_message) - })?; - - // TODO: this is just taking the first header.. - // We should actually transform all headers into EntryAspect::Headers and just the first one - // into an EntryAspect content (What about ordering? Using the headers timestamp?) Ok(EntryAspect::Content( - entry_with_meta.entry, - headers[0].clone(), + entry_with_header.entry, + entry_with_header.header, )) } diff --git a/core/src/network/reducers/publish_header_entry.rs b/core/src/network/reducers/publish_header_entry.rs index 6a00b4cf6e..8aef98f858 100644 --- a/core/src/network/reducers/publish_header_entry.rs +++ b/core/src/network/reducers/publish_header_entry.rs @@ -21,6 +21,7 @@ use lib3h_protocol::{ }; use holochain_persistence_api::cas::content::{Address, AddressableContent}; +use crate::state::StateWrapper; /// Send to network a request to publish a header entry alone @@ -34,7 +35,7 @@ fn publish_header( let header_entry_header = create_new_chain_header( &header_entry, &root_state.agent(), - root_state, + &StateWrapper::from(root_state.clone()), &None, &Vec::new(), )?; diff --git a/core/src/nucleus/actions/build_validation_package.rs b/core/src/nucleus/actions/build_validation_package.rs index 7418eaa1db..7054d2dd27 100644 --- a/core/src/nucleus/actions/build_validation_package.rs +++ b/core/src/nucleus/actions/build_validation_package.rs @@ -19,6 +19,7 @@ use holochain_core_types::{ }; use snowflake; use std::{convert::TryInto, pin::Pin, sync::Arc, thread, vec::Vec}; +use crate::state::StateWrapper; pub async fn build_validation_package<'a>( entry: &'a Entry, @@ -94,7 +95,7 @@ pub async fn build_validation_package<'a>( agent::state::create_new_chain_header( &entry, &context.state()?.agent(), - &state, + &StateWrapper::from(state), &None, provenances, )? From ff89573f490f59dfa5865e1faa897453d4e2ca5a Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Sat, 17 Aug 2019 10:03:37 -0700 Subject: [PATCH 14/18] Update CHANGELOG-UNRELEASED.md Co-Authored-By: Nicolas Luck --- CHANGELOG-UNRELEASED.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG-UNRELEASED.md b/CHANGELOG-UNRELEASED.md index 20527e89f7..707b8a4735 100644 --- a/CHANGELOG-UNRELEASED.md +++ b/CHANGELOG-UNRELEASED.md @@ -7,7 +7,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added * Ability to provide passphrase to lock/unlock keystores via IPC unix domain socket added. [#1646](https://github.com/holochain/holochain-rust/pull/1646) -* New logging implementation added as a subcrate : a fast logger with a filtering capability using regex expressions, please so [logging](logging) for more details. * Adds publishing of headers again after rollback. Header publishing is now its own action rather than part of the `Publish` action that plays nicely with the testing framework. It also adds header entries to the author list so they are gossiped properly. [#1640](https://github.com/holochain/holochain-rust/pull/1640). * Documentation for our links ecosystem [#1628](https://github.com/holochain/holochain-rust/pull/1628) From faeafe980982bf66c7058ec77864b10097f7b2b2 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Fri, 6 Sep 2019 11:15:59 -0600 Subject: [PATCH 15/18] adds agent publish to initialize --- core/src/workflows/application.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/workflows/application.rs b/core/src/workflows/application.rs index ee3c9ca8a3..676301b278 100644 --- a/core/src/workflows/application.rs +++ b/core/src/workflows/application.rs @@ -3,6 +3,7 @@ use crate::{ instance::Instance, network::actions::{ publish_header_entry::publish_header_entry, + publish::publish, initialize_network::initialize_network, }, nucleus::actions::{call_init::call_init, initialize::initialize_chain}, @@ -28,7 +29,7 @@ pub async fn initialize( Ok(_) => false, Err(err) => { log_debug!(context, - "dna/initialize: Couldn't get DNA and agent from chain: {:?}", + "dna/initialize: No DNA and agent in chain so assuming uninitialized: {:?}", err ); await!(initialize_chain(dna.clone(), &instance_context))?; @@ -41,7 +42,9 @@ pub async fn initialize( await!(initialize_network(&instance_context))?; if first_initialization { - // 4. (first initialization only) Publish the headers of the agent and DNA entries. + // 4. (first initialization only) Publish the agent entry and headers of the agent and DNA entries. + await!(publish(context.agent_id.address(), &context))?; + let dna_entry = Entry::Dna(Box::new(dna.clone())); await!(publish_header_entry(dna_entry.address(), &context))?; let agent_id_entry = Entry::AgentId(context.agent_id.clone()); From 2914e08cfe14af9fc687a8a54bc9d1e459f01b03 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 25 Sep 2019 09:06:14 +1000 Subject: [PATCH 16/18] adds further testing on link deletion but it turns out its just a flaky fail --- Cargo.lock | 5 +++++ app_spec/test/files/links.js | 14 +++++++------- cli/test.key | 1 - 3 files changed, 12 insertions(+), 8 deletions(-) delete mode 100644 cli/test.key diff --git a/Cargo.lock b/Cargo.lock index 884437ed6e..3765f872a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2894,16 +2894,21 @@ version = "0.0.30-alpha6" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hdk 0.0.30-alpha6", "holochain_conductor_api 0.0.30-alpha6", "holochain_core 0.0.30-alpha6", "holochain_core_types 0.0.30-alpha6", "holochain_dpki 0.0.30-alpha6", "holochain_json_api 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "holochain_json_derive 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "holochain_net 0.0.30-alpha6", "holochain_persistence_api 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "holochain_wasm_utils 0.0.30-alpha6", "jsonrpc-ws-server 10.0.1 (git+https://github.com/holochain/jsonrpc?branch=broadcaster-getter)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lib3h_sodium 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/app_spec/test/files/links.js b/app_spec/test/files/links.js index c7a455bc10..7fdbba0bfb 100644 --- a/app_spec/test/files/links.js +++ b/app_spec/test/files/links.js @@ -43,23 +43,23 @@ module.exports = scenario => { //bob expects zero links t.ok(bob_agent_posts_expect_empty.Ok) - t.equal(bob_agent_posts_expect_empty.Ok.links.length, 0); - //alice expects zero alice + t.equal(bob_agent_posts_expect_empty.Ok.links.length, 0); // #!# fails with expected: 0 actual: 2 + //alice expects zero links t.ok(alice_agent_posts_expect_empty.Ok) t.equal(alice_agent_posts_expect_empty.Ok.links.length, 0); - //different chain hash up to this point so we should be able to create a link with the same data await alice.app.callSync("simple", "create_link",{ "base":alice.app.agentId, "target": "Posty" }) - //get alice posts - const alice_posts_not_empty = await bob.app.call("simple", "get_my_links",{ "base": alice.app.agentId,"status_request" : "Live" }) + //get posts as Alice and as Bob + const alice_posts_not_empty = await alice.app.call("simple", "get_my_links",{ "base": alice.app.agentId,"status_request" : "Live" }) + const bob_posts_not_empty = await bob.app.call("simple", "get_my_links",{ "base": alice.app.agentId,"status_request" : "Live" }) //expect 1 post t.ok(alice_posts_not_empty.Ok) t.equal(alice_posts_not_empty.Ok.links.length, 1); - - + t.ok(bob_posts_not_empty.Ok) + t.equal(bob_posts_not_empty.Ok.links.length, 1); //#!# fails with expected: 1 actual: 2 }) diff --git a/cli/test.key b/cli/test.key deleted file mode 100644 index 1417350939..0000000000 --- a/cli/test.key +++ /dev/null @@ -1 +0,0 @@ -{"passphrase_check":"eyJzYWx0IjpbMTMzLDk1LDYzLDQzLDg2LDE4LDE3Nyw1LDYzLDI0LDE2Myw3Miw1NSw5Myw1OCwxNTddLCJub25jZSI6WzE1OCwxNTgsNiwxMjgsMjI5LDk2LDg4LDk3LDEzMSwxMzAsMjEwLDc3LDE2MSw3NSwyNTEsMTcxLDE3MiwyMCwyMzMsMjQyLDE5OCw4MSwxMDUsMTI5XSwiY2lwaGVyIjpbOTcsMTM1LDEzLDIxNiwyMDAsNTksNiwxMTksMTgxLDI1Miw0MiwxMjMsMjI2LDIzNiwyMzcsMTg4LDEwOSwyMjAsMzMsMTc1LDI1NSwxNTEsMTQ0LDIyOSwyMjUsMTM2LDIxMiw1OSwxNjMsMTg3LDIwNCw2MSwyMzgsMjE4LDE3OSwxNTksMTY0LDQ0LDk2LDE1MCwxNDcsMTQzLDI4LDI1MSwyMjQsMzgsMzgsMTkxLDQ2LDEzNCwxOCwxNjAsNDUsMTY0LDg1LDE0Nl19","secrets":{"primary_keybundle:enc_key":{"blob_type":"EncryptingKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbMTc4LDEyNiw1OCwxMTQsNzQsMTU3LDEzOSwyMzQsMTg3LDI0OSwyNSwzOSwxNDAsMTAwLDEwLDEyNF0sIm5vbmNlIjpbMjUsMTkwLDE1NSwxNDEsMjU0LDExNywxMjksMTM5LDEwNCwzOSwxNDAsMTQzLDEwMSwxOTMsMTY2LDIwMyw1LDIwNiwxOTAsMTU0LDE3MiwxODQsMjA1LDEwMV0sImNpcGhlciI6WzE2MiwxLDIyNCwxMjUsMjIxLDE1NSwyMyw5NSwzNCwxMDAsMTI2LDIzMCw4MSwyMzksMTYzLDE2OCwxNjcsMTI4LDMwLDIxMCwxMTYsNTYsMTgzLDEyOSw0MCwyMzQsMjAzLDE1NCwxMzQsODcsMTI5LDE2OCw0MiwyMywxOTMsODIsMTIsMjA3LDM2LDE2MSwxODYsOTcsMjI3LDIwMCwyMjMsMTY1LDQ1LDE3MSwyMTYsMTA5LDIzOSwxNzIsMTQ3LDM0LDE1MSw3MywyMjksMjE5LDE2OCwxNDYsMTY3LDI1NSw3OCwxNDgsNjMsMzYsMjgsMjMxLDQ1LDU0LDQzLDEzOSwxOTYsMTc1LDE3NCwyLDQxLDIxOSwxMjUsMjQ3LDIwMyw2MSwxNDQsMjE4LDEzNCw2NywyNiwyMzNdfQ=="},"primary_keybundle:sign_key":{"blob_type":"SigningKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbMjUwLDM4LDYzLDAsMjQ2LDE5LDE5OSwxNTQsMTcxLDEyNiwxMDIsMTYzLDkwLDE4OCwxNzQsMjA4XSwibm9uY2UiOlsyMzAsMzcsMjI0LDE4MCwxNDcsMjQzLDEzMCwxNjUsMjUwLDgyLDQsMzYsMTY3LDgzLDE4NSwyOCwxMzEsMTkwLDEsMjEyLDk1LDEzNSwyLDI0MV0sImNpcGhlciI6WzExNywyNCwxNzUsMjEzLDEsMTcwLDM2LDIzLDIyMCw0OSwxODMsMjMyLDEwNywxMzAsNzUsOTcsMTk1LDY1LDIsNTMsMTkyLDk3LDE1Myw5NSwxMzQsMTk4LDE0MywxMjgsOTYsMTYwLDc3LDEzMCw4NiwyMDcsMjIsNzYsMTM1LDE4MywxNTYsMTM3LDEyNiwxMzgsNjcsMTIzLDM2LDQsMjA0LDIzMCwxNjYsNzgsMzEsMjE3LDE3NywxNTIsMjUyLDkzLDIzMiwyMTgsMTM5LDE1OCw1MCwxMjUsMTg4LDE5MiwxODQsMTYxLDMzLDg5LDk4LDcyLDE3NiwxMDgsNjEsMjcsMTAsMTcwLDg4LDE3MSwyMzAsMjI3LDExOSwxNDIsNTYsMjI1LDQ4LDgwLDYsODEsMTEsMTUyLDIxOSwyMzQsMjUyLDI1Miw0NiwxOCwxMTQsMjYsMTExLDkzLDE4NywxMTIsMjAxLDExOSwxODcsMTg3LDIwNiwxMTcsNjIsMTM4LDExLDYsMTMzLDE3MSw1Miw2MCwyNDAsMTEzLDU2LDU1XX0="},"root_seed":{"blob_type":"Seed","seed_type":"OneShot","hint":"","data":"eyJzYWx0IjpbMjgsMzMsMzQsMjUzLDI0LDkyLDE4LDE4Miw0OCwyMjgsMjM3LDE0NywyNDMsNjEsMjE5LDEwMl0sIm5vbmNlIjpbNDksOTYsNDMsMTEzLDIwMywxNzcsMTY3LDg2LDE3MCwyMzQsNzcsNzUsMTI4LDQyLDM1LDE2NCw0MCwxNjMsMjU0LDQyLDMxLDEwOSwxMDUsMTY3XSwiY2lwaGVyIjpbMTg4LDI1MCw3NSw4NiwyMDUsMTExLDE2OCw4MSwyMTQsNTgsOTMsMTcwLDUzLDExLDIyMCwzOSwyMDYsNCwxMjcsMzMsODMsNjEsMjIzLDE4Niw0MSwxMTIsMjA3LDE4MiwyMTQsMTQsMjMxLDExNCwyMDAsMjIyLDIzOCwxNDgsMTg4LDczLDQ1LDQxLDE4Myw2OSwxODEsNDIsMjA1LDE5NSwxNTQsMTUwXX0="}}} \ No newline at end of file From 6daa9e71559884a23d530cf98cfa5fa15e440282 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 25 Sep 2019 09:18:51 +1000 Subject: [PATCH 17/18] fmt --- core_types/src/lib.rs | 4 ++-- core_types/src/network/entry_aspect.rs | 4 ++-- core_types/src/network/mod.rs | 2 +- core_types/src/network/query.rs | 4 +--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core_types/src/lib.rs b/core_types/src/lib.rs index 3842e05765..c97cbd6ed6 100644 --- a/core_types/src/lib.rs +++ b/core_types/src/lib.rs @@ -29,8 +29,8 @@ extern crate regex; #[macro_use] extern crate maplit; extern crate hcid; -extern crate wasmi; extern crate lib3h_protocol; +extern crate wasmi; pub mod chain_header; pub mod crud_status; pub mod eav; @@ -44,11 +44,11 @@ pub mod chain_migrate; pub mod dna; pub mod hdk_version; pub mod link; +pub mod network; pub mod signature; pub mod time; pub mod ugly; pub mod validation; -pub mod network; pub const GIT_HASH: &str = env!( "GIT_HASH", diff --git a/core_types/src/network/entry_aspect.rs b/core_types/src/network/entry_aspect.rs index efbbe73139..84275fd95a 100644 --- a/core_types/src/network/entry_aspect.rs +++ b/core_types/src/network/entry_aspect.rs @@ -1,5 +1,5 @@ -use chrono::{offset::FixedOffset, DateTime}; use crate::{chain_header::ChainHeader, entry::Entry, link::link_data::LinkData}; +use chrono::{offset::FixedOffset, DateTime}; use holochain_json_api::{error::JsonError, json::JsonString}; use holochain_persistence_api::cas::content::{Address, AddressableContent, Content}; use lib3h_protocol::data_types::EntryAspectData; @@ -163,8 +163,8 @@ impl fmt::Debug for EntryAspect { #[cfg(test)] pub mod tests { use super::*; - use chrono::{offset::FixedOffset, DateTime}; use crate::chain_header::test_chain_header; + use chrono::{offset::FixedOffset, DateTime}; #[test] fn can_convert_into_entry_aspect_data() { diff --git a/core_types/src/network/mod.rs b/core_types/src/network/mod.rs index 074287ab93..b77d7895e9 100644 --- a/core_types/src/network/mod.rs +++ b/core_types/src/network/mod.rs @@ -1,2 +1,2 @@ -pub mod query; pub mod entry_aspect; +pub mod query; diff --git a/core_types/src/network/query.rs b/core_types/src/network/query.rs index 64d0c9137b..bba1b4dc73 100644 --- a/core_types/src/network/query.rs +++ b/core_types/src/network/query.rs @@ -1,6 +1,4 @@ -use crate::{ - chain_header::ChainHeader, crud_status::CrudStatus, entry::EntryWithMetaAndHeader, -}; +use crate::{chain_header::ChainHeader, crud_status::CrudStatus, entry::EntryWithMetaAndHeader}; use holochain_json_api::{error::JsonError, json::JsonString}; use holochain_persistence_api::{cas::content::Address, eav::Value}; From ed0b40419345e96965b8b2f1a81d8e12743cec3b Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Wed, 25 Sep 2019 11:35:22 +1000 Subject: [PATCH 18/18] clippify --- core/src/network/handler/mod.rs | 8 +++++--- core/src/network/reducers/publish.rs | 2 +- core/src/network/reducers/publish_header_entry.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/network/handler/mod.rs b/core/src/network/handler/mod.rs index db06a37a86..98caffbd39 100644 --- a/core/src/network/handler/mod.rs +++ b/core/src/network/handler/mod.rs @@ -254,9 +254,11 @@ fn get_content_aspect( context: Arc, ) -> Result { let state = context.state() - .ok_or(HolochainError::InitializationFailed( - String::from("In get_content_aspect: no state found") - ))?; + .ok_or_else(|| { + HolochainError::InitializationFailed( + String::from("In get_content_aspect: no state found") + ) + })?; // Optimistically look for entry in chain... let maybe_chain_header = state.agent() diff --git a/core/src/network/reducers/publish.rs b/core/src/network/reducers/publish.rs index 772e76a25c..f1c5f4a7ad 100644 --- a/core/src/network/reducers/publish.rs +++ b/core/src/network/reducers/publish.rs @@ -157,7 +157,7 @@ fn reduce_publish_inner( } }), _ => Err(HolochainError::NotImplemented( - format!("reduce_publish_inner not implemented for {}", entry_with_header.entry.entry_type()).into(), + format!("reduce_publish_inner not implemented for {}", entry_with_header.entry.entry_type()), )), } } diff --git a/core/src/network/reducers/publish_header_entry.rs b/core/src/network/reducers/publish_header_entry.rs index 8aef98f858..97132d763c 100644 --- a/core/src/network/reducers/publish_header_entry.rs +++ b/core/src/network/reducers/publish_header_entry.rs @@ -42,7 +42,7 @@ fn publish_header( send( network_state, Lib3hClientProtocol::PublishEntry(ProvidedEntryData { - space_address: network_state.dna_address.clone().unwrap().into(), + space_address: network_state.dna_address.clone().unwrap(), provider_agent_id: network_state.agent_id.clone().unwrap().into(), entry: EntryData { entry_address: header_entry.address().clone(),