diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d913d275..c0206cfe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Makefile` to help node developers setup their environment - Added experimental sunodo validator mode - Added instructions on how to run the node with Docker +- Added Cartesi Machine C API wrapper ### Changed diff --git a/cmd/cartesi-machine/README.md b/cmd/cartesi-machine/README.md new file mode 100644 index 000000000..4b16c4422 --- /dev/null +++ b/cmd/cartesi-machine/README.md @@ -0,0 +1,31 @@ +# Go bindings to the Cartesi Machine C API + +## Quick Start + +Ensure that the emulator headers and libraries are installed or point to them with: +``` +export CGO_CFLAGS="-I/foo/machine-emulator/src" +export CGO_LDFLAGS="-L/foo/machine-emulator/src" + +``` + +Build +``` +go build +``` + +Point to the directory containing the image files +``` +export CARTESI_IMAGES_PATH= +``` + +Run +``` +go run cmd/cartesi-machine/main.go --help +go run cmd/cartesi-machine/main.go +go run cmd/cartesi-machine/main.go --command="ls -l" +go run cmd/cartesi-machine/main.go --max-mcycle=0 --store=/tmp/maquina +go run cmd/cartesi-machine/main.go --load=/tmp/maquina --command="ls -l" +go run cmd/cartesi-machine/main.go --load=/tmp/maquina --initial-hash --final-hash +go run cmd/cartesi-machine/main.go --remote-address="localhost:5000"--load=/tmp/maquina --initial-hash --final-hash --command="ls -l" +``` \ No newline at end of file diff --git a/cmd/cartesi-machine/main.go b/cmd/cartesi-machine/main.go new file mode 100644 index 000000000..490c7e6dd --- /dev/null +++ b/cmd/cartesi-machine/main.go @@ -0,0 +1,195 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Simple command line interface to the Cartesi Machine C API wrapper + +package main + +import ( + "flag" + "fmt" + "math" + "os" + "strings" + + "github.com/cartesi/rollups-node/pkg/emulator" +) + +func main() { + var machine *emulator.Machine + defer machine.Free() + var mgr *emulator.RemoteMachineManager + defer mgr.Free() + var err error + runtimeConfig := &emulator.MachineRuntimeConfig{} + + // Parse command line arguments + loadDir := flag.String("load", "", "load machine previously stored in ") + storeDir := flag.String("store", "", "store machine to , where \"%h\" is substituted by the state hash in the directory name") + remoteAddress := flag.String("remote-address", "", "use a remote cartesi machine listening to
instead of running a local cartesi machine") + remoteShutdown := flag.Bool("remote-shutdown", false, "shutdown the remote cartesi machine after the execution") + noRemoteCreate := flag.Bool("no-remote-create", false, "use existing cartesi machine in the remote server instead of creating a new one") + noRemoteDestroy := flag.Bool("no-remote-destroy", false, "do not destroy the cartesi machine in the remote server after the execution") + ramImage := flag.String("ram-image", "", "name of file containing RAM image") + dtbImage := flag.String("dtb-image", "", "name of file containing DTB image (default: auto generated flattened device tree)") + maxMcycle := flag.Uint64("max-mcycle", math.MaxUint64, "stop at a given mcycle") + initialHash := flag.Bool("initial-hash", false, "print initial state hash before running machine") + finalHash := flag.Bool("final-hash", false, "print final state hash when done") + commandLine := flag.String("command", "", "command to run in the machine") + flag.Parse() + + // Connect to remote server and load/get machine + if remoteAddress != nil && *remoteAddress != "" { + fmt.Println("Connecting to remote server at ", *remoteAddress) + if mgr, err = emulator.NewRemoteMachineManager(*remoteAddress); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating remote machine manager: ", err) + os.Exit(1) + } + if noRemoteCreate != nil && *noRemoteCreate { + fmt.Println("Using existing remote machine") + if machine, err = mgr.GetMachine(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error getting remote machine: ", err) + os.Exit(1) + } + } else if loadDir != nil && *loadDir != "" { + fmt.Println("Loading remote machine from ", *loadDir) + if machine, err = mgr.LoadMachine(*loadDir, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error loading machine: ", err) + os.Exit(1) + } + } + } else if loadDir != nil && *loadDir != "" { + fmt.Println("Loading machine from ", *loadDir) + if machine, err = emulator.LoadMachine(*loadDir, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error loading machine: ", err) + os.Exit(1) + } + } + + // No machine yet: build configuration and create machine + if machine == nil { + // build machine configuration + images_path := strings.TrimRight(os.Getenv("CARTESI_IMAGES_PATH"), "/") + "/" + cfg := emulator.NewDefaultMachineConfig() + cfg.Processor.Mimpid = math.MaxUint64 + cfg.Processor.Marchid = math.MaxUint64 + cfg.Processor.Mvendorid = math.MaxUint64 + cfg.Ram.ImageFilename = images_path + "linux.bin" + if ramImage != nil && *ramImage != "" { + fmt.Println("Using RAM image: ", *ramImage) + cfg.Ram.ImageFilename = *ramImage + } + cfg.Ram.Length = 64 << 20 + cfg.FlashDrive = []emulator.MemoryRangeConfig{ + { + Start: 0x80000000000000, + Length: 0xffffffffffffffff, + Shared: false, + ImageFilename: images_path + "rootfs.ext2", + }, + } + cfg.Dtb.Bootargs = "quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init" + if dtbImage != nil && *dtbImage != "" { + cfg.Dtb.ImageFilename = *dtbImage + } + cfg.Dtb.Init = `echo "Opa!" + busybox mkdir -p /run/drive-label && echo "root" > /run/drive-label/pmem0\ + USER=dapp + ` + if commandLine != nil && *commandLine != "" { + cfg.Dtb.Init = *commandLine + } + // create machine using configuration + if mgr == nil { + fmt.Println("Creating local machine") + if machine, err = emulator.NewMachine(cfg, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating machine: ", err) + os.Exit(1) + } + } else { + fmt.Println("Creating remote machine") + if machine, err = mgr.NewMachine(cfg, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating remote machine: ", err) + os.Exit(1) + } + + } + } + + // No machine yet? Too bad + if machine == nil { + fmt.Fprintln(os.Stderr, "****** No machine to run") + os.Exit(1) + } + + // Print initial hash + if initialHash != nil && *initialHash { + var hash *emulator.MerkleTreeHash + if hash, err = machine.GetRootHash(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error getting root hash: ", err) + os.Exit(1) + } + fmt.Println("Initial hash: ", hash.String()) + } + + // Run machine + var breakReason emulator.BreakReason + if breakReason, err = machine.Run(*maxMcycle); err != nil { + fmt.Fprintln(os.Stderr, "****** Error running machine: ", err) + os.Exit(1) + } + switch breakReason { + case emulator.BreakReasonFailed: + fmt.Println("Machine failed") + case emulator.BreakReasonHalted: + fmt.Println("Machine halted") + case emulator.BreakReasonYieldedManually: + fmt.Println("Machine yielded manually") + case emulator.BreakReasonYieldedAutomatically: + fmt.Println("Machine yielded automatically") + case emulator.BreakReasonYieldedSoftly: + fmt.Println("Machine yielded softly") + case emulator.BreakReasonReachedTargetMcycle: + fmt.Println("Machine reached target mcycle") + default: + fmt.Println("Machine stopped for unknown reason") + } + + cycle, _ := machine.ReadCSR(emulator.ProcCsrMcycle) + fmt.Println("mcycle: ", cycle) + + // Print final hash + if finalHash != nil && *finalHash { + var hash *emulator.MerkleTreeHash + if hash, err = machine.GetRootHash(); err == nil { + fmt.Println("Final hash: ", hash.String()) + } + } + + // Store machine + if storeDir != nil && *storeDir != "" { + fmt.Println("Storing machine in ", *storeDir) + if err = machine.Store(*storeDir); err != nil { + fmt.Fprintln(os.Stderr, "****** Error storing machine: ", err) + os.Exit(1) + } + } + + // Cleanup + if mgr != nil { + if !*noRemoteDestroy { + fmt.Println("Destroying remote machine") + if err = machine.Destroy(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error destroying remote machine: ", err) + os.Exit(1) + } + } + if *remoteShutdown { + fmt.Println("Shutting down remote machine") + if err = mgr.Shutdown(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error shutting down remote server: ", err) + os.Exit(1) + } + } + } +} diff --git a/pkg/emulator/.gitignore b/pkg/emulator/.gitignore new file mode 100644 index 000000000..496ee2ca6 --- /dev/null +++ b/pkg/emulator/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go new file mode 100644 index 000000000..afd2a03ca --- /dev/null +++ b/pkg/emulator/emulator.go @@ -0,0 +1,955 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Cartesi Machine C API wrapper + +package emulator + +/* +#cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc +#include "machine-c-api.h" +#include "jsonrpc-machine-c-api.h" +#include +*/ +import "C" + +import ( + "encoding/hex" + "fmt" + "unsafe" +) + +type ErrorCode int32 + +const ( + ErrorCodeOk ErrorCode = C.CM_ERROR_OK + ErrorCodeInvalidArgument ErrorCode = C.CM_ERROR_INVALID_ARGUMENT + ErrorCodeDomainError ErrorCode = C.CM_ERROR_DOMAIN_ERROR + ErrorCodeLengthError ErrorCode = C.CM_ERROR_LENGTH_ERROR + ErrorCodeOutOfRange ErrorCode = C.CM_ERROR_OUT_OF_RANGE + ErrorCodeLogicError ErrorCode = C.CM_ERROR_LOGIC_ERROR + ErrorCodeBadOptionalAccess ErrorCode = C.CM_ERROR_BAD_OPTIONAL_ACCESS + ErrorCodeRuntimeError ErrorCode = C.CM_ERROR_RUNTIME_ERROR + ErrorCodeRangeError ErrorCode = C.CM_ERROR_RANGE_ERROR + ErrorCodeOverflowError ErrorCode = C.CM_ERROR_OVERFLOW_ERROR + ErrorCodeUnderflowError ErrorCode = C.CM_ERROR_UNDERFLOW_ERROR + ErrorCodeRegexError ErrorCode = C.CM_ERROR_REGEX_ERROR + ErrorCodeSystemIosBaseFailure ErrorCode = C.CM_ERROR_SYSTEM_IOS_BASE_FAILURE + ErrorCodeFilesystemError ErrorCode = C.CM_ERROR_FILESYSTEM_ERROR + ErrorCodeAtomicTxError ErrorCode = C.CM_ERROR_ATOMIC_TX_ERROR + ErrorCodeNonexistingLocalTime ErrorCode = C.CM_ERROR_NONEXISTING_LOCAL_TIME + ErrorCodeAmbiguousLocalTime ErrorCode = C.CM_ERROR_AMBIGUOUS_LOCAL_TIME + ErrorCodeFormatError ErrorCode = C.CM_ERROR_FORMAT_ERROR + ErrorCodeBadTypeid ErrorCode = C.CM_ERROR_BAD_TYPEID + ErrorCodeBadCast ErrorCode = C.CM_ERROR_BAD_CAST + ErrorCodeBadAnyCast ErrorCode = C.CM_ERROR_BAD_ANY_CAST + ErrorCodeBadWeakPtr ErrorCode = C.CM_ERROR_BAD_WEAK_PTR + ErrorCodeBadFunctionCall ErrorCode = C.CM_ERROR_BAD_FUNCTION_CALL + ErrorCodeBadAlloc ErrorCode = C.CM_ERROR_BAD_ALLOC + ErrorCodeBadArrayNewLength ErrorCode = C.CM_ERROR_BAD_ARRAY_NEW_LENGTH + ErrorCodeBadException ErrorCode = C.CM_ERROR_BAD_EXCEPTION + ErrorCodeBadVariantAccess ErrorCode = C.CM_ERROR_BAD_VARIANT_ACCESS + ErrorCodeException ErrorCode = C.CM_ERROR_EXCEPTION + ErrorCodeUnknown ErrorCode = C.CM_ERROR_UNKNOWN +) + +func isFailure(cerr C.int) bool { + return ErrorCode(cerr) != ErrorCodeOk +} + +type Error struct { + Code ErrorCode + Msg string +} + +func (e *Error) Error() string { + return fmt.Sprintf("cartesi machine error %d (%s)", e.Code, e.Msg) +} + +func newError(code C.int, message *C.char) error { + defer C.cm_delete_cstring(message) + if code != C.CM_ERROR_OK { + return &Error{Code: ErrorCode(code), Msg: C.GoString(message)} + } + return nil +} + +type BreakReason int32 + +const ( + BreakReasonFailed BreakReason = C.CM_BREAK_REASON_FAILED + BreakReasonHalted BreakReason = C.CM_BREAK_REASON_HALTED + BreakReasonYieldedManually BreakReason = C.CM_BREAK_REASON_YIELDED_MANUALLY + BreakReasonYieldedAutomatically BreakReason = C.CM_BREAK_REASON_YIELDED_AUTOMATICALLY + BreakReasonYieldedSoftly BreakReason = C.CM_BREAK_REASON_YIELDED_SOFTLY + BreakReasonReachedTargetMcycle BreakReason = C.CM_BREAK_REASON_REACHED_TARGET_MCYCLE +) + +type ProcessorCSR int32 + +const ( + ProcCsrPc ProcessorCSR = C.CM_PROC_PC + ProcCsrFcsr ProcessorCSR = C.CM_PROC_FCSR + ProcCsrMvendorid ProcessorCSR = C.CM_PROC_MVENDORID + ProcCsrMarchid ProcessorCSR = C.CM_PROC_MARCHID + ProcCsrMimpid ProcessorCSR = C.CM_PROC_MIMPID + ProcCsrMcycle ProcessorCSR = C.CM_PROC_MCYCLE + ProcCsrIcycleinstret ProcessorCSR = C.CM_PROC_ICYCLEINSTRET + ProcCsrMstatus ProcessorCSR = C.CM_PROC_MSTATUS + ProcCsrMtvec ProcessorCSR = C.CM_PROC_MTVEC + ProcCsrMscratch ProcessorCSR = C.CM_PROC_MSCRATCH + ProcCsrMepc ProcessorCSR = C.CM_PROC_MEPC + ProcCsrMcause ProcessorCSR = C.CM_PROC_MCAUSE + ProcCsrMtval ProcessorCSR = C.CM_PROC_MTVAL + ProcCsrMisa ProcessorCSR = C.CM_PROC_MISA + ProcCsrMie ProcessorCSR = C.CM_PROC_MIE + ProcCsrMip ProcessorCSR = C.CM_PROC_MIP + ProcCsrMedeleg ProcessorCSR = C.CM_PROC_MEDELEG + ProcCsrMideleg ProcessorCSR = C.CM_PROC_MIDELEG + ProcCsrMcounteren ProcessorCSR = C.CM_PROC_MCOUNTEREN + ProcCsrMenvcfg ProcessorCSR = C.CM_PROC_MENVCFG + ProcCsrStvec ProcessorCSR = C.CM_PROC_STVEC + ProcCsrSscratch ProcessorCSR = C.CM_PROC_SSCRATCH + ProcCsrSepc ProcessorCSR = C.CM_PROC_SEPC + ProcCsrScause ProcessorCSR = C.CM_PROC_SCAUSE + ProcCsrStval ProcessorCSR = C.CM_PROC_STVAL + ProcCsrSatp ProcessorCSR = C.CM_PROC_SATP + ProcCsrScounteren ProcessorCSR = C.CM_PROC_SCOUNTEREN + ProcCsrSenvcfg ProcessorCSR = C.CM_PROC_SENVCFG + ProcCsrIlrsc ProcessorCSR = C.CM_PROC_ILRSC + ProcCsrIflags ProcessorCSR = C.CM_PROC_IFLAGS + ProcCsrClintMtimecmp ProcessorCSR = C.CM_PROC_CLINT_MTIMECMP + ProcCsrHtifTohost ProcessorCSR = C.CM_PROC_HTIF_TOHOST + ProcCsrHtifFromhost ProcessorCSR = C.CM_PROC_HTIF_FROMHOST + ProcCsrHtifIhalt ProcessorCSR = C.CM_PROC_HTIF_IHALT + ProcCsrHtifIconsole ProcessorCSR = C.CM_PROC_HTIF_ICONSOLE + ProcCsrHtifIyield ProcessorCSR = C.CM_PROC_HTIF_IYIELD + ProcCsrUarchPc ProcessorCSR = C.CM_PROC_UARCH_PC + ProcCsrUarchCycle ProcessorCSR = C.CM_PROC_UARCH_CYCLE + ProcCsrUarchHaltFlag ProcessorCSR = C.CM_PROC_UARCH_HALT_FLAG +) + +type MachineRuntimeConfig struct { + Concurrency ConcurrencyRuntimeConfig + Htif HtifRuntimeConfig + SkipRootHashCheck bool + SkipVersionCheck bool + SoftYield bool +} + +type HtifRuntimeConfig struct { + NoConsolePutchar bool +} + +type ConcurrencyRuntimeConfig struct { + UpdateMerkleTree uint64 +} + +type MachineConfig struct { + Processor ProcessorConfig + Ram RamConfig + Dtb DtbConfig + FlashDrive []MemoryRangeConfig + Tlb TlbConfig + Clint ClintConfig + Htif HtifConfig + Cmio CmioConfig + Uarch UarchConfig +} + +type ProcessorConfig struct { + X [32]uint64 + F [32]uint64 + Pc uint64 + Fcsr uint64 + Mvendorid uint64 + Marchid uint64 + Mimpid uint64 + Mcycle uint64 + Icycleinstret uint64 + Mstatus uint64 + Mtvec uint64 + Mscratch uint64 + Mepc uint64 + Mcause uint64 + Mtval uint64 + Misa uint64 + Mie uint64 + Mip uint64 + Medeleg uint64 + Mideleg uint64 + Mcounteren uint64 + Menvcfg uint64 + Stvec uint64 + Sscratch uint64 + Sepc uint64 + Scause uint64 + Stval uint64 + Satp uint64 + Scounteren uint64 + Senvcfg uint64 + Ilrsc uint64 + Iflags uint64 +} + +type RamConfig struct { + Length uint64 + ImageFilename string +} + +type DtbConfig struct { + Bootargs string + Init string + Entrypoint string + ImageFilename string +} + +type MemoryRangeConfig struct { + Start uint64 + Length uint64 + Shared bool + ImageFilename string +} + +type TlbConfig struct { + ImageFilename string +} + +type ClintConfig struct { + Mtimecmp uint64 +} + +type HtifConfig struct { + Fromhost uint64 + Tohost uint64 + ConsoleGetchar bool + YieldManual bool + YieldAutomatic bool +} + +type CmioConfig struct { + HsaValue bool + RxBuffer MemoryRangeConfig + TxBuffer MemoryRangeConfig +} + +type UarchRamConfig struct { + ImageFilename string +} + +type UarchProcessorConfig struct { + X [32]uint64 + Pc uint64 + Cycle uint64 + HaltFlag bool +} + +type UarchConfig struct { + Processor UarchProcessorConfig + Ram UarchRamConfig +} + +//////////////////////////////////////// +// Helpers and utils +//////////////////////////////////////// + +type ourMemoryRangeConfig struct { + cref *C.cm_memory_range_config +} + +func (config *MemoryRangeConfig) makeCRef() (ref *ourMemoryRangeConfig) { + ref = &ourMemoryRangeConfig{ + cref: (*C.cm_memory_range_config)(C.calloc(1, C.sizeof_cm_memory_range_config)), + } + c := ref.cref + c.start = (C.uint64_t)(config.Start) + c.length = (C.uint64_t)(config.Length) + c.shared = (C.bool)(config.Shared) + c.image_filename = makeCString(&config.ImageFilename) + return ref +} + +func (configRef *ourMemoryRangeConfig) free() { + if configRef == nil || configRef.cref == nil { + return + } + C.free(unsafe.Pointer(configRef.cref.image_filename)) + C.free(unsafe.Pointer(configRef.cref)) + configRef.cref = nil +} + +// cm_machine_runtime_config allocated by us +type ourMachineRuntimeConfigCRef struct { + cref *C.cm_machine_runtime_config +} + +func (config *MachineRuntimeConfig) makeCRef() (ref *ourMachineRuntimeConfigCRef) { + ref = &ourMachineRuntimeConfigCRef{ + cref: (*C.cm_machine_runtime_config)(C.calloc(1, C.sizeof_cm_machine_runtime_config)), + } + cRuntime := ref.cref + cRuntime.skip_root_hash_check = (C.bool)(config.SkipRootHashCheck) + cRuntime.skip_version_check = (C.bool)(config.SkipVersionCheck) + cRuntime.soft_yield = (C.bool)(config.SoftYield) + + cHtif := &ref.cref.htif + htif := &config.Htif + cHtif.no_console_putchar = (C.bool)(htif.NoConsolePutchar) + + cConcurrency := &ref.cref.concurrency + concurrency := &config.Concurrency + cConcurrency.update_merkle_tree = (C.uint64_t)(concurrency.UpdateMerkleTree) + + return ref +} + +func (configRef *ourMachineRuntimeConfigCRef) free() { + if configRef == nil || configRef.cref == nil { + return + } + C.free(unsafe.Pointer(configRef.cref)) + configRef.cref = nil +} + +// cm_machine_config allocated by us +type ourMachineConfigCRef struct { + cref *C.cm_machine_config +} + +func (config *MachineConfig) makeCRef() (ref *ourMachineConfigCRef) { + ref = &ourMachineConfigCRef{ + cref: (*C.cm_machine_config)(C.calloc(1, C.sizeof_cm_machine_config)), + } + // Processor + cProcessor := &ref.cref.processor + processor := &config.Processor + for i := 0; i < 31; i++ { + cProcessor.x[i+1] = (C.uint64_t)(processor.X[i]) + } + for i := 0; i < 31; i++ { + cProcessor.f[i+1] = (C.uint64_t)(processor.F[i]) + } + cProcessor.pc = (C.uint64_t)(processor.Pc) + cProcessor.fcsr = (C.uint64_t)(processor.Fcsr) + cProcessor.mvendorid = (C.uint64_t)(processor.Mvendorid) + cProcessor.marchid = (C.uint64_t)(processor.Marchid) + cProcessor.mimpid = (C.uint64_t)(processor.Mimpid) + cProcessor.mcycle = (C.uint64_t)(processor.Mcycle) + cProcessor.icycleinstret = (C.uint64_t)(processor.Icycleinstret) + cProcessor.mstatus = (C.uint64_t)(processor.Mstatus) + cProcessor.mtvec = (C.uint64_t)(processor.Mtvec) + cProcessor.mscratch = (C.uint64_t)(processor.Mscratch) + cProcessor.mepc = (C.uint64_t)(processor.Mepc) + cProcessor.mcause = (C.uint64_t)(processor.Mcause) + cProcessor.mtval = (C.uint64_t)(processor.Mtval) + cProcessor.misa = (C.uint64_t)(processor.Misa) + cProcessor.mie = (C.uint64_t)(processor.Mie) + cProcessor.mip = (C.uint64_t)(processor.Mip) + cProcessor.medeleg = (C.uint64_t)(processor.Medeleg) + cProcessor.mideleg = (C.uint64_t)(processor.Mideleg) + cProcessor.mcounteren = (C.uint64_t)(processor.Mcounteren) + cProcessor.menvcfg = (C.uint64_t)(processor.Menvcfg) + cProcessor.stvec = (C.uint64_t)(processor.Stvec) + cProcessor.sscratch = (C.uint64_t)(processor.Sscratch) + cProcessor.sepc = (C.uint64_t)(processor.Sepc) + cProcessor.scause = (C.uint64_t)(processor.Scause) + cProcessor.stval = (C.uint64_t)(processor.Stval) + cProcessor.satp = (C.uint64_t)(processor.Satp) + cProcessor.scounteren = (C.uint64_t)(processor.Scounteren) + cProcessor.senvcfg = (C.uint64_t)(processor.Senvcfg) + cProcessor.ilrsc = (C.uint64_t)(processor.Ilrsc) + cProcessor.iflags = (C.uint64_t)(processor.Iflags) + + cRam := &ref.cref.ram + ram := &config.Ram + cRam.length = (C.uint64_t)(ram.Length) + cRam.image_filename = makeCString(&ram.ImageFilename) + + cDtb := &ref.cref.dtb + dtb := &config.Dtb + cDtb.bootargs = makeCString(&dtb.Bootargs) + cDtb.init = makeCString(&dtb.Init) + cDtb.entrypoint = makeCString(&dtb.Entrypoint) + cDtb.image_filename = makeCString(&dtb.ImageFilename) + + // flash + cFlashDrive := &ref.cref.flash_drive + flashDrive := &config.FlashDrive + cFlashDrive.count = (C.ulong)(len(*flashDrive)) + cFlashDrive.entry = (*C.cm_memory_range_config)(C.calloc((C.ulong)(len(*flashDrive)), C.sizeof_cm_memory_range_config)) + for i, v := range *flashDrive { + offset := C.sizeof_cm_memory_range_config * i + addr := unsafe.Pointer(uintptr(unsafe.Pointer(cFlashDrive.entry)) + uintptr(offset)) + mr := (*C.cm_memory_range_config)(addr) + mr.start = (C.uint64_t)(v.Start) + mr.length = (C.uint64_t)(v.Length) + mr.shared = (C.bool)(v.Shared) + mr.image_filename = makeCString(&v.ImageFilename) + } + + cTlb := &ref.cref.tlb + tlb := &config.Tlb + cTlb.image_filename = makeCString(&tlb.ImageFilename) + + cClint := &ref.cref.clint + clint := &config.Clint + cClint.mtimecmp = (C.uint64_t)(clint.Mtimecmp) + + cHtif := &ref.cref.htif + htif := &config.Htif + cHtif.tohost = (C.uint64_t)(htif.Tohost) + cHtif.fromhost = (C.uint64_t)(htif.Fromhost) + cHtif.console_getchar = (C.bool)(htif.ConsoleGetchar) + cHtif.yield_manual = (C.bool)(htif.YieldManual) + cHtif.yield_automatic = (C.bool)(htif.YieldAutomatic) + + cCmio := &ref.cref.cmio + cmio := &config.Cmio + cCmio.has_value = (C.bool)(cmio.HsaValue) + cCmio.rx_buffer.start = (C.uint64_t)(cmio.RxBuffer.Start) + cCmio.rx_buffer.length = (C.uint64_t)(cmio.RxBuffer.Length) + cCmio.rx_buffer.shared = (C.bool)(cmio.RxBuffer.Shared) + cCmio.rx_buffer.image_filename = makeCString(&cmio.RxBuffer.ImageFilename) + cCmio.tx_buffer.start = (C.uint64_t)(cmio.TxBuffer.Start) + cCmio.tx_buffer.length = (C.uint64_t)(cmio.TxBuffer.Length) + cCmio.tx_buffer.shared = (C.bool)(cmio.TxBuffer.Shared) + cCmio.tx_buffer.image_filename = makeCString(&cmio.TxBuffer.ImageFilename) + + cUarch := &ref.cref.uarch + uarch := &config.Uarch + + cUarchProcessor := &cUarch.processor + uarchProcessor := &uarch.Processor + for i := 0; i < 32; i++ { + cUarchProcessor.x[i] = (C.uint64_t)(uarchProcessor.X[i]) + } + cUarchProcessor.pc = (C.uint64_t)(uarchProcessor.Pc) + cUarchProcessor.cycle = (C.uint64_t)(uarchProcessor.Cycle) + cUarchProcessor.halt_flag = (C.bool)(uarchProcessor.HaltFlag) + + cUarchRam := &cUarch.ram + uarchRam := &uarch.Ram + cUarchRam.image_filename = makeCString(&uarchRam.ImageFilename) + + return ref +} + +func (configCRef *ourMachineConfigCRef) free() { + if configCRef == nil || configCRef.cref == nil { + return + } + C.free(unsafe.Pointer(configCRef.cref.ram.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.dtb.bootargs)) + C.free(unsafe.Pointer(configCRef.cref.dtb.init)) + C.free(unsafe.Pointer(configCRef.cref.dtb.entrypoint)) + C.free(unsafe.Pointer(configCRef.cref.dtb.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.flash_drive.entry)) + + C.free(unsafe.Pointer(configCRef.cref.cmio.rx_buffer.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.cmio.tx_buffer.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.uarch.ram.image_filename)) + C.free(unsafe.Pointer(configCRef.cref)) + configCRef.cref = nil +} + +// cm_machine_config allocated by the emulator +type theirMachineConfigCRef struct { + cref *C.cm_machine_config +} + +func (configCRef *theirMachineConfigCRef) free() { + if configCRef != nil && configCRef.cref != nil { + C.cm_delete_machine_config(configCRef.cref) + configCRef.cref = nil + } +} + +func (configCRef *theirMachineConfigCRef) makeGoRef() (cfg *MachineConfig) { + cfg = &MachineConfig{} + c := configCRef.cref + // Processor + processor := &cfg.Processor + for i := 0; i < 30; i++ { + processor.X[i] = (uint64)(c.processor.x[i+1]) + } + for i := 0; i < 31; i++ { + processor.F[i] = (uint64)(c.processor.f[i+1]) + } + processor.Pc = (uint64)(c.processor.pc) + processor.Fcsr = (uint64)(c.processor.fcsr) + processor.Mvendorid = (uint64)(c.processor.mvendorid) + processor.Marchid = (uint64)(c.processor.marchid) + processor.Mimpid = (uint64)(c.processor.mimpid) + processor.Mcycle = (uint64)(c.processor.mcycle) + processor.Icycleinstret = (uint64)(c.processor.icycleinstret) + processor.Mstatus = (uint64)(c.processor.mstatus) + processor.Mtvec = (uint64)(c.processor.mtvec) + processor.Mscratch = (uint64)(c.processor.mscratch) + processor.Mepc = (uint64)(c.processor.mepc) + processor.Mcause = (uint64)(c.processor.mcause) + processor.Mtval = (uint64)(c.processor.mtval) + processor.Misa = (uint64)(c.processor.misa) + processor.Mie = (uint64)(c.processor.mie) + processor.Mip = (uint64)(c.processor.mip) + processor.Medeleg = (uint64)(c.processor.medeleg) + processor.Mideleg = (uint64)(c.processor.mideleg) + processor.Mcounteren = (uint64)(c.processor.mcounteren) + processor.Menvcfg = (uint64)(c.processor.menvcfg) + processor.Stvec = (uint64)(c.processor.stvec) + processor.Sscratch = (uint64)(c.processor.sscratch) + processor.Sepc = (uint64)(c.processor.sepc) + processor.Scause = (uint64)(c.processor.scause) + processor.Stval = (uint64)(c.processor.stval) + processor.Satp = (uint64)(c.processor.satp) + processor.Scounteren = (uint64)(c.processor.scounteren) + processor.Senvcfg = (uint64)(c.processor.senvcfg) + processor.Ilrsc = (uint64)(c.processor.ilrsc) + processor.Iflags = (uint64)(c.processor.iflags) + + // Ram + ram := &cfg.Ram + ram.Length = (uint64)(c.ram.length) + ram.ImageFilename = C.GoString(c.ram.image_filename) + + // Dtb + dtb := &cfg.Dtb + dtb.Bootargs = C.GoString(c.dtb.bootargs) + dtb.Init = C.GoString(c.dtb.init) + dtb.Entrypoint = C.GoString(c.dtb.entrypoint) + dtb.ImageFilename = C.GoString(c.dtb.image_filename) + + // FlashDrive + //flashDrive := &cfg.FlashDrive + for i := 0; i < int(c.flash_drive.count); i++ { + offset := C.sizeof_cm_memory_range_config * i + addr := unsafe.Pointer(uintptr(unsafe.Pointer(c.flash_drive.entry)) + uintptr(offset)) + mr := (*C.cm_memory_range_config)(addr) + cfg.FlashDrive = append(cfg.FlashDrive, MemoryRangeConfig{ + Start: (uint64)(mr.start), + Length: (uint64)(mr.length), + Shared: (bool)(mr.shared), + ImageFilename: C.GoString(mr.image_filename), + }) + } + + // Tlb + tlb := &cfg.Tlb + tlb.ImageFilename = C.GoString(c.tlb.image_filename) + + // Clint + clint := &cfg.Clint + clint.Mtimecmp = (uint64)(c.clint.mtimecmp) + + // Htif + htif := &cfg.Htif + htif.Tohost = (uint64)(c.htif.tohost) + htif.Fromhost = (uint64)(c.htif.fromhost) + htif.ConsoleGetchar = (bool)(c.htif.console_getchar) + htif.YieldManual = (bool)(c.htif.yield_manual) + htif.YieldAutomatic = (bool)(c.htif.yield_automatic) + + // CMIO + cmio := &cfg.Cmio + cmio.HsaValue = (bool)(c.cmio.has_value) + cmio.RxBuffer = MemoryRangeConfig{ + Start: (uint64)(c.cmio.rx_buffer.start), + Length: (uint64)(c.cmio.rx_buffer.length), + Shared: (bool)(c.cmio.rx_buffer.shared), + ImageFilename: C.GoString(c.cmio.rx_buffer.image_filename), + } + cmio.TxBuffer = MemoryRangeConfig{ + Start: (uint64)(c.cmio.tx_buffer.start), + Length: (uint64)(c.cmio.tx_buffer.length), + Shared: (bool)(c.cmio.tx_buffer.shared), + ImageFilename: C.GoString(c.cmio.tx_buffer.image_filename), + } + + // Uarch + uarch := &cfg.Uarch + uarchProcessor := &uarch.Processor + for i := 0; i < 32; i++ { + uarchProcessor.X[i] = (uint64)(c.uarch.processor.x[i]) + } + uarchProcessor.Pc = (uint64)(c.uarch.processor.pc) + uarchProcessor.Cycle = (uint64)(c.uarch.processor.cycle) + uarchProcessor.HaltFlag = (bool)(c.uarch.processor.halt_flag) + + uarchRam := &uarch.Ram + uarchRam.ImageFilename = C.GoString(c.uarch.ram.image_filename) + + return cfg +} + +func makeCString(s *string) *C.char { + if s == nil || *s == "" { + return nil + } + return C.CString(*s) +} + +/////////////////////////// +// Public API +/////////////////////////// + +func NewDefaultMachineConfig() *MachineConfig { + ref := theirMachineConfigCRef{} + defer ref.free() + ref.cref = C.cm_new_default_machine_config() + return ref.makeGoRef() +} + +// a connection to the remote jsonrpc machine manager +type RemoteMachineManager struct { + RemoteAddress string + cref *C.cm_jsonrpc_mg_mgr +} + +// a local or remote machine +type Machine struct { + cref *C.cm_machine + remoteManager *RemoteMachineManager +} + +func (mgr *RemoteMachineManager) Free() { + if mgr != nil && mgr.cref != nil { + C.cm_delete_jsonrpc_mg_mgr(mgr.cref) + mgr.cref = nil + } +} + +func (machine *Machine) Free() { + if machine == nil || machine.cref == nil { + return + } + C.cm_delete_machine(machine.cref) + machine.cref = nil +} + +func (machine *Machine) Store(dir string) error { + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + var cerr *C.char + if e := C.cm_store(machine.cref, cDir, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_create_machine(configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_load_machine(cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + + } + return machine, nil +} + +func GetDefaultConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_get_default_config(&theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (m *Machine) Run(mcycleEnd uint64) (BreakReason, error) { + var cerr *C.char + var creason C.CM_BREAK_REASON + if e := C.cm_machine_run(m.cref, C.uint64_t(mcycleEnd), &creason, &cerr); isFailure(e) { + return BreakReasonFailed, newError(e, cerr) + } + return (BreakReason)(creason), nil +} + +func (machine *Machine) GetInitialConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_get_initial_config(machine.cref, &theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (machine *Machine) Destroy() error { + var cerr *C.char + if e := C.cm_destroy(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadCSR(r ProcessorCSR) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_csr(machine.cref, C.CM_PROC_CSR(r), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) WriteCSR(r ProcessorCSR, val uint64) error { + var cerr *C.char + if e := C.cm_write_csr(machine.cref, C.CM_PROC_CSR(r), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadX(i int) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_x(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) WriteX(i int, val uint64) error { + var cerr *C.char + if e := C.cm_write_x(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadF(i int) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_f(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) ReadIFlagsX() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_X(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) ResetIFlagsX() error { + var cerr *C.char + if e := C.cm_reset_iflags_X(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) SetIFlagsX() error { + var cerr *C.char + if e := C.cm_set_iflags_X(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadIFlagsY() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_Y(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) ResetIFlagsY() error { + var cerr *C.char + if e := C.cm_reset_iflags_Y(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) SetIFlagsY() error { + var cerr *C.char + if e := C.cm_set_iflags_Y(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadIFlagsH() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_H(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) SetIFlagsH() error { + var cerr *C.char + if e := C.cm_set_iflags_H(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +type MerkleTreeHash [32]byte + +func (hash *MerkleTreeHash) String() string { + return hex.EncodeToString(hash[:]) +} + +func (machine *Machine) GetRootHash() (*MerkleTreeHash, error) { + var chash C.cm_hash + var cerr *C.char + if e := C.cm_get_root_hash(machine.cref, &chash, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + hash := &MerkleTreeHash{} + for i := 0; i < 32; i++ { + hash[i] = byte(chash[i]) + } + return hash, nil +} + +func (machine *Machine) WriteF(i int, val uint64) error { + var cerr *C.char + if e := C.cm_write_f(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func NewRemoteMachineManager(remoteAddress string) (*RemoteMachineManager, error) { + manager := &RemoteMachineManager{RemoteAddress: remoteAddress} + cRemoteAddress := C.CString(remoteAddress) + defer C.free(unsafe.Pointer(cRemoteAddress)) + var cerr *C.char + if e := C.cm_create_jsonrpc_mg_mgr(cRemoteAddress, &manager.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return manager, nil +} + +func (mgr *RemoteMachineManager) Shutdown() error { + var cerr *C.char + if e := C.cm_jsonrpc_shutdown(mgr.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (mgr *RemoteMachineManager) Fork() (*string, error) { + var cNewAddress *C.char = nil + var cerr *C.char + if e := C.cm_jsonrpc_fork(mgr.cref, &cNewAddress, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + newAddress := C.GoString(cNewAddress) + C.cm_delete_cstring(cNewAddress) + return &newAddress, nil +} + +func (mgr *RemoteMachineManager) NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{remoteManager: mgr} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_create_jsonrpc_machine(mgr.cref, configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (mgr *RemoteMachineManager) GetDefaultConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_jsonrpc_get_default_config(mgr.cref, &theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (mgr *RemoteMachineManager) LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{remoteManager: mgr} + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_load_jsonrpc_machine(mgr.cref, cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (mgr *RemoteMachineManager) GetMachine() (*Machine, error) { + machine := &Machine{remoteManager: mgr} + var cerr *C.char + if e := C.cm_get_jsonrpc_machine(mgr.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (machine *Machine) WriteMemory(address uint64, data []byte) error { + var cerr *C.char + if e := C.cm_write_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.size_t(len(data)), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadMemory(address uint64, length uint64) ([]byte, error) { + data := make([]byte, length) + var cerr *C.char + if e := C.cm_read_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.uint64_t(length), &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return data, nil +} + +func (machine *Machine) ReplaceMemoryRange(newRange *MemoryRangeConfig) error { + newRangeRef := newRange.makeCRef() + defer newRangeRef.free() + var cerr *C.char + if e := C.cm_replace_memory_range(machine.cref, newRangeRef.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) Snapshot() error { + var cerr *C.char + if e := C.cm_snapshot(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) Rollback() error { + var cerr *C.char + if e := C.cm_rollback(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} diff --git a/pkg/emulator/emulator_test.go b/pkg/emulator/emulator_test.go new file mode 100644 index 000000000..3711fbfc5 --- /dev/null +++ b/pkg/emulator/emulator_test.go @@ -0,0 +1,474 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Test Cartesi Machine C API wrapper + +package emulator + +import ( + "math" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGetDefaultConfig(t *testing.T) { + cfg, err := GetDefaultConfig() + assert.Nil(t, err) + assert.NotNil(t, cfg) +} + +func TestNewDefaultMachineConfig(t *testing.T) { + cfg := NewDefaultMachineConfig() + assert.NotNil(t, cfg) +} + +func TestNewLocalMachine(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + defer machine.Free() + assert.Nil(t, err) + assert.NotNil(t, machine) + sharedMachineTests(t, machine) +} + +func TestReadWriteMemoryOnLocalMachine(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + defer machine.Free() + sharedTestReadWriteMemory(t, machine) +} + +func TestReadWriteMemoryOnRemoteMachine(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // connect to remote server + mgr, err := NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + sharedTestReadWriteMemory(t, machine) +} + +func sharedTestReadWriteMemory(t *testing.T, machine *Machine) { + // read existing data + existingData, err := machine.ReadMemory(rxBufferStartAddress, 1024) + assert.Nil(t, err) + assert.NotNil(t, existingData) + assert.Equal(t, 1024, len(existingData)) + // Prepare new data + var newData [1024]byte + for i := 0; i < 1024; i++ { + newData[i] = byte(i % 256) + } + assert.NotEqual(t, newData[:], existingData) + // write new data + err = machine.WriteMemory(rxBufferStartAddress, newData[:]) + assert.Nil(t, err) + // read back the data + readBackData, err := machine.ReadMemory(rxBufferStartAddress, 1024) + assert.Nil(t, err) + assert.NotNil(t, readBackData) + assert.Equal(t, newData[:], readBackData) + + // create a byte array full of 0xda of the size of rxBufferLength + var newRxBufferData [rxBufferLength]byte + for i := 0; i < rxBufferLength; i++ { + newRxBufferData[i] = 0xda + } + // create a temporary file and write the new data to it + tempFile, err := os.CreateTemp("", "rxBuffer") + assert.Nil(t, err) + defer os.Remove(tempFile.Name()) + _, err = tempFile.Write(newRxBufferData[:]) + assert.Nil(t, err) + tempFile.Close() + // initialize a MemoryRangeConfig pointing to this file + newRxBuffer := &MemoryRangeConfig{ + Start: rxBufferStartAddress, + Length: rxBufferLength, + Shared: false, + ImageFilename: tempFile.Name(), + } + // replace the rxBuffer with the new data + err = machine.ReplaceMemoryRange(newRxBuffer) + assert.Nil(t, err) + // read back memory and check if it was replaced + readBackRxData, err := machine.ReadMemory(rxBufferStartAddress, rxBufferLength) + assert.Nil(t, err) + assert.NotNil(t, readBackRxData) + assert.Equal(t, newRxBufferData[:], readBackRxData) + +} + +func TestRunLocalMachineHappyPath(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + defer machine.Free() + assert.Nil(t, err) + assert.NotNil(t, machine) + var iflagsH bool + var mcycle uint64 + var hashBefore *MerkleTreeHash + var hashAfter *MerkleTreeHash + var breakReason BreakReason + // assert initial state + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(0), mcycle) + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // Advance one cycle + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) // still not halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(1), mcycle) // advanced one cycle + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed + storedHash := hashAfter + // Store machine + tempDir, err := os.MkdirTemp("", "testmachines") + assert.Nil(t, err) + defer os.RemoveAll(tempDir) + storePath := tempDir + "/machine" + err = machine.Store(storePath) + assert.Nil(t, err) + // run until halt + hashBefore = hashAfter + breakReason, err = machine.Run(math.MaxUint64) + assert.Nil(t, err) + assert.Equal(t, BreakReasonHalted, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) // halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Greater(t, mcycle, uint64(1)) + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed again + + // load a second machine from empDir, err := ioutil.TempDir("", "example") + machine2, err := LoadMachine(storePath, runtimeConfig) + defer machine2.Free() + assert.Nil(t, err) + assert.NotNil(t, machine2) + var hashMachine2 *MerkleTreeHash + hashMachine2, err = machine2.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, storedHash.String(), hashMachine2.String()) // hash is the same +} + +func TestNewRemoteMachine(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // connect to remote server + mgr, err := NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + sharedMachineTests(t, machine) +} + +func TestRemoteMachineHappyPath(t *testing.T) { + var err error + remoteAddress := makeRemoteAddress() + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // Connect to remote server + var mgr *RemoteMachineManager + mgr, err = NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // get default config from remote server + var remoteDefCfg *MachineConfig + remoteDefCfg, err = mgr.GetDefaultConfig() + assert.Nil(t, err) + assert.NotNil(t, remoteDefCfg) + + // create a remote machine + var machine *Machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err = mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() // destroy machine from server + machine.Free() + }() + // assert initial machine state + var iflagsH bool + var mcycle uint64 + var hashBefore *MerkleTreeHash + var finalHash *MerkleTreeHash + var breakReason BreakReason + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(0), mcycle) + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // Advance one cycle + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) // still not halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(1), mcycle) // advanced one cycle + finalHash, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed + storedHash := finalHash + // Store machine + tempDir, err := os.MkdirTemp("", "testmachines") + assert.Nil(t, err) + defer os.RemoveAll(tempDir) + storePath := tempDir + "/machine" + err = machine.Store(storePath) + assert.Nil(t, err) + // run until halt + hashBefore = finalHash + breakReason, err = machine.Run(math.MaxUint64) + assert.Nil(t, err) + assert.Equal(t, BreakReasonHalted, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) // halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Greater(t, mcycle, uint64(1)) + finalHash, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed again + // fork the remote server + var secondRemoteAddress *string + secondRemoteAddress, err = mgr.Fork() + assert.Nil(t, err) + assert.NotEqual(t, remoteAddress, secondRemoteAddress) + assert.NotEqual(t, "", secondRemoteAddress) + assert.NotEqual(t, remoteAddress, secondRemoteAddress) + // connect to the forked server + var mgr2 *RemoteMachineManager + mgr2, err = NewRemoteMachineManager(*secondRemoteAddress) + assert.Nil(t, err) + assert.NotNil(t, mgr2) + // Get forked machine + var forkedMachine *Machine + forkedMachine, err = mgr2.GetMachine() + assert.Nil(t, err) + assert.NotNil(t, forkedMachine) + defer func() { + forkedMachine.Destroy() + forkedMachine.Free() + }() + var forkedHash *MerkleTreeHash + forkedHash, err = forkedMachine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, finalHash.String(), forkedHash.String()) + // destroy existing machine on mgr2 + err = forkedMachine.Destroy() + assert.Nil(t, err) + // Load stored machine on mgr2 + var loadedMachine *Machine + loadedMachine, err = mgr2.LoadMachine(storePath, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, loadedMachine) + defer func() { + loadedMachine.Destroy() + loadedMachine.Free() + }() + var loadedHash *MerkleTreeHash + loadedHash, err = loadedMachine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, storedHash.String(), loadedHash.String()) +} + +func TestSnapshot(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // Connect to remote server + var mgr *RemoteMachineManager + var err error + mgr, err = NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create a machine + var machine *Machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err = mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + // get current hash + var hashBefore *MerkleTreeHash + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // take a snapshot + err = machine.Snapshot() + assert.Nil(t, err) + // advance one cycle + var breakReason BreakReason + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + // get new hash + var hashAfter *MerkleTreeHash + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) + // rollback + err = machine.Rollback() + assert.Nil(t, err) + // get hash after rollback + var hashAfterRollback *MerkleTreeHash + hashAfterRollback, err = machine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, hashBefore.String(), hashAfterRollback.String()) + +} + +func sharedMachineTests(t *testing.T, m *Machine) { + cfg, err := m.GetInitialConfig() + assert.Nil(t, err) + assert.NotNil(t, cfg) + + // Toggle flags that control dapp execution + // read and toggle IFlagsY + var iflagsY bool + iflagsY, err = m.ReadIFlagsY() + assert.Nil(t, err) + assert.False(t, iflagsY) + err = m.SetIFlagsY() + assert.Nil(t, err) + iflagsY, err = m.ReadIFlagsY() + assert.Nil(t, err) + assert.True(t, iflagsY) + // read and toggle IFlagsH + var iflagsH bool + iflagsH, err = m.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + err = m.SetIFlagsH() + assert.Nil(t, err) + iflagsH, err = m.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) +} + +const rxBufferStartAddress = 0x60000000 +const rxBufferLength = 1 << 21 + +func makeMachineConfig() *MachineConfig { + images_path := strings.TrimRight(os.Getenv("CARTESI_IMAGES_PATH"), "/") + "/" + cfg := NewDefaultMachineConfig() + cfg.Processor.Mimpid = math.MaxUint64 + cfg.Processor.Marchid = math.MaxUint64 + cfg.Processor.Mvendorid = math.MaxUint64 + cfg.Ram.ImageFilename = images_path + "linux.bin" + cfg.Ram.Length = 64 << 20 + cfg.FlashDrive = []MemoryRangeConfig{ + { + Start: 0x80000000000000, + Length: 0xffffffffffffffff, + Shared: false, + ImageFilename: images_path + "rootfs.ext2", + }, + } + cfg.Dtb.Bootargs = "quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init" + cfg.Dtb.Init = `echo "Opa!" + busybox mkdir -p /run/drive-label && echo "root" > /run/drive-label/pmem0\ + USER=dapp + ` + + cfg.Cmio.HsaValue = true + cfg.Cmio.RxBuffer = MemoryRangeConfig{ + Start: rxBufferStartAddress, + Length: rxBufferLength, + } + cfg.Cmio.TxBuffer = MemoryRangeConfig{ + Start: 0x60200000, + Length: 1 << 21, + } + + return cfg +} + +func makeRemoteAddress() string { + remoteServerPort := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PORT") // example: 3333 + return "localhost:" + remoteServerPort +} + +func launchRemoteServer(t *testing.T) *exec.Cmd { + remoteMachineServerPath := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PATH") + // launch remote server + cmd := exec.Command(remoteMachineServerPath, "--server-address="+makeRemoteAddress()) + err := cmd.Start() + assert.Nil(t, err) + time.Sleep(2 * time.Second) + return cmd +}