Holochain Upgrade 0.3 → 0.4

For existing hApps that are currently using Holochain 0.3, here’s the guide to get you upgraded to 0.4.

NOTE: Holonix, our developer shell environment, has also been updated. We’re not supporting the old Holonix for Holochain 0.4 and beyond, so you’ll need to upgrade to the new Holonix at the same time if you haven’t already!

Quick instructions

To upgrade your hApp written for Holochain 0.3, follow these steps:

  1. Check whether your flake.nix contains github:holochain/holonix. If it does, then you can continue to the next step. Otherwise it will contain github:holochain/holochain. If so, then follow the Holonix upgrade guide to update to the newest Holonix command-line developer environment (we’re not providing a 0.4 version of Holochain through the old Holonix).

  2. Update your flake.nix to use the 0.4 version of Holochain by changing the version number in the line holonix.url = "github:holochain/holonix?ref=main-0.3" from 0.3 to 0.4. This will take effect later when you enter a new Nix shell. It’s important to update your Nix flake lockfile at this point, to ensure you benefit from the cache we provide:

    nix flake update && nix develop
    
  3. Update your project’s package dependencies (see below).

  4. Follow the breaking change update instructions below to get your code working again.

  5. Try running your tests:

    npm test
    

    and starting the application:

    npm start
    
  6. Be aware of some changes that won’t break your app but may affect its runtime behavior. Read the guide at the bottom.

Update your package dependencies

Rust

Update the hdk and hdi version strings in the project’s root Cargo.toml file:

 [workspace.dependencies]
-hdi = "=0.4.6"
-hdk = "=0.3.6"
+hdi = "=0.5.0" # Pick a later version of these libraries if you prefer.
+hdk = "=0.4.0"
 serde = "1.0"

The latest version numbers of these libraries can be found on crates.io: hdi, hdk.

Once you’ve updated your Cargo.toml you need to update your Cargo.lock and check whether your project can still build. To do this in one step you can run:

cargo build

(Optional) Update other Rust dependencies

Running a Cargo build, like suggested above, will update as few dependencies as it can. This is good for stability because it’s just making the changes you asked for. However, sometimes you do need to update other dependencies to resolve build issues.

This section is marked as optional because it’s possible that new dependencies could introduce new issues as well as fixing existing conflicts or problems. To make it possible to roll back this change, it might be a good idea to commit the changes you’ve made so far to source control. Then you can run:

cargo update

This will update your Cargo.lock with the latest versions of all libraries that the constraints in your Cargo.toml files will allow. Now you should try building your project again to see if that has resolved your issue.

JavaScript

Command-line tools

If you’ve created your hApp using our scaffolding tool, you should be able to follow these instructions. If you’ve created your own project folder layout, adapt these instructions to fit.

Edit your project’s root package.json file to update the developer tools:

   "devDependencies": {
     "@holochain-playground/cli": "^0.300.1",
-    "@holochain/hc-spin": "0.300.3",
+    "@holochain/hc-spin": "^0.400.0",
     // more dependencies
   },

Tryorama tests

Edit your project’s tests/package.json file:

   "dependencies": {
     // some dependencies
-    "@holochain/client": "^0.17.1",
-    "@holochain/tryorama": "^0.16.0",
+    "@holochain/client": "^0.18.0",
+    "@holochain/tryorama": "^0.17.0",
     // more dependencies
   },

UI

You’ll update the UI package dependencies similarly to the test package. Edit ui/package.json:

   "dependencies": {
-    "@holochain/client": "^0.17.1",
+    "@holochain/client": "^0.18.0",
     // more dependencies
   },

Then in your project’s root folder, run your package manager’s update command to update the lockfile and install new package versions for your command-line tools, tests, and UI. Use the command that matches your chosen package manager. For example, if you’re using npm:

npm install

Update your application code

Here are all the breaking changes you need to know about in order to update your app for Holochain 0.4.

Unstable features removed by default

The biggest change for 0.4 is that some features are marked unstable and aren’t compiled into Holochain by default. We’re doing this to focus on a stable core Holochain with the minimal feature set that most projects need. The removed features are still available as Rust feature flags, so you can compile a custom Holochain binary if you need them. Here’s the full list, along with their feature flags:

If your DNA needs to call a host function that depends on an unstable feature, you’ll need to do two things:

  1. Build a custom Holochain binary with both the specific feature you need (see the list above) and unstable-functions enabled.
  2. Enable the unstable-functions flag for the hdi and hdk dependencies in your zomes’ Cargo.toml (see the section after the next).

Note that you’ll need to make sure your users are running your custom conductor binary. If you compile your zomes without unstable-functions enabled for hdi or hdk, users with the flag enabled in Holochain will still be able to use your hApp, but if you compile your zomes with unstable-functions, users with the flag(s) disabled won’t be able to use your hApp.

CloneCellId changes

The CloneCellId::CellId enum variant has become DnaHash and contains, naturally, a DnaHash value. This type is used when enabling, disabling, or deleting clones from the app API or a coordinator zome.

In coordinator zomes

Edit any coordinator zome code that uses functions from hdk::clone:

 use hdk::prelude::*;

 fn create_chat_room(name: String) -> ExternResult<CellId> {
   // ... instantiate cell_id
   let create_input = CreateCloneCellInput {
     cell_id: cell_id,
     membrane_proof: None,
     name: Some(name),
   };
   let cloned_cell = create_clone_cell(create_input)?;
   let enable_input = EnableCloneCellInput {
-    clone_cell_id: CloneCellId::CellId(cloned_cell.cell_id.clone()),
+    clone_cell_id: CloneCellId::DnaHash(cloned_cell.cell_id.dna_hash().clone()),
   };
   enable_clone_cell(enable_input)?;
   Ok(cloned_cell.cell_id)
 }

 fn remove_chat_room(cell_id: CellId) -> ExternResult<()> {
   let disable_input = DisableCloneCellInput {
-    clone_cell_id: CloneCellId::CellId(cell_id.clone()),
+    clone_cell_id: CloneCellId::DnaHash(cell_id.dna_hash().clone()),
   };
   disable_clone_cell(disable_input)?;
   let delete_input = DeleteCloneCellInput {
-    clone_cell_id: CloneCellId::CellId(cell_id.clone()),
+    clone_cell_id: CloneCellId::DnaHash(cell_id.dna_hash().clone()),
   };
   delete_clone_cell(delete_input)
 }

In JavaScript front-end

Edit any client code that manipulates cloned cells by cell ID to use DNA hash instead:

 import { AppWebsocket, CellId } from "@holochain/client";

 let client: AppClient = await AppWebsocket.connect();

 async function archiveChatRoom(cell_id: CellId) {
   await client.disableCloneCell({
-    clone_cell_id: cell_id,
+    clone_cell_id: cell_id[0],
   });
 }

 async function reopenChatRoom(cell_id: CellId) {
   await client.enableCloneCell({
-    clone_cell_id: cell_id,
+    clone_cell_id: cell_id[0],
   });
 }

If you’re writing an application that uses the admin API, AdminClient#deleteCloneCell changes in the same way as enableCloneCell and disableCloneCell.

JavaScript client now receives system signals

For JavaScript front ends and Tryorama tests, the signal handler callback for AppWebsocket.prototype.on("signal", cb) should now take a Signal. Update your code to look like this:

-import { AppClient, AppSignal, AppWebsocket } from "@holochain/client";
+import { AppClient, AppWebsocket, Signal, SignalType } from "@holochain/client";

 let client: AppClient = AppWebsocket.connect();
-client.on("signal", (appSignal: AppSignal) => {
+client.on("signal", (signal: Signal) => {
+  if (!(SignalType.App in signal)) return;
+  const appSignal = signal[SignalType.App];
   console.log(`Received app signal from cell [${appSignal.cell_id}] and zome ${appSignal.zome_name} with payload ${appSignal.payload}`);
 });

(Note that currently you’ll only ever see a system signal if you’re using countersigning, which is disabled by default.)

Change in enum serialization

The default serialization for unit-like enum variants has changed. Previously, they would look like this (JSON representation):

{
    "variant1": null
}

Now they look like this:

"variant1"

(Enum variants with data still follow the { "variant": <data> } pattern.)

This will affect any entries or entry fields that start as instances of a Rust enum and are sent to the front end. If your front end is written in JavaScript, you’ll need to be aware of this and update your front-end models to match. If your front end is written in Rust, you’re likely defining your entry types in a separate module that you’re importing directly into the client, which will handle proper deserialization for you.

OpenChain and CloseChain actions changed

If you’re one of the rare folks who have been using these two actions, the structs have changed. Take a look at the Rustdoc and update your code accordingly:

InstallApp agent key is optional

Info

This change is only relevant if you’re using the Rust client to access the admin API, for instance if you’re building a custom runtime.

The agent_key field in the InstallApp payload is now optional, and a key will be generated if you don’t supply one.

If you’re using the JavaScript client to interact with the conductor, update the JS client lib and test it — you shouldn’t need to change any code.

If you’re using the Rust client, first update the Rust client lib, then update your code. Edit your UI project’s Cargo.toml file:

 [dependencies]
-holochain_client = "0.5.3"
+holochain_client = "0.6.0"

Then edit anywhere in your Rust code that uses the install_app function:

 use holochain_client::*;

 fn install_app() -> Result<()> {
   let admin_ws = AdminWebsocket::connect((Ipv4Addr::LOCALHOST, 30_000)).await?
   // ... set up arguments
   let input_payload = InstallAppPayload {
     app_bundle_source,
-    agent_key,
+    Some(agent_key),
     Some(installed_app_id),
     membrane_proofs,
     Some(network_seed),
   };
   let response = admin_ws.install_app(input_payload).await?;
   // ... do things
   Ok()
 }

Deprecated validation op functionality removed

In your integrity zome’s validation functions, you deal with DHT operations, or ops. They are somewhat complex, so FlatOp was introduced to make things simpler. It was originally called OpType, and until now that old name was a deprecated alias of FlatOp. The old type has finally been removed, along with the Op::to_type method (use OpHelper::flattened instead).

In your integrity zome, any time you use Op::to_type, change it like this:

 #[hdk_extern]
 pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
-  match op.to_type::<(), ()>()? {
+  match op.flattened::<(), ()>()? {
-    OpType::StoreEntry(store_entry) => {
+    FlatOp::StoreEntry(store_entry) => {
       // do things
     }
-    OpType::RegisterUpdate(update_entry) => {
+    FlatOp::RegisterUpdate(update_entry) => {
       // do things
     }
     // etc
     _ => Ok(ValidateCallbackResult::Valid),
   }
 }

New data in AppInfo response

Holochain 0.4 introduces a new flow for app installation that lets an agent supply a membrane proof later with a new ProvideMemproofs endpoint. This allows them to get their freshly generated agent key, submit it to some sort of membrane proof service, and get a membrane proof.

Because of this, after the membrane proof has been provided, the DisabledAppReason enum, used in the AppInfo response, will be a new NotStartedAfterProvidingMemproofs variant until the app is started.

The only change you should need to make to existing code is to make sure you’re handling this new variant in your match blocks (Rust) or switch blocks (JavaScript).

CountersigningSuccess information changed

The CountersigningSuccess signal sent to the client now gives the action hash of the successful countersigned commit rather than its entry hash. In your client code, whether JavaScript or Rust, update your signal handler to deal with a HoloHash<Action> rather than a HoloHash<Entry> when it receives this kind of signal.

Subtle changes

DHT sharding is disabled by default

This feature needs more performance and correctness testing before it’s production-ready, but it can be turned on by compiling Holochain with the unstable-sharding flag enabled.

If you leave this feature disabled, you’ll need to set gossip arc clamping to either "empty" or "full", and we recommend doing this for all participants in a network. We’ve observed bugs in networks that have a mixture of unsharded (arc-clamped) and sharded peers.

Dynamic database encryption, folder structure changes

Previous conductor and keystore SQLite databases used a hardcoded encryption key; 0.4 now uses a dynamic key. This means you won’t be able to import data from 0.3, so we recommend providing a data import/export feature in your hApp with instructions to help people move their data over on upgrade to 0.4.

The locations of the database folders and files have also changed (although if 0.4 finds an unencrypted database in the old locations, it’ll move the data. If you want it to encrypt the data at the same time, set an environment variable HOLOCHAIN_MIGRATE_UNENCRYPTED to a truthy value before running the hApp in 0.4.).

WebRTC signalling server change

Info

This is only relevant if you’re maintaining your own infrastructure or building your own runtime.

The old signalling server has been replaced with a new one called sbd. If you’re hosting your own server, switch to a new sbd server binary. If you’re using Holo’s public infrastructure, switch from wss://signal.holo.host to wss://sbd-0.main.infra.holo.host in your conductor config file. (The new URL will automatically be in any conductor config generated by the dev tools for 0.4.)

It looks like you are using Internet Explorer. While the basic content is available, this is no longer a supported browser by the manufacturer, and no attention is being given to having IE work well here.