Command-line interface
In the simplest usage scenario, the cartesi-machine
command-line utility can be used to define a Cartesi Machine and run it until it halts.
The command-line utility, however, is very versatile.
It was designed to simplify the most common prototyping tasks.
Initialization
The following command instructs cartesi-machine
to build a Cartesi Machine.
The machine uses rom.bin
as the ROM image, has 64MiB of RAM, uses linux.bin
as the RAM image, and uses rootfs.ext2
as the root file-system.
(The rom.bin
, linux.bin
, and rootfs.ext2
files are generated by the Emulator SDK, and sample files are available in the playground.)
Once initialization is complete, the machine executes the command ls /bin
and exits.
cartesi-machine \
--rom-image="/opt/cartesi/share/images/rom.bin" \
--ram-length=64Mi \
--ram-image="/opt/cartesi/share/images/linux.bin" \
--flash-drive="label:root,filename:/opt/cartesi/share/images/rootfs.ext2" \
-- "ls /bin"
The --rom-image
, --ram-image
, --ram-length
, and --flash-drive
command-line options have the values in the example as default, so these options can be omitted.
To remove these default settings, use the command-line options --no-ram-image
and --no-root-flash-drive
, respectively.
(The machine needs a ROM image, and, if needed, you can simply specify a different one.)
The simplified command-line is
cartesi-machine -- "ls /bin"
The output is
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
arch dmesg linux64 nuke stty
ash dnsdomainname ln pidof su
base32 dumpkmap login ping sync
base64 echo ls pipe_progress tar
busybox egrep lsattr printenv touch
cat false lsblk ps true
chattr fdflush mk_cmds pwd umount
chgrp fgrep mkdir resume uname
chmod findmnt mknod rm usleep
chown getopt mktemp rmdir vi
compile_et grep more run-parts watch
cp gunzip mount sed wdctl
cpio gzip mountpoint setarch zcat
cttyhack hostname mt setpriv
date kill mv setserial
dd link netstat sh
df linux32 nice sleep
Halted
Cycles: 67557971
It shows the Cartesi Machine splash screen, followed by the listing of directory /bin/
.
The listing was produced by the command that follows the --
separator in the command line.
The Linux kernel passes this unmodified to /sbin/init
, and the Cartesi-provided /sbin/init
script executes the command before gracefully halting the machine.
In many of the documentation examples, the utilities invoked from the command-line executed by a Cartesi Machine are in the default search path for executables. (This is setup by the Cartesi-provided /sbin/init
script itself.)
When in doubt, or when using your own executables installed in custom locations, make sure to invoke them by using their full paths (e.g., /bin/ls
or /bin/sh
instead of simply ls
and sh
.)
Interactive sessions
By default, the cartesi-machine
utility executes the Cartesi Machine in non-interactive mode.
Verifiable computations must always be run in non-interactive sessions.
User interaction with a Cartesi Machine via the console is, after all, not reproducible.
Nevertheless, during development, it is often convenient to directly interact with the emulator, as if using a computer console.
The command-line option -i
(short for --htif-console-getchar
) instructs the emulator to monitor the console for input, and to make this input available to the Linux kernel.
Typically, this option will be used in conjunction with the --
separator and the command sh
, causing the Cartesi-provided /sbin/init
script to drop into an interactive shell.
Interaction with the shell enables the exploration of the embedded Linux distribution from the inside.
Exiting the shell returns control back to /sbin/init
, which then gracefully halts the machine.
For example, if an interactive session is started with the following command
cartesi-machine -i -- sh
it drops into the shell.
Running the command ls /bin
causes the listing of directory /bin
to appear.
The command exit
causes the shell to exit.
The output is
Running in interactive mode!
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
cartesi-machine:~ # ls /bin
arch dmesg linux64 nuke stty
ash dnsdomainname ln pidof su
base32 dumpkmap login ping sync
base64 echo ls pipe_progress tar
busybox egrep lsattr printenv touch
cat false lsblk ps true
chattr fdflush mk_cmds pwd umount
chgrp fgrep mkdir resume uname
chmod findmnt mknod rm usleep
chown getopt mktemp rmdir vi
compile_et grep more run-parts watch
cp gunzip mount sed wdctl
cpio gzip mountpoint setarch zcat
cttyhack hostname mt setpriv
date kill mv setserial
dd link netstat sh
df linux32 nice sleep
cartesi-machine:~ # exit
Halted
Cycles: 188631827
When running in interactive mode, not even the final cycle count is reproducible. To avoid busy wait for new interactive input, the emulator sleeps from one Cartesi Machine timer interrupt to the next, skipping Cartesi Machine cycles forward so programs running inside to stay roughly in sync with wall-clock time outside. This dynamic balancing act is sure to vary between executions and across different computers.
Flash drives
The command-line option --flash-drive=label:<label>,filename:<filename>
can be used to add between 1 and 8 flash drives to the Cartesi Machine.
Here, the string <label>
is the label for the flash drive, and <filename>
points to an image file with the initial contents of the flash drive.
When the image file contains a valid file-system, the Cartesi-provided /sbin/init
script will automatically mount this file-system at /mnt/<label>
.
To enable transparency, Cartesi Machine flash drives are mapped into the machine's 64-bit address space.
The start and length are set, respectively, by the start:<number>
and length:<number>
parameters to --flash-drive
.
By default, the start of the first flash drive (which typically holds the root file-system) is set to the beginning of the second half of the address space (i.e., at offset 263).
Additional flash drives are automatically spaced uniformly within that second half of the address space.
They are therefore separated by 260 bytes, which “should be enough separation for everyone”.
(The machine will fail to instantiate if there is any overlap between the ranges occupied by multiple drives.)
If the start
of any drive is specified, then the starts for all drives must be specified.
When the length
parameter is omitted, the cartesi-machine
utility automatically sets the size of a flash drive to match the size of its image file.
Because RISC-V uses 4KiB pages, image files must have a size multiple of 4KiB.
(The truncate
utility can be used to pad a file with zeros so its size is a multiple of 4KiB.)
For convenience, numbers can be specified in decimal or hexadecimal (e.g., 4096
or 0x1000
) and may include a suffix multiplier (i.e., Ki
to multiply by 210, Mi
to multiply by 220, and Gi
to multiply by 230).
They can also use the C programming language shift left notation to multiply by arbitrary powers of 2 (e.g. 1 << 24
meaning 224).
When the length
of a drive is specified, the filename
parameter can be omitted.
In that case, the drive starts in a pristine state: i.e., filled with zeros.
If, however, both length
and filename
are specified, then the length
must exactly match the size of image file referred to by the filename
parameter.
The positioning of flash drives in the machine's address space has implications on certain operations, discussed in detail under the blockchain perspective, that involve the manipulation of hashes of the Cartesi Machine state.
The preferred file-system type is ext2
.
This is because ext2
image files can be easily created with the genext2fs
command-line utility (available in Ubuntu as its own package), and manipulated with e2ls
, e2cp
, e2rm
, etc (command-line utilities available in Ubuntu from the e2tools
package).
These utilities come pre-installed in the playground image.
Support for ext4
is also enabled by default in the kernel.
(Support for additional file-systems can be enabled by modifying the configuration the Emulator SDK uses to produce linux.bin
in the kernel/
subdirectory.)
For example,
mkdir foo
echo "Hello world!" > foo/bar.txt
tar \
--sort=name \
--mtime="2022-01-01" \
--owner=1000 \
--group=1000 \
--numeric-owner \
-cf foo.tar \
--directory=foo .
genext2fs \
-f \
-b 1024 \
-a foo.tar \
foo.ext2
cartesi-machine \
--flash-drive="label:foo,filename:foo.ext2" \
-- "cat /mnt/foo/bar.txt"
Here, a flash drive with label foo
is initialized with the contents of an ext2
file-system in the image file foo.ext2
.
The genext2fs
command on its own would would produce a file-system that is not reproducible, in the sense that running it in different systems, or even running it twice in the same system may produce a different ./foo.ext2
file.
This is because the utility records modification times, user and group IDs, etc.
Worse still, it would traverse the files in the foo/
directory in an unspecified order, progressively adding them to the foo.ext2
file-system.
Using the -f
(faketime) option eliminates the modification times problem, but does nothing to fix the remaining issues.
Enter the tar
command.
It sorts all files before adding them to the archive.
It also allows us to specify the modification time, user and group IDS, etc.
The genext2fs
then takes the reproducible tar
file and creates a reproducible ext2
file-system from it.
The Cartesi-provided /sbin/init
mounts this as /mnt/foo
.
The command executed in the machine simply copies the contents of /mnt/foo/bar
to the terminal.
The output is
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Hello world!
Halted
Cycles: 72134050
Persistent flash drives
The emulator never modifies the ROM and RAM image files.
They are simply loaded into host memory and only this copy is exposed to changes caused by code executing in the target.
(The --dump-pmas
command-line option can be used to inspect the modified copies for debugging purposes. See below.)
By default, the emulator does not modify the image files for any of the flash drives either. However, since these image files can be very large, the emulator does not pre-allocate any host memory for flash drives. Instead, it uses the operating system's memory mapping capabilities. The operating system reads to host memory only those pages from the image file that are actually read by code executing in the target. (Naturally, when a state hash is requested, all image files are read from disk in their entirety and processed. See below.) These image files are mapped to host memory in a copy-on-write fashion. When code running in the target causes the emulator to write to a mapped image file, the operating system makes a copy of the page before modification and replaces the mapping to point to the fresh copy. The image files are never written to. For example, running the machine
cartesi-machine \
--flash-drive="label:foo,filename:foo.ext2" \
-- "ls /mnt/foo/*.txt && cp /mnt/foo/bar.txt /mnt/foo/baz.txt && ls /mnt/foo/*.txt"
produces the output
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
/mnt/foo/bar.txt
cp: can't create '/mnt/foo/baz.txt': Permission denied
Halted
Cycles: 72008644
indicating that the file-system was modified, at least from the perspective of the target.
However, inspecting the foo.ext2
image file from outside the emulator shows it is unchanged.
e2ls -al foo.ext2:*.txt
12 100644 501 20 13 30-Jun-2020 19:40 bar.txt
This behavior is appropriate when the flash drives will only be used as inputs.
For output flash drives, target changes to the drives must reflect on the associated image files.
For that purpose, the parameter shared
can be passed to command-line option --flash-drive
, causing the imaged files to be mapped to host memory in a shared fashion.
For example,
cartesi-machine \
--flash-drive="label:foo,filename:foo.ext2,shared" \
-- "ls /mnt/foo/*.txt && cp /mnt/foo/bar.txt /mnt/foo/baz.txt && ls /mnt/foo/*.txt"
produces exactly the same output as before.
However, the image file foo.ext2
has now indeed been modified.
e2ls -al foo.ext2:*.txt
12 100644 501 20 13 30-Jun-2020 19:40 bar.txt
13 100644 0 0 13 1-Jan-1970 00:00 baz.txt
Limiting execution
Typically, the cartesi-machine
utility only returns when the Cartesi Machine halts. For example, running
cartesi-machine
produces the output
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Nothing to do.
Halted
Cycles: 62388529
Here, the Cartesi-provided /sbin/init
simply reports there is nothing to do before halting gracefully.
This takes many millions of cycles to complete: time mostly spent initializing the Linux kernel.
The machine's processor includes a control and status register (CSR), named mcycle
, that starts at 0 and is incremented after every instruction cycle.
The maximum cycle can be specified with the command-line option --max-mcycle=<number>
.
For example, adding the --max-mcycle=46598940
command-line option
cartesi-machine --max-mcycle=46598940
produces the output
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
Cycles: 46598940
Note the execution was interrupted before the splash screen was even completed.
The ability to limit computation to an arbitrary number of cycles is fundamental to the verifiability of Cartesi Machines, as is explained in detail under the blockchain perspective.
Progress feedback
A target application can inform the host of its progress by using a Cartesi-specific /dev/yield
Linux device.
Within the target, the Linux device can be controlled in the command-line with the utility /opt/cartesi/bin/yield
, pre-installed in the root file-system rootfs.ext2
.
The progress feedback is accessed via the automatic progress <permil>
command-line option.
For example, during the execution of the loop,
cartesi-machine \
--htif-yield-automatic \
-- $'for i in $(seq 0 5 1000); do yield automatic progress $i; done'
the cartesi-machine
utility receives control back from the emulator at every iteration, when the target executes the yield
utility.
(The directory /opt/cartesi/bin/
is in the default search path for executable setup by /sbin/init
.)
If the --htif-yield-automatic
command-line option to cartesi-machine
is omitted, the emulator essentially ignores such yield requests from the target.
Each time cartesi-machine
receives control due to a yield, it prints a progress message (shown at 44% below) and resumes the emulator so it can continue working.
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Progress: 44.00
This feature is most useful when the emulator is controlled programmatically, via its Lua, C++, or gRPC interfaces, where Cartesi Machines typically run disconnected from the console.
In these situations, the progress device can be used to drive a dynamic user interface element that reassures users progress is being made during long, silent computations.
Its handling by cartesi-machine
, which does have access to the console, is simply to help with prototyping and debugging.
The protocols followed by the yield
utility to interact with the /dev/yield
driver and by the driver itself to communicate with the HTIF device are explained in detail under the target perspective.
In particular, the section explains the manual yield commands (enabled by the --htif-yield-manual
command-line option) needed for proper operation of Cartesi Rollups.
State hashes
The cartesi-machine
utility can also be used to print Cartesi Machine state hashes.
State hashes are Merkle tree root hashes of the entire 64-bit address space of the Cartesi Machine, where the leaves are aligned 64-bit words.
(See the Hash view of states for an explanation of Merkle trees.)
Since Cartesi Machines are transparent, the contents of this address space encompass the entire machine state, including all processor CSRs and general-purpose registers, the contents of RAM and ROM, of all flash drives, and of all other devices connected to the board.
State hashes therefore work as cryptographic signatures of the machine, and implicitly of the computation they are about to execute.
To obtain the state hash right before execution starts, use the command-line option --initial-hash
.
Conversely, to obtain the state hash right after execution is done, use the option --final-hash
.
For example,
cartesi-machine \
--max-mcycle=46598940 \
--initial-hash \
--final-hash
produces the output
0: 1392f3d52dcaa5b070fa1e91b377e8d31fecf242a5ab4e3a84cd191d5699e456
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
Cycles: 46598940
46598940: a00da7ebbe0d5d4b9fee481b9b8718fa4f91fb70bd791b5d984acb66f5c10db9
The initial state hash 1392f3d5...
is the Merkle tree root hash for the initial Cartesi Machine state.
Since Cartesi Machines are reproducible, the initial state hash also works as a promise on the result of the entire computation.
In other words, the “final state hash” a00da7eb...
is the “only” possible outcome for the --final-hash
at cycle 46598940, given the result of the --initial-hash
operation was 1392f3d5...
.
The scare quotes around “only” are pedantic. It is true that there are a multitude of machine states that produce the same state hash. After all, the Keccak-256 state hashes fit in 256-bits, whereas machine states can take gigabytes. There are therefore many more possible machine states than possible state hashes. By the pigeonhole principle, there must be multiple machines with the same hash (i.e., hash collisions). However, given only the state hash, finding a Cartesi Machine with that state hash should be virtually impossible. Given a Cartesi Machine and its state hash, finding a second (distinct) Cartesi Machine with the same state hash should also be virtually impossible. Even finding two different Cartesi Machines that have the same state hash (any hash) should be virtually impossible. Cryptographic hash functions, such as Keccak-256, were designed specifically to have these properties.
Allowing the machine to run until it halts
cartesi-machine \
--initial-hash \
--final-hash
produces instead the output
0: 1392f3d52dcaa5b070fa1e91b377e8d31fecf242a5ab4e3a84cd191d5699e456
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Nothing to do.
Halted
Cycles: 62388529
62388529: faa438df6e6dd027aab3710223852303964fe20f93af43c919120bc7ff3d27ba
Naturally, the initial state hash is the same as before.
However, the final state hash faa438df...
now pertains to cycle 62388529, where the machine is halted.
This is the “only” possible state hash for a halted machine that started from state hash 1392f3d5...
.
Persistent Cartesi Machines
At any point in their execution, Cartesi Machines can be stored to disk.
A stored machine can later be loaded to continue its execution from where it left off.
To store a machine to a given <directory>
, use the command-line option --store=<directory>
.
(In <directory>
, the %h
escape will be replaced by the state hash in hex.)
The machine is stored as it was right before cartesi-machine
returns to the command line.
For example, to store the machine corresponding to state hash a00da7eb...
cartesi-machine \
--max-mcycle=46598940 \
--store="machine-a00da7eb"
This command creates a directory machine-a00da7eb/
, containing a variety of files that allow the Cartesi Machine emulator to recreate a machine state.
Every image file is copied into the directory, so no external dependencies remain.
If the machine initialization involved large image files or a considerable amount of RAM, this operation may consume significant disk space. It will also take the time required by the copying of image files into the directory, and by the computation of the state hash.
If the directory already exists, the operation will fail.
(This prevents the overwriting of a Cartesi Machine by mistake.)
Once created, the directory can be compressed and transferred to other hosts.
To restore the corresponding Cartesi Machine, use the command-line option --load=<directory>
.
For example,
cartesi-machine \
--load="machine-a00da7eb" \
--initial-hash \
--final-hash
produces the output
Loading machine: please wait
46598940: a00da7ebbe0d5d4b9fee481b9b8718fa4f91fb70bd791b5d984acb66f5c10db9
\ / MACHINE
'
Nothing to do.
Halted
Cycles: 62388529
62388529: faa438df6e6dd027aab3710223852303964fe20f93af43c919120bc7ff3d27ba
Note that, other than --load
, no initialization command-line options were used.
These initializations were used to define the machine before it was stored: their values are implicitly encoded in the stored state.
The machine continues from where it left off, and reaches the same final state hash faa438df...
, as if it had never been interrupted.
Note also that the initial state hash a00da7eb...
after --load
matches the final state hash before --store
.
After all, they are state hashes concerning the state of the same machine at the same cycle.
In fact, --store
writes this state hash inside the directory, and --load
verifies that the state hash of the restored machine matches what it found in the directory.
The cartesi-machine-stored-hash
command-line utility can be used to extract the state hash from a stored Cartesi Machine.
The command
cartesi-machine-stored-hash machine-a00da7eb
produces the output
a00da7ebbe0d5d4b9fee481b9b8718fa4f91fb70bd791b5d984acb66f5c10db9
Running as root
Starting at version 4.0 of rootfs.ext2
, the Cartesi-provided /sbin/init
script runs the target application (or any initial command) as user uid=1000(dapp)
group gid=1000(dapp)
.
This can be seen by running the command:
cartesi-machine \
-- id
It shows the user and group are indeed dapp
.
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
uid=1000(dapp) gid=1000(dapp) groups=1000(dapp)
Halted
Cycles: 65938834
To instead run your target application as uid=0(root) gid=0(root)
, pass the parameter single=yes
:
cartesi-machine \
--append-rom-bootargs="single=yes" \
-- id
This produces the output:
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
uid=0(root) gid=0(root)
Halted
Cycles: 65904418
It shows the user and group are now root
.
Although running as root is not recommended, the feature can be used to perform setup tasks that require elevated permissions.
Cartesi Machine templates
Templates are one of the key uses for Cartesi Machines stored to disk. Cartesi Machine templates are machines in which the contents of one or more flash drives are still unknown. To put it another way, Cartesi Machine templates behave like functions whose parameters are the yet-to-be-defined contents of one or more flash drives.
As discussed in detail under the blockchain perspective, starting from template hashes, the hashes of the flash drives, and a small amount of additional information, it is possible to obtain the state hash of the instantiated template—the state hash for a Cartesi Machine with drives replaced by their actual contents. This is how a smart contract can specify a computation to be performed off-chain over arbitrary input. Starting from the template hash, and in possession of the flash drive hashes, it instantiates the template, generating the initial state hash for the corresponding Cartesi Machine.
As an example, consider a Cartesi Machine that operates as an arbitrary-precision arithmetic expression evaluator.
The machine will take the expression in text format, inside a raw input flash drive labelled input
, and will copy the output in text format into a raw output flash drive, labelled output
(shared
, of course, so the output persists after the emulator is done).
Raw flash drives are flash drives that do not contain file-systems.
Instead, they contain data in any application-specific format.
Inside the Cartesi Machine, the dd
or devio
command-line utilities can be used to read data from or write data to
raw flash drives, assuming they have permission to access the underlying block device.
To simplify the examples in the documentation, we will simply run them as root
.
(Note that this is not recommended in deployed applications.)
The bc
command-line utility is the perfect tool to evaluate the arithmetic expressions.
The command passed to cartesi-machine
below reads the contents of the raw input flash drive using the dd
command-line utility, extracts a zero-terminated string from it using a tiny Lua script run by the lua
interpreter, pipes the result to bc
, and finally uses dd
again to write its results to the raw output flash drive.
Here is the sample playground session
rm -f output.raw
truncate -s 4K output.raw
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--append-rom-bootargs="single=yes" \
--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)'
lua5.3 -e 'print((string.unpack("z", io.read("a"))))' < output.raw
Using the truncate
command-line utility, the session creates a 4KiB file output.raw
containing only zeros to serve as the output drive image.
Then, it creates the input.raw
file for use as the input drive image containing the expression 6*2^1024 + 3*2^512\n
to be evaluated.
This file is then padded with zeros to 4KiB in size by the truncate
utility.
The session then invokes the cartesi-machine
command-line utility to evaluate the expression.
The output of the cartesi-machine
command is
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Halted
Cycles: 85860002
Once the emulator returns, the session uses a tiny Lua script, run by the playground's lua5.3
Lua interpreter, to print the contents of the output drive, which reads
10786158809173895446375831144734148401707861873653839436405804869463\
96054833005778796250863934445216126720683279228360145952738612886499\
73495708458383684478649003115037698421037988831222501494715481595948\
96901677837132352593468675094844090688678579236903861342030923488978\
36036892526733668721977278692363075584
This is indeed the result of 6×21024+3×2512.
To create the template, simply omit the input and output image filenames.
This will cause the Cartesi Machine to assume both drives are filled with zeros.
Then, limit the computation with --max-mcycle=0
, to prevent the Cartesi Machine from running.
Finally, use the --store="calculator-template"
command-line option to store the Cartesi Machine template.
The --final-hash
command-line option prints the resulting template hash.
cartesi-machine \
--append-rom-bootargs="single=yes" \
--flash-drive="label:input,length:1<<12" \
--flash-drive="label:output,length:1<<12" \
--max-mcycle=0 \
--final-hash \
--store="calculator-template" \
-- $'dd status=none if=$(flashdrive input) | lua -e \'print((string.unpack("z", io.read("a"))))\' | bc | dd status=none of=$(flashdrive output)'
The result is as follows
Cycles: 0
0: bce76332fea7cd5a2cdd28a8e85937bded73364a6cd868c36e80d88804cf64f8
Storing machine: please wait
The directory calculator-template/
now contains the Cartesi Machine template.
And indeed, running
cartesi-machine-stored-hash calculator-template/
we can see from the output
370ffe6edd287bda120b6d6a3f6902e02dd1e3ca2cfaf5092970c847a38f0cb3
that the stored template hash is 370ffe6e...
.
Templates are typically used by programs that control the emulator with the C++, Lua, or gRPC interfaces.
The --replace-flash-drive=start:<start>,length:<length>,filename:<filename>
command-line option of the cartesi-machine
utility can be used to replace an existing flash drive right before a machine is run.
(The --replace-memory-range
command-line option is a synonym for --replace-flash-drive
.)
The flash drive to be replaced must be specified by its start
and length
.
(Labels do not identify flash drives, they only provide convenient names for partitions.)
This functionality can be used to test templates.
For example, the following command loads the calculator template, and replaces its pristine input drive with a drive containing the contents of the input.raw
file.
Then, it replaces the pristine output drive so the machine saves results in the file output.raw
.
rm -f output.raw
truncate -s 4K output.raw
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--load="calculator-template" \
--replace-flash-drive="start:0x9000000000000000,length:1<<12,filename:input.raw" \
--replace-flash-drive="start:0xA000000000000000,length:1<<12,filename:output.raw,shared"
lua5.3 -e 'print((string.unpack("z", io.read("a"))))' < output.raw
The result of running the command is, as expected,
10786158809173895446375831144734148401707861873653839436405804869463\
96054833005778796250863934445216126720683279228360145952738612886499\
73495708458383684478649003115037698421037988831222501494715481595948\
96901677837132352593468675094844090688678579236903861342030923488978\
36036892526733668721977278692363075584
State value proofs
The cartesi-machine
command-line utility can generate proofs concerning the contents of the machine state.
To generate a proof concerning the state as it is before the machine starts running, use the --initial-proof=address:<number>,log2_size:<number>[,filename=<filename>]
option.
For proofs concerning the state after the emulator is done, use --final-proof
instead.
In either case, the filename field is optional.
When provided, the proof will be written to the corresponding file.
Otherwise, the contents will be displayed on screen.
State value proofs are proofs that a given node in the Merkle tree of the Cartesi Machine state has a given label (i.e., a given associated hash).
Each Merkle tree node covers a contiguous range of the machine's 64-bit address space.
The size of a range is always a power of 2 (i.e., the <log2_size>
power of 2).
Since the leaves have size 8 (for 64-bits), the valid values for <log2_size>
are 3…64.
The range corresponding to each node starts at an <address>
that is a multiple of its size.
For example, to generate a proof that the Cartesi Machine template above indeed contains a pristine input drive, use the command line
cartesi-machine \
--load="calculator-template" \
--max-mcycle=0 \
--initial-hash \
--initial-proof="address:0x9000000000000000,log2_size:12,filename:pristine-input-proof"
Recall the first flash drive, the one with the rootfs.ext2
image file, is present by default, and is automatically placed at starting address 0x8000000000000000
.
The input flash drive is therefore the second drive.
It is automatically spaced by 260 bytes relative to the first drive, so that its starting address is 0x9000000000000000
.
The output of the command is
Loading machine: please wait
0: 370ffe6edd287bda120b6d6a3f6902e02dd1e3ca2cfaf5092970c847a38f0cb3
Cycles: 0
In addition, the pristine-input-proof
file now contains a JSON structure with the requested proof
{
"target_address": 10376293541461622784,
"log2_target_size": 12,
"log2_root_size": 64,
"target_hash": "d8b96e5b7f6f459e9cb6a2f41bf276c7b85c10cd4662c04cbbb365434726c0a0",
"sibling_hashes": [
"7674c8ed1dcd5768ee452d76e093e93bcb003cf639169c042ec3ea100eb7f51f",
"785b01e980fc82c7e3532ce81876b778dd9f1ceeba4478e86411fb6fdd790683",
"41187451383460762c06d1c8a72b9cd718866ad4b689e10c9a8c38fe5ef045bd",
"5ba02fe28593d850f733209bb22f04bbc5537b30b206fd31eb1cb388b54cec29",
"6d4fe42ea8d1a120c03cf9c50622c2afe4acb0dad98fd62d07ab4e828a94495f",
"ced9a87b2a6a87e70bf251bb5075ab222138288164b2eda727515ea7de12e249",
"909efab43c42c0cb00695fc7f1ffe67c75ca894c3c51e1e5e731360199e600f6",
"414217a618ccb14caa9e92e8c61673afc9583662e812adba1f87a9c68202d60e",
"fa6a452470f8d645bebfad9779594fc0784bb764a22e3a8181d93db7bf97893c",
"27a31085634b6ec78864b6d8201c7e93903d75815067e378289a3d072ae172da",
"f75c40174a91f9ae6b8647854a156029f0b88b83316663ce574a4978277bb6bb",
"06cc0a6fd12230ea586dae83019fb9e06034ed2803c98d554b93c9a52348caff",
"712e55805248b92e8677d90f6d284d1d6ffaff2c430657042a0e82624fa3717b",
"214947127506073e44d5408ba166c512a0b86805d07f5a44d3c41706be2bc15e",
"7bdd613713ada493cc17efd313206380e6a685b8198475bbd021c6e9d94daab2",
"5ea69e2f7c7d2ccc85b7e654c07e96f0636ae4044fe0e38590b431795ad0f864",
"c61ce68b20307a1a81f71ca645b568fcd319ccbb5f651e87b707d37c39e15f94",
"76e1424883a45ec49d497ddaf808a5521ca74a999ab0b3c7aa9c80f85e93977e",
"91b4feecbe1789717021a158ace5d06744b40f551076b67cd63af60007f8c998",
"455306d01081bc3384f82c5fb2aacaa19d89cdfa46cc916eac61121475ba2e61",
"a1611f1b276b26530f58d7247df459ce1f86db1d734f6f811932f042cee45d0e",
"29927c21dd71e3f656826de5451c5da375aadecbd59d5ebf3a31fae65ac1b316",
"5d8b6aa5934f817252c028c90f56d413b9d5d10d89790707dae2fabb249f6499",
"8dff81e014ce25f2d132497923e267363963cdf4302c5049d63131dc03fd95f6",
"bec80f4f5d1daa251988826cef375c81c36bf457e09687056f924677cb0bccf9",
"847a230d34dfb71ed56f2965a7f6c72e6aa33c24c303fd67745d632656c5ef90",
"e63624cbd316a677cad529bbe4e97b9144e4bc06c4afd1de55dd3e1175f90423",
"a57b9796fdcb2eda87883c2640b072b140b946bfdf6575cacc066fdae04f6951",
"85d8820921ff5826148b60e6939acd7838e1d7f20562bff8ee4b5ec4a05ad997",
"1373a814641d6a1dcef97b883fee61bb84fe60a3409340217e629cc7e4dcc93b",
"d5d218ef5a296dda8ddc355f3f50c3d0b660a51dfa4d98a6a5a33564556cf83c",
"3abc751df07437834ba5acb32328a396994aebb3c40f759c2d6d7a3cb5377e55",
"674857e543d1d5b639058dd908186597e366ad5f3d9c7ceaff44d04d1550b8d3",
"21e2d8fa914e2559bb72bf0ab78c8ab92f00ef0d0d576eccdd486b64138a4172",
"4fd085aceaa7f542d787ee4196d365f3cc566e7bbcfbfd451230c48d804c017d",
"3c5126b9c7e33c8e5a5ac9738b8bd31247fb7402054f97b573e8abb9faad219f",
"fdc242788f654b57a4fb32a71b335ef6ff9a4cc118b282b53bdd6d6192b7a82c",
"fedc0d0dbbd855c8ead673544899b0960e4a5a7ca43b4ef90afe607de7698cae",
"766c5e8ac9a88b35b05c34747e6507f6b044ab66180dc76ac1a696de03189593",
"3e2337b715f6ac9a6a272622fdc2d67fcfe1da3459f8dab4ed7e40a657a54c36",
"f065ec220c1fd4ba57e341261d55997f85d66d32152526736872693d2b437a23",
"13e466a8935afff58bb533b3ef5d27fba63ee6b0fd9e67ff20af9d50deee3f8b",
"27d86025599a41233848702f0cfc0437b445682df51147a632a0a083d2d38b5e",
"99af665835aabfdc6740c7e2c3791a31c3cdc9f5ab962f681b12fc092816a62f",
"2b573c267a712a52e1d06421fe276a03efb1889f337201110fdc32a81f8e1524",
"7a71f6ee264c5d761379b3d7d617ca83677374b49d10aec50505ac087408ca89",
"f7549f26cc70ed5e18baeb6c81bb0625cb95bb4019aeecd40774ee87ae29ec51",
"2122e31e4bbd2b7c783d79cc30f60c6238651da7f0726f767d22747264fdb046",
"91e3eee5ca7a3da2b3053c9770db73599fb149f620e3facef95e947c0ee860b7",
"63e8806fa0d4b197a259e8c3ac28864268159d0ac85f8581ca28fa7d2c0c03eb",
"c9695393027fb106a8153109ac516288a88b28a93817899460d6310b71cf1e61",
"d8b96e5b7f6f459e9cb6a2f41bf276c7b85c10cd4662c04cbbb365434726c0a0"
],
"root_hash": "370ffe6edd287bda120b6d6a3f6902e02dd1e3ca2cfaf5092970c847a38f0cb3"
}
The root_hash
value 370ffe6e...
is the expected initial state hash seen in the output of the cartesi-machine
command.
The address
value 10376293541461622784
is the same as 0x9000000000000000
in decimal.
The log2_size
value 12
refers to the size of the 4KiB input drive.
The target_hash
value d8b96e5b7...
in the proof gives the hash of the input drive.
The hash of the input drive can be also computed externally with the merkle-tree-hash
command-line utility.
The utility can produce the hash of any file with a power-of-2 size.
The --tree-log2-size=<log2_size>
option specifies the size.
If an input file is smaller than the specified size, the utility assumes the missing data is composed entirely of bytes 0.
The utility deals efficiently with zero paddings of any size because pristine hashes for all power-of-2 sizes can be precomputed.
For example, to quickly generate the hash for a pristine input with 4KiB size, run
head -c 0 | merkle-tree-hash --tree-log2-size=12
to obtain
d8b96e5b7f6f459e9cb6a2f41bf276c7b85c10cd4662c04cbbb365434726c0a0
As expected, the hash values match.
The sibling_hashes
array contains the hashes of the siblings to all nodes in the path from the root all the way down to the target node (excluding the root, which has no sibling).
In a process explained in the blockchain perspective, using the address
field, the target_hash
hash, and the sibling_hashes
array, it is possible to go up the tree computing the hashes along the path, until the root hash is produced.
If the root hash obtained by this process matches the expected root hash, the proof is valid.
Otherwise, something is amiss.
(Incidentally, from the hash of its sibling, the last entry in sibling_hashes
, it is possible to ascertain that the neighboring range to the input drive also contains 4KiB of bytes 0.)
To compute the hash for the desired input.raw
file with contents 6*2^1024 + 3*2^512\n
, padded with zeros, run
echo "6*2^1024 + 3*2^512" | merkle-tree-hash --tree-log2-size=12
to obtain
2c92c99754e85e3e2a29edd84228a62b051f9f55a5563f8decc7c6d5d9d8ef64
Using a process similar to the proof verification described above, it is possible to go up the Merkle tree for the template using the sibling_hashes
array in the proof, but starting from the hash 2c92c997...
of the desired input.raw
image rather than hash d8b96e5b...
of the template's pristine drive.
The result is the initial state hash for the instantiated template: the same that can be seen in the initial state hash produced by the cartesi-machine
command-line
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--load="calculator-template" \
--replace-flash-drive="start:0x9000000000000000,length:1<<12,filename:input.raw" \
--initial-hash \
--initial-proof="address:0x9000000000000000,log2_size:12,filename:input-proof" \
--max-mcycle=0
The contents of the input-proof
are
{
"target_address": 10376293541461622784,
"log2_target_size": 12,
"log2_root_size": 64,
"target_hash": "2c92c99754e85e3e2a29edd84228a62b051f9f55a5563f8decc7c6d5d9d8ef64",
"sibling_hashes": [
"53bfe1357f9416dd72a9e443ed0484080380a5865df1507b6bff8f1f40b29abe",
"785b01e980fc82c7e3532ce81876b778dd9f1ceeba4478e86411fb6fdd790683",
"41187451383460762c06d1c8a72b9cd718866ad4b689e10c9a8c38fe5ef045bd",
"5ba02fe28593d850f733209bb22f04bbc5537b30b206fd31eb1cb388b54cec29",
"6d4fe42ea8d1a120c03cf9c50622c2afe4acb0dad98fd62d07ab4e828a94495f",
"ced9a87b2a6a87e70bf251bb5075ab222138288164b2eda727515ea7de12e249",
"909efab43c42c0cb00695fc7f1ffe67c75ca894c3c51e1e5e731360199e600f6",
"414217a618ccb14caa9e92e8c61673afc9583662e812adba1f87a9c68202d60e",
"fa6a452470f8d645bebfad9779594fc0784bb764a22e3a8181d93db7bf97893c",
"27a31085634b6ec78864b6d8201c7e93903d75815067e378289a3d072ae172da",
"f75c40174a91f9ae6b8647854a156029f0b88b83316663ce574a4978277bb6bb",
"06cc0a6fd12230ea586dae83019fb9e06034ed2803c98d554b93c9a52348caff",
"712e55805248b92e8677d90f6d284d1d6ffaff2c430657042a0e82624fa3717b",
"214947127506073e44d5408ba166c512a0b86805d07f5a44d3c41706be2bc15e",
"7bdd613713ada493cc17efd313206380e6a685b8198475bbd021c6e9d94daab2",
"5ea69e2f7c7d2ccc85b7e654c07e96f0636ae4044fe0e38590b431795ad0f864",
"c61ce68b20307a1a81f71ca645b568fcd319ccbb5f651e87b707d37c39e15f94",
"76e1424883a45ec49d497ddaf808a5521ca74a999ab0b3c7aa9c80f85e93977e",
"91b4feecbe1789717021a158ace5d06744b40f551076b67cd63af60007f8c998",
"455306d01081bc3384f82c5fb2aacaa19d89cdfa46cc916eac61121475ba2e61",
"a1611f1b276b26530f58d7247df459ce1f86db1d734f6f811932f042cee45d0e",
"29927c21dd71e3f656826de5451c5da375aadecbd59d5ebf3a31fae65ac1b316",
"5d8b6aa5934f817252c028c90f56d413b9d5d10d89790707dae2fabb249f6499",
"8dff81e014ce25f2d132497923e267363963cdf4302c5049d63131dc03fd95f6",
"bec80f4f5d1daa251988826cef375c81c36bf457e09687056f924677cb0bccf9",
"847a230d34dfb71ed56f2965a7f6c72e6aa33c24c303fd67745d632656c5ef90",
"e63624cbd316a677cad529bbe4e97b9144e4bc06c4afd1de55dd3e1175f90423",
"a57b9796fdcb2eda87883c2640b072b140b946bfdf6575cacc066fdae04f6951",
"85d8820921ff5826148b60e6939acd7838e1d7f20562bff8ee4b5ec4a05ad997",
"1373a814641d6a1dcef97b883fee61bb84fe60a3409340217e629cc7e4dcc93b",
"d5d218ef5a296dda8ddc355f3f50c3d0b660a51dfa4d98a6a5a33564556cf83c",
"3abc751df07437834ba5acb32328a396994aebb3c40f759c2d6d7a3cb5377e55",
"674857e543d1d5b639058dd908186597e366ad5f3d9c7ceaff44d04d1550b8d3",
"21e2d8fa914e2559bb72bf0ab78c8ab92f00ef0d0d576eccdd486b64138a4172",
"4fd085aceaa7f542d787ee4196d365f3cc566e7bbcfbfd451230c48d804c017d",
"3c5126b9c7e33c8e5a5ac9738b8bd31247fb7402054f97b573e8abb9faad219f",
"fdc242788f654b57a4fb32a71b335ef6ff9a4cc118b282b53bdd6d6192b7a82c",
"fedc0d0dbbd855c8ead673544899b0960e4a5a7ca43b4ef90afe607de7698cae",
"766c5e8ac9a88b35b05c34747e6507f6b044ab66180dc76ac1a696de03189593",
"3e2337b715f6ac9a6a272622fdc2d67fcfe1da3459f8dab4ed7e40a657a54c36",
"f065ec220c1fd4ba57e341261d55997f85d66d32152526736872693d2b437a23",
"13e466a8935afff58bb533b3ef5d27fba63ee6b0fd9e67ff20af9d50deee3f8b",
"27d86025599a41233848702f0cfc0437b445682df51147a632a0a083d2d38b5e",
"99af665835aabfdc6740c7e2c3791a31c3cdc9f5ab962f681b12fc092816a62f",
"2b573c267a712a52e1d06421fe276a03efb1889f337201110fdc32a81f8e1524",
"7a71f6ee264c5d761379b3d7d617ca83677374b49d10aec50505ac087408ca89",
"f7549f26cc70ed5e18baeb6c81bb0625cb95bb4019aeecd40774ee87ae29ec51",
"2122e31e4bbd2b7c783d79cc30f60c6238651da7f0726f767d22747264fdb046",
"91e3eee5ca7a3da2b3053c9770db73599fb149f620e3facef95e947c0ee860b7",
"63e8806fa0d4b197a259e8c3ac28864268159d0ac85f8581ca28fa7d2c0c03eb",
"c9695393027fb106a8153109ac516288a88b28a93817899460d6310b71cf1e61",
"d8b96e5b7f6f459e9cb6a2f41bf276c7b85c10cd4662c04cbbb365434726c0a0"
],
"root_hash": "8cb551c358a7cb0680d0f288b27efdbe17dbb65dbca85ab90ead446f1037f018"
}
The target_hash
value 2c92c997...
reflects the hash computed for the input, whereas root_hash
value 8cb551c3...
differs from 370ffe6e...
obtained for template, as expected.
Moreover, the sibling_hashes
entries in the template Cartesi Machine and in the instantiated Cartesi Machine remain the same, reflecting the fact that there were no other changes in the machine's initial state.
Another useful proof is the one for the output drive, once the machine is halted. To obtain this proof, run
rm -f output.raw
truncate -s 4K output.raw
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--load="calculator-template" \
--replace-flash-drive="start:0x9000000000000000,length:1<<12,filename:input.raw" \
--replace-flash-drive="start:0xa000000000000000,length:1<<12,filename:output.raw,shared" \
--final-hash \
--final-proof="address:0xa000000000000000,log2_size:12,filename:output-proof"
This produces the output
Loading machine: please wait
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Halted
Cycles: 85941645
85941645: bbf5015f59cf06252e160b600ceb6b624935333a1a3561d8684fb8fd696cc3fd
The contents of the output-proof
are
{
"target_address": 11529215046068469760,
"log2_target_size": 12,
"log2_root_size": 64,
"target_hash": "b15a6b8aab8a423c725f9ad55fd46c4481ba91008f3a01593192de37a7a41565",
"sibling_hashes": [
"66ce8ce2fe6a2a099af9407a1f407eabf29c53fb38825dd6042e6d92ea86786e",
"785b01e980fc82c7e3532ce81876b778dd9f1ceeba4478e86411fb6fdd790683",
"e989736814d39b0a523b034d86f8add116cb290b0e6e5b11688b216408dc070f",
"6d1ab973982c7ccbe6c1fae02788e4422ae22282fa49cbdb04ba54a7a238c6fc",
"6d4fe42ea8d1a120c03cf9c50622c2afe4acb0dad98fd62d07ab4e828a94495f",
"ced9a87b2a6a87e70bf251bb5075ab222138288164b2eda727515ea7de12e249",
"909efab43c42c0cb00695fc7f1ffe67c75ca894c3c51e1e5e731360199e600f6",
"414217a618ccb14caa9e92e8c61673afc9583662e812adba1f87a9c68202d60e",
"fa6a452470f8d645bebfad9779594fc0784bb764a22e3a8181d93db7bf97893c",
"27a31085634b6ec78864b6d8201c7e93903d75815067e378289a3d072ae172da",
"f75c40174a91f9ae6b8647854a156029f0b88b83316663ce574a4978277bb6bb",
"06cc0a6fd12230ea586dae83019fb9e06034ed2803c98d554b93c9a52348caff",
"712e55805248b92e8677d90f6d284d1d6ffaff2c430657042a0e82624fa3717b",
"214947127506073e44d5408ba166c512a0b86805d07f5a44d3c41706be2bc15e",
"7bdd613713ada493cc17efd313206380e6a685b8198475bbd021c6e9d94daab2",
"5ea69e2f7c7d2ccc85b7e654c07e96f0636ae4044fe0e38590b431795ad0f864",
"c61ce68b20307a1a81f71ca645b568fcd319ccbb5f651e87b707d37c39e15f94",
"76e1424883a45ec49d497ddaf808a5521ca74a999ab0b3c7aa9c80f85e93977e",
"91b4feecbe1789717021a158ace5d06744b40f551076b67cd63af60007f8c998",
"455306d01081bc3384f82c5fb2aacaa19d89cdfa46cc916eac61121475ba2e61",
"a1611f1b276b26530f58d7247df459ce1f86db1d734f6f811932f042cee45d0e",
"29927c21dd71e3f656826de5451c5da375aadecbd59d5ebf3a31fae65ac1b316",
"5d8b6aa5934f817252c028c90f56d413b9d5d10d89790707dae2fabb249f6499",
"8dff81e014ce25f2d132497923e267363963cdf4302c5049d63131dc03fd95f6",
"bec80f4f5d1daa251988826cef375c81c36bf457e09687056f924677cb0bccf9",
"847a230d34dfb71ed56f2965a7f6c72e6aa33c24c303fd67745d632656c5ef90",
"e63624cbd316a677cad529bbe4e97b9144e4bc06c4afd1de55dd3e1175f90423",
"a57b9796fdcb2eda87883c2640b072b140b946bfdf6575cacc066fdae04f6951",
"85d8820921ff5826148b60e6939acd7838e1d7f20562bff8ee4b5ec4a05ad997",
"1373a814641d6a1dcef97b883fee61bb84fe60a3409340217e629cc7e4dcc93b",
"d5d218ef5a296dda8ddc355f3f50c3d0b660a51dfa4d98a6a5a33564556cf83c",
"3abc751df07437834ba5acb32328a396994aebb3c40f759c2d6d7a3cb5377e55",
"674857e543d1d5b639058dd908186597e366ad5f3d9c7ceaff44d04d1550b8d3",
"21e2d8fa914e2559bb72bf0ab78c8ab92f00ef0d0d576eccdd486b64138a4172",
"4fd085aceaa7f542d787ee4196d365f3cc566e7bbcfbfd451230c48d804c017d",
"3c5126b9c7e33c8e5a5ac9738b8bd31247fb7402054f97b573e8abb9faad219f",
"fdc242788f654b57a4fb32a71b335ef6ff9a4cc118b282b53bdd6d6192b7a82c",
"fedc0d0dbbd855c8ead673544899b0960e4a5a7ca43b4ef90afe607de7698cae",
"766c5e8ac9a88b35b05c34747e6507f6b044ab66180dc76ac1a696de03189593",
"3e2337b715f6ac9a6a272622fdc2d67fcfe1da3459f8dab4ed7e40a657a54c36",
"f065ec220c1fd4ba57e341261d55997f85d66d32152526736872693d2b437a23",
"13e466a8935afff58bb533b3ef5d27fba63ee6b0fd9e67ff20af9d50deee3f8b",
"27d86025599a41233848702f0cfc0437b445682df51147a632a0a083d2d38b5e",
"99af665835aabfdc6740c7e2c3791a31c3cdc9f5ab962f681b12fc092816a62f",
"2b573c267a712a52e1d06421fe276a03efb1889f337201110fdc32a81f8e1524",
"7a71f6ee264c5d761379b3d7d617ca83677374b49d10aec50505ac087408ca89",
"f7549f26cc70ed5e18baeb6c81bb0625cb95bb4019aeecd40774ee87ae29ec51",
"2122e31e4bbd2b7c783d79cc30f60c6238651da7f0726f767d22747264fdb046",
"91e3eee5ca7a3da2b3053c9770db73599fb149f620e3facef95e947c0ee860b7",
"63e8806fa0d4b197a259e8c3ac28864268159d0ac85f8581ca28fa7d2c0c03eb",
"c9695393027fb106a8153109ac516288a88b28a93817899460d6310b71cf1e61",
"d8b96e5b7f6f459e9cb6a2f41bf276c7b85c10cd4662c04cbbb365434726c0a0"
],
"root_hash": "bbf5015f59cf06252e160b600ceb6b624935333a1a3561d8684fb8fd696cc3fd"
}
Note how the root_hash
field in the proof matches the final state hash bbf5015f...
output by the cartesi-machine
command-line utility.
To see that the target_hash
field matches the output.raw
drive, use the merkle-tree-hash
command-line utility
merkle-tree-hash --tree-log2-size=12 < output.raw
to obtain
b15a6b8aab8a423c725f9ad55fd46c4481ba91008f3a01593192de37a7a41565
The cartesi-machine
command-line utility accepts an arbitrary number of --initial-proof
and --final-proof
parameters.
They are computed one-by-one, and either printed or stored in the specified files, as requested.
To read more about proofs, refer to the blockchain perspective.
Remote Cartesi Machines
The cartesi-machine
command-line utility, as used until now, has always instantiated its own local Cartesi Machine.
However, it can also be used to control a remote Cartesi Machine.
Remote Cartesi Machines are managed by the remote-cartesi-machine
server.
The server exposes a gRPC interface through which the cartesi-machine
command-line utility (or any other software) can control the machine remotely.
To avoid confusion, it is best to run the server and client in separate shells in the playground container.
Leaving the existing shell for the client, open a separate shell for the server (For example, by running docker exec -it <container-name> /bin/bash
), then run
remote-cartesi-machine \
--server-address=localhost:8080
The --server-address=<address>
command-line option specifies the address and port the server will listen to.
In this case, since we selected localhost:8080
, the client must run in the same container in order to communicate with the server.
To be accessible from outside the container, the --server-address
option would have to refer to an address and port that were exposed by the container.
To instruct the cartesi-machine
command-line utility to connect with the server, add the command-line option
--remote-address=<address>
to specify the remote server to connect to, and the --checkin-address=<address>
option to specify an address the server will use to notify the client when it is ready.
The option --remote-shutdown
causes the server to be shutdown by the client when the client exits.
(Otherwise, the server will remain available for the next client.)
All other options work as before.
Keep in mind that any image files referred to by an option passed to the command-line utility cartesi-machine
must be accessible to the remote-cartesi-machine
server (and not necessarily to the client).
Additionally, terminal output for the Cartesi Machine instantiated by the server will appear in the remote shell where the server was run (not the client's shell).
Terminal input, when enabled, must also happen via the remote shell.
With this in mind, running the command in the client shell
cartesi-machine \
--remote-address=localhost:8080 \
--checkin-address=localhost:8081 \
--remote-shutdown
produces the following output on the client shell
Listening for checkin at 'localhost:8081'
Connecting to remote cartesi machine at 'localhost:8080'
Connected: remote version is 0.6.0
Halted
Cycles: 62388529
Shutting down remote cartesi machine
and the following output on the server shell
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Nothing to do.
The client first binds to the check-in address, connects to the remote address, and prints out the version returned by the server.
It then asks the server to instantiate a machine (by sending the configuration over) and run it.
The machine that runs in the server prints out the splash screen, boots Linux, and cedes control to the
Cartesi-provided /sbin/init
script.
The /sbin/init
script figures out there is nothing to do and halts the machine.
The client detects the machine is halted and shuts down the server, as requested.
When it is desirable to leave the server running and preserve the instantiated machine, omit the --remote-shutdown
command-line option and add the --no-remote-destroy
.
For example, assuming the remote server has just been run:
remote-cartesi-machine \
--server-address=localhost:8080
use the cartesi-machine
command-line utility to instantiate and run a Cartesi Machine for 2^2O cycles:
cartesi-machine \
--remote-address=localhost:8080 \
--checkin-address=localhost:8081 \
--no-remote-destroy \
--max-mcycle=1Mi \
-- echo "Still here!"
The client shell shows:
Listening for checkin at 'localhost:8081'
Connecting to remote cartesi machine at 'localhost:8080'
Connected: remote version is 0.6.0
Cycles: 1048576
Shutting down remote cartesi machine
To continue execution of the same Cartesi Machine until it ends, rather than instantiating a new one, use the cartesi-machine
command-line utility with the option --no-remote-create
:
cartesi-machine \
--remote-address=localhost:8080 \
--checkin-address=localhost:8081 \
--no-remote-create
The client shell now shows:
Listening for checkin at 'localhost:8081'
Connecting to remote cartesi machine at 'localhost:8080'
Connected: remote version is 0.6.0
Halted
Cycles: 66003491
Shutting down remote cartesi machine
The server shell shows the execution of both sessions:
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Still here!
Remote Cartesi Machines have one ability that local Cartesi Machines lack: they can create a state snapshot they can later rollback to. Snapshots and rollbacks are the foundation on which the state inspection mechanism of Rolling Cartesi Machines is based, as well as the feature that enables the rejection of inputs to state advances. Both situations require the state of the Rolling Cartesi Machine to remain unchanged. Before Rolling Cartesi Machines were introduced, snapshots and rollbacks were used exclusively to enable efficient dispute resolution.
Rolling Cartesi Machines
Applications involving Rolling Cartesi Machines are not designed to interact with the cartesi-machine
command-line utility.
Instead, they rely on a variety of software components that allow a front-end to post to the blockchain requests to advance the state of the server, that poll the blockchain for advance-state requests posted by others so a local copy of the server can be kept in sync, and that allow the front-end to inspect the state of the server.
Nevertheless, in debugging or prototyping tasks, the cartesi-machine
command-line utility can simulate the external environment that a target application (running inside a Rolling Cartesi Machine) would encounter in production.
To use this functionality, the developer creates a sequence of advance-state requests as numbered files, or a single inspect-state request as a file, and instructs the cartesi-machine
command-line utility to feed them to the target application.
As each request is processed, the utility stores the responses as separate files.
An advance-state request is composed by input metadata and an input. The input metadata include a variety of fields that are important for the operation of Cartesi Rollups (message sender, block number, timestamp, epoch index, input index). The input contains only an application-specific payload. Recall that, as responses, the target application can issue vouchers, notices, reports, and exceptions. In contrast, an inspect state request carries only a query and, as response, produces only reports and exceptions. Like the input to an advance-state request, the query in an inspect-state request consists of an application-specific payload.
Target applications running inside Rolling Cartesi Machines communicate with the outside world using the Cartesi-specific /dev/rollup
Linux device.
The device can be accessed directly, via ioctl
calls to the driver, via the /opt/cartesi/bin/rollup
command-line utility, or by means of an HTTP service.
The /dev/rollup
device owns a number of memory ranges that are used to pass data in and out of the machine, and uses the /dev/yield
device to return control to the host and notify it of important events.
In a nutshell, the process is as follows.
When the target application attempts to obtain the next request from the device, the /dev/rollup
device uses the /dev/yield
device to issue a manual yield command returns control to the host (in our case, the cartesi-machine
command-line utility).
The host then copies the next request to the appropriate memory ranges and resumes the machine, so the device can pass the request over to the target application for processing.
When the target application asks the device to output data (a voucher, notice, report, or exception), the /dev/rollup
device copies the data to the appropriate memory ranges, then uses the /dev/yield
device to issue an automatic yield command that notifies the host that a new output is available.
The host can then collect the output (in our case, saving it to files or printing it to the terminal) and resume the machine so the target application can continue processing the request.
When debugging production code, developers can obtain from Cartesi Rollups, as files, the input metadata and input associated to each advance-state request, so the sequence can be replayed locally in the command line. When prototyping, developers can create their own files simulating requests that test the behavior of their target application under customized conditions.
Encoding requests
The rollup-memory-range
command-line utility can encode input metadata, inputs, queries, vouchers, notices, and reports to files.
For example, the following commands create the input metadata and input files for two distinct advance-state requests
(saved as epoch-0-input-metadata-0.bin
, epoch-0-input-0.bin
, epoch-0-input-metadata-1.bin
, and epoch-0-input-1.bin
), and one query for an inspect state request (saved as query.bin
):
for i in 1 2; do
rollup-memory-range encode input-metadata > epoch-0-input-metadata-$i.bin <<-EOF
{
"msg_sender": $(printf '"0x%040d"' $i)
"block_number": 0,
"time_stamp": 0,
"epoch_index": 0,
"input_index": $i
}
EOF
rollup-memory-range encode input > epoch-0-input-$i.bin <<-EOF
{
"payload": "hello from input $i!"
}
EOF
done
rollup-memory-range encode input > query.bin <<-EOF
{
"payload": "hello from query!"
}
EOF
Listing the files created
ls *.bin
We see
epoch-0-input-1.bin epoch-0-input-metadata-1.bin query.bin
epoch-0-input-2.bin epoch-0-input-metadata-2.bin
Running a simple target application
The command-line utility /opt/cartesi/bin/ioctl-echo-loop
, pre-installed in the root file-system rootfs.ext2
, is a simple Rolling Cartesi Machine application that merely outputs (as vouchers, notices, or reports) the payload it receives as the input to advance-state or query to inspect-state.
It is perfect to showcase the input-output mechanism.
Since Rolling Cartesi Machines rely on the snapshot/rollback functionality of Remote Cartesi Machines, running a Rolling Cartesi Machine in the command line requires using the remote-cartesi-machine
server in combination with the cartesi-machine
client.
With the files just created by rollup-memory-ranges
in the working directory
ls *.bin
epoch-0-input-1.bin epoch-0-input-metadata-1.bin query.bin
epoch-0-input-2.bin epoch-0-input-metadata-2.bin
run the remote server with the command
remote-cartesi-machine \
--server-address=localhost:8080
Then, from a different shell into the same container, run the client with the command
cartesi-machine \
--remote-address=localhost:8080 \
--checkin-address=localhost:8081 \
--remote-shutdown \
--rollup \
--rollup-advance-state=epoch_index:0,input_index_begin:1,input_index_end:3,hashes \
--rollup-inspect-state \
-- ioctl-echo-loop --vouchers=1 --notices=1 --reports=1 --reject=1
The command-line option --rollup
is a shortcut that combines a variety of settings needed by the Rolling Cartesi Machine functionality.
The command-line option --rollup-advance-state
instructs the utility to look for input metadata and input files to use in a sequence of advance-state requests.
By default, the name of the input metadata and input files are epoch-%e-input-metadata-%i.bin
and epoch-%e-input-%i.bin
, respectively, where %e
is replaced by the epoch index and %i
by the input index.
The epoch index is given by the parameter epoch_index=<number>
, and the input index progressively takes the values in the (open ended) range given by parameters input_index_begin=<number>
and input_index_end=<number>
.
In the example, two advance-state requests will be performed for epoch 0: one for input 1 and one for input 2.
The file names therefore match those of the encoded files present in the working directory.
The parameter hashes
instructs the utility to print the state hashes between state advances, both before and after it writes the request data to the appropriate memory ranges in the machine state.
The command-line option --rollup-inspect-state
causes the utility to create an inspect-state request right after all advance-state requests have been carried out (if any).
By default, the query is loaded from a file query.bin
.
Finally, the command-line for ioctl-echo-loop
instructs it to echo each payload as one voucher, one notice, and one report, and to reject the input with index 1.
As a result of these commands, the server shell simply shows the splash screen and a message from ioctl-echo-loop
declaring what will be echoed:
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Echoing as 1 voucher copies, 1 notice copies, and 1 report copies
The client shell shows a lot more activity:
Listening for checkin at 'localhost:8081'
Connecting to remote cartesi machine at 'localhost:8080'
Connected: remote version is 0.6.0
Manual yield rx-accepted (0x100000000 data)
...
The client starts by printing information about the remote server it connected to. It then runs the machine in a loop, occasionally transferring information in and out.
The first manual yield rx-accepted
signals the point at which the target application attempted to obtain the first request.
In other words, the application is ready.
...
Epoch 0 before input 1
65523739: 3b038dea784432b1339830cadb5ec4c6ed6fcd49dfb92576daa0243a70039d3a
Loading epoch-0-input-metadata-1.bin
Loading epoch-0-input-1.bin
65523739: e994e56a784c99f5da16df152731cc62f3f99ca05f65ac0f8ffa63605c8f96d2
Automatic yield tx-voucher (0x300000000 data)
Cycles: 65606624
Storing epoch-0-input-1-voucher-0.bin
Automatic yield tx-notice (0x400000000 data)
Cycles: 65619533
Storing epoch-0-input-1-notice-0.bin
Automatic yield tx-report (0x500000000 data)
Cycles: 65620606
Storing epoch-0-input-1-report-0.bin
Manual yield rx-rejected (0x200000000 data)
Cycles: 65621222
Storing epoch-0-input-1-voucher-hashes.bin
...
Upon receiving control back from the machine at cycle 65523739, the client prints the epoch index 0 and input index 1, prints state hash 3b038dea...
, loads files epoch-0-input-metadata-1.bin
and epoch-0-input-1.bin
into the appropriate memory ranges, prints the modified state hash e994e56a...
, and resumes the machine.
The ioctl-echo-loop
application reads the payload from the request input and echoes it into one voucher, one notice, and one report.
These operations generate the different flavors of automatic yield
that can be seen in the client output: a tx-voucher
at cycle 65606624, a tx-notice
at cycle 65619533, and a tx-report
at cycle 65620606.
For each of them, the client receives control back from the machine.
It reads the appropriate memory range to store files epoch-0-input-1-voucher-0.bin
, epoch-0-input-1-notice-0.bin
, and epoch-0-input-1-report-0.bin
, respectively.
The manual yield rx-rejected
at cycle 65621222 signals that the ioctl-echo-loop
application is done processing input index 1 of epoch 0 (and rejected it!).
During the processing of an advance-state request, when the /dev/rollup
Linux device receives a voucher, it appends its hash into a memory array.
It does the same for the notices it receives.
Since there will be no more vouchers or notices generated for the input (it was, after all, rejected), the client saves the hash arrays into files epoch-0-index-1-voucher-hashes.bin
and epoch-0-index-1-notice-hashes.bin
, respectively.
In production, when an input to an advance state request is rejected, the Server Manager will rollback the Rolling Cartesi Machine to the state it was before the input was processed.
Moreover, all vouchers and notices are deleted (the reports are preserved).
To make prototyping realistic, the cartesi-machine
client also rolls the state back when an input is rejected by the target.
This can be confirmed by the fact that the cycle counts (65523739) and state hashes (3b038dea...
) following the epoch 0 input 1 and epoch 0 input 2 are the same.
(The files with the vouchers and notices issued before rejection, as well as the files with the arrays of hashes, are left in place for the developer's inspection.)
...
Epoch 0 before input 2
65523739: 3b038dea784432b1339830cadb5ec4c6ed6fcd49dfb92576daa0243a70039d3a
Loading epoch-0-input-metadata-2.bin
Loading epoch-0-input-2.bin
65523739: 7b8af5767f3fbf359b6d809c65175e49c7eee15c847e41a71190d3b7b967406d
Automatic yield tx-voucher (0x300000000 data)
Cycles: 65606624
Storing epoch-0-input-2-voucher-0.bin
Automatic yield tx-notice (0x400000000 data)
Cycles: 65619533
Storing epoch-0-input-2-notice-0.bin
Automatic yield tx-report (0x500000000 data)
Cycles: 65620606
Storing epoch-0-input-2-report-0.bin
Manual yield rx-accepted (0x100000000 data)
Cycles: 65621221
Storing epoch-0-input-2-voucher-hashes.bin
Storing epoch-0-input-2-notice-hashes.bin
...
A similar procedure is followed when processing the advance-state request with input index 2 of epoch index 0, except this time the input is accepted.
Indeed, when the manual yield rx-accepted
at cycle 65621221 is received by the client, it has run out of advance-state requests to return.
...
Before query
Loading query.bin
Automatic yield tx-report (0x500000000 data)
Cycles: 65622895
Storing query-report-0.bin
Manual yield rx-accepted (0x100000000 data)
Cycles: 65623509
Shutting down remote cartesi machine
The client now moves to the inspect-state request.
It loads query.bin
, copies it to the appropriate memory range, and resumes the machine so the ioctl-echo-loop
application can respond to the inspect-state request.
The automatic yield tx-report
at cycle 65622895 signals the application issued a report, which the client then saves as query-report-0.bin
.
Finally, when the subsequent manual yield rx-accepted
is received at cycle 656066240, the client simply shuts down the remote Cartesi Machine server and exits.
Here is the complete list of .bin
files after the client exits:
ls *.bin
epoch-0-input-1-notice-0.bin epoch-0-input-2-report-0.bin
epoch-0-input-1-notice-hashes.bin epoch-0-input-2-voucher-0.bin
epoch-0-input-1-report-0.bin epoch-0-input-2-voucher-hashes.bin
epoch-0-input-1-voucher-0.bin epoch-0-input-2.bin
epoch-0-input-1-voucher-hashes.bin epoch-0-input-metadata-1.bin
epoch-0-input-1.bin epoch-0-input-metadata-2.bin
epoch-0-input-2-notice-0.bin query-report-0.bin
epoch-0-input-2-notice-hashes.bin query.bin
Decoding responses
The rollup-memory-range
command-line utility can decode input metadata, inputs, queries, vouchers, notices, reports, and hashes.
For example, we can decode the files we encoded for the first advance-state request we created above with the commands:
rollup-memory-range decode input-metadata < epoch-0-input-metadata-2.bin
{
"msg_sender":"0x0000000000000000000000000000000000000002",
"block_number":0,
"time_stamp":0,
"epoch_index":0,
"input_index":2
}
rollup-memory-range decode input < epoch-0-input-2.bin
{
"payload":"hello from input 2!"
}
A voucher carries an address and a payload.
The ioctl-echo-loop
utility copies the msg-sender field from the input metadata to the address field of the voucher, and the payload from the input to the payload of the voucher.
We can see this by decoding the voucher epoch-0-input-2-voucher-0.bin
file with the commands
rollup-memory-range decode voucher < epoch-0-input-2-voucher-0.bin
{
"address":"0x0000000000000000000000000000000000000002",
"payload":"hello from input 2!"
}
The results match what was expected from the ioctl-echo-loop
utility.
Notices and reports carry only a payload. To decode them, run, for example:
rollup-memory-range decode notice < epoch-0-input-2-notice-0.bin
{
"payload":"hello from input 2!"
}
rollup-memory-range decode report < query-report-0.bin
{
"payload":"hello from query!"
}
Once again, the echo program does its job as expected.
Rolling Cartesi Machine templates
A Rolling Cartesi Machine template is a machine that has been configured to support Cartesi Rollups, is running a target application in a request-processing loop, is ready to process the next request, and has been stored.
As an example, we will create a Rolling Cartesi Machine template for an arbitrary-precision arithmetic expression evaluator that outputs, as a notices, the result of computation it receives as inputs to advance-state requests.
We will, once again, rely on the bc
command-line utility to perform the computations.
To interact with the /dev/rollup
Linux device (i.e., to obtain the advance-state request inputs and to generate the notices), we will use the /opt/cartesi/bin/rollup
command-line utility.
Shell scripts become surprisingly powerful with the help of the rollup
and jq
command-line utilities.
A bc
-based arbitrary precision application, for example, might look like this:
#!/bin/sh
reqfile=$(mktemp /tmp/calc.XXXXXX)
status="accept"
while :
do
rollup $status > "$reqfile"
request_type=$(jq -j .request_type < "$reqfile")
status="reject"
if [ "$request_type" = "advance_state" ];
then
jq -j '.data.payload' < "$reqfile" | \
bc | \
tr -d '\\\n' | \
jq -R '{ payload: . }' | \
rollup notice > /dev/null && \
status="accept"
fi
done
rm "$reqfile"
The rollup
command-line utility supports the commands accept
, reject
, voucher
, notice
, report
, and exception
.
It uses JSON objects as inputs and outputs.
The accept
and reject
commands accept or reject the previous request and output the next request.
For advance-state requests, the output is in the format
{
"request_type": "advance_state"
"data": {
"metadata": {
"msg_sender": <hash>,
"timestamp": <number>,
"block_number": <number>,
"epoch_index": <number>,
"input_index": <number>
},
"payload": <string>
},
}
Appropriately, the notice
command generates a notice.
The input format is as follows
{
"payload": <string>
}
and the output (not used by calc.sh
) gives the index of the just-output notice as follows
{
"index": <number>
}
The loop in the calc.sh
script calls rollup finish
to obtain the next request (and accept or reject the previous).
It uses jq
to extract the request_type
field and, if it is an "advance_state"
request, it uses jq
again to extract the "payload"
field inside the "data"
field.
This is passed to the bc
utility, which outputs the result split into lines terminated by \
.
The tr
utility joins the lines back together.
The result is again fed to jq
, which assembles the proper JSON object with a "payload"
field that is passed to rollup notice
.
We add the command line option --assert-rolling-template
to help catch errors.
When enabled, it will cause cartesi-machine
to exit with a status-code reporting failure if the generated machine is not Rolling Cartesi Machine template compatible.
To use calc.sh
in a Rolling Cartesi Machine template, first create a filesystem with the program:
mkdir calc
cp calc.sh calc
chmod +x calc/calc.sh
tar \
--sort=name \
--mtime="2022-01-01" \
--owner=1000 \
--group=1000 \
--numeric-owner \
-cf calc.tar \
--directory=calc .
genext2fs \
-f \
-b 1024 \
-a calc.tar \
calc.ext2
Then, follow a procedure similar to the creation of Cartesi Machine templates, using the command line
cartesi-machine \
--rollup \
--flash-drive=label:calc,filename:calc.ext2 \
--store="rolling-calculator-template" \
--assert-rolling-template \
-- /mnt/calc/calc.sh
The result is as follows
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
Manual yield rx-accepted (0x100000000 data)
Cycles: 76814049
Storing machine: please wait
The machine execution stops when the first call to rollup finish
yields, and the machine at that state is stored in directory "rolling-calculator-template"
.
In production, if the target application finds an irrecoverable error during initialization, it should abort with an exception.
In that case, the cartesi-machine
command-line utility will detect the exception, print it to the console, and exit with a status-code reporting failure.
To test the template, create a couple advance-state requests (input and input metadata):
rollup-memory-range encode input-metadata > epoch-0-input-metadata-1.bin <<-EOF
{
"msg_sender": $(printf '"0x%040d"' 1)
"block_number": 0,
"time_stamp": 0,
"epoch_index": 0,
"input_index": 1
}
EOF
rollup-memory-range encode input > epoch-0-input-1.bin <<-EOF
{
"payload": "invalid input"
}
EOF
rollup-memory-range encode input-metadata > epoch-0-input-metadata-2.bin <<-EOF
{
"msg_sender": $(printf '"0x%040d"' 2)
"block_number": 0,
"time_stamp": 0,
"epoch_index": 0,
"input_index": 2
}
EOF
rollup-memory-range encode input > epoch-0-input-2.bin <<-EOF
{
"payload": "6*2^1024 + 3*2^512"
}
EOF
With the files just created by rollup-memory-ranges
in the working directory, run the remote server with the command
ls *.bin
epoch-0-input-1.bin epoch-0-input-metadata-1.bin
epoch-0-input-2.bin epoch-0-input-metadata-2.bin
remote-cartesi-machine \
--server-address=localhost:8080
Then, from a different shell into the same container, run the client with the command
cartesi-machine \
--remote-address=localhost:8080 \
--checkin-address=localhost:8081 \
--remote-shutdown \
--rollup \
--rollup-advance-state=epoch_index:0,input_index_begin:1,input_index_end:3,hashes \
--load="rolling-calculator-template"
The client shell shows
Listening for checkin at 'localhost:8081'
Connecting to remote cartesi machine at 'localhost:8080'
Connected: remote version is 0.6.0
Loading machine: please wait
Manual yield rx-accepted (0x100000000 data)
Cycles: 76814049
Epoch 0 before input 1
76814049: c21c9af376e7549906c252f468e7c4d74f8ea4bc9af0ce2defdfb9facdb2add8
Loading epoch-0-input-metadata-1.bin
Loading epoch-0-input-1.bin
76814049: 68cefae426b0589152d8ad36c47c0e0739b17e7bbcbdc1e3e61d1836e03c5be8
Manual yield rx-rejected (0x200000000 data)
Cycles: 116070336
Storing epoch-0-input-1-voucher-hashes.bin
Storing epoch-0-input-1-notice-hashes.bin
Epoch 0 before input 2
76814049: c21c9af376e7549906c252f468e7c4d74f8ea4bc9af0ce2defdfb9facdb2add8
Loading epoch-0-input-metadata-2.bin
Loading epoch-0-input-2.bin
76814049: 34eb84e7149b168badef7c9a330a6bc40a1c10de94276431876923beda2ba219
Automatic yield tx-notice (0x400000000 data)
Cycles: 113292509
Storing epoch-0-input-2-notice-0.bin
Manual yield rx-accepted (0x100000000 data)
Cycles: 116831231
Storing epoch-0-input-2-voucher-hashes.bin
Storing epoch-0-input-2-notice-hashes.bin
Shutting down remote cartesi machine
It starts by loading the machine from directory "rolling-calculator-template"
and printing again the same yielded state that held when the server template was created.
Then, the first input is rejected, as the payload "invalid input"
is not an expression that bc
can understand.
Finally, the second input, with payload "6*2^1024 + 3*2^512"
, is accepted.
Indeed, to see the result of the computation specified in the second input, run
rollup-memory-range decode notice < epoch-0-input-2-notice-0.bin | \
jq -r .payload | \
fold -w 68
to produce
10786158809173895446375831144734148401707861873653839436405804869463
96054833005778796250863934445216126720683279228360145952738612886499
73495708458383684478649003115037698421037988831222501494715481595948
96901677837132352593468675094844090688678579236903861342030923488978
36036892526733668721977278692363075584
The server shell shows only the error message output by bc
and rollup
.
In production, these error messages should have been captured and output as a report, rather than being allowed to leak into the console.
bc: bad expression at 'input'
[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal
Rarely used options
:::warning This is an advanced section, not needed by regular users of the Cartesi platform. :::
The command-line option --append-rom-bootargs=<string>
can be used to append any <string>
to the kernel command-line.
A detailed description of all kernel command-line parameters is beyond the scope of this document.
Please refer to the appropriate section of the kernel documentation.
For example, to prevent clutter in the console, the cartesi-machine
utility automatically adds the quiet
option to the kernel command-line, disabling most log messages.
To override this setting and see more of the log messages output to console, use the loglevel=<n>
parameter.
cartesi-machine \
--append-rom-bootargs="loglevel=8"
The output is
[ 0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[ 0.000000] Linux version 5.5.19-ctsi-6 (developer@buildkitsandbox) (gcc version 10.2.0 (crosstool-NG 1.24.0.199_dd20ee5)) #1 Mon Aug 29 20:18:47 UTC 2022
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x0000000083feffff]
[ 0.000000] Normal empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080200000-0x0000000083feffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x0000000083feffff]
[ 0.000000] On node 0 totalpages: 15856
[ 0.000000] DMA32 zone: 217 pages used for memmap
[ 0.000000] DMA32 zone: 0 pages reserved
[ 0.000000] DMA32 zone: 15856 pages, LIFO batch:3
[ 0.000000] software IO TLB: mapped [mem 0x83eed000-0x83eed800] (0MB)
[ 0.000000] elf_hwcap is 0x1101
[ 0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[ 0.000000] pcpu-alloc: [0] 0
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 15639
[ 0.000000] Kernel command line: console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw quiet swiotlb=noforce mtdparts=flash.0:-(root) loglevel=8
[ 0.000000] Dentry cache hash table entries: 8192 (order: 4, 65536 bytes, linear)
[ 0.000000] Inode-cache hash table entries: 4096 (order: 3, 32768 bytes, linear)
[ 0.000000] Sorting __ex_table...
[ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[ 0.000000] Memory: 57472K/63424K available (3582K kernel code, 200K rwdata, 605K rodata, 128K init, 242K bss, 5952K reserved, 0K cma-reserved)
[ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[ 0.000000] NR_IRQS: 0, nr_irqs: 0, preallocated irqs: 0
[ 0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]
[ 0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x1d854df40, max_idle_ns: 3526361616960 ns
[ 0.000033] sched_clock: 64 bits at 1000kHz, resolution 1000ns, wraps every 2199023255500ns
[ 0.000534] Console: colour dummy device 80x25
[ 0.004712] printk: console [hvc0] enabled
[ 0.004893] Calibrating delay loop (skipped), value calculated using timer frequency.. 2.00 BogoMIPS (lpj=10000)
[ 0.005156] pid_max: default: 32768 minimum: 301
[ 0.005808] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
[ 0.006011] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
[ 0.009544] devtmpfs: initialized
[ 0.011817] random: get_random_u32 called from bucket_table_alloc.isra.0+0x6c/0x11c with crng_init=0
[ 0.012501] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
[ 0.012949] futex hash table entries: 256 (order: 0, 6144 bytes, linear)
[ 0.014444] NET: Registered protocol family 16
[ 0.031225] clocksource: Switched to clocksource riscv_clocksource
[ 0.050902] NET: Registered protocol family 2
[ 0.053463] tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear)
[ 0.053715] TCP established hash table entries: 512 (order: 0, 4096 bytes, linear)
[ 0.053974] TCP bind hash table entries: 512 (order: 0, 4096 bytes, linear)
[ 0.054197] TCP: Hash tables configured (established 512 bind 512)
[ 0.054558] UDP hash table entries: 256 (order: 1, 8192 bytes, linear)
[ 0.054801] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)
[ 0.055442] NET: Registered protocol family 1
[ 0.057002] workingset: timestamp_bits=62 max_order=14 bucket_order=0
[ 0.354216] physmap-flash 8000000000000000.flash: physmap platform flash device: [mem 0x8000000000000000-0x8000000004ffffff]
[ 0.354494] 1 cmdlinepart partitions found on MTD device flash.0
[ 0.354647] Creating 1 MTD partitions on "flash.0":
[ 0.354786] 0x000000000000-0x000005000000 : "root"
[ 0.359122] NET: Registered protocol family 17
[ 0.361116] VFS: Mounted root (ext2 filesystem) on device 31:0.
[ 0.361486] devtmpfs: mounted
[ 0.361890] Freeing unused kernel memory: 128K
[ 0.362007] This architecture does not have kernel memory protection.
[ 0.362164] Run /sbin/init as init process
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
[ 0.466375] random: crng init done
Nothing to do.
[ 0.567990] reboot: Power down
Halted
Cycles: 63873507
To clear the kernel command-line, use the option --no-rom-bootargs
.
Notice that, without any options, the machine will not operate properly.
In particular, as explained under the Lua interface, flash-drives use kernel command-line arguments.
For example, running the cartesi-machine
command-line utility with no arguments produces a kernel command-line
equivalent to running the command
cartesi-machine \
--no-rom-bootargs \
--append-rom-bootargs="console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw quiet swiotlb=noforce mtdparts=flash.0:-(root)"
The command-line option --periodic-hashes=<number-period>[,<number-start>]
causes the command-line utility to periodically obtain and print the state hash.
The <number-period>
argument gives the distance between hashes in cycles. The optional <number-start>
argument gives the starting cycle for the periodic hashes. (Both --initial-hash
and --final-hash
are implied by this option.)
For example, to see the last 10 state hashes from the calculator machine computation, run the command
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--load="calculator-template" \
--replace-flash-drive="start:0x9000000000000000,length:1<<12,filename:input.raw" \
--periodic-hashes=1,85941635
The output is
Loading machine: please wait
0: 8cb551c358a7cb0680d0f288b27efdbe17dbb65dbca85ab90ead446f1037f018
.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'
85941635: baec7c3805d03d80ed82abffbdb07e8ce444321057d8c128e4e8191fa6c63125
85941636: 4381ad2b52fe6dca408d1c3f922c4adbcc119b554867dfb5e467651bbe69866f
85941637: 1e7397164110cbd88b2f6fbe6b412287c763b51ef2b0f46ae613a0e390af1bad
85941638: 29dcc26b33fbdf08145d4edf378fb7fc167def96dcd442424520ccf08ff1e3c3
85941639: 230d31a46ad24ae2c097e337e27bfba82541e0b7f3e514e19bbf9b73a7601730
85941640: 7bfe0c514f93e8765781216221b7efabb02a3f883fadac2f57ef715636598952
85941641: 97baf0dd63c88a826f0f4cac452d78fd6b54bc0f15be4c74cc3d0bda0857e62e
85941642: bd589544dd0bdffb664a6b9d502c79672d6f7e9ff0a41ad5132bc67d83621ef1
85941643: 57e749c5d441bfac0f9bf5fa4a346809cfa9ed2c96514edf8182cd776fc80cd2
85941644: 88d976d5fcbea213c338f307a69c93c5aa0ff339db1d8e244b305f45eff122c6
Halted
Cycles: 85941645
85941645: bbf5015f59cf06252e160b600ceb6b624935333a1a3561d8684fb8fd696cc3fd
The command-line option --dump-pmas
causes the emulator to dump the contents of all mapped spans in the address space to files.
Each span produces a file <start>--<length>.bin
.
Every other byte in the address space has value 0.
This is useful to inspect the entire state of the machine from outside the emulator.
The command-line options --store-config
and --load-config
store or load a file with information that can be used to initialize the exact same Cartesi Machine that the cartesi-machine
command-line utility will use.
The format of these configuration files is explained in detail under the Lua interface to Cartesi Machines.
In particular, the --store-config
option, without arguments, dumps to screen all the options used to define the Cartesi Machine.
This information can be very useful when debugging problems.
The remaining options in the command-line utility cartesi-machine
are mostly useful for low-level tests and debugging.
As such, they require some context.
During verification, the blockchain mediates a verification game between the disputing parties. This process is explained in detail under the the blockchain perspective. In a nutshell, both parties started from a Cartesi Machine that has a known and agreed upon initial state hash. (E.g., an agreed upon template that was instantiated with an agreed upon input drive.) At the end of the computation, these parties now disagree on the state hash for the halted machine. The state hash evolves as the machine executes steps in its fetch-execute loop. The first stage of the verification game therefore searches for the step of disagreement: the particular cycle such that the parties agree on the state hash before the step, but disagree on the state hash after the step. Once this step of disagreement is identified, one of the parties sends to the blockchain a log of state accesses that happen along the step, including cryptographic proofs for every value read from or written to the state. This log proves to the blockchain that the execution of the step transitions the state in such a way that it reaches the state hash claimed by the submitting party.
The --step
command-line option instructs cartesi-machine
to dump to screen an abridged, user-friendly version of this state access log.
For the sake of completeness, consider the example in which the Cartesi Machine was stopped while it drew the splash screen. The example below shows the step it was about to execute
cartesi-machine \
--max-mcycle=46598940 \
--step > /dev/null
and produces the log
Cycles: 46598940
Gathering step proof: please wait
begin step
hash a00da7eb
1: read mcycle@0x120(288): 0x2c70b1c(46598940)
hash a00da7eb
2: read iflags.H@0x1d0(464): 0x18(24)
hash a00da7eb
3: read iflags.Y@0x1d0(464): 0x18(24)
hash a00da7eb
4: read iflags.X (superfluous)@0x1d0(464): 0x18(24)
hash a00da7eb
5: write iflags.X@0x1d0(464): 0x18(24) -> 0x18(24)
begin set_rtc_interrupt
end set_rtc_interrupt
begin raise_interrupt_if_any
hash a00da7eb
6: read mip@0x170(368): 0x0(0)
hash a00da7eb
7: read mie@0x168(360): 0x2aa(682)
end raise_interrupt_if_any
begin fetch_insn
hash a00da7eb
8: read pc@0x100(256): 0x80002fa0(2147495840)
begin translate_virtual_address
hash a00da7eb
9: read iflags.PRV@0x1d0(464): 0x18(24)
hash a00da7eb
10: read mstatus@0x130(304): 0xa00000820(42949675040)
end translate_virtual_address
begin find_pma_entry
hash a00da7eb
11: read pma.istart@0x800(2048): 0x800000f9(2147483897)
hash a00da7eb
12: read pma.ilength@0x808(2056): 0x4000000(67108864)
end find_pma_entry
hash a00da7eb
13: read memory@0x80002fa0(2147495840): 0x7378300f6b023(2031360633384995)
end fetch_insn
begin sd
hash a00da7eb
14: read x@0x68(104): 0x40008000(1073774592)
hash a00da7eb
15: read x@0x78(120): 0x10100000000000a(72339069014638602)
begin translate_virtual_address
hash a00da7eb
16: read iflags.PRV@0x1d0(464): 0x18(24)
hash a00da7eb
17: read mstatus@0x130(304): 0xa00000820(42949675040)
end translate_virtual_address
begin find_pma_entry
hash a00da7eb
18: read pma.istart@0x800(2048): 0x800000f9(2147483897)
hash a00da7eb
19: read pma.ilength@0x808(2056): 0x4000000(67108864)
hash a00da7eb
20: read pma.istart@0x810(2064): 0x1069(4201)
hash a00da7eb
21: read pma.ilength@0x818(2072): 0xf000(61440)
hash a00da7eb
22: read pma.istart@0x820(2080): 0x80000000000002d9(9223372036854776537)
hash a00da7eb
23: read pma.ilength@0x828(2088): 0x5000000(83886080)
hash a00da7eb
24: read pma.istart@0x830(2096): 0x4000841a(1073775642)
hash a00da7eb
25: read pma.ilength@0x838(2104): 0x1000(4096)
end find_pma_entry
hash a00da7eb
26: write htif.tohost@0x40008000(1073774592): 0x10100000000000d(72339069014638605) -> 0x10100000000000a(72339069014638602)
hash 8ebc16f9
27: read htif.iconsole@0x40008018(1073774616): 0x2(2)
hash 8ebc16f9
28: write htif.fromhost@0x40008008(1073774600): 0x0(0) -> 0x101000000000000(72339069014638592)
hash 7fa4fe27
29: write pc@0x100(256): 0x80002fa0(2147495840) -> 0x80002fa4(2147495844)
end sd
hash bac08fcc
30: read minstret@0x128(296): 0x2c70aef(46598895)
hash bac08fcc
31: write minstret@0x128(296): 0x2c70aef(46598895) -> 0x2c70af0(46598896)
hash 65519976
32: read mcycle@0x120(288): 0x2c70b1c(46598940)
hash 65519976
33: write mcycle@0x120(288): 0x2c70b1c(46598940) -> 0x2c70b1d(46598941)
end step
Understanding these logs in detail is unnecessary for all but the most low-level internal development at Cartesi. It requires deep knowledge of not only RISC-V architecture, but also how Cartesi's emulator implements it. The material is therefore beyond the scope of this document.
This particular example, however, was hand-picked for illustration purposes.
The RISC-V instruction being executed, sd
, writes the 64-bit word 0x010100000000000a
to address 0x40008000
(access #23).
This is the memory-mapped address of HTIF's tohost
CSR.
The value refers to the console subdevice (DEV=0x01
) , command putchar
(CMD=0x01
), and causes the device to output a line-feed (DATA=0x0a
) to the emulator's console.
I.e., the instruction is completing the row \ / CARTESI
in the splash screen.
The command-line option --json-steps=<filename>
outputs a machine-readable version of the step log for each cycle executed by the emulator.
It is used by internal integration tests that verify the consistency between the Cartesi Machine as implemented by the off-chain emulator and as implemented by the on-chain step verification function.
Needless to say, even for brief computations, the resulting log files can be very large.
The --rollup
command-line option sets the --htif-yield-automatic
and --htif-yield-manual
options for the /dev/yield
device.
See the target perspective for details on automatic and manual yield commands and how they are used by Cartesi Rollups.
The --rollup
option also configures a variety of memory ranges used by the /dev/rollup
device.
There are five memory ranges: rollup-rx-buffer, rollup-input-metadata, rollup-tx-buffer, rollup-voucher-hashes, and rollup-notice-hashes.
The values implied by the --rollup
command-line option are --rollup-rx-buffer=start:0x60000000,length:2<<20
, --rollup-tx-buffer=start:0x60200000,length:2<<20
, --rollup-input-metadata=start:0x60400000,length:4096
, --rollup-voucher-hashes=start:0x60600000,length:2<<20
, and --rollup-notice-hashes=start:0x60800000,length:2<<20
.