Now that we have the basic project structure ready, let's focus on the main part of our DApp, which is the off-chain computation to be performed by the Cartesi Machine.
First of all, let's
cd into the
For this computation, we will use the Linux bc tool, which is capable of calculating the result of an arbitrary mathematical expression given as a string. This is illustrated in detail in the Cartesi Machine host section.
We can use the playground to help us configure and test an appropriate machine. To do that, let's run it in interactive mode:
docker run -it --rm cartesi/playground:0.3.0 /bin/bash
Now, let's create some test input data:
echo "2^71 + 36^12" > input.raw
As explained in the Cartesi Machine section, the underlying RISC-V technology requires all drives to have a size that is a multiple of 4KiB. Descartes normally takes care of this, but for our tests we will need to ensure it by using the handy
truncate tool available within the playground. This tool will pad the file with zeros in case it is smaller than the specified size:
truncate -s 4K input.rawtruncate -s 4K output.raw
At this point, we can now exercise a machine execution that uses the
bc tool to compute the result of the given input expression:
cartesi-machine \--flash-drive="label:input,length:1<<12,filename:input.raw" \--flash-drive="label:output,length:1<<12,filename:output.raw,shared" \-- $'dd status=none if=$(flashdrive input) | lua -e \'print((string.unpack("z", io.read("a"))))\' | bc | dd status=none of=$(flashdrive output)'
Here, we have two
flash-drive declarations, one for the input and one for the output, both with a specified size of 4KiB. The command to be executed will use the
dd tool to read the raw data from the input drive, pipe it through a tiny Lua script to ensure it is read as a null-terminated string, give it as input to the
bc tool, and finally write the result to the output drive. Additionally, we include
filename:output.raw,shared, which instructs the machine to read from our test input file and write to the test output file in a persistent manner. Full details about these parameters and how to use them are given in the Cartesi Machine host section.
After executing the above command, we can inspect the results written to the
Which is indeed the result of computing
2^71 + 36^12, as expected.
We can now exit the playground Docker by typing:
Having exercised how our machine will work, we can now turn to building a final version of it that will be used by the Descartes nodes in our development environment.
Recalling the previous machine built for the Hello World DApp, let's create a bash script called
build-cartesi-machine.sh back in our
touch build-cartesi-machine.shchmod +x build-cartesi-machine.sh
Now, edit the file and place the following contents into it:
#!/bin/bash# 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:input,length:1<<12" \--flash-drive="label:output,length:1<<12" \-- $'dd status=none if=$(flashdrive input) | lua -e \'print((string.unpack("z", io.read("a"))))\' | bc | 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
As explained in more detail in the Hello World tutorial, this script will create a template machine to be executed upon request, and store its contents in a directory specified by the user. In order to do that, we have specified
max-mcycle=0, so that the machine halts without running any cycles. Then, we added the parameter
--store="$MACHINE_TEMP_DIR" to specify that the machine's specification should be stored in the specified directory. Finally, we have removed the
filename configurations from the flash drives, since the input and output data will now be handled automatically by Descartes.
With all of this set, build the machine by executing:
The output of the above command should then be:
0: a278371ed8d52efa6aba9f825ba8130d2604b363b3ceb51c1bd3a210f400fd8aCycles: 0Storing machine: please wait
After executing this command, the machine's specification will be stored in the appropriate directory within our Descartes environment. Moreover, we are informed that its initial template hash is
a278371e..., which serves as an identifier of this machine and will thus be necessary to instantiate the computation from a smart contract, as will be explored in the next section.
Finally, move back to the
calculator home directory: