Currently, applications running inside a Cartesi Machine operate in a loop that obtains a request and process it. If the request is an inspect-state request, the entire application state is reverted upon completion of the query. If the request is an advance-state request, the application can revert its entire state by rejecting the input, or preserve its entire state and move on to the next request.
There are a variety of issues with this design:
-
Reliability. From the point of view of an application, it is processing an unending sequence of advance-state requests. This is similar to a program running forever in a real computer. Although there are techniques for writing reliable software that can run indefinitely in this way, they are onerous to the developers that follow them (think avionics code, or nuclear reactor code). In fact, most developers are unwilling to follow them, and most often unprepared as well. Instead, it would be much easier for developers to write software that is capable of processing a single request when starting fresh;
-
Upgradability. Should there be any bug in the application, or the need for any new feature, it is almost impossible to upgrade the application. Should a new version of the emulator or a new Linux kernel be released, with potential performance improvements, the application won’t benefit from it. When the application state can lives anywhere in the application memory or the filesystem, there is no way to safely replace only the application code. It would be best to keep application state separate from all the rest;
-
Front-end scalability and convenience (“Bare-metal inspects”). It is almost impossible to inspect the state of the application from the outside. One would need to figure out where the desired information lives inside the machine (once again, anywhere in the application memory or filesystem) and fetch it. Since this is so difficult, one approach is for the inspect-state request to be sent to the application running inside the machine, for that application itself to serve those requests. This has scalability problems. The alternative is for the front-end to keep a copy of the application outside, kept in sync by notices or reports emitted by the application running inside the machine for every state change. This is awkward. It would be best to provision for the up-to-date application state to be available outside the Cartesi Machine, so it can be accessed by front-end applications running natively, in bare metal;
-
Backend scalability (“Bare-metal advances”). Applications can be carefully written in such a way that they update their state in exactly the same way regardless of whether they are executed inside the Cartesi Machine or outside it. Such applications could potentially scale much better by running, optimistically, bare-metal. In case of disputes, the latest agreed-upon state could be inserted into a Cartesi Machine that would be used for arbitration purposes only. This is only possible if that state is kept separate from the rest of the application in a way that does not depend where it runs.
There is an alternative design that addresses all these issues.
Each Cartesi Machine would reserve a special region to hold the application state (let’s call this the “context”). When first run, the application would initialize the context and attempt to obtain the next request for processing, as normal. We would save the machine at this stage (minus the context) as a “template”, and store the context separately. When an advance-state request arrives, we would spawn a copy of the template machine, and insert the current context and the current input in it, and run it. The machine would make the desired changes to the context and produce whatever outputs it wants to in response to the input it received. Once it is done, if it accepted the input, we would update the outside copy of the context and drop the machine. If it rejected the input, we would drop both the machine and the modified context.
From the point of view of the code running inside the machine, it processes a single input. There is no compounding of changes outside of the context. For example, if the application uses garbage-collection, it is as if no trash ever piles on between inputs. If there are memory leaks, they do not carry over from one input to the next. This is a much more robust approach. The only resource that must be very carefully managed is the context itself.
Upgrading the kernel or the application code is simply a matter of replacing the template. All you need to do is ensure the application in the new template understands the context representation used by the previous version.
It is possible to run exactly the same application outside the Cartesi Machine, and it will load the context as if it was inside. This allows inspect-state requests to be served natively and even in parallel.
If the application is written carefully, it can even process advance-state requests outside and produce the same sequence of context updates for the same sequence of inputs as if it was run on the inside.
There are a couple downsides, of course.
-
Applications must be written in a way that does not rely on any state being preserved between advance-state requests. Whatever state is modified outside the context while processing an input will be reverted to its original value before the next input is processed. This includes local and global variables, files etc;
-
For applications to benefit from front-end scalability, the representation of the context must be carefully chosen to be understandable both inside and outside the Cartesi Machine;
-
For applications to benefit from back-end scalability, in addition to the requirements on front-end scalability, the application code itself must be carefully written to be reproducible.
Nevertheless, these disadvantages are far outweighed by the advantages.