From 1f32f20a998e86389ba2f59757f16ca895dbc0dd Mon Sep 17 00:00:00 2001 From: Renan Santos Date: Thu, 21 Mar 2024 16:42:47 -0300 Subject: [PATCH] refactor: remove tests (temporarily) and reorganize code to fit Go conventions --- cmd/cartesi-machine/main.go | 2 +- pkg/emulator/.gitignore | 1 - pkg/emulator/emulator.go | 433 ++++--------------------------- pkg/emulator/emulator_test.go | 474 ---------------------------------- pkg/emulator/machine.go | 224 ++++++++++++++++ pkg/emulator/remote.go | 92 +++++++ 6 files changed, 364 insertions(+), 862 deletions(-) delete mode 100644 pkg/emulator/.gitignore delete mode 100644 pkg/emulator/emulator_test.go create mode 100644 pkg/emulator/machine.go create mode 100644 pkg/emulator/remote.go diff --git a/cmd/cartesi-machine/main.go b/cmd/cartesi-machine/main.go index 490c7e6dd..78cf2ac1b 100644 --- a/cmd/cartesi-machine/main.go +++ b/cmd/cartesi-machine/main.go @@ -17,7 +17,7 @@ import ( func main() { var machine *emulator.Machine - defer machine.Free() + defer machine.Delete() var mgr *emulator.RemoteMachineManager defer mgr.Free() var err error diff --git a/pkg/emulator/.gitignore b/pkg/emulator/.gitignore deleted file mode 100644 index 496ee2ca6..000000000 --- a/pkg/emulator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store \ No newline at end of file diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go index afd2a03ca..3c316a6d4 100644 --- a/pkg/emulator/emulator.go +++ b/pkg/emulator/emulator.go @@ -1,16 +1,11 @@ // (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 -*/ +// #cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" import "C" import ( @@ -19,6 +14,10 @@ import ( "unsafe" ) +// ------------------------------------------------------------------------------------------------ +// Error +// ------------------------------------------------------------------------------------------------ + type ErrorCode int32 const ( @@ -53,10 +52,6 @@ const ( ErrorCodeUnknown ErrorCode = C.CM_ERROR_UNKNOWN ) -func isFailure(cerr C.int) bool { - return ErrorCode(cerr) != ErrorCodeOk -} - type Error struct { Code ErrorCode Msg string @@ -66,14 +61,18 @@ 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) +func newError(code C.int, msg *C.char) error { + defer C.cm_delete_cstring(msg) if code != C.CM_ERROR_OK { - return &Error{Code: ErrorCode(code), Msg: C.GoString(message)} + return &Error{Code: ErrorCode(code), Msg: C.GoString(msg)} } return nil } +// ------------------------------------------------------------------------------------------------ +// Types +// ------------------------------------------------------------------------------------------------ + type BreakReason int32 const ( @@ -249,9 +248,37 @@ type UarchConfig struct { Ram UarchRamConfig } -//////////////////////////////////////// -// Helpers and utils -//////////////////////////////////////// +// ------------------------------------------------------------------------------------------------ +// MachineConfig +// ------------------------------------------------------------------------------------------------ + +func NewDefaultMachineConfig() *MachineConfig { + ref := theirMachineConfigCRef{} + defer ref.free() + ref.cref = C.cm_new_default_machine_config() + return ref.makeGoRef() +} + +func GetDefaultMachineConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var msg *C.char + code := C.cm_get_default_config(&theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +// ------------------------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------------------------ + +type MerkleTreeHash [32]byte + +func (hash *MerkleTreeHash) String() string { + return hex.EncodeToString(hash[:]) +} type ourMemoryRangeConfig struct { cref *C.cm_memory_range_config @@ -376,7 +403,8 @@ func (config *MachineConfig) makeCRef() (ref *ourMachineConfigCRef) { 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)) + 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)) @@ -586,370 +614,3 @@ func makeCString(s *string) *C.char { } 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 deleted file mode 100644 index 3711fbfc5..000000000 --- a/pkg/emulator/emulator_test.go +++ /dev/null @@ -1,474 +0,0 @@ -// (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 -} diff --git a/pkg/emulator/machine.go b/pkg/emulator/machine.go new file mode 100644 index 000000000..7d06e141b --- /dev/null +++ b/pkg/emulator/machine.go @@ -0,0 +1,224 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package emulator + +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" +import "C" +import "unsafe" + +// A local or remote machine. +type Machine struct { + c *C.cm_machine + remote *RemoteMachineManager +} + +func (machine *Machine) GetInitialConfig() (*MachineConfig, error) { + var msg *C.char + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + code := C.cm_get_initial_config(machine.c, &theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +func NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var msg *C.char + code := C.cm_create_machine(configRef.cref, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +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 msg *C.char + code := C.cm_load_machine(cDir, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (machine *Machine) Store(dir string) error { + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + var msg *C.char + code := C.cm_store(machine.c, cDir, &msg) + return newError(code, msg) +} + +func (machine *Machine) Delete() { + if machine.c != nil { + C.cm_delete_machine(machine.c) + machine.c = nil + } +} + +func (machine *Machine) Destroy() error { + var msg *C.char + code := C.cm_destroy(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Snapshot() error { + var msg *C.char + code := C.cm_snapshot(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Rollback() error { + var msg *C.char + code := C.cm_rollback(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Run(mcycleEnd uint64) (BreakReason, error) { + var msg *C.char + var reason C.CM_BREAK_REASON + code := C.cm_machine_run(machine.c, C.uint64_t(mcycleEnd), &reason, &msg) + if err := newError(code, msg); err != nil { + return BreakReasonFailed, err + } + return (BreakReason)(reason), nil +} + +func (machine *Machine) GetRootHash() (*MerkleTreeHash, error) { + var msg *C.char + var chash C.cm_hash + code := C.cm_get_root_hash(machine.c, &chash, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + hash := &MerkleTreeHash{} + for i := 0; i < 32; i++ { + hash[i] = byte(chash[i]) + } + return hash, nil +} + +func (machine *Machine) ReplaceMemoryRange(newRange *MemoryRangeConfig) error { + var msg *C.char + newRangeRef := newRange.makeCRef() + defer newRangeRef.free() + code := C.cm_replace_memory_range(machine.c, newRangeRef.cref, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadMemory(address, length uint64) ([]byte, error) { + var msg *C.char + data := make([]byte, length) + code := C.cm_read_memory(machine.c, + C.uint64_t(address), + (*C.uchar)(unsafe.Pointer(&data[0])), + C.uint64_t(length), + &msg) + return data, newError(code, msg) +} + +func (machine *Machine) WriteMemory(address uint64, data []byte) error { + var msg *C.char + code := C.cm_write_memory(machine.c, + C.uint64_t(address), + (*C.uchar)(unsafe.Pointer(&data[0])), + C.size_t(len(data)), + &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadCSR(r ProcessorCSR) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_csr(machine.c, C.CM_PROC_CSR(r), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteCSR(r ProcessorCSR, value uint64) error { + var msg *C.char + code := C.cm_write_csr(machine.c, C.CM_PROC_CSR(r), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadX(i int) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_x(machine.c, C.int(i), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteX(i int, value uint64) error { + var msg *C.char + code := C.cm_write_x(machine.c, C.int(i), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadF(i int) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_f(machine.c, C.int(i), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteF(i int, value uint64) error { + var msg *C.char + code := C.cm_write_f(machine.c, C.int(i), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsX() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_X(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) ResetIFlagsX() error { + var msg *C.char + code := C.cm_reset_iflags_X(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) SetIFlagsX() error { + var msg *C.char + code := C.cm_set_iflags_X(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsY() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_Y(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) ResetIFlagsY() error { + var msg *C.char + code := C.cm_reset_iflags_Y(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) SetIFlagsY() error { + var msg *C.char + code := C.cm_set_iflags_Y(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsH() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_H(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) SetIFlagsH() error { + var msg *C.char + code := C.cm_set_iflags_H(machine.c, &msg) + return newError(code, msg) +} diff --git a/pkg/emulator/remote.go b/pkg/emulator/remote.go new file mode 100644 index 000000000..039e19e79 --- /dev/null +++ b/pkg/emulator/remote.go @@ -0,0 +1,92 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package emulator + +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" +import "C" +import "unsafe" + +// A connection to the remote jsonrpc machine manager. +type RemoteMachineManager struct { + c *C.cm_jsonrpc_mg_mgr + + Address string +} + +func NewRemoteMachineManager(address string) (*RemoteMachineManager, error) { + manager := &RemoteMachineManager{Address: address} + cRemoteAddress := C.CString(address) + defer C.free(unsafe.Pointer(cRemoteAddress)) + var msg *C.char + code := C.cm_create_jsonrpc_mg_mgr(cRemoteAddress, &manager.c, &msg) + return manager, newError(code, msg) +} + +func (remote *RemoteMachineManager) Delete() { + if remote.c != nil { + C.cm_delete_jsonrpc_mg_mgr(remote.c) + remote.c = nil + } +} + +func (remote *RemoteMachineManager) NewMachine( + config *MachineConfig, + runtime *MachineRuntimeConfig, +) (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + code := C.cm_create_jsonrpc_machine(remote.c, configRef.cref, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) LoadMachine( + directory string, + runtime *MachineRuntimeConfig, +) (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + dir := C.CString(directory) + defer C.free(unsafe.Pointer(dir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + code := C.cm_load_jsonrpc_machine(remote.c, dir, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) GetMachine() (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + code := C.cm_get_jsonrpc_machine(remote.c, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) GetDefaultMachineConfig() (*MachineConfig, error) { + var msg *C.char + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + code := C.cm_jsonrpc_get_default_config(remote.c, &theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +func (remote *RemoteMachineManager) Fork() (newAddress string, _ error) { + var msg *C.char + var address *C.char + defer C.cm_delete_cstring(address) + code := C.cm_jsonrpc_fork(remote.c, &address, &msg) + return C.GoString(address), newError(code, msg) +} + +func (remote *RemoteMachineManager) Shutdown() error { + var msg *C.char + code := C.cm_jsonrpc_shutdown(remote.c, &msg) + return newError(code, msg) +}