Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build guide: zome functions and lifecycle callbacks #512

Open
wants to merge 34 commits into
base: main
Choose a base branch
from

Conversation

pdaoust
Copy link
Collaborator

@pdaoust pdaoust commented Jan 15, 2025

Two pages here, with some cleanup/extra examples on the CRUD pages now that we've described the call-zome lifecycle and relaxed chain top ordering in detail. Depends on #511 and shouldn't be merged until that one is merged.

(Apologies to reviewers; somehow some fixes that were supposed to get added to another PR got added to this one instead.)

@pdaoust pdaoust requested review from mattyg and c12i January 15, 2025 21:45
@pdaoust
Copy link
Collaborator Author

pdaoust commented Jan 15, 2025

Closes #491 , #493 , #496, and #510 . I guess I didn't keep the scope of this one so tidy.

@pdaoust pdaoust marked this pull request as draft January 15, 2025 22:18
@pdaoust
Copy link
Collaborator Author

pdaoust commented Jan 15, 2025

Converting to draft cuz I discovered a code example that got left unwritten 😬

@pdaoust pdaoust marked this pull request as ready for review January 20, 2025 18:45

Here's an `init` callback that [links](/build/links-paths-and-anchors/) the [agent's ID](/build/identifiers/#agent) to the [DNA hash](/build/identifiers/#dna) as a sort of "I'm here" note. (It depends on a couple things being defined in your integrity zome; we'll show the integrity zome after this sample for completeness.)

```rust
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggled to find a meaningful init callback example; this is about as minimal as I can think of for the "link agent ID to well-known hash" use case.

// You don't need to tell Holochain about it with the `hdk_entry_types` macro,
// because it never gets stored -- we only use it to calculate a hash.
#[hdk_entry_helper]
pub struct Anchor(pub Vec<u8>);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to avoid paths and anchors here, to reduce complexity, but maybe they're not as complex as all that. I'll go check the API and consider it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let mut hash = holo_hash::blake2b_256(url.as_str().as_bytes());
hash.extend_from_slice(&[0, 0, 0, 0]);
ExternalHash::from_raw_36(hash)

Can always use hashes that aren't defined as a type as a base for discovery.

The type Anchor is already defined in the HDI. Why does a type need to be defined here?

Copy link
Collaborator Author

@pdaoust pdaoust Jan 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that matter, it could just be an ExternalHash whose contents are "my hovercraft is full of eels!!".as_bytes(), but then you need to turn the hashing feature flag on in holo_hash in order to use .from_raw_32, and I want to avoid mucking about in cargo files just to illustrate an example. Speaking of which, (a) the above sample requires that same the encoding feature flag, and (b) I'm a bit concerned about padding the hash with 0x00000000 to supply loc bytes; will that cause problems in routing? (I understand you won't need to specify the loc bytes anymore with kitsune2, which is awesome.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defining a type was a misguided attempt to get away from introducing the concept of paths and anchors here, and needing to mention that you need to define a link type for the anchor and whatnot, but perhaps what I did is just as out-of-scope. Maybe I'll just byte the bullet and use the built-in stuff.

#[hdk_entry_helper]
pub struct Anchor(pub Vec<u8>);

pub fn get_participant_registration_anchor_hash() -> ExternResult<EntryHash> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be argued that this breaks the "don't put functions in your integrity code" guideline, but really -- the implementation for a participants anchor probably ain't gonna change, and if it does, it probably constitutes a change to the 'rules of the game' anyway.


Here's an example that uses `post_commit` to tell someone a movie loan has been created for them. It uses the integrity zome examples from [Identifiers](/build/identifiers/#in-dht-data).

```rust
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is way more elaborate than I'd like, but I honestly can't think of any simpler example for post_commit. Please share simpler ones if you can think of any. I suppose I could just send a local signal containing the contents of an entyr on successful write, but that seems useless cuz you can just bundle that data up in the zome function's return value.


1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to finally simplify this list -- for each CRUD function it basically just enumerated the call-zome workflow over and over again but differed in the description of the action it created. Now it pushes that burden off to the zome functions page.

// you need to do is wrap your movie in the corresponding enum variant.
&EntryTypes::Movie(movie),
)?;
```

### Create with relaxed chain top ordering
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we've described the call-zome workflow adequately, we can introduce relaxed chain top ordering.

MovieLoanHasBeenCreatedForYou(ActionHash),
}

#[hdk_extern]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be #[hdk_extern(infallible)]?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not allowed to error

src/pages/build/entries.md Outdated Show resolved Hide resolved

## Integrity zomes

Your [integrity zome](/build/zomes/#integrity) may define three callbacks, `validate`, `entry_defs`, and `genesis_self_check`. All of these functions **cannot have side effects**; any attempt to write data will fail. They also cannot access data that changes over time or across agents, such as the current cell's [agent ID](/build/identifiers/#agent) or a collection of [links](/build/links-paths-and-anchors/) in the [DHT](/concepts/4_dht).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amount of information required to implement entry_defs should make it out of scope for an app build guide. The section below skips talking about it anyway.

Suggested change
Your [integrity zome](/build/zomes/#integrity) may define three callbacks, `validate`, `entry_defs`, and `genesis_self_check`. All of these functions **cannot have side effects**; any attempt to write data will fail. They also cannot access data that changes over time or across agents, such as the current cell's [agent ID](/build/identifiers/#agent) or a collection of [links](/build/links-paths-and-anchors/) in the [DHT](/concepts/4_dht).
Your [integrity zome](/build/zomes/#integrity) may define two callbacks, `validate`, and `genesis_self_check`. These functions **cannot have side effects**; any attempt to write data will fail. They also cannot access data that changes over time or across agents, such as the current cell's [agent ID](/build/identifiers/#agent) or a collection of [links](/build/links-paths-and-anchors/) in the [DHT](/concepts/4_dht).

Comment on lines 47 to 49
### Define an `entry_defs` callback

You don't need to write this callback by hand; you can let the `hdk_entry_types` macro do it for you. Read the [Define an entry type section under Entries](/build/entries/#define-an-entry-type) to find out how.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Define an `entry_defs` callback
You don't need to write this callback by hand; you can let the `hdk_entry_types` macro do it for you. Read the [Define an entry type section under Entries](/build/entries/#define-an-entry-type) to find out how.

src/pages/build/callbacks-and-lifecycle-hooks.md Outdated Show resolved Hide resolved
}
```

And here's an example of one that rejects everything. You'll note that the outer result is `Ok`; you should generally reserve `Err` for unexpected failures such as inability to deserialize data. However, Holochain will treat both `Ok(Invalid)` and `Err` as invalid operations that should be rejected.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unsound advice. You should not return errors unless you are forced to a host call failing and Err is NOT treated as invalid. See the test handle_error_in_op_validation.

It is possible that validation might run into a disk fault while querying the database. Does that make the op invalid? No, but app validation still needs to signal that it couldn't complete for some reason. Therefore, the outcome from the validation callback must be an error and that must signal a retry of validation for that op at a later time.

If some content that you expect to know the format of, does not deserialize then the right thing to do would always be to return Ok(Invalid("reason")).

I'm thinking we should avoid giving any details of validation here. It's a complex topic and deserves its own page where reviewers are specifically looking at the details of validation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome. This claim I was feeling very shaky about. Cunningham's Law wins again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: giving details about validation, yes, it's a complex topic, but I'm already working on a page. This is just a stub, as mentioned in a previous paragraph.

src/pages/build/callbacks-and-lifecycle-hooks.md Outdated Show resolved Hide resolved
fns.insert((zome_info()?.name, "recv_remote_signal".into()));
create_cap_grant(CapGrantEntry {
tag: "".into(),
access: CapAccess::Unrestricted,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is it worth noting that this permits whatever code is placed in the recv_remote_signal to be run by anybody.

I think we're introducing a lot of topics here because the reader needs to understands permissions and remote calls. I think it's okay but it should come with a warning that for example, if you commit data in the recv_remote_signal function then it will be authored by the receiver.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it needs its own page too, and we can talk about all the caveats there. I'll add the warning though.


<!-- TODO: move this to the signals page after it's written -->

Agents in a network can send messages to each other via [remote signals](/concepts/9_signals/#remote-signals). In order to handle these signals, your coordinator zome needs to define a `recv_remote_signal` callback. Remote signals get routed from the emitting coordinator zome on the sender's machine to the same one on the receiver's machine, so there's no need for a coordinator to handle message types it doesn't know about.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about that actually. It's a cell scoped message but there's no guarantees that you know anything about the coordinator on the other end. You know Holochain will keep it within the network defined by the DNA and you know it's trying to send to a specified agent but you can't know they aren't running a different coordinator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so there's no need for a coordinator to handle message types it doesn't know about.

That doesn't seem like a natural consequence of the preceding sentence. The first sentence should probably stop and this should be a new sentence, not a ,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct to say it tries to dispatch to the same zome? FWIU send_remote_signal is roughly equivalent to

call_remote(
    agent_id,
    zome_info()?.name,
    "recv_remote_signal",
    secret,
    payload
).ok();

Does it feel like details about coordinator swapping are pertinent here? Happy to put that in, but also trying to be attentive to not getting into the details.

Copy link
Member

@ThetaSinner ThetaSinner Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should explain coordinator updates or packaging here. I'm advocating for specific language but not more detail So rather than "to the same one on the receiver's machine", something like "to the coordinator zome with the same role on the receiver's machine"

Copy link
Collaborator Author

@pdaoust pdaoust Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, okay, I thought you were suggesting I add something about coordinator swapping, which I agree is overkill. Phew.

src/pages/build/callbacks-and-lifecycle-hooks.md Outdated Show resolved Hide resolved
}

#[hdk_extern]
pub fn post_commit(actions: Vec<SignedActionHashed>) -> ExternResult<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example can't be run because the integrity types are missing?

Copy link
Collaborator Author

@pdaoust pdaoust Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see preceding paragraph -- the integrity types are defined in another doc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants