From 9f37088e7777caac77d4b62732b1867e16144e99 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:38:02 -0300 Subject: [PATCH 1/2] feat(emulator): bump emulator (v0.21.0) and rootfs (v0.18.0). make it build --- Dockerfile | 6 +- Makefile | 4 +- cmd/cartesi-rollups-machine-tool/main.go | 30 ++++- .../replay_dave.lua | 11 +- control.template | 2 +- internal/manager/instance.go | 12 +- internal/manager/instance_test.go | 27 +--- pkg/emulator/emulator.go | 2 +- pkg/emulator/machine.go | 17 ++- pkg/emulator/remote.go | 2 +- pkg/emulator/types.go | 116 +++++++++--------- pkg/machine/backend.go | 2 +- pkg/machine/backend_test.go | 10 +- pkg/machine/implementation.go | 25 +--- pkg/machine/implementation_test.go | 82 +++---------- pkg/machine/libcartesi.go | 6 +- pkg/machine/libcartesi_test.go | 15 +-- pkg/machine/machine.go | 10 +- pkg/machine/machine_test.go | 15 +-- test/dependencies | 4 +- test/dependencies.sha256 | 4 +- 21 files changed, 173 insertions(+), 229 deletions(-) diff --git a/Dockerfile b/Dockerfile index cfb172621..948be5798 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # syntax=docker.io/docker/dockerfile:1 -ARG EMULATOR_VERSION=0.20.0 +ARG EMULATOR_VERSION=0.21.0-test6 # Build directories. ARG GO_BUILD_PATH=/build/cartesi/go @@ -29,8 +29,8 @@ RUN < Provides: cartesi-rollups-node -Depends: cartesi-machine-emulator (>= 0.20.0), cartesi-machine-emulator (<< 0.21.0) +Depends: cartesi-machine-emulator (>= 0.21.0), cartesi-machine-emulator (<< 0.22.0) Section: net Priority: optional Multi-Arch: no diff --git a/internal/manager/instance.go b/internal/manager/instance.go index 56274e8a1..c32f4e678 100644 --- a/internal/manager/instance.go +++ b/internal/manager/instance.go @@ -299,16 +299,8 @@ func (m *MachineInstanceImpl) Advance(ctx context.Context, input []byte, epochIn advanceCtx, cancel := context.WithTimeout(ctx, m.advanceTimeout) defer cancel() - if computeHashes { - // write the checkpoint hash before processing - err = fork.WriteCheckpointHash(advanceCtx, prevMachineHash) - if err != nil { - return nil, errors.Join(err, fork.Close()) - } - } - // Process the input - advanceResp, err := fork.Advance(advanceCtx, input, computeHashes) + advanceResp, err := fork.Advance(advanceCtx, input, prevMachineHash, computeHashes) status, err := toInputStatus(advanceResp.Accepted, err) if err != nil { return nil, errors.Join(err, fork.Close()) @@ -409,7 +401,7 @@ func (m *MachineInstanceImpl) Inspect(ctx context.Context, query []byte) (*Inspe defer cancel() // Process the query - accepted, reports, inspectErr := fork.Inspect(inspectCtx, query) + accepted, reports, inspectErr := fork.Inspect(inspectCtx, query, machine.Hash{}) // Create the result result := &InspectResult{ diff --git a/internal/manager/instance_test.go b/internal/manager/instance_test.go index b22f0bb6e..ab91298b3 100644 --- a/internal/manager/instance_test.go +++ b/internal/manager/instance_test.go @@ -507,9 +507,6 @@ func (s *MachineInstanceSuite) TestAdvance() { require := s.Require() inner, fork, machine := s.setupAdvance() - // Set up WriteCheckpointHash to succeed - fork.CheckpointHashError = nil - res, err := machine.Advance(context.Background(), []byte{}, 0, 5, true) require.Nil(err) require.NotNil(res) @@ -523,20 +520,6 @@ func (s *MachineInstanceSuite) TestAdvance() { _ = inner }) - s.Run("CollectHashesWriteCheckpointError", func() { - require := s.Require() - _, fork, machine := s.setupAdvance() - - errCheckpoint := errors.New("checkpoint write error") - fork.CheckpointHashError = errCheckpoint - - res, err := machine.Advance(context.Background(), []byte{}, 0, 5, true) - require.Error(err) - require.Nil(res) - require.ErrorIs(err, errCheckpoint) - require.Equal(uint64(5), machine.processedInputs.Load()) - }) - s.Run("SequentialAdvances", func() { // Advance is serialized by advanceMutex — concurrent advance on the // same machine never happens by design. This test verifies that two @@ -1475,8 +1458,6 @@ type MockRollupsMachine struct { HashReturn machine.Hash HashError error - CheckpointHashError error - AdvanceAcceptedReturn bool AdvanceOutputsReturn []machine.Output AdvanceReportsReturn []machine.Report @@ -1516,11 +1497,7 @@ func (m *MockRollupsMachine) OutputsHashProof(_ context.Context) ([]machine.Hash return m.OutputsHashProofReturn, m.OutputsHashProofError } -func (m *MockRollupsMachine) WriteCheckpointHash(_ context.Context, _ machine.Hash) error { - return m.CheckpointHashError -} - -func (m *MockRollupsMachine) Advance(_ context.Context, _ []byte, _ bool) (*machine.AdvanceResponse, error) { +func (m *MockRollupsMachine) Advance(_ context.Context, _ []byte, _ machine.Hash, _ bool) (*machine.AdvanceResponse, error) { return &machine.AdvanceResponse{ Accepted: m.AdvanceAcceptedReturn, Outputs: m.AdvanceOutputsReturn, @@ -1531,7 +1508,7 @@ func (m *MockRollupsMachine) Advance(_ context.Context, _ []byte, _ bool) (*mach }, m.AdvanceError } -func (m *MockRollupsMachine) Inspect(_ context.Context, _ []byte) (bool, []machine.Report, error) { +func (m *MockRollupsMachine) Inspect(_ context.Context, _ []byte, _ machine.Hash) (bool, []machine.Report, error) { return m.InspectAcceptedReturn, m.InspectReportsReturn, m.InspectError } diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go index cc3e634fb..3761aabdd 100644 --- a/pkg/emulator/emulator.go +++ b/pkg/emulator/emulator.go @@ -8,7 +8,7 @@ package emulator // #cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc // #include -// #include "cartesi-machine/jsonrpc-machine-c-api.h" +// #include "cartesi-machine/cm-jsonrpc.h" import "C" import ( diff --git a/pkg/emulator/machine.go b/pkg/emulator/machine.go index d03315c52..03d1e9161 100644 --- a/pkg/emulator/machine.go +++ b/pkg/emulator/machine.go @@ -8,7 +8,7 @@ package emulator // #include // #include -// #include "cartesi-machine/machine-c-api.h" +// #include "cartesi-machine/cm.h" import "C" import ( @@ -383,16 +383,22 @@ func (m *Machine) CollectMCycleRootHashes(mcycleEnd, mcyclePeriod, mcyclePhase u } // collect_uarch_cycle_root_hashes -func (m *Machine) CollectUarchCycleRootHashes(mcycleEnd uint64, log2BundleMcycleCount int32) ([]byte, error) { +func (m *Machine) CollectUarchCycleRootHashes(mcycleEnd uint64, log2BundleUarchCycleCount int32, revertUarchTail string) ([]byte, error) { var err error var result []byte m.callCAPI(func() { var cResult *C.char + var revertUarchTailC *C.char + if revertUarchTail != "" { + revertUarchTailC = C.CString(revertUarchTail) + defer C.free(unsafe.Pointer(revertUarchTailC)) + } err = newError(C.cm_collect_uarch_cycle_root_hashes( m.ptr, C.uint64_t(mcycleEnd), - C.int32_t(log2BundleMcycleCount), + C.int32_t(log2BundleUarchCycleCount), + revertUarchTailC, &cResult)) result = []byte(C.GoString(cResult)) }) @@ -404,7 +410,7 @@ func (m *Machine) CollectUarchCycleRootHashes(mcycleEnd uint64, log2BundleMcycle } // send_cmio_response -func (m *Machine) SendCmioResponse(reason uint16, data []byte) error { +func (m *Machine) SendCmioResponse(revertRootHash Hash, reason uint16, data []byte) error { var err error m.callCAPI(func() { @@ -413,9 +419,10 @@ func (m *Machine) SendCmioResponse(reason uint16, data []byte) error { if sizeData > 0 { ptrData = (*C.uint8_t)(unsafe.Pointer(&data[0])) } - + //var cHash C.cm_hash = revertRootHash err = newError(C.cm_send_cmio_response( m.ptr, + (*[32]C.uint8_t)(unsafe.Pointer(&revertRootHash)), C.uint16_t(reason), ptrData, sizeData, diff --git a/pkg/emulator/remote.go b/pkg/emulator/remote.go index aedb0ad4e..551c9e5ff 100644 --- a/pkg/emulator/remote.go +++ b/pkg/emulator/remote.go @@ -7,7 +7,7 @@ package emulator // #include -// #include "cartesi-machine/jsonrpc-machine-c-api.h" +// #include "cartesi-machine/cm-jsonrpc.h" import "C" import ( diff --git a/pkg/emulator/types.go b/pkg/emulator/types.go index bf8bc22b8..917264a18 100644 --- a/pkg/emulator/types.go +++ b/pkg/emulator/types.go @@ -7,7 +7,7 @@ package emulator // #include -// #include "cartesi-machine/jsonrpc-machine-c-api.h" +// #include "cartesi-machine/cm-jsonrpc.h" import "C" import "fmt" @@ -18,18 +18,18 @@ import "fmt" type ErrorCode int32 const ( - ErrCodeOk ErrorCode = C.CM_ERROR_OK - ErrCodeInvalidArgument ErrorCode = C.CM_ERROR_INVALID_ARGUMENT - ErrCodeDomainError ErrorCode = C.CM_ERROR_DOMAIN_ERROR - ErrCodeLengthError ErrorCode = C.CM_ERROR_LENGTH_ERROR - ErrCodeOutOfRange ErrorCode = C.CM_ERROR_OUT_OF_RANGE - ErrCodeLogicError ErrorCode = C.CM_ERROR_LOGIC_ERROR - ErrCodeRuntimeError ErrorCode = C.CM_ERROR_RUNTIME_ERROR - ErrCodeRangeError ErrorCode = C.CM_ERROR_RANGE_ERROR - ErrCodeOverflowError ErrorCode = C.CM_ERROR_OVERFLOW_ERROR - ErrCodeUnderflowError ErrorCode = C.CM_ERROR_UNDERFLOW_ERROR - ErrCodeRegexError ErrorCode = C.CM_ERROR_REGEX_ERROR - ErrCodeSystemError ErrorCode = C.CM_ERROR_SYSTEM_ERROR + ErrCodeOk ErrorCode = C.CM_ERROR_OK + ErrCodeInvalidArgument ErrorCode = C.CM_ERROR_INVALID_ARGUMENT + ErrCodeDomainError ErrorCode = C.CM_ERROR_DOMAIN_ERROR + ErrCodeLengthError ErrorCode = C.CM_ERROR_LENGTH_ERROR + ErrCodeOutOfRange ErrorCode = C.CM_ERROR_OUT_OF_RANGE + ErrCodeLogicError ErrorCode = C.CM_ERROR_LOGIC_ERROR + ErrCodeRuntimeError ErrorCode = C.CM_ERROR_RUNTIME_ERROR + ErrCodeRangeError ErrorCode = C.CM_ERROR_RANGE_ERROR + ErrCodeOverflowError ErrorCode = C.CM_ERROR_OVERFLOW_ERROR + ErrCodeUnderflowError ErrorCode = C.CM_ERROR_UNDERFLOW_ERROR + //ErrCodeRegexError ErrorCode = C.CM_ERROR_REGEX_ERROR + //ErrCodeSystemError ErrorCode = C.CM_ERROR_SYSTEM_ERROR ErrCodeBadTypeid ErrorCode = C.CM_ERROR_BAD_TYPEID ErrCodeBadCast ErrorCode = C.CM_ERROR_BAD_CAST ErrCodeBadAnyCast ErrorCode = C.CM_ERROR_BAD_ANY_CAST @@ -123,23 +123,23 @@ type ( const ( // type - YieldAutomatic CmioYieldCommand = C.CM_CMIO_YIELD_COMMAND_AUTOMATIC - YieldManual CmioYieldCommand = C.CM_CMIO_YIELD_COMMAND_MANUAL + YieldAutomatic CmioYieldCommand = C.CM_HTIF_YIELD_CMD_AUTOMATIC + YieldManual CmioYieldCommand = C.CM_HTIF_YIELD_CMD_MANUAL // NOTE: these values do not form an enum (e.g., automatic-progress == manual-accepted). // reason - request - AutomaticYieldReasonProgress CmioYieldReason = C.CM_CMIO_YIELD_AUTOMATIC_REASON_PROGRESS - AutomaticYieldReasonOutput CmioYieldReason = C.CM_CMIO_YIELD_AUTOMATIC_REASON_TX_OUTPUT - AutomaticYieldReasonReport CmioYieldReason = C.CM_CMIO_YIELD_AUTOMATIC_REASON_TX_REPORT + AutomaticYieldReasonProgress CmioYieldReason = C.CM_HTIF_YIELD_AUTOMATIC_REASON_PROGRESS + AutomaticYieldReasonOutput CmioYieldReason = C.CM_HTIF_YIELD_AUTOMATIC_REASON_TX_OUTPUT + AutomaticYieldReasonReport CmioYieldReason = C.CM_HTIF_YIELD_AUTOMATIC_REASON_TX_REPORT - ManualYieldReasonAccepted CmioYieldReason = C.CM_CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED - ManualYieldReasonRejected CmioYieldReason = C.CM_CMIO_YIELD_MANUAL_REASON_RX_REJECTED - ManualYieldReasonException CmioYieldReason = C.CM_CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION + ManualYieldReasonAccepted CmioYieldReason = C.CM_HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED + ManualYieldReasonRejected CmioYieldReason = C.CM_HTIF_YIELD_MANUAL_REASON_RX_REJECTED + ManualYieldReasonException CmioYieldReason = C.CM_HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION // reason - reply - YieldReasonAdvanceState CmioYieldReason = C.CM_CMIO_YIELD_REASON_ADVANCE_STATE - YieldReasonInspectState CmioYieldReason = C.CM_CMIO_YIELD_REASON_INSPECT_STATE + YieldReasonAdvanceState CmioYieldReason = C.CM_HTIF_YIELD_REASON_ADVANCE_STATE + YieldReasonInspectState CmioYieldReason = C.CM_HTIF_YIELD_REASON_INSPECT_STATE ) // cm_reg addresses/CSRs @@ -257,41 +257,41 @@ const ( REG_HTIF_ICONSOLE RegID = C.CM_REG_HTIF_ICONSOLE REG_HTIF_IYIELD RegID = C.CM_REG_HTIF_IYIELD // Microarchitecture registers - REG_UARCH_X0 RegID = C.CM_REG_UARCH_X0 - REG_UARCH_X1 RegID = C.CM_REG_UARCH_X1 - REG_UARCH_X2 RegID = C.CM_REG_UARCH_X2 - REG_UARCH_X3 RegID = C.CM_REG_UARCH_X3 - REG_UARCH_X4 RegID = C.CM_REG_UARCH_X4 - REG_UARCH_X5 RegID = C.CM_REG_UARCH_X5 - REG_UARCH_X6 RegID = C.CM_REG_UARCH_X6 - REG_UARCH_X7 RegID = C.CM_REG_UARCH_X7 - REG_UARCH_X8 RegID = C.CM_REG_UARCH_X8 - REG_UARCH_X9 RegID = C.CM_REG_UARCH_X9 - REG_UARCH_X10 RegID = C.CM_REG_UARCH_X10 - REG_UARCH_X11 RegID = C.CM_REG_UARCH_X11 - REG_UARCH_X12 RegID = C.CM_REG_UARCH_X12 - REG_UARCH_X13 RegID = C.CM_REG_UARCH_X13 - REG_UARCH_X14 RegID = C.CM_REG_UARCH_X14 - REG_UARCH_X15 RegID = C.CM_REG_UARCH_X15 - REG_UARCH_X16 RegID = C.CM_REG_UARCH_X16 - REG_UARCH_X17 RegID = C.CM_REG_UARCH_X17 - REG_UARCH_X18 RegID = C.CM_REG_UARCH_X18 - REG_UARCH_X19 RegID = C.CM_REG_UARCH_X19 - REG_UARCH_X20 RegID = C.CM_REG_UARCH_X20 - REG_UARCH_X21 RegID = C.CM_REG_UARCH_X21 - REG_UARCH_X22 RegID = C.CM_REG_UARCH_X22 - REG_UARCH_X23 RegID = C.CM_REG_UARCH_X23 - REG_UARCH_X24 RegID = C.CM_REG_UARCH_X24 - REG_UARCH_X25 RegID = C.CM_REG_UARCH_X25 - REG_UARCH_X26 RegID = C.CM_REG_UARCH_X26 - REG_UARCH_X27 RegID = C.CM_REG_UARCH_X27 - REG_UARCH_X28 RegID = C.CM_REG_UARCH_X28 - REG_UARCH_X29 RegID = C.CM_REG_UARCH_X29 - REG_UARCH_X30 RegID = C.CM_REG_UARCH_X30 - REG_UARCH_X31 RegID = C.CM_REG_UARCH_X31 - REG_UARCH_PC RegID = C.CM_REG_UARCH_PC - REG_UARCH_CYCLE RegID = C.CM_REG_UARCH_CYCLE - REG_UARCH_HALT_FLAG RegID = C.CM_REG_UARCH_HALT_FLAG + REG_UARCH_X0 RegID = C.CM_REG_UARCH_X0 + REG_UARCH_X1 RegID = C.CM_REG_UARCH_X1 + REG_UARCH_X2 RegID = C.CM_REG_UARCH_X2 + REG_UARCH_X3 RegID = C.CM_REG_UARCH_X3 + REG_UARCH_X4 RegID = C.CM_REG_UARCH_X4 + REG_UARCH_X5 RegID = C.CM_REG_UARCH_X5 + REG_UARCH_X6 RegID = C.CM_REG_UARCH_X6 + REG_UARCH_X7 RegID = C.CM_REG_UARCH_X7 + REG_UARCH_X8 RegID = C.CM_REG_UARCH_X8 + REG_UARCH_X9 RegID = C.CM_REG_UARCH_X9 + REG_UARCH_X10 RegID = C.CM_REG_UARCH_X10 + REG_UARCH_X11 RegID = C.CM_REG_UARCH_X11 + REG_UARCH_X12 RegID = C.CM_REG_UARCH_X12 + REG_UARCH_X13 RegID = C.CM_REG_UARCH_X13 + REG_UARCH_X14 RegID = C.CM_REG_UARCH_X14 + REG_UARCH_X15 RegID = C.CM_REG_UARCH_X15 + REG_UARCH_X16 RegID = C.CM_REG_UARCH_X16 + REG_UARCH_X17 RegID = C.CM_REG_UARCH_X17 + REG_UARCH_X18 RegID = C.CM_REG_UARCH_X18 + REG_UARCH_X19 RegID = C.CM_REG_UARCH_X19 + REG_UARCH_X20 RegID = C.CM_REG_UARCH_X20 + REG_UARCH_X21 RegID = C.CM_REG_UARCH_X21 + REG_UARCH_X22 RegID = C.CM_REG_UARCH_X22 + REG_UARCH_X23 RegID = C.CM_REG_UARCH_X23 + REG_UARCH_X24 RegID = C.CM_REG_UARCH_X24 + REG_UARCH_X25 RegID = C.CM_REG_UARCH_X25 + REG_UARCH_X26 RegID = C.CM_REG_UARCH_X26 + REG_UARCH_X27 RegID = C.CM_REG_UARCH_X27 + REG_UARCH_X28 RegID = C.CM_REG_UARCH_X28 + REG_UARCH_X29 RegID = C.CM_REG_UARCH_X29 + REG_UARCH_X30 RegID = C.CM_REG_UARCH_X30 + REG_UARCH_X31 RegID = C.CM_REG_UARCH_X31 + REG_UARCH_PC RegID = C.CM_REG_UARCH_PC + REG_UARCH_CYCLE RegID = C.CM_REG_UARCH_CYCLE + REG_UARCH_HALT RegID = C.CM_REG_UARCH_HALT // Views of registers REG_HTIF_TOHOST_DEV RegID = C.CM_REG_HTIF_TOHOST_DEV REG_HTIF_TOHOST_CMD RegID = C.CM_REG_HTIF_TOHOST_CMD diff --git a/pkg/machine/backend.go b/pkg/machine/backend.go index 9d0e7d6a8..f2dd2d5c6 100644 --- a/pkg/machine/backend.go +++ b/pkg/machine/backend.go @@ -41,7 +41,7 @@ type Backend interface { IsAtManualYield(timeout time.Duration) (bool, error) ReadMCycle(timeout time.Duration) (uint64, error) - SendCmioResponse(reason uint16, data []byte, timeout time.Duration) error + SendCmioResponse(revertRootHash Hash, reason uint16, data []byte, timeout time.Duration) error ReceiveCmioRequest(timeout time.Duration) (cmd uint8, reason uint16, data []byte, err error) WriteMemory(address uint64, data []byte, timeout time.Duration) error diff --git a/pkg/machine/backend_test.go b/pkg/machine/backend_test.go index 927ca382b..c60e77c65 100644 --- a/pkg/machine/backend_test.go +++ b/pkg/machine/backend_test.go @@ -40,8 +40,8 @@ func (m *MockBackend) ReadMCycle(timeout time.Duration) (uint64, error) { return args.Get(0).(uint64), args.Error(1) } -func (m *MockBackend) SendCmioResponse(reason uint16, data []byte, timeout time.Duration) error { - args := m.Called(reason, data, timeout) +func (m *MockBackend) SendCmioResponse(revertRootHash Hash, reason uint16, data []byte, timeout time.Duration) error { + args := m.Called(revertRootHash, reason, data, timeout) return args.Error(0) } @@ -105,7 +105,7 @@ func randomFakeHash() Hash { // SetupAccepted configures the mock for a successful advance/inspect operation func (m *MockBackend) SetupAccepted(reqType requestType) { hash := randomFakeHash() - m.On("SendCmioResponse", uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + m.On("SendCmioResponse", mock.Anything, uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) m.On("Run", mock.AnythingOfType("uint64"), mock.AnythingOfType("time.Duration")).Return(YieldedManually, nil) m.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), nil) m.On("ReceiveCmioRequest", mock.AnythingOfType("time.Duration")).Return( @@ -116,7 +116,7 @@ func (m *MockBackend) SetupAccepted(reqType requestType) { // SetupRejected configures the mock for a rejected advance/inspect operation func (m *MockBackend) SetupRejected(reqType requestType) { hash := randomFakeHash() - m.On("SendCmioResponse", uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + m.On("SendCmioResponse", mock.Anything, uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) m.On("Run", mock.AnythingOfType("uint64"), mock.AnythingOfType("time.Duration")).Return(YieldedManually, nil) m.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), nil) m.On("ReceiveCmioRequest", mock.AnythingOfType("time.Duration")).Return( @@ -126,7 +126,7 @@ func (m *MockBackend) SetupRejected(reqType requestType) { // SetupException configures the mock for an exception during advance/inspect func (m *MockBackend) SetupException(reqType requestType) { - m.On("SendCmioResponse", uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + m.On("SendCmioResponse", mock.Anything, uint16(reqType), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) m.On("Run", mock.AnythingOfType("uint64"), mock.AnythingOfType("time.Duration")).Return(YieldedManually, nil) m.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), nil) m.On("ReceiveCmioRequest", mock.AnythingOfType("time.Duration")).Return( diff --git a/pkg/machine/implementation.go b/pkg/machine/implementation.go index c089e3b3a..1c8d27b68 100644 --- a/pkg/machine/implementation.go +++ b/pkg/machine/implementation.go @@ -50,7 +50,6 @@ const ( const maxOutputs = 65536 // 2^16 const maxReports = 65536 // 2^16 -const CheckpointAddress uint64 = 0xfe0 // value from machine api: CM_AR_SHADOW_REVERT_ROOT_HASH_START const TxBufferAddress uint64 = 0x60800000 const HashLog2Size = 5 // 32 bytes @@ -165,23 +164,10 @@ func (m *machineImpl) OutputsHashProof(ctx context.Context) ([]Hash, error) { return siblings, nil } -func (m *machineImpl) WriteCheckpointHash(ctx context.Context, hash Hash) error { - if err := checkContext(ctx); err != nil { - return err - } - - err := m.backend.WriteMemory(CheckpointAddress, hash[:], m.params.FastDeadline) - if err != nil { - err := fmt.Errorf("could not write checkpoint hash in to machine memory: %w", err) - return errors.Join(ErrMachineInternal, err) - } - return nil -} - // Advance sends an input to the machine and processes it -func (m *machineImpl) Advance(ctx context.Context, input []byte, computeHashes bool) (*AdvanceResponse, error) { +func (m *machineImpl) Advance(ctx context.Context, input []byte, checkpointHash Hash, computeHashes bool) (*AdvanceResponse, error) { // TODO: return the exception reason - accepted, outputs, reports, hashes, remaining, data, err := m.process(ctx, input, AdvanceStateRequest, computeHashes) + accepted, outputs, reports, hashes, remaining, data, err := m.process(ctx, input, checkpointHash, AdvanceStateRequest, computeHashes) if err != nil { return &AdvanceResponse{ Accepted: accepted, @@ -210,9 +196,9 @@ func (m *machineImpl) Advance(ctx context.Context, input []byte, computeHashes b } // Inspect sends a query to the machine and returns the results -func (m *machineImpl) Inspect(ctx context.Context, query []byte) (bool, []Report, error) { +func (m *machineImpl) Inspect(ctx context.Context, query []byte, checkpointHash Hash) (bool, []Report, error) { // TODO: return the exception reason - accepted, _, reports, _, _, _, err := m.process(ctx, query, InspectStateRequest, false) + accepted, _, reports, _, _, _, err := m.process(ctx, query, checkpointHash, InspectStateRequest, false) return accepted, reports, err } @@ -346,6 +332,7 @@ func (m *machineImpl) readMCycle(ctx context.Context) (uint64, error) { func (m *machineImpl) process( ctx context.Context, request []byte, + checkpointHash Hash, reqType requestType, computeHashes bool, ) (bool, []Output, []Report, []Hash, uint64, []byte, error) { @@ -357,7 +344,7 @@ func (m *machineImpl) process( return false, nil, nil, nil, 0, nil, ErrPayloadLengthLimitExceeded } - err := m.backend.SendCmioResponse(uint16(reqType), request, m.params.FastDeadline) + err := m.backend.SendCmioResponse(checkpointHash, uint16(reqType), request, m.params.FastDeadline) if err != nil { return false, nil, nil, nil, 0, nil, err } diff --git a/pkg/machine/implementation_test.go b/pkg/machine/implementation_test.go index b56183948..48aa2553c 100644 --- a/pkg/machine/implementation_test.go +++ b/pkg/machine/implementation_test.go @@ -249,56 +249,11 @@ func (s *ImplementationSuite) TestOutputsHashProof() { require.ErrorIs(err, ErrCanceled) } -// Test WriteCheckpointHash method -func (s *ImplementationSuite) TestWriteCheckpointHash() { - require := s.Require() - ctx := context.Background() - - // Test successful write - mockBackend := NewMockBackend() - hash := randomFakeHash() - mockBackend.On("WriteMemory", CheckpointAddress, hash[:], mock.AnythingOfType("time.Duration")). - Return(nil) - machine := &machineImpl{ - backend: mockBackend, - logger: s.logger, - params: model.ExecutionParameters{ - FastDeadline: time.Second * 5, - }, - } - - err := machine.WriteCheckpointHash(ctx, hash) - require.NoError(err) - mockBackend.AssertExpectations(s.T()) - - // Test write with backend error - mockBackend2 := NewMockBackend() - mockBackend2.On("WriteMemory", CheckpointAddress, hash[:], mock.AnythingOfType("time.Duration")). - Return(errors.New("write failed")) - machine2 := &machineImpl{ - backend: mockBackend2, - logger: s.logger, - params: model.ExecutionParameters{ - FastDeadline: time.Second * 5, - }, - } - err = machine2.WriteCheckpointHash(ctx, hash) - require.Error(err) - require.ErrorIs(err, ErrMachineInternal) - require.Contains(err.Error(), "could not write checkpoint hash") - mockBackend2.AssertExpectations(s.T()) - - // Test write with canceled context - canceledCtx, cancel := context.WithCancel(ctx) - cancel() - err = machine.WriteCheckpointHash(canceledCtx, hash) - require.ErrorIs(err, ErrCanceled) -} - // Test Advance method func (s *ImplementationSuite) TestAdvance() { require := s.Require() ctx := context.Background() + expectedHash := randomFakeHash() // Test successful advance (accepted) mockBackend := NewMockBackend() @@ -317,7 +272,7 @@ func (s *ImplementationSuite) TestAdvance() { } input := []byte("test input") - resp, err := machine.Advance(ctx, input, false) + resp, err := machine.Advance(ctx, input, expectedHash, false) require.NoError(err) require.True(resp.Accepted) require.Empty(resp.Outputs) @@ -339,7 +294,7 @@ func (s *ImplementationSuite) TestAdvance() { AdvanceMaxDeadline: time.Second * 10, }, } - resp, err = machine2.Advance(ctx, input, false) + resp, err = machine2.Advance(ctx, input, expectedHash, false) require.NoError(err) require.False(resp.Accepted) require.Empty(resp.Outputs) @@ -361,7 +316,7 @@ func (s *ImplementationSuite) TestAdvance() { AdvanceMaxDeadline: time.Second * 10, }, } - resp, err = machine3.Advance(ctx, input, false) + resp, err = machine3.Advance(ctx, input, expectedHash, false) require.ErrorIs(err, ErrException) require.False(resp.Accepted) require.Equal(Hash{}, resp.OutputsHash) @@ -382,14 +337,14 @@ func (s *ImplementationSuite) TestAdvance() { }, } largeInput := make([]byte, 10) - _, err = machine4.Advance(ctx, largeInput, false) + _, err = machine4.Advance(ctx, largeInput, expectedHash, false) require.ErrorIs(err, ErrPayloadLengthLimitExceeded) mockBackend4.AssertExpectations(s.T()) // Test advance with invalid hash length mockBackend5 := NewMockBackend() mockBackend5.On("CmioRxBufferSize").Return(uint64(1024)) - mockBackend5.On("SendCmioResponse", uint16(AdvanceStateRequest), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + mockBackend5.On("SendCmioResponse", expectedHash, uint16(AdvanceStateRequest), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) mockBackend5.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), nil) mockBackend5.On("Run", mock.AnythingOfType("uint64"), mock.AnythingOfType("time.Duration")).Return(YieldedManually, nil) mockBackend5.On("ReceiveCmioRequest", mock.AnythingOfType("time.Duration")).Return( @@ -405,7 +360,7 @@ func (s *ImplementationSuite) TestAdvance() { AdvanceMaxDeadline: time.Second * 10, }, } - _, err = machine5.Advance(ctx, input, false) + _, err = machine5.Advance(ctx, input, expectedHash, false) require.Error(err) require.ErrorIs(err, ErrHashLength) mockBackend5.AssertExpectations(s.T()) @@ -415,6 +370,7 @@ func (s *ImplementationSuite) TestAdvance() { func (s *ImplementationSuite) TestInspect() { require := s.Require() ctx := context.Background() + expectedHash := randomFakeHash() // Test successful inspect (accepted) mockBackend := NewMockBackend() @@ -433,7 +389,7 @@ func (s *ImplementationSuite) TestInspect() { } query := []byte("test query") - accepted, reports, err := machine.Inspect(ctx, query) + accepted, reports, err := machine.Inspect(ctx, query, expectedHash) require.NoError(err) require.True(accepted) require.Empty(reports) @@ -453,7 +409,7 @@ func (s *ImplementationSuite) TestInspect() { InspectMaxDeadline: time.Second * 10, }, } - accepted, reports, err = machine2.Inspect(ctx, query) + accepted, reports, err = machine2.Inspect(ctx, query, expectedHash) require.NoError(err) require.False(accepted) require.Empty(reports) @@ -473,7 +429,7 @@ func (s *ImplementationSuite) TestInspect() { InspectMaxDeadline: time.Second * 10, }, } - accepted, reports, err = machine3.Inspect(ctx, query) + accepted, reports, err = machine3.Inspect(ctx, query, expectedHash) require.ErrorIs(err, ErrException) require.False(accepted) require.Empty(reports) @@ -494,7 +450,7 @@ func (s *ImplementationSuite) TestInspect() { }, } largeQuery := make([]byte, 10) - _, _, err = machine4.Inspect(ctx, largeQuery) + _, _, err = machine4.Inspect(ctx, largeQuery, expectedHash) require.ErrorIs(err, ErrPayloadLengthLimitExceeded) mockBackend4.AssertExpectations(s.T()) } @@ -994,11 +950,12 @@ func (s *ImplementationSuite) TestStep() { func (s *ImplementationSuite) TestProcess() { require := s.Require() ctx := context.Background() + expectedHash := randomFakeHash() // Test successful process mockBackend := NewMockBackend() mockBackend.On("CmioRxBufferSize").Return(uint64(1024)) - mockBackend.On("SendCmioResponse", mock.AnythingOfType("uint16"), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + mockBackend.On("SendCmioResponse", expectedHash, mock.AnythingOfType("uint16"), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) mockBackend.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), nil) mockBackend.On("Run", mock.AnythingOfType("uint64"), mock.AnythingOfType("time.Duration")).Return(YieldedManually, nil) mockBackend.On("ReceiveCmioRequest", mock.AnythingOfType("time.Duration")).Return( @@ -1017,7 +974,7 @@ func (s *ImplementationSuite) TestProcess() { } input := []byte("test input") - accepted, outputs, reports, _, _, data, err := machine.process(ctx, input, AdvanceStateRequest, false) + accepted, outputs, reports, _, _, data, err := machine.process(ctx, input, expectedHash, AdvanceStateRequest, false) require.NoError(err) require.True(accepted) require.Empty(outputs) @@ -1039,7 +996,7 @@ func (s *ImplementationSuite) TestProcess() { AdvanceMaxDeadline: time.Second * 10, }, } - _, _, _, _, _, _, err = machine2.process(ctx, input, AdvanceStateRequest, false) + _, _, _, _, _, _, err = machine2.process(ctx, input, expectedHash, AdvanceStateRequest, false) require.ErrorIs(err, ErrPayloadLengthLimitExceeded) mockBackend2.AssertExpectations(s.T()) @@ -1047,6 +1004,7 @@ func (s *ImplementationSuite) TestProcess() { mockBackend3 := NewMockBackend() mockBackend3.On("CmioRxBufferSize").Return(uint64(1024)) mockBackend3.On("SendCmioResponse", + expectedHash, mock.AnythingOfType("uint16"), mock.Anything, mock.AnythingOfType("time.Duration"), @@ -1062,7 +1020,7 @@ func (s *ImplementationSuite) TestProcess() { AdvanceMaxDeadline: time.Second * 10, }, } - _, _, _, _, _, _, err = machine3.process(ctx, input, AdvanceStateRequest, false) + _, _, _, _, _, _, err = machine3.process(ctx, input, expectedHash, AdvanceStateRequest, false) require.Error(err) require.Contains(err.Error(), "send failed") mockBackend3.AssertExpectations(s.T()) @@ -1070,7 +1028,7 @@ func (s *ImplementationSuite) TestProcess() { // Test process with run error mockBackend4 := NewMockBackend() mockBackend4.On("CmioRxBufferSize").Return(uint64(1024)) - mockBackend4.On("SendCmioResponse", mock.AnythingOfType("uint16"), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) + mockBackend4.On("SendCmioResponse", expectedHash, mock.AnythingOfType("uint16"), mock.Anything, mock.AnythingOfType("time.Duration")).Return(nil) mockBackend4.On("ReadMCycle", mock.AnythingOfType("time.Duration")).Return(uint64(0), errors.New("read cycle failed")) machine4 := &machineImpl{ backend: mockBackend4, @@ -1083,7 +1041,7 @@ func (s *ImplementationSuite) TestProcess() { AdvanceMaxDeadline: time.Second * 10, }, } - _, _, _, _, _, _, err = machine4.process(ctx, input, AdvanceStateRequest, false) + _, _, _, _, _, _, err = machine4.process(ctx, input, expectedHash, AdvanceStateRequest, false) require.Error(err) require.Contains(err.Error(), "read cycle failed") mockBackend4.AssertExpectations(s.T()) diff --git a/pkg/machine/libcartesi.go b/pkg/machine/libcartesi.go index 7d396f1de..764719d49 100644 --- a/pkg/machine/libcartesi.go +++ b/pkg/machine/libcartesi.go @@ -21,7 +21,7 @@ type RemoteMachineInterface interface { GetRootHash() (emulator.Hash, error) GetProof(address uint64, log2size int32) (string, error) ReadReg(reg emulator.RegID) (uint64, error) - SendCmioResponse(reason uint16, data []byte) error + SendCmioResponse(revertRootHash emulator.Hash, reason uint16, data []byte) error ReceiveCmioRequest() (uint8, uint16, []byte, error) WriteMemory(address uint64, data []byte) error Store(directory string) error @@ -167,11 +167,11 @@ func (e *LibCartesiBackend) ReadMCycle(timeout time.Duration) (uint64, error) { return cycle, nil } -func (e *LibCartesiBackend) SendCmioResponse(reason uint16, data []byte, timeout time.Duration) error { +func (e *LibCartesiBackend) SendCmioResponse(revertRootHash Hash, reason uint16, data []byte, timeout time.Duration) error { if err := e.inner.SetTimeout(timeout.Milliseconds()); err != nil { return fmt.Errorf("failed to set operation timeout: %w", err) } - return e.inner.SendCmioResponse(reason, data) + return e.inner.SendCmioResponse(revertRootHash, reason, data) } func (e *LibCartesiBackend) ReceiveCmioRequest(timeout time.Duration) (uint8, uint16, []byte, error) { diff --git a/pkg/machine/libcartesi_test.go b/pkg/machine/libcartesi_test.go index 4f78d08b1..4967dbd08 100644 --- a/pkg/machine/libcartesi_test.go +++ b/pkg/machine/libcartesi_test.go @@ -214,12 +214,13 @@ func (s *LibCartesiSuite) TestSendCmioResponse() { require := s.Require() data := []byte("test data") + expectedHash := randomFakeHash() // Test successful send s.mockRemoteMachine.On("SetTimeout", int64(5000)).Return(nil) - s.mockRemoteMachine.On("SendCmioResponse", uint16(1), data).Return(nil) + s.mockRemoteMachine.On("SendCmioResponse", expectedHash, uint16(1), data).Return(nil) - err := s.backend.SendCmioResponse(1, data, 5*time.Second) + err := s.backend.SendCmioResponse(expectedHash, 1, data, 5*time.Second) require.NoError(err) s.mockRemoteMachine.AssertExpectations(s.T()) @@ -228,7 +229,7 @@ func (s *LibCartesiSuite) TestSendCmioResponse() { s.backend = &LibCartesiBackend{inner: s.mockRemoteMachine} s.mockRemoteMachine.On("SetTimeout", int64(5000)).Return(errors.New("timeout error")) - err = s.backend.SendCmioResponse(1, data, 5*time.Second) + err = s.backend.SendCmioResponse(expectedHash, 1, data, 5*time.Second) require.Error(err) require.Contains(err.Error(), "failed to set operation timeout") s.mockRemoteMachine.AssertExpectations(s.T()) @@ -237,9 +238,9 @@ func (s *LibCartesiSuite) TestSendCmioResponse() { s.mockRemoteMachine = new(MockRemoteMachine) s.backend = &LibCartesiBackend{inner: s.mockRemoteMachine} s.mockRemoteMachine.On("SetTimeout", int64(5000)).Return(nil) - s.mockRemoteMachine.On("SendCmioResponse", uint16(1), data).Return(errors.New("send error")) + s.mockRemoteMachine.On("SendCmioResponse", expectedHash, uint16(1), data).Return(errors.New("send error")) - err = s.backend.SendCmioResponse(1, data, 5*time.Second) + err = s.backend.SendCmioResponse(expectedHash, 1, data, 5*time.Second) require.Error(err) require.Contains(err.Error(), "send error") s.mockRemoteMachine.AssertExpectations(s.T()) @@ -449,8 +450,8 @@ func (m *MockRemoteMachine) ReadReg(reg emulator.RegID) (uint64, error) { return args.Get(0).(uint64), args.Error(1) } -func (m *MockRemoteMachine) SendCmioResponse(reason uint16, data []byte) error { - args := m.Called(reason, data) +func (m *MockRemoteMachine) SendCmioResponse(revertRootHash Hash, reason uint16, data []byte) error { + args := m.Called(revertRootHash, reason, data) return args.Error(0) } diff --git a/pkg/machine/machine.go b/pkg/machine/machine.go index ccf8cb237..70809df55 100644 --- a/pkg/machine/machine.go +++ b/pkg/machine/machine.go @@ -67,20 +67,22 @@ type Machine interface { OutputsHash(ctx context.Context) (Hash, error) // OutputsHashProof returns the proof that the outputs merkle root hash is stored in the cmio tx buffer. OutputsHashProof(ctx context.Context) ([]Hash, error) - // WriteCheckpointHash writes the given checkpoint hash to the machine memory. - WriteCheckpointHash(ctx context.Context, hash Hash) error // Advance sends an input to the machine. + // The checkpointHash is the machine's root hash before processing the input, + // sent along with the request so the machine can revert to it if needed. // It always returns a non-nil AdvanceResponse, even on error paths. // The response contains whether the request was accepted, // the corresponding outputs, reports, and the hash of the outputs. // In case the request is not accepted, the response does not contain outputs. - Advance(ctx context.Context, input []byte, computeHashes bool) (*AdvanceResponse, error) + Advance(ctx context.Context, input []byte, checkpointHash Hash, computeHashes bool) (*AdvanceResponse, error) // Inspect sends a query to the machine. + // The checkpointHash is the machine's root hash before processing the query, + // sent along with the request so the machine can revert to it if needed. // It returns a boolean indicating whether or not the request was accepted // It also returns the corresponding reports. - Inspect(ctx context.Context, query []byte) (bool, []Report, error) + Inspect(ctx context.Context, query []byte, checkpointHash Hash) (bool, []Report, error) // Store saves the machine state to the specified path. Store(ctx context.Context, path string) error diff --git a/pkg/machine/machine_test.go b/pkg/machine/machine_test.go index 1d613d9ae..ec2c75470 100644 --- a/pkg/machine/machine_test.go +++ b/pkg/machine/machine_test.go @@ -233,7 +233,7 @@ func (s *MachineSuite) TestMachineInterface() { require.Equal(Hash{6, 7, 8, 9, 10}, outputsHash) // Test Advance - advanceResp, err := machine.Advance(ctx, []byte("input"), false) + advanceResp, err := machine.Advance(ctx, []byte("input"), Hash{}, false) require.NoError(err) require.True(advanceResp.Accepted) require.Len(advanceResp.Outputs, 2) @@ -244,7 +244,7 @@ func (s *MachineSuite) TestMachineInterface() { require.Equal(Hash{11, 12, 13, 14, 15}, advanceResp.OutputsHash) // Test Inspect - accepted, inspectReports, err := machine.Inspect(ctx, []byte("query")) + accepted, inspectReports, err := machine.Inspect(ctx, []byte("query"), Hash{}) require.NoError(err) require.True(accepted) require.Len(inspectReports, 1) @@ -298,12 +298,12 @@ func (s *MachineSuite) TestMachineInterfaceErrors() { require.Contains(err.Error(), "outputs hash error") // Test Advance error - _, err = machine.Advance(ctx, []byte("input"), false) + _, err = machine.Advance(ctx, []byte("input"), Hash{}, false) require.Error(err) require.Contains(err.Error(), "advance error") // Test Inspect error - _, _, err = machine.Inspect(ctx, []byte("query")) + _, _, err = machine.Inspect(ctx, []byte("query"), Hash{}) require.Error(err) require.Contains(err.Error(), "inspect error") @@ -369,11 +369,7 @@ func (m *MockMachine) OutputsHashProof(_ context.Context) ([]Hash, error) { return m.OutputsHashProofReturn, m.OutputsHashProofError } -func (m *MockMachine) WriteCheckpointHash(_ context.Context, _ Hash) error { - return m.CheckpointHashError -} - -func (m *MockMachine) Advance(_ context.Context, _ []byte, _ bool) (*AdvanceResponse, error) { +func (m *MockMachine) Advance(_ context.Context, _ []byte, _ Hash, _ bool) (*AdvanceResponse, error) { return &AdvanceResponse{ Accepted: m.AdvanceAcceptedReturn, Outputs: m.AdvanceOutputsReturn, @@ -386,6 +382,7 @@ func (m *MockMachine) Advance(_ context.Context, _ []byte, _ bool) (*AdvanceResp func (m *MockMachine) Inspect(_ context.Context, _ []byte, + _ Hash, ) (bool, []Report, error) { return m.InspectAcceptedReturn, m.InspectReportsReturn, m.InspectError } diff --git a/test/dependencies b/test/dependencies index 092280d4b..e31c55718 100644 --- a/test/dependencies +++ b/test/dependencies @@ -1,2 +1,2 @@ -https://github.com/cartesi/image-kernel/releases/download/v0.20.0/linux-6.5.13-ctsi-1-v0.20.0.bin -https://github.com/cartesi/machine-guest-tools/releases/download/v0.17.2/rootfs-tools.ext2 +https://github.com/cartesi/image-kernel/releases/download/v0.21.0-test1/linux-6.5.13-ctsi-2-uio-test1-v0.21.0.bin +https://github.com/cartesi/machine-guest-tools/releases/download/v0.18.0-test7/rootfs-tools.ext2 diff --git a/test/dependencies.sha256 b/test/dependencies.sha256 index 92e988b1e..ce9bdf838 100644 --- a/test/dependencies.sha256 +++ b/test/dependencies.sha256 @@ -1,2 +1,2 @@ -65dd100ff6204346ac2f50f772721358b5c1451450ceb39a154542ee27b4c947 test/downloads/linux-6.5.13-ctsi-1-v0.20.0.bin -675a49e3c9bada29f25d5b559707b34553b94280c03f44ccb8203c2cf453b541 test/downloads/rootfs-tools.ext2 +9fb5aaa623f35c0ef2138c8dadf1b0a1e388e0f51758cb6a5477ec5cecd3a029 test/downloads/linux-6.5.13-ctsi-2-uio-test1-v0.21.0.bin +274b92629d5568fcb62a309398c7ab95bf6e2c6538c346eaf41e59b8f39f4035 test/downloads/rootfs-tools.ext2 From 69822b4f324df6e7268451dbb3ff120cbf5bd62e Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:22:59 -0300 Subject: [PATCH 2/2] feat: use collect mcycle root hashes API --- pkg/emulator/machine.go | 5 +- pkg/machine/libcartesi.go | 151 ++++++++++++--------------------- pkg/machine/libcartesi_test.go | 5 ++ 3 files changed, 59 insertions(+), 102 deletions(-) diff --git a/pkg/emulator/machine.go b/pkg/emulator/machine.go index 03d1e9161..66921fb1c 100644 --- a/pkg/emulator/machine.go +++ b/pkg/emulator/machine.go @@ -354,7 +354,7 @@ func (m *Machine) Run(mcycleEnd uint64) (BreakReason, error) { } // collect_mcycle_root_hashes -func (m *Machine) CollectMCycleRootHashes(mcycleEnd, mcyclePeriod, mcyclePhase uint64, log2BundleMcycleCount int32, previousBackTree string) ([]byte, error) { +func (m *Machine) CollectMCycleRootHashes(mcycleEnd, log2McyclePeriod, mcyclePhase uint64, log2BundleMcycleCount int32, previousBackTree string) ([]byte, error) { var err error var result []byte @@ -368,7 +368,7 @@ func (m *Machine) CollectMCycleRootHashes(mcycleEnd, mcyclePeriod, mcyclePhase u err = newError(C.cm_collect_mcycle_root_hashes( m.ptr, C.uint64_t(mcycleEnd), - C.uint64_t(mcyclePeriod), + C.uint64_t(log2McyclePeriod), C.uint64_t(mcyclePhase), C.int32_t(log2BundleMcycleCount), previousBackTreeC, @@ -419,7 +419,6 @@ func (m *Machine) SendCmioResponse(revertRootHash Hash, reason uint16, data []by if sizeData > 0 { ptrData = (*C.uint8_t)(unsafe.Pointer(&data[0])) } - //var cHash C.cm_hash = revertRootHash err = newError(C.cm_send_cmio_response( m.ptr, (*[32]C.uint8_t)(unsafe.Pointer(&revertRootHash)), diff --git a/pkg/machine/libcartesi.go b/pkg/machine/libcartesi.go index 764719d49..df08f4730 100644 --- a/pkg/machine/libcartesi.go +++ b/pkg/machine/libcartesi.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "math/bits" "time" "github.com/cartesi/rollups-node/pkg/emulator" @@ -28,6 +29,7 @@ type RemoteMachineInterface interface { Delete() ForkServer() (*emulator.RemoteMachine, string, uint32, error) ShutdownServer() error + CollectMCycleRootHashes(mcycleEnd, log2McyclePeriod, mcyclePhase uint64, log2BundleMcycleCount int32, previousBackTree string) ([]byte, error) } type proofJson struct { @@ -230,124 +232,75 @@ func (e *LibCartesiBackend) CmioRxBufferSize() uint64 { return 1 << emulator.CmioRxBufferLog2Size } +func decodeBreakReason(s string) BreakReason { + switch s { + case "yielded_automatically": + return YieldedAutomatically + case "yielded_manually": + return YieldedManually + case "yielded_softly": + return YieldedSoftly + case "reached_target_mcycle": + return ReachedTargetMcycle + case "halted": + return Halted + case "failed": + return Failed + default: + return Failed + } +} + func (e *LibCartesiBackend) RunAndCollectRootHashes( mcycleEnd uint64, state *HashCollectorState, timeout time.Duration, ) (reason BreakReason, err error) { + return e.RunAndCollectRootHashesNew(mcycleEnd, state, timeout) +} + +func (e *LibCartesiBackend) RunAndCollectRootHashesNew( + mcycleEnd uint64, + state *HashCollectorState, + timeout time.Duration, +) (reason BreakReason, err error) { + if state == nil { return Failed, errors.New("nil state") } if state.Period == 0 { return Failed, errors.New("State.Period must be > 0") } - - // Set up timeout management: calculate absolute deadline if timeout is specified - var deadline time.Time - hasDeadline := timeout > 0 - if hasDeadline { - deadline = time.Now().Add(timeout) - } - remaining := func() time.Duration { - if !hasDeadline { - return 0 - } - d := time.Until(deadline) - if d <= 0 { - return time.Nanosecond - } - return d + log2Period := uint64(bits.Len64(state.Period) - 1) + if (uint64(1) << log2Period != state.Period) { + return Failed, fmt.Errorf("period must be a power of 2, got %v.", state.Period) } - checkDeadline := func() error { - if hasDeadline && time.Now().After(deadline) { - return errors.New("runWithRootHashes: deadline exceeded") - } - return nil + if err := e.inner.SetTimeout(timeout.Milliseconds()); err != nil { + return Failed, fmt.Errorf("failed to set operation timeout: %w", err) } - if err := checkDeadline(); err != nil { + rawResult, err := e.inner.CollectMCycleRootHashes(mcycleEnd, log2Period, state.Phase, state.BundleLog2, "") + if err != nil { return Failed, err } - cur, err := e.ReadMCycle(remaining()) + result := struct { + RootHashes []string `json:"hashes"` + MCyclePhase uint64 `json:"mcycle_phase"` + BreakReason string `json:"break_reason"` + BackTree json.RawMessage `json:"back_tree,omitempty"` + }{} + err = json.Unmarshal(rawResult, &result) if err != nil { - return Failed, err + return Failed, fmt.Errorf("failed to unmarshal CollectMCycleRootHashes result: %w", err) } - collected := (uint64)(0) - - for { - if err := checkDeadline(); err != nil { - return Failed, err - } - if cur >= mcycleEnd { - // No more cycles to execute - return ReachedTargetMcycle, nil - } - - // Calculate the next collection point: distance to the next multiple of the period - // This ensures we collect hashes at regular intervals aligned with the period - var step uint64 - if r := state.Phase % state.Period; r == 0 { - step = state.Period - } else { - step = state.Period - r - } - - nextHashCycle := cur + step - target := min(nextHashCycle, mcycleEnd) - - // Run the machine until target cycle or until it yields/halts - br, err := e.Run(target, remaining()) - if err != nil { - return Failed, err - } - - // Check where we stopped after the run - if err := checkDeadline(); err != nil { - return Failed, err - } - pos, err := e.ReadMCycle(remaining()) - if err != nil { - return Failed, err - } - - advanced := pos - cur - state.Phase = (state.Phase + advanced) % state.Period - cur = pos - - // Only collect hash if we reached the exact boundary (pos == nextHashCycle) - // This ensures "hash after each complete period", matching the C API behavior - // and avoiding duplicate collections if the machine stops early due to yields - if pos == nextHashCycle { - if err := checkDeadline(); err != nil { - return Failed, err - } - h, err := e.GetRootHash(remaining()) - if err != nil { - return Failed, err - } - - state.Hashes = append(state.Hashes, h) - - collected++ - if state.MaxHashes > 0 && collected >= state.MaxHashes { - return YieldedSoftly, nil - } - } - - switch br { - case ReachedTargetMcycle: - if cur >= mcycleEnd { - return ReachedTargetMcycle, nil - } - case YieldedManually: - return br, nil - case YieldedAutomatically, YieldedSoftly, Halted: - return br, nil - case Failed: - return Failed, errors.New("run failed") - default: - return Failed, errors.New("unknown break reason") + // convert from base64 and append to collector state + for i, base64Hash := range result.RootHashes { + hash := Hash{} + if err := decodeB64To32(&hash, base64Hash); err != nil { + return Failed, fmt.Errorf("received an invalid hash during RunAndCollectRootHashes at index %v, with value: %v.", i, base64Hash) } + state.Hashes = append(state.Hashes, hash) } + return decodeBreakReason(result.BreakReason), nil } diff --git a/pkg/machine/libcartesi_test.go b/pkg/machine/libcartesi_test.go index 4967dbd08..16fe7b8ad 100644 --- a/pkg/machine/libcartesi_test.go +++ b/pkg/machine/libcartesi_test.go @@ -486,3 +486,8 @@ func (m *MockRemoteMachine) ShutdownServer() error { args := m.Called() return args.Error(0) } + +func (m *MockRemoteMachine) CollectMCycleRootHashes(mcycleEnd, log2McyclePeriod, mcyclePhase uint64, log2BundleMcycleCount int32, previousBackTree string) ([]byte, error) { + args := m.Called(mcycleEnd, log2McyclePeriod, mcyclePhase, log2BundleMcycleCount, previousBackTree) + return args.Get(0).([]byte), args.Error(1) +}