-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: main
Are you sure you want to change the base?
Conversation
Converting to draft cuz I discovered a code example that got left unwritten 😬 |
…ucture-zome-functions
|
||
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 |
There was a problem hiding this comment.
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>); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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)]
?
There was a problem hiding this comment.
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
|
||
## 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). |
There was a problem hiding this comment.
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.
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). |
### 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### 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. |
} | ||
``` | ||
|
||
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
fns.insert((zome_info()?.name, "recv_remote_signal".into())); | ||
create_cap_grant(CapGrantEntry { | ||
tag: "".into(), | ||
access: CapAccess::Unrestricted, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ,
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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.
} | ||
|
||
#[hdk_extern] | ||
pub fn post_commit(actions: Vec<SignedActionHashed>) -> ExternResult<()> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Co-authored-by: ThetaSinner <[email protected]>
…holochain/docs-pages into feat/guide/app-structure-zome-functions
it's always a missing semicolon Co-authored-by: ThetaSinner <[email protected]>
…holochain/docs-pages into feat/guide/app-structure-zome-functions
…holochain/docs-pages into feat/guide/app-structure-zome-functions
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.)