Skip to main content

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.

note

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

note

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.

note

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....

info

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.

note

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 templatethe 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 364. 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

pristine-input-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

input-proof
{
"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

output-proof
{
"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.

note

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:

calc.sh
#!/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".

note

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.

© 2024 Cartesi Foundation Ltd. All rights reserved.

The Cartesi Project is commissioned by the Cartesi Foundation.

We use cookies to ensure that we give you the best experience on our website. By using the website, you agree to the use of cookies.