Once the Radicle Registry goes live, integrating updates and breaking changes will no longer a mere casualty solved by hard resetting the chain but a problem we need to get our heads around, ensuring that our public network keeps being compatible version after version.
While we, developers, always build and run the latest versions of our software, the story is usually different when it comes to users. Users don’t know how the internals work, error messages might not be as clear for them, they are not in the loop about new versions as eagerly as we are, etc.
For that reason, thinking about the process we have to follow to update our software is also a matter of thinking about how to keep the user community engaged.
We currently provide two binaries, radicle-registry-node
and radicle-registry-cli
. The binary providing the biggest challenges is the first, as it involves the ledger itself, or runtime as we call it at a technical level. However, there are three categories of updates to our public software: updates to the CLI, updates to the node, and updates to the runtime. Let’s start with the latter.
Updates to the runtime
The most challenging chunk. Within this category, we see another three different categories, ordered by increasing complexity.
Implementation changes
Changes that don’t change the business logic of the ledger neither its chain state. Say, a minor dependency update, some refactor work, etc.
The first question that comes to mind is, how can we define objectively what is “just” an implementation change? One possible answer is that the tests are left untouched without decreased code coverage. Another is that the CLI remains compatible without any change to it.
Since this is the simplest form of runtime updates, we want to start here. How do we go about updating the runtime executed by the running nodes without stopping the world? We will do that by compiling a new WASM version and broadcast it to all nodes via a transaction, that will be captured in the block that includes it. Here, we need to build a tool to prepare and submit this transaction.
We figured that users who download and install the last version of our radicle-registry-node
binary should, by default, run the latest native version of the runtime, which - at that point - is the same as the wasm counterpart. That would be helpful due to the improved performance that a native implementation offers. However, once a new WASM version is received, said version should be used to produce blocks instead of the native, which becomes obsolete at that point.
Logic changes
These are changes that affect the behaviour of the ledger. Say, a new validation step that is added against a transaction, for example.
Such changes to the runtime should result in changes to the tests, be it adding, adjusting, or deleting some. However, just like for implementation changes, these also shouldn’t require any change to the CLI. However, since such updates might break previously successful user patterns, we will have a publicly announced changelog describing such changes and how to re-adjust. The documentation provided at registry.radicle.xyz/docs will also be updated.
State changes
Here lives the hairiest of the beasts. Say, we move from having the members of an organization stored as account ids to user ids. Such change fundamentally impacts the way different versions parse and handle data from the chain. For those new to the block-chain, as I am, this is an issue because rewriting the chain state is not the way to go to, unlike one would do when running a migration on a PostgreSQL instance.
We could decide, in this early stage, to hard-fork when faced with such type of change, meaning that we would run a new, parallel network where these breaking changes would start making their way in a new, fresh ground. However, that is not going to cut once we go main net, so we are focused on learning how to do this properly as early as possible.
In other words, we need to be backwards-compatible concerning the chain state. We need to devise a way to deserialize and support older forms of the data that might still be on-chain, as well as the new one. Given the example above, when deserializing Orgs, we need to think of the old and the new variants, the one where its members were account ids and the one where they now are user ids. To help users adjust to new realities, we must provide methods for them to “migrate their data” to its latest form, not only giving control to users but also keeping everything tracked in verifiable transactions.
This category might introduce breaking changes to the CLI. To cover for this, we need to have our client ensure that the version of the runtime the node it is connected to is not greater than the one it supports. That means that the state changes that impact the CLI require a new version of the CLI to be installed. Not all state changes will lead to this path though, as some types stored on-chain aren’t necessarily the types exposed to our client.
Changes to the CLI
Changes to the radicle-registry-cli live on the simpler side of the spectrum. We will be providing new binary versions when appropriate via our releases page, which might require updates to our documentation. The docs will need to point out the version of the CLI and node they refer to, to make potential observable deviations in behaviour or offer explicit to our users. However, users won’t be checking the docs nor the releases page all the time, so we are considering leaving a friendly announcement from within the CLI let you know that an update is available.
Changes to the node
Much of what applies to the CLI applies to the node as well. The one type of change to the radicle-registry-node that would require further consideration would be a change to the consensus. Such change would require a hard-fork however, which isn’t something we are considering for the foreseeable future.