Now that we have built our basic DApp project, we will shift our focus towards the off-chain part of the DApp.
As we said before, our DApp's goal is to instantiate an off-chain computation that simply returns "Hello World!". In this context, the first step we'll take is to specify this computation as a reproducible and verifiable Cartesi Machine template, so that on-chain code can safely execute the off-chain computation. This process is described below.
In order to specify and test our Cartesi Machine, we will make use of the
cartesi/playground Docker image already showcased in the Cartesi Machine host perspective section. With the playground, we will take advantage of numerous Cartesi Machine features available at the command line interface.
First of all, create a directory called
cartesi-machine within the
helloworld project home directory, and
cd into it:
mkdir cartesi-machinecd cartesi-machine
To illustrate how we are going to use the playground, try executing the following command:
docker run \--rm cartesi/playground:0.3.0 cartesi-machine \-- $'echo Hello World!'
Step by step, this command will execute the following tasks:
cartesi/playgroundDocker image and instantiate a container for it;
cartesi-machinecommand within the image, whose only argument specifies the computation as "echo Hello World!". As such, the command will spin up a Cartesi Machine, print "Hello World" to the console, and power down.
The result of such a command will look like this:
Running as root./ \/ \\---/---\ /----\\ X \\----/ \---/---\\ / CARTESI\ / MACHINE'Hello World!HaltedCycles: 63517101
The machine instantiated above runs fine, however in order to make it usable by on-chain code we'll need to make a couple of improvements.
First of all, instead of executing the computation, we must specify the Cartesi Machine as a computation template. In practice, this means that no actual computation is to take place at the moment. Rather, we want the computation specification to be stored in a way that can later be executed by a Descartes node, upon request.
Other than that, we must specify the machine in such a way that the computation output can be actually read by the Descartes node. This means that, instead of simply printing "Hello World!" to the screen, we need to specify that the string should be written to an output drive, from which the Descartes node will be able to pick it up.
With these ideas in mind, let us look at a full-fledged Cartesi Machine specification that can be used by our Descartes DApp:
docker run \-e USER=$(id -u -n) \-e GROUP=$(id -g -n) \-e UID=$(id -u) \-e GID=$(id -g) \-v `pwd`:/home/$(id -u -n) \-w /home/$(id -u -n) \--rm cartesi/playground:0.3.0 cartesi-machine \--max-mcycle=0 \--initial-hash \--store=stored_machine \--flash-drive="label:output,length:1<<12" \-- $'echo Hello World! | dd status=none of=$(flashdrive output)'
Let's take a closer look at what this means. First of all, we run the Docker container adequately mapping the current user and group information, so as to ensure generated files have the expected owner. Aside from that, we map the current directory on the host to the home directory within the container, and set that as the current working directory within the Docker container. As such, programs running inside the container have access to the host filesystem and can write files to it.
The most interesting part is of course within the Cartesi Machine command itself, where a number of new arguments should be noted. First of all, we define
max-mcycle=0 to ensure the machine does not execute a single cycle at all. Then,
initial-hash is specified just so that the machine's initial template hash is printed on screen, which is helpful to keep track of what is being generated. This hash actually works as an identifier for the specified computation.
A vital argument for our tutorial is the
store=stored_machine parameter, which specifies a directory where the machine template definition will be written to disk. This stored machine is what the Descartes node will use to actually perform the computation later on. Note that, since we have mapped the current directory using the Docker
-v argument, this directory will be written to the host filesystem.
Another essential argument is
flash-drive="label:output,length:1<<12", which specifies that the Cartesi Machine now has a 4KiB output drive. As such, we can use that in our final command line
$'echo Hello World! | dd status=none of=$(flashdrive output)', which now determines that the bytes of our "Hello World!" string should be written to the specified output drive.
The console output when running the above command should be:
0: 8b5377cce5040546071c4d5401537eb541e6c8c2608831cb83051140514e3f5dCycles: 0Storing machine: please wait
This informs us that the machine's initial template hash is
8b5377cc..., that it did not run any cycles, and that it stored the machine specification. Looking within the specified
stored_machine directory, we can indeed verify that the machine's contents were stored there:
ls stored_machine/0000000000001000-f000.bin 0000000080000000-4000000.bin 8000000000000000-3c00000.bin 9000000000000000-1000.bin config hash
Now that we have defined what our Hello World Cartesi Machine looks like, all we need to do is make the generated stored machine available to the Descartes nodes running inside the Descartes SDK Environment.
In order to do that, we'll code a handy shell script that wraps it all up, so that it's easy to make changes to the machine if desired. Inside our
cartesi-machine subdirectory, create a file called
build-cartesi-machine.sh, and make sure it is executable:
touch build-cartesi-machine.shchmod +x build-cartesi-machine.sh
Edit the file and place the following contents into it:
# general definitionsMACHINES_DIR=.MACHINE_TEMP_DIR=__temp_machineCARTESI_PLAYGROUND_DOCKER=cartesi/playground:0.3.0# set machines directory to specified path if providedif [ $1 ]; thenMACHINES_DIR=$1fi# removes machine temp store directory if it existsif [ -d "$MACHINE_TEMP_DIR" ]; thenrm -r $MACHINE_TEMP_DIRfi# builds machine (running with 0 cycles)# - initial (template) hash is printed on screen# - machine is stored in temporary directorydocker run \-e USER=$(id -u -n) \-e GROUP=$(id -g -n) \-e UID=$(id -u) \-e GID=$(id -g) \-v `pwd`:/home/$(id -u -n) \-w /home/$(id -u -n) \--rm $CARTESI_PLAYGROUND_DOCKER cartesi-machine \--max-mcycle=0 \--initial-hash \--store="$MACHINE_TEMP_DIR" \--flash-drive="label:output,length:1<<12" \-- $'echo Hello World! | dd status=none of=$(flashdrive output)'# defines target directory as being within $MACHINES_DIR and named after the stored machine's hashMACHINE_TARGET_DIR=$MACHINES_DIR/$(docker run \-e USER=$(id -u -n) \-e GROUP=$(id -g -n) \-e UID=$(id -u) \-e GID=$(id -g) \-v `pwd`:/home/$(id -u -n) \-h playground \-w /home/$(id -u -n) \--rm $CARTESI_PLAYGROUND_DOCKER cartesi-machine-stored-hash $MACHINE_TEMP_DIR/)# moves stored machine to the target directoryif [ -d "$MACHINE_TARGET_DIR" ]; thenrm -r $MACHINE_TARGET_DIRfimv $MACHINE_TEMP_DIR $MACHINE_TARGET_DIR
This script accepts an optional parameter specifying where the stored machine contents should be moved to. This is useful to specify the directory where the Descartes nodes effectively read stored machines. Moreover, the nodes expect machine directories to be named after the machine's template hash, so the script makes use of the
cartesi-machine-stored-hash tool, available within the playground Docker, to extract that hash from the stored contents and properly name the final directory name.
Finally, if the Descartes SDK Environment is running in a relative directory at
../../descartes-env, we can build the Hello World machine and make it available to the Descartes nodes by running:
The output should be the same as before, but now the contents will be neatly stored in the expected form within the Descartes environment's
ls ../../descartes-env/machines8b5377cce5040546071c4d5401537eb541e6c8c2608831cb83051140514e3f5dls ../../descartes-env/machines/8b5377cce5040546071c4d5401537eb541e6c8c2608831cb83051140514e3f5d/0000000000001000-f000.bin 0000000080000000-4000000.bin 8000000000000000-3c00000.bin 9000000000000000-1000.bin config hash
And that's it, we now have running Descartes nodes that are capable of performing our Hello World computation!
Let's finish off by returning to the
helloworld home directory: