use crate::{
agent::actions::commit::commit_entry,
context::Context,
entry::CanPublish,
network::actions::{publish::publish, publish_header_entry::publish_header_entry},
nucleus::{
actions::build_validation_package::build_validation_package,
validation::{validate_entry, ValidationContext},
},
};
use holochain_core_types::{
entry::Entry,
error::HolochainError,
signature::Provenance,
validation::{EntryLifecycle, ValidationData},
};
use holochain_persistence_api::cas::content::{Address, AddressableContent};
use holochain_wasm_utils::api_serialization::commit_entry::CommitEntryResult;
use crate::wasm_engine::callback::links_utils::get_link_entries;
use std::{sync::Arc, vec::Vec};
#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CORE)]
#[allow(clippy::ptr_arg)]
pub async fn author_entry<'a>(
entry: &'a Entry,
maybe_link_update_delete: Option<Address>,
context: &'a Arc<Context>,
provenances: &'a Vec<Provenance>,
) -> Result<CommitEntryResult, HolochainError> {
let address = entry.address();
log_debug!(
context,
"workflow/authoring_entry: {} with content: {:?}",
address,
entry
);
if let Entry::LinkAdd(link_data) = entry {
get_link_entries(&link_data.link, context)?;
}
if let Entry::LinkRemove((link_data, _)) = entry {
get_link_entries(&link_data.link, context)?;
}
let validation_package = build_validation_package(&entry, context.clone(), provenances)?;
let validation_data = ValidationData {
package: validation_package,
lifecycle: EntryLifecycle::Chain,
};
log_debug!(
context,
"workflow/authoring_entry/{}: validating...",
address
);
validate_entry(
entry.clone(),
maybe_link_update_delete.clone(),
validation_data,
&context,
ValidationContext::Authoring,
)
.await?;
log_debug!(context, "worflow/authoring_entry {}: is valid!", address);
log_debug!(
context,
"workflow/authoring_entry/{}: committing...",
address
);
let addr = commit_entry(entry.clone(), maybe_link_update_delete, &context).await?;
log_debug!(context, "workflow/authoring_entry/{}: committed", address);
if entry.entry_type().can_publish(context) {
log_debug!(
context,
"workflow/authoring_entry/{}: publishing...",
address
);
publish(entry.address(), &context).await?;
log_debug!(context, "workflow/authoring_entry/{}: published!", address);
} else {
log_debug!(
context,
"workflow/authoring_entry/{}: entry is private, no publishing",
address
);
}
log_debug!(
context,
"debug/workflow/authoring_entry/{}: publishing header...",
address
);
publish_header_entry(entry.address(), &context).await?;
log_debug!(
context,
"debug/workflow/authoring_entry/{}: header published!",
address
);
Ok(CommitEntryResult::new(addr))
}
#[cfg(test)]
pub mod tests {
use crate::{
holochain_wasm_utils::holochain_persistence_api::cas::content::AddressableContent,
nucleus::actions::{
get_entry::get_entry_from_dht,
tests::{instance_by_name, test_dna},
},
workflows::author_entry::author_entry,
};
use holochain_core_types::{
chain_header::ChainHeader,
entry::{test_entry_with_value, Entry},
};
use std::{thread, time};
#[allow(dead_code)]
fn enable_logging_for_test() {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "trace");
}
let _ = env_logger::builder()
.default_format_timestamp(false)
.default_format_module_path(false)
.is_test(true)
.try_init();
}
#[test]
fn test_commit_with_dht_publish() {
enable_logging_for_test();
let mut dna = test_dna();
dna.uuid = "test_commit_with_dht_publish".to_string();
let netname = Some("test_commit_with_dht_publish, the network");
let (_instance1, context1) = instance_by_name("jill", dna.clone(), netname.clone());
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(1000));
let mut entry: Option<Entry> = None;
let mut tries = 0;
while entry.is_none() && tries < 10 {
tries = tries + 1;
{
entry = get_entry_from_dht(&context2, &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!(
entry,
Some(test_entry_with_value("{\"stuff\":\"test entry value\"}"))
);
}
#[test]
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));
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 mut entry: Option<Entry> = 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!(entry, Some(header_entry),);
}
#[test]
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");
let (_instance2, context2) = instance_by_name("jack", dna.clone(), netname);
let (_instance1, context1) = instance_by_name("jill", dna.clone(), netname);
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));
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));
let state = &context1.state().unwrap();
let jill_headers: Vec<ChainHeader> = state.agent().iter_chain().collect();
let header = jill_headers
.first()
.expect("Must be at least one header in chain");
let mut jack_headers: Vec<ChainHeader> = Vec::new();
let mut next_header_addr = header.address();
loop {
let mut entry: Option<Entry> = 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; }
} else {
panic!(format!(
"Could not retrieve header at address: {}",
next_header_addr
))
}
}
assert_eq!(jack_headers.len(), 4,);
assert_eq!(jack_headers, jill_headers,);
}
}