pub mod error;
pub mod guest_callback;
pub mod host_fn;
pub mod real_ribosome;
mod check_clone_access;
use self::guest_callback::{
entry_defs::EntryDefsInvocation, genesis_self_check::GenesisSelfCheckResult,
};
use self::{
error::RibosomeError,
guest_callback::genesis_self_check::{GenesisSelfCheckHostAccess, GenesisSelfCheckInvocation},
};
use crate::conductor::api::CellConductorHandle;
use crate::conductor::api::CellConductorReadHandle;
use crate::conductor::api::ZomeCallParamsSigned;
use crate::conductor::error::ConductorResult;
use crate::core::ribosome::guest_callback::entry_defs::EntryDefsResult;
use crate::core::ribosome::guest_callback::genesis_self_check::v1::GenesisSelfCheckHostAccessV1;
use crate::core::ribosome::guest_callback::genesis_self_check::v2::GenesisSelfCheckHostAccessV2;
use crate::core::ribosome::guest_callback::init::InitInvocation;
use crate::core::ribosome::guest_callback::init::InitResult;
use crate::core::ribosome::guest_callback::post_commit::PostCommitInvocation;
use crate::core::ribosome::guest_callback::validate::ValidateInvocation;
use crate::core::ribosome::guest_callback::validate::ValidateResult;
use crate::core::ribosome::guest_callback::CallStream;
use derive_more::Constructor;
use error::RibosomeResult;
use guest_callback::entry_defs::EntryDefsHostAccess;
use guest_callback::init::InitHostAccess;
use guest_callback::post_commit::PostCommitHostAccess;
use guest_callback::validate::ValidateHostAccess;
use holo_hash::AgentPubKey;
use holochain_keystore::MetaLairClient;
use holochain_nonce::*;
use holochain_p2p::DynHolochainP2pDna;
use holochain_serialized_bytes::prelude::*;
use holochain_state::host_fn_workspace::HostFnWorkspace;
use holochain_state::host_fn_workspace::HostFnWorkspaceRead;
use holochain_state::nonce::WitnessNonceResult;
use holochain_types::prelude::*;
use holochain_types::zome_types::GlobalZomeTypes;
use holochain_zome_types::block::BlockTargetId;
use mockall::automock;
use must_future::MustBoxFuture;
use std::iter::Iterator;
use std::sync::Arc;
use tokio::sync::broadcast;
use wasmer::RuntimeError;
#[derive(Clone)]
pub struct CallContext {
pub(crate) zome: Zome,
pub(crate) function_name: FunctionName,
pub(crate) auth: InvocationAuth,
pub(crate) host_context: HostContext,
}
impl CallContext {
pub fn new(
zome: Zome,
function_name: FunctionName,
host_context: HostContext,
auth: InvocationAuth,
) -> Self {
Self {
zome,
function_name,
host_context,
auth,
}
}
pub fn zome(&self) -> &Zome {
&self.zome
}
pub fn function_name(&self) -> &FunctionName {
&self.function_name
}
pub fn host_context(&self) -> HostContext {
self.host_context.clone()
}
pub fn auth(&self) -> InvocationAuth {
self.auth.clone()
}
pub fn switch_host_context(
&self,
transform: impl Fn(&HostContext) -> Result<HostContext, RuntimeError>,
) -> Result<CallContext, RuntimeError> {
Ok(Self {
zome: self.zome.clone(),
function_name: self.function_name.clone(),
host_context: transform(&self.host_context)?,
auth: self.auth.clone(),
})
}
}
#[derive(Clone, Debug)]
pub enum HostContext {
EntryDefs(EntryDefsHostAccess),
GenesisSelfCheckV1(GenesisSelfCheckHostAccessV1),
GenesisSelfCheckV2(GenesisSelfCheckHostAccessV2),
Init(InitHostAccess),
PostCommit(PostCommitHostAccess),
Validate(ValidateHostAccess),
ZomeCall(ZomeCallHostAccess),
}
impl From<&HostContext> for HostFnAccess {
fn from(host_access: &HostContext) -> Self {
match host_access {
HostContext::ZomeCall(access) => access.into(),
HostContext::GenesisSelfCheckV1(access) => access.into(),
HostContext::GenesisSelfCheckV2(access) => access.into(),
HostContext::Validate(access) => access.into(),
HostContext::Init(access) => access.into(),
HostContext::EntryDefs(access) => access.into(),
HostContext::PostCommit(access) => access.into(),
}
}
}
impl HostContext {
pub fn workspace(&self) -> HostFnWorkspaceRead {
self.maybe_workspace().expect(
"Gave access to a host function that uses the workspace without providing a workspace",
)
}
pub fn maybe_workspace(&self) -> Option<HostFnWorkspaceRead> {
match self.clone() {
Self::ZomeCall(ZomeCallHostAccess { workspace, .. })
| Self::Init(InitHostAccess { workspace, .. })
| Self::PostCommit(PostCommitHostAccess { workspace, .. }) => Some(workspace.into()),
Self::Validate(ValidateHostAccess { workspace, .. }) => Some(workspace),
_ => None,
}
}
pub fn workspace_write(&self) -> &HostFnWorkspace {
match self {
Self::ZomeCall(ZomeCallHostAccess { workspace, .. })
| Self::Init(InitHostAccess { workspace, .. })
| Self::PostCommit(PostCommitHostAccess { workspace, .. }) => workspace,
_ => panic!(
"Gave access to a host function that writes to the workspace without providing a workspace"
),
}
}
pub fn keystore(&self) -> &MetaLairClient {
match self {
Self::ZomeCall(ZomeCallHostAccess { keystore, .. })
| Self::Init(InitHostAccess { keystore, .. })
| Self::PostCommit(PostCommitHostAccess { keystore, .. }) => keystore,
_ => panic!(
"Gave access to a host function that uses the keystore without providing a keystore"
),
}
}
pub fn network(&self) -> DynHolochainP2pDna {
match self {
Self::ZomeCall(ZomeCallHostAccess { network, .. })
| Self::Init(InitHostAccess { network, .. })
| Self::PostCommit(PostCommitHostAccess { network, .. }) => network.clone(),
Self::Validate(ValidateHostAccess { network, .. }) => network.clone(),
_ => panic!(
"Gave access to a host function that uses the network without providing a network"
),
}
}
pub fn signal_tx(&mut self) -> &mut broadcast::Sender<Signal> {
match self {
Self::ZomeCall(ZomeCallHostAccess { signal_tx, .. })
| Self::Init(InitHostAccess { signal_tx, .. })
| Self::PostCommit(PostCommitHostAccess { signal_tx, .. })
=> signal_tx,
_ => panic!(
"Gave access to a host function that uses the signal broadcaster without providing one"
),
}
}
pub fn call_zome_handle(&self) -> &CellConductorReadHandle {
match self {
Self::ZomeCall(ZomeCallHostAccess {
call_zome_handle, ..
})
| Self::Init(InitHostAccess { call_zome_handle, .. })
| Self::PostCommit(PostCommitHostAccess { call_zome_handle: Some(call_zome_handle), .. })
=> call_zome_handle,
_ => panic!(
"Gave access to a host function that uses the call zome handle without providing a call zome handle"
),
}
}
}
#[derive(Clone, Debug)]
pub struct FnComponents(pub Vec<String>);
impl Iterator for FnComponents {
type Item = String;
fn next(&mut self) -> Option<String> {
match self.0.len() {
0 => None,
_ => {
let ret = self.0.join("_");
self.0.pop();
Some(ret)
}
}
}
}
impl From<Vec<String>> for FnComponents {
fn from(vs: Vec<String>) -> Self {
Self(vs)
}
}
impl FnComponents {
pub fn into_inner(self) -> Vec<String> {
self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ZomesToInvoke {
AllIntegrity,
All,
One(Zome),
OneIntegrity(IntegrityZome),
OneCoordinator(CoordinatorZome),
}
impl ZomesToInvoke {
pub fn one(zome: Zome) -> Self {
Self::One(zome)
}
pub fn one_integrity(zome: IntegrityZome) -> Self {
Self::OneIntegrity(zome)
}
pub fn one_coordinator(zome: CoordinatorZome) -> Self {
Self::OneCoordinator(zome)
}
}
#[derive(Clone, Debug)]
pub enum InvocationAuth {
LocalCallback,
Cap(AgentPubKey, Option<CapSecret>),
}
impl InvocationAuth {
pub fn new(agent_pubkey: AgentPubKey, cap_secret: Option<CapSecret>) -> Self {
Self::Cap(agent_pubkey, cap_secret)
}
}
pub trait Invocation: Clone + Send + Sync {
fn zomes(&self) -> ZomesToInvoke;
fn fn_components(&self) -> FnComponents;
fn host_input(self) -> Result<ExternIO, SerializedBytesError>;
fn auth(&self) -> InvocationAuth;
}
impl ZomeCallInvocation {
pub async fn verify_grant(
&self,
host_access: &ZomeCallHostAccess,
) -> RibosomeResult<ZomeCallAuthorization> {
let check_function = (self.zome.zome_name().clone(), self.fn_name.clone());
let check_agent = self.provenance.clone();
let check_secret = self.cap_secret;
let maybe_grant: Option<CapGrant> = host_access
.workspace
.source_chain()
.as_ref()
.expect("Must have source chain to make zome calls")
.valid_cap_grant(check_function, check_agent, check_secret)
.await?;
Ok(if maybe_grant.is_some() {
ZomeCallAuthorization::Authorized
} else {
ZomeCallAuthorization::BadCapGrant
})
}
pub async fn verify_nonce(
&self,
host_access: &ZomeCallHostAccess,
) -> RibosomeResult<ZomeCallAuthorization> {
Ok(
match host_access
.call_zome_handle
.witness_nonce_from_calling_agent(
self.provenance.clone(),
self.nonce,
self.expires_at,
)
.await
.map_err(Box::new)?
{
WitnessNonceResult::Fresh => ZomeCallAuthorization::Authorized,
nonce_result => ZomeCallAuthorization::BadNonce(format!("{nonce_result:?}")),
},
)
}
pub async fn verify_blocked_provenance(
&self,
host_access: &ZomeCallHostAccess,
) -> ConductorResult<ZomeCallAuthorization> {
if host_access
.call_zome_handle
.is_blocked(
BlockTargetId::Cell(CellId::new(
(*self.cell_id.dna_hash()).clone(),
self.provenance.clone(),
)),
Timestamp::now(),
)
.await?
{
Ok(ZomeCallAuthorization::BlockedProvenance)
} else {
Ok(ZomeCallAuthorization::Authorized)
}
}
pub async fn is_authorized(
&self,
host_access: &ZomeCallHostAccess,
) -> ConductorResult<ZomeCallAuthorization> {
Ok(match self.verify_nonce(host_access).await? {
ZomeCallAuthorization::Authorized => match self.verify_grant(host_access).await? {
ZomeCallAuthorization::Authorized => {
self.verify_blocked_provenance(host_access).await?
}
unauthorized => unauthorized,
},
unauthorized => unauthorized,
})
}
}
mockall::mock! {
Invocation {}
impl Invocation for Invocation {
fn zomes(&self) -> ZomesToInvoke;
fn fn_components(&self) -> FnComponents;
fn host_input(self) -> Result<ExternIO, SerializedBytesError>;
fn auth(&self) -> InvocationAuth;
}
impl Clone for Invocation {
fn clone(&self) -> Self;
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ZomeCallInvocation {
pub cell_id: CellId,
pub zome: Zome,
pub cap_secret: Option<CapSecret>,
pub fn_name: FunctionName,
pub payload: ExternIO,
pub provenance: AgentPubKey,
pub nonce: Nonce256Bits,
pub expires_at: Timestamp,
}
impl Invocation for ZomeCallInvocation {
fn zomes(&self) -> ZomesToInvoke {
ZomesToInvoke::One(self.zome.to_owned())
}
fn fn_components(&self) -> FnComponents {
vec![self.fn_name.to_owned().into()].into()
}
fn host_input(self) -> Result<ExternIO, SerializedBytesError> {
Ok(self.payload)
}
fn auth(&self) -> InvocationAuth {
InvocationAuth::Cap(self.provenance.clone(), self.cap_secret)
}
}
impl ZomeCallInvocation {
pub async fn try_from_params(
conductor_api: CellConductorHandle,
params: ZomeCallParams,
) -> RibosomeResult<Self> {
let ZomeCallParams {
cap_secret,
cell_id,
expires_at,
fn_name,
nonce,
payload,
provenance,
zome_name,
} = params;
let zome = conductor_api
.get_zome(&cell_id, &zome_name)
.map_err(|conductor_api_error| RibosomeError::from(Box::new(conductor_api_error)))?;
Ok(Self {
cell_id,
zome,
cap_secret,
fn_name,
payload,
provenance,
nonce,
expires_at,
})
}
}
impl From<ZomeCallInvocation> for ZomeCallParams {
fn from(inv: ZomeCallInvocation) -> Self {
let ZomeCallInvocation {
cell_id,
zome,
fn_name,
cap_secret,
payload,
provenance,
nonce,
expires_at,
} = inv;
Self {
cell_id,
provenance,
zome_name: zome.zome_name().clone(),
fn_name,
cap_secret,
payload,
nonce,
expires_at,
}
}
}
#[derive(Clone, Constructor)]
pub struct ZomeCallHostAccess {
pub workspace: HostFnWorkspace,
pub keystore: MetaLairClient,
pub network: DynHolochainP2pDna,
pub signal_tx: broadcast::Sender<Signal>,
pub call_zome_handle: CellConductorReadHandle,
}
impl std::fmt::Debug for ZomeCallHostAccess {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ZomeCallHostAccess").finish()
}
}
impl From<ZomeCallHostAccess> for HostContext {
fn from(zome_call_host_access: ZomeCallHostAccess) -> Self {
Self::ZomeCall(zome_call_host_access)
}
}
impl From<&ZomeCallHostAccess> for HostFnAccess {
fn from(_: &ZomeCallHostAccess) -> Self {
Self::all()
}
}
#[automock]
#[allow(async_fn_in_trait)]
pub trait RibosomeT: Sized + std::fmt::Debug + Send + Sync {
fn dna_def_hashed(&self) -> &DnaDefHashed;
fn dna_hash(&self) -> &DnaHash;
fn dna_file(&self) -> &DnaFile;
async fn zome_info(&self, zome: Zome) -> RibosomeResult<ZomeInfo>;
fn zomes_to_invoke(&self, zomes_to_invoke: ZomesToInvoke) -> Vec<Zome> {
match zomes_to_invoke {
ZomesToInvoke::AllIntegrity => self
.dna_def_hashed()
.integrity_zomes
.iter()
.map(|(n, d)| (n.clone(), d.clone().erase_type()).into())
.collect(),
ZomesToInvoke::All => self
.dna_def_hashed()
.all_zomes()
.map(|(n, d)| (n.clone(), d.clone()).into())
.collect(),
ZomesToInvoke::One(zome) => vec![zome],
ZomesToInvoke::OneIntegrity(zome) => vec![zome.erase_type()],
ZomesToInvoke::OneCoordinator(zome) => vec![zome.erase_type()],
}
}
fn zome_name_to_id(&self, zome_name: &ZomeName) -> RibosomeResult<ZomeIndex> {
match self
.dna_def_hashed()
.all_zomes()
.position(|(name, _)| name == zome_name)
{
Some(index) => Ok(holochain_zome_types::action::ZomeIndex::from(index as u8)),
None => Err(RibosomeError::ZomeNotExists(zome_name.to_owned())),
}
}
fn get_integrity_zome(&self, zome_index: &ZomeIndex) -> Option<IntegrityZome>;
fn call_stream<I: Invocation + 'static>(
&self,
host_context: HostContext,
invocation: I,
) -> CallStream;
fn maybe_call<I: Invocation + 'static>(
&self,
host_context: HostContext,
invocation: &I,
zome: &Zome,
to_call: &FunctionName,
) -> MustBoxFuture<'static, Result<Option<ExternIO>, RibosomeError>>
where
Self: 'static;
async fn get_const_fn(&self, zome: &Zome, name: &str) -> Result<Option<i32>, RibosomeError>;
fn list_callbacks(&self) {
unimplemented!()
}
fn list_zome_fns(&self) {
unimplemented!()
}
async fn run_genesis_self_check(
&self,
access: GenesisSelfCheckHostAccess,
invocation: GenesisSelfCheckInvocation,
) -> RibosomeResult<GenesisSelfCheckResult>;
async fn run_init(
&self,
access: InitHostAccess,
invocation: InitInvocation,
) -> RibosomeResult<InitResult>;
async fn run_entry_defs(
&self,
access: EntryDefsHostAccess,
invocation: EntryDefsInvocation,
) -> RibosomeResult<EntryDefsResult>;
async fn run_post_commit(
&self,
access: PostCommitHostAccess,
invocation: PostCommitInvocation,
) -> RibosomeResult<()>;
async fn run_validate(
&self,
access: ValidateHostAccess,
invocation: ValidateInvocation,
) -> RibosomeResult<ValidateResult>;
async fn call_zome_function(
&self,
access: ZomeCallHostAccess,
invocation: ZomeCallInvocation,
) -> RibosomeResult<ZomeCallResponse>;
fn zome_types(&self) -> &Arc<GlobalZomeTypes>;
}
pub fn weigh_placeholder() -> EntryRateWeight {
EntryRateWeight::default()
}
#[cfg(test)]
pub mod wasm_test {
use crate::core::ribosome::FnComponents;
use crate::test_utils;
use core::time::Duration;
use hdk::prelude::*;
use holochain_nonce::fresh_nonce;
use holochain_wasm_test_utils::TestWasm;
use holochain_zome_types::zome_io::ZomeCallParams;
pub fn now() -> Duration {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
}
#[tokio::test(flavor = "multi_thread")]
async fn verify_zome_call_test() {
holochain_trace::test_run();
let test_utils::RibosomeTestFixture {
conductor,
alice,
alice_pubkey,
bob_pubkey,
..
} = test_utils::RibosomeTestFixture::new(TestWasm::Capability).await;
let now = Timestamp::now();
let (nonce, expires_at) = fresh_nonce(now).unwrap();
let alice_zome_call_params = ZomeCallParams {
provenance: alice_pubkey.clone(),
cell_id: alice.cell_id().clone(),
zome_name: TestWasm::Capability.coordinator_zome_name(),
fn_name: "needs_cap_claim".into(),
cap_secret: None,
payload: ExternIO::encode(()).unwrap(),
nonce,
expires_at,
};
let mut bob_zome_call_params = alice_zome_call_params.clone();
bob_zome_call_params.provenance = bob_pubkey.clone();
let bob_call_result = conductor.raw_handle().call_zome(bob_zome_call_params).await;
match bob_call_result {
Ok(Ok(ZomeCallResponse::Unauthorized(..))) => { }
_ => panic!("{bob_call_result:?}"),
}
let alice_call_result_0 = conductor
.raw_handle()
.call_zome(alice_zome_call_params.clone())
.await;
match alice_call_result_0 {
Ok(Ok(ZomeCallResponse::Ok(_))) => { }
_ => panic!("{alice_call_result_0:?}"),
}
let alice_call_result_1 = conductor
.raw_handle()
.call_zome(alice_zome_call_params)
.await;
match alice_call_result_1 {
Ok(Ok(ZomeCallResponse::Unauthorized(..))) => { }
_ => panic!("{bob_call_result:?}"),
}
}
#[test]
fn fn_components_iterate() {
let fn_components = FnComponents::from(vec!["foo".into(), "bar".into(), "baz".into()]);
let expected = vec!["foo_bar_baz", "foo_bar", "foo"];
assert_eq!(fn_components.into_iter().collect::<Vec<String>>(), expected,);
}
}