Hello World¶
WIP
This article is currently a work in progress and subject to frequent change. See the changelog for details.
Time & Level
Time: ~2 hours | Level: Beginner
Welcome to the hello world tutorial. It’s a little strange to do a hello world tutorial as number five, but we really wanted to show a Holochain app from an agent perspective. This is the first time the agent will be interacting with the world.
The previous tutorials have come from the local perspective of a single agent. However, the real power of Holochain comes from interacting with other agents.
What will you learn¶
You will learn how to share data between two agents. To achieve, this you will run two conductors—Alice and Bob. Then, add an entry to Alice’s local chain. Finally, retrieve that entry from Bob’s instance.
Why it matters¶
Holochain applications are about creating cooperation between multiple agents; by sharing data among the agents, you can validate one another’s entries.
Make your entry public¶
So far, the only entries you have had have been private. If you want your users to be able to share data, then you can set the entry to ‘public’ in the definition.
Open up your zomes/hello/code/src/lib.rs
file.
Change the entry sharing to Sharing::Public
:
fn person_entry_def() -> ValidatingEntryType { entry!( name: "person", description: "Person to say hello to", - sharing: Sharing::Private, + sharing: Sharing::Public, validation_package: || { hdk::ValidationPackageDefinition::Entry }, validation: | _validation_data: hdk::EntryValidationData<Person>| { Ok(()) } ) }
Add Bob to the test¶
Previously, you wrote a test where Alice made a few zome calls and verified the results. You can now use Bob in your tests to interact with Alice to verify that the entries can be shared between agents running the same DNA.
The aim here is for Alice to create a person, which Bob can then retrieve.
Open up your test/index.js
file.
Before Bob can retrieve Alice’s person, Bob will need to be able to see the person that Alice committed. This goes back to an idea that will come up a lot in Holochain—eventual consistency. In a nutshell, an Agent that is connected to the same network as another agent will eventually come into agreement on what data exists.
To make sure this has happened, add this line to the end of the scenario:
await new Promise(r => setTimeout(r, 1000));
This one line says a lot about the nature of a Holochain application. The word
await
shows that we are in an asynchronous world, so we want to wait for consistency to be achieved. What kind of situation might lead to this line not being sufficient? Hint: Think about networks that might not be perfect.
Get Bob to retrieve Alice’s person using the same address she did when she created the entry:
const bob_retrieve_result = await bob.call( 'cc_tuts', 'hello', 'retrieve_person', {address: alice_person_address}, );
The result is checked and stored:
t.ok(bob_retrieve_result.Ok); const bobs_person = bob_retrieve_result.Ok;
Finally, a deeper check makes sure the contents of the two persons match:
t.deepEqual(bobs_person, {name: 'Alice'});
Check your code
/// NB: The tryorama config patterns are still not quite stabilized. /// See the tryorama README [https://github.com/holochain/tryorama] /// for a potentially more accurate example const path = require('path'); const { Orchestrator, Config, combine, localOnly, tapeExecutor, } = require('@holochain/tryorama'); process.on('unhandledRejection', error => { // Will print "unhandledRejection err is not defined" console.error('got unhandledRejection:', error); }); const dnaPath = path.join(__dirname, '../dist/cc_tuts.dna.json'); const orchestrator = new Orchestrator({ middleware: combine( // use the tape harness to run the tests, injects the tape API into each scenario // as the second argument tapeExecutor(require('tape')), // specify that all "players" in the test are on the local machine, rather than // on remote machines localOnly, ), }); const dna = Config.dna(dnaPath, 'cc_tuts'); const config = Config.gen( { cc_tuts: dna, }, { network: { type: 'sim2h', sim2h_url: 'ws://localhost:9000', }, }, ); orchestrator.registerScenario('Test hello holo', async (s, t) => { const {alice, bob} = await s.players({alice: config, bob: config}, true); const result = await alice.call('cc_tuts', 'hello', 'hello_holo', {}); t.ok(result.Ok); t.deepEqual(result, {Ok: 'Hello Holo'}); const create_result = await alice.call('cc_tuts', 'hello', 'create_person', { person: {name: 'Alice'}, }); t.ok(create_result.Ok); const alice_person_address = create_result.Ok; await new Promise(r => setTimeout(r, 1000)); const retrieve_result = await alice.call( 'cc_tuts', 'hello', 'retrieve_person', {address: alice_person_address}, ); t.ok(retrieve_result.Ok); t.deepEqual(retrieve_result, {Ok: {name: 'Alice'}}); await new Promise(r => setTimeout(r, 1000)); const bob_retrieve_result = await bob.call( 'cc_tuts', 'hello', 'retrieve_person', {address: alice_person_address}, ); t.ok(bob_retrieve_result.Ok); const bobs_person = bob_retrieve_result.Ok; t.deepEqual(bobs_person, {name: 'Alice'}); }); orchestrator.run();
Run sim2h¶
Again, you will need to run the sim2h server in a seperate terminal window:
Run in nix-shell
sim2h_server
Run the test¶
Enter the nix-shell if you don’t have it open already:
nix-shell https://github.com/holochain/holonix/archive/release-0.0.85.tar.gz
Now, run the test and make sure it passes:
Run in nix-shell
hc test
If everything went okay, at the end you will see:
# tests 7 # pass 7 # ok
Run the app and two UIs¶
Now, the fun part—you get to play with what you just wrote. You’re going to need a few terminals to do this.
You will tell hc
to use sim2h networking because you are actually
using two separate conductors in this tutorial.
You will also need to give each conductor a different agent name.
Update the DNA hash in the bundle file¶
Now that you’ve changed your DNA (even though it’s a tiny change), it has a new hash. You’ll need to update your bundle.toml
file.
Run in nix-shell
hc hash
Copy the new DNA hash into your bundle.toml
file; you can use the update script from the GUI tutorial.
Terminal one¶
You should have the sim2h server already running after the test. If not, start it now.
Run in nix-shell
sim2h_server
Terminal two¶
Now it’s time to start a conductor for Alice.
Run in nix-shell
hc run --networked sim2h --agent-name Alice --port 8888
Terminal three¶
Start a second coductor for Bob on a different port:
Run in nix-shell
hc run --networked sim2h --agent-name Bob --port 8889
Open up the browser¶
Open two tabs.
Tab Alice¶
Go to 127.0.0.1:8888
.
Tab Bob¶
Go to 127.0.0.1:8889
.
Tab Alice¶
Create a person entry with your name:
Tab Bob¶
Copy the address from the Alice tab and retrieve the person entry:
Hooray! Alice and Bob are now able to share data on the DHT.
Solution
You can check the full solution to this tutorial on here.
Key takeaways¶
- Entries need to be explicitly marked public or they will only be committed to an agent’s local chain.
- A public entry will be passed to other agents via gossip, validated and held.
- Another agent can retrieve your public entries.