From 6e38d7b144772f8ddcd9d85d54b2951a327c9a77 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Tue, 22 Sep 2026 01:24:02 +0000 Subject: [PATCH 1/2] feat(machine-tool): replay and prove through the Go machine stack Replace the Lua Dave replay and the cartesi-machine CLI invocations with the in-process machine stack. replay now drives a manager.MachineInstance through internal/replay.Run (per-input verification against persisted canonical evidence), stores with CreateSnapshot, and reads the root in-memory; prove accounts-drive loads the stored machine with machine.Load and builds the proof with the new machine.Machine.GetProof. Expose GetProof on the machine interface with the implementation and the test-mock stubs. Drop the --template (template now comes from the DB and is hash-verified), --lua, --cartesi-sdk-root, and --cartesi-machine flags, and delete replay_dave.lua with its Go plumbing. Build cartesi-rollups-machine-tool with CGO and the libcartesi rpath like the other machine-linked artifacts, and update the withdrawal-lifecycle script and integration test for the flag removal. --- Makefile | 2 +- cmd/cartesi-rollups-machine-tool/main.go | 308 +++++------------- .../replay_dave.go | 155 --------- .../replay_dave.lua | 100 ------ .../replay_dave_test.go | 110 ------- internal/advancer/advancer_test.go | 5 + internal/advancer/determinism_test.go | 6 + internal/inspect/hardening_test.go | 1 + internal/inspect/inspect_test.go | 5 + internal/manager/instance.go | 6 + internal/manager/instance_test.go | 6 + internal/manager/manager_test.go | 4 + internal/manager/types.go | 1 + pkg/machine/implementation.go | 13 + pkg/machine/machine.go | 4 + pkg/machine/machine_test.go | 95 ++++++ scripts/withdrawal-lifecycle | 1 - test/integration/withdrawal_lifecycle_test.go | 4 +- 18 files changed, 228 insertions(+), 598 deletions(-) delete mode 100644 cmd/cartesi-rollups-machine-tool/replay_dave.go delete mode 100644 cmd/cartesi-rollups-machine-tool/replay_dave.lua delete mode 100644 cmd/cartesi-rollups-machine-tool/replay_dave_test.go diff --git a/Makefile b/Makefile index 5ee243aae..32d4ea2f0 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ GO_ARTIFACTS := $(addprefix cartesi-rollups-,node cli evm-reader advancer valida # These artifacts embed the machine runtime and therefore require libcartesi. # Keep this list explicit: every other artifact is built with CGO_ENABLED=0, so # the normal build fails if a C dependency leaks into a pure service or tool. -MACHINE_GO_ARTIFACTS := cartesi-rollups-node cartesi-rollups-advancer +MACHINE_GO_ARTIFACTS := cartesi-rollups-node cartesi-rollups-advancer cartesi-rollups-machine-tool PURE_GO_ARTIFACTS := $(filter-out $(MACHINE_GO_ARTIFACTS),$(GO_ARTIFACTS)) # fixme(vfusco): path on all oses diff --git a/cmd/cartesi-rollups-machine-tool/main.go b/cmd/cartesi-rollups-machine-tool/main.go index 56d603468..6885ad3b8 100644 --- a/cmd/cartesi-rollups-machine-tool/main.go +++ b/cmd/cartesi-rollups-machine-tool/main.go @@ -4,22 +4,22 @@ package main import ( - "bytes" "context" - "encoding/base64" "encoding/json" "errors" "fmt" + "log/slog" "os" - "os/exec" "path/filepath" "strings" "github.com/cartesi/rollups-node/cmd/cartesi-rollups-machine-tool/accountdrive" "github.com/cartesi/rollups-node/internal/config" - "github.com/cartesi/rollups-node/internal/model" + "github.com/cartesi/rollups-node/internal/manager" + "github.com/cartesi/rollups-node/internal/replay" "github.com/cartesi/rollups-node/internal/repository" "github.com/cartesi/rollups-node/internal/repository/factory" + "github.com/cartesi/rollups-node/pkg/machine" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/spf13/cobra" @@ -29,63 +29,55 @@ const defaultInputPageSize = uint64(500) func main() { config.SetDefaults() - if err := newRootCommand().ExecuteContext(context.Background()); err != nil { + logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + if err := newRootCommand(logger).ExecuteContext(context.Background()); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } -func newRootCommand() *cobra.Command { +func newRootCommand(logger *slog.Logger) *cobra.Command { root := &cobra.Command{ Use: "cartesi-rollups-machine-tool", Short: "Rollups-aware helper for replaying machines and generating withdrawal proofs", } - root.AddCommand(newReplayCommand()) - root.AddCommand(newProveCommand()) + root.AddCommand(newReplayCommand(logger)) + root.AddCommand(newProveCommand(logger)) return root } -func newReplayCommand() *cobra.Command { +func newReplayCommand(logger *slog.Logger) *cobra.Command { var opts replayOptions cmd := &cobra.Command{ Use: "replay", - Short: "Replay accepted inputs from the node database into a machine template", + Short: "Replay completed inputs from the node database into a machine template", RunE: func(cmd *cobra.Command, _ []string) error { opts.HasToEpoch = cmd.Flags().Changed("to-epoch") opts.HasToInputIndex = cmd.Flags().Changed("to-input-index") - return runReplay(cmd.Context(), opts) + return runReplay(cmd.Context(), logger, opts) }, } - cmd.Flags().StringVar(&opts.Template, "template", "", "Stored machine template path") cmd.Flags().StringVar(&opts.Application, "application", "", "Application name or address") cmd.Flags().StringVar(&opts.DatabaseConnection, "database-connection", "", "Database connection string") cmd.Flags().StringVar(&opts.Store, "store", "", "Output stored machine path") - cmd.Flags().StringVar(&opts.CartesiMachine, "cartesi-machine", "cartesi-machine", "cartesi-machine executable") - cmd.Flags().StringVar(&opts.Lua, "lua", "lua5.4", "Lua executable used by Dave/PRT replay") - cmd.Flags().StringVar(&opts.CartesiSDKRoot, "cartesi-sdk-root", "", "Cartesi SDK root used to resolve Lua modules") - cmd.Flags().Uint64Var(&opts.ToEpoch, "to-epoch", 0, "Replay accepted inputs in epochs up to this epoch") - cmd.Flags().Uint64Var(&opts.ToInputIndex, "to-input-index", 0, "Replay accepted inputs up to this input index") - cobra.CheckErr(cmd.MarkFlagRequired("template")) + cmd.Flags().Uint64Var(&opts.ToEpoch, "to-epoch", 0, "Replay completed inputs in epochs up to this epoch") + cmd.Flags().Uint64Var(&opts.ToInputIndex, "to-input-index", 0, "Replay completed inputs up to this input index") cobra.CheckErr(cmd.MarkFlagRequired("application")) cobra.CheckErr(cmd.MarkFlagRequired("store")) return cmd } type replayOptions struct { - Template string Application string DatabaseConnection string Store string - CartesiMachine string - Lua string - CartesiSDKRoot string ToEpoch uint64 ToInputIndex uint64 HasToEpoch bool HasToInputIndex bool } -func runReplay(ctx context.Context, opts replayOptions) error { +func runReplay(ctx context.Context, logger *slog.Logger, opts replayOptions) error { if opts.DatabaseConnection == "" { dsn, err := config.GetDatabaseConnection() if err != nil { @@ -111,124 +103,96 @@ func runReplay(ctx context.Context, opts replayOptions) error { return fmt.Errorf("application %q not found", opts.Application) } - inputs, lastInputIndex, err := collectReplayInputs(ctx, repo, opts) + toInputExclusive, err := resolveReplayUpperBound(ctx, repo, app.Name, opts) if err != nil { return err } - tmp, err := os.MkdirTemp("", "cartesi-rollups-machine-tool-replay-*") + instance, err := manager.NewMachineInstance(ctx, app, logger, true) if err != nil { - return fmt.Errorf("create temp dir: %w", err) + return fmt.Errorf("create machine instance: %w", err) } - defer os.RemoveAll(tmp) //nolint:errcheck + defer instance.Close() - if err := replayInputs(ctx, opts, tmp, app, inputs); err != nil { - return err + result, err := replay.Run(ctx, repo, instance, replay.Options{ + Application: app, + FromInput: 0, + ToInputExclusive: toInputExclusive, + BatchSize: defaultInputPageSize, + Verification: repository.ReplayVerificationCanonical, + }) + if err != nil { + return fmt.Errorf("replay: %w", err) + } + if !instance.HasRuntime() { + return fmt.Errorf("replay completed in a terminal machine state; snapshotting is unsupported") + } + + if err := instance.CreateSnapshot(ctx, instance.ProcessedInputs(), opts.Store); err != nil { + return fmt.Errorf("store machine: %w", err) } - root, err := readStoredMachineRoot(ctx, opts.CartesiMachine, opts.Store) + root, err := instance.Hash(ctx) if err != nil { - return err + return fmt.Errorf("read machine root: %w", err) } summary := struct { - ProcessedInputs int `json:"processed_inputs"` + ProcessedInputs uint64 `json:"processed_inputs"` LastInputIndex string `json:"last_input_index,omitempty"` MachineRoot string `json:"machine_root"` Store string `json:"store"` }{ - ProcessedInputs: len(inputs), - MachineRoot: root, + ProcessedInputs: result.ReplayedInputs, + MachineRoot: common.BytesToHash(root[:]).Hex(), Store: opts.Store, } - if lastInputIndex != nil { - summary.LastInputIndex = fmt.Sprintf("0x%x", *lastInputIndex) + if result.ReplayedInputs > 0 { + summary.LastInputIndex = fmt.Sprintf("0x%x", toInputExclusive-1) } return json.NewEncoder(os.Stdout).Encode(summary) } -func replayInputs( - ctx context.Context, - opts replayOptions, - tmp string, - app *model.Application, - inputs []*model.Input, -) error { - if app.IsDaveConsensus() && len(inputs) > 0 { - return replayDaveInputs(ctx, opts, tmp, inputs) - } - return replayInputsBatch(ctx, opts, tmp, inputs) -} - -func replayInputsBatch(ctx context.Context, opts replayOptions, tmp string, inputs []*model.Input) error { - if _, err := writeReplayInputFiles(tmp, inputs); err != nil { - return err - } - - args := []string{ - "--quiet", - "--no-revert", - "--load=" + opts.Template, - fmt.Sprintf("--cmio-advance-state=input:%s,input_index_begin:0,input_index_end:%d", - filepath.Join(tmp, "input-%i.bin"), len(inputs)), - "--store=" + opts.Store, - } - return runCommand(ctx, opts.CartesiMachine, args...) -} - -func collectReplayInputs( +// resolveReplayUpperBound resolves the exclusive upper input bound of the +// replay range: the end of the requested epoch, or one past the requested +// input index. +func resolveReplayUpperBound( ctx context.Context, repo repository.Repository, + application string, opts replayOptions, -) ([]*model.Input, *uint64, error) { - status := model.InputCompletionStatus_Accepted - filter := repository.InputFilter{Status: &status} - var offset uint64 - var result []*model.Input - var lastInputIndex *uint64 - for { - inputs, _, err := repo.ListInputs(ctx, opts.Application, filter, - repository.Pagination{Limit: defaultInputPageSize, Offset: offset}, false) +) (uint64, error) { + if opts.HasToEpoch { + epoch, err := repo.GetEpoch(ctx, application, opts.ToEpoch) if err != nil { - return nil, nil, fmt.Errorf("list inputs: %w", err) - } - if len(inputs) == 0 { - break - } - for _, input := range inputs { - if opts.HasToEpoch && input.EpochIndex > opts.ToEpoch { - return result, lastInputIndex, nil - } - if opts.HasToInputIndex && input.Index > opts.ToInputIndex { - return result, lastInputIndex, nil - } - result = append(result, input) - idx := input.Index - lastInputIndex = &idx + return 0, fmt.Errorf("get epoch %d: %w", opts.ToEpoch, err) } - if uint64(len(inputs)) < defaultInputPageSize { - break + if epoch == nil { + return 0, fmt.Errorf("epoch %d not found for application %q", opts.ToEpoch, application) } - offset += uint64(len(inputs)) + return epoch.InputIndexUpperBound, nil } - return result, lastInputIndex, nil + if opts.ToInputIndex == ^uint64(0) { + return 0, fmt.Errorf("to-input-index overflow") + } + return opts.ToInputIndex + 1, nil } -func newProveCommand() *cobra.Command { +func newProveCommand(logger *slog.Logger) *cobra.Command { prove := &cobra.Command{ Use: "prove", Short: "Generate Rollups proof files from a stored machine", } - prove.AddCommand(newProveAccountsDriveCommand()) + prove.AddCommand(newProveAccountsDriveCommand(logger)) return prove } -func newProveAccountsDriveCommand() *cobra.Command { +func newProveAccountsDriveCommand(logger *slog.Logger) *cobra.Command { var opts proveAccountsDriveOptions cmd := &cobra.Command{ Use: "accounts-drive", Short: "Generate accounts-drive root and account withdrawal proofs", RunE: func(cmd *cobra.Command, _ []string) error { - return runProveAccountsDrive(cmd.Context(), opts) + return runProveAccountsDrive(cmd.Context(), logger, opts) }, } cmd.Flags().StringVar(&opts.Snapshot, "snapshot", "", "Stored machine snapshot path") @@ -239,7 +203,6 @@ func newProveAccountsDriveCommand() *cobra.Command { cmd.Flags().Uint8Var(&opts.Log2LeavesPerAccount, "log2-leaves-per-account", 0, "Log2 of leaves per account") cmd.Flags().StringVar(&opts.OutDriveRootProof, "out-drive-root-proof", "", "Output JSON for prove-drive-root") cmd.Flags().StringVar(&opts.OutWithdrawProof, "out-withdraw-proof", "", "Output JSON for withdraw") - cmd.Flags().StringVar(&opts.CartesiMachine, "cartesi-machine", "cartesi-machine", "cartesi-machine executable") cobra.CheckErr(cmd.MarkFlagRequired("snapshot")) cobra.CheckErr(cmd.MarkFlagRequired("account")) cobra.CheckErr(cmd.MarkFlagRequired("out-drive-root-proof")) @@ -255,10 +218,9 @@ type proveAccountsDriveOptions struct { Log2LeavesPerAccount uint8 OutDriveRootProof string OutWithdrawProof string - CartesiMachine string } -func runProveAccountsDrive(ctx context.Context, opts proveAccountsDriveOptions) error { +func runProveAccountsDrive(ctx context.Context, logger *slog.Logger, opts proveAccountsDriveOptions) error { if !common.IsHexAddress(opts.Account) { return fmt.Errorf("invalid account address %q", opts.Account) } @@ -286,13 +248,20 @@ func runProveAccountsDrive(ctx context.Context, opts proveAccountsDriveOptions) if err != nil { return err } - machineProof, err := generateMachineProof(ctx, opts.CartesiMachine, opts.Snapshot, driveStart, log2DriveSize) + + m, err := machine.Load(ctx, logger, machine.DefaultConfig(opts.Snapshot)) if err != nil { - return err + return fmt.Errorf("load stored machine: %w", err) } - if !strings.EqualFold(strip0x(machineProof.TargetHash), accountProof.DriveRoot.Hex()[2:]) { + defer m.Close() + + machineProof, err := m.GetProof(ctx, driveStart, int32(log2DriveSize), 64) //nolint:mnd // full machine memory + if err != nil { + return fmt.Errorf("get accounts-drive proof: %w", err) + } + if common.Hash(machineProof.TargetHash) != accountProof.DriveRoot { return fmt.Errorf("accounts-drive root mismatch: machine proof has %s, local drive has %s", - ensure0x(machineProof.TargetHash), accountProof.DriveRoot.Hex()) + common.Hash(machineProof.TargetHash).Hex(), accountProof.DriveRoot.Hex()) } if err := writeDriveRootProof(opts.OutDriveRootProof, machineProof); err != nil { @@ -313,7 +282,7 @@ func runProveAccountsDrive(ctx context.Context, opts proveAccountsDriveOptions) Account: account, AccountIndex: fmt.Sprintf("0x%x", accountProof.AccountIndex), AccountsDriveMerkleRoot: accountProof.DriveRoot.Hex(), - MachineRoot: ensure0x(machineProof.RootHash), + MachineRoot: common.BytesToHash(machineProof.RootHash[:]).Hex(), DriveRootProofFile: opts.OutDriveRootProof, WithdrawProofFile: opts.OutWithdrawProof, } @@ -349,80 +318,16 @@ func findStoredDrive(snapshot string, start uint64, length uint64) (string, erro return "", fmt.Errorf("accounts drive not found in stored machine: start=0x%x length=0x%x", start, length) } -type cartesiMachineProof struct { - TargetAddress uint64 `json:"target_address"` - Log2TargetSize uint8 `json:"log2_target_size"` - Log2RootSize uint8 `json:"log2_root_size"` - TargetHash string `json:"target_hash"` - SiblingHashes []string `json:"sibling_hashes"` - RootHash string `json:"root_hash"` -} - -func generateMachineProof( - ctx context.Context, - cartesiMachine string, - snapshot string, - address uint64, - log2Size uint8, -) (*cartesiMachineProof, error) { - tmp, err := os.CreateTemp("", "cartesi-rollups-machine-proof-*.json") - if err != nil { - return nil, fmt.Errorf("create proof temp file: %w", err) - } - tmpPath := tmp.Name() - tmp.Close() - defer os.Remove(tmpPath) //nolint:errcheck - - args := []string{ - "--quiet", - "--no-revert", - "--load=" + snapshot, - fmt.Sprintf("--initial-proof=address:0x%x,log2_size:%d,filename:%s", address, log2Size, tmpPath), - "--", - "/bin/true", - } - if err := runCommand(ctx, cartesiMachine, args...); err != nil { - return nil, err - } - - raw, err := os.ReadFile(tmpPath) //nolint:gosec - if err != nil { - return nil, fmt.Errorf("read machine proof: %w", err) - } - var proof cartesiMachineProof - if err := json.Unmarshal(raw, &proof); err != nil { - return nil, fmt.Errorf("parse machine proof: %w", err) - } - - // convert the file hashes from base64 to hex - proof.RootHash, err = base64ToHex(proof.RootHash) - if err != nil { - return nil, err - } - proof.TargetHash, err = base64ToHex(proof.TargetHash) - if err != nil { - return nil, err - } - for i := range proof.SiblingHashes { - proof.SiblingHashes[i], err = base64ToHex(proof.SiblingHashes[i]) - if err != nil { - return nil, err - } - } - - return &proof, nil -} - -func writeDriveRootProof(path string, proof *cartesiMachineProof) error { +func writeDriveRootProof(path string, proof *machine.MemoryProof) error { out := struct { AccountsDriveMerkleRoot string `json:"accounts_drive_merkle_root"` Proof []string `json:"proof"` }{ - AccountsDriveMerkleRoot: ensure0x(proof.TargetHash), - Proof: make([]string, len(proof.SiblingHashes)), + AccountsDriveMerkleRoot: common.BytesToHash(proof.TargetHash[:]).Hex(), + Proof: make([]string, len(proof.Siblings)), } - for i, sibling := range proof.SiblingHashes { - out.Proof[i] = ensure0x(sibling) + for i, sibling := range proof.Siblings { + out.Proof[i] = common.BytesToHash(sibling[:]).Hex() } return writeJSON(path, out) } @@ -454,56 +359,3 @@ func writeJSON(path string, value any) error { } return nil } - -func runCommand(ctx context.Context, name string, args ...string) error { - return runCommandWithEnv(ctx, name, nil, args...) -} - -func runCommandWithEnv(ctx context.Context, name string, env []string, args ...string) error { - cmd := exec.CommandContext(ctx, name, args...) //nolint:gosec - if len(env) > 0 { - cmd.Env = append(os.Environ(), env...) - } - var stderr bytes.Buffer - cmd.Stderr = &stderr - cmd.Stdout = ioDiscardUnlessDebug() - if err := cmd.Run(); err != nil { - return fmt.Errorf("%s %s failed: %w\n%s", name, strings.Join(args, " "), err, stderr.String()) - } - return nil -} - -func ioDiscardUnlessDebug() *bytes.Buffer { - return &bytes.Buffer{} -} - -func readStoredMachineRoot(ctx context.Context, cartesiMachine string, store string) (string, error) { - const minProofLog2Size = 5 - proof, err := generateMachineProof(ctx, cartesiMachine, store, 0, minProofLog2Size) - if err != nil { - return "", fmt.Errorf("read stored machine root: %w", err) - } - return ensure0x(proof.RootHash), nil -} - -func ensure0x(s string) string { - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - return "0x" + s[2:] - } - return "0x" + s -} - -func strip0x(s string) string { - return strings.TrimPrefix(strings.TrimPrefix(s, "0x"), "0X") -} - -func base64ToHex(s string) (string, error) { - raw, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", fmt.Errorf("expected %q to be a hash in base64. %w", s, err) - } - if len(raw) != common.HashLength { - return "", fmt.Errorf("expected %q to decode to %d bytes, got %d", s, common.HashLength, len(raw)) - } - return common.BytesToHash(raw).Hex(), nil -} diff --git a/cmd/cartesi-rollups-machine-tool/replay_dave.go b/cmd/cartesi-rollups-machine-tool/replay_dave.go deleted file mode 100644 index f2003a68c..000000000 --- a/cmd/cartesi-rollups-machine-tool/replay_dave.go +++ /dev/null @@ -1,155 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package main - -import ( - "context" - _ "embed" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - - "github.com/cartesi/rollups-node/internal/model" -) - -//go:embed replay_dave.lua -var replayDaveLua string - -func replayDaveInputs(ctx context.Context, opts replayOptions, tmp string, inputs []*model.Input) error { - manifest, err := writeReplayInputManifest(tmp, inputs) - if err != nil { - return err - } - script := filepath.Join(tmp, "replay-dave.lua") - if err := os.WriteFile(script, []byte(replayDaveLua), 0600); err != nil { - return fmt.Errorf("write Dave replay Lua script: %w", err) - } - - args := []string{ - script, - "--template", opts.Template, - "--store", opts.Store, - "--inputs-manifest", manifest, - } - return runCommandWithEnv(ctx, opts.Lua, replayDaveLuaEnv(opts), args...) -} - -func writeReplayInputManifest(tmp string, inputs []*model.Input) (string, error) { - paths, err := writeReplayInputFiles(tmp, inputs) - if err != nil { - return "", err - } - manifest := filepath.Join(tmp, "inputs.txt") - var contents strings.Builder - for _, path := range paths { - contents.WriteString(path) - contents.WriteByte('\n') - } - if err := os.WriteFile(manifest, []byte(contents.String()), 0600); err != nil { - return "", fmt.Errorf("write Dave replay input manifest: %w", err) - } - return manifest, nil -} - -func writeReplayInputFiles(tmp string, inputs []*model.Input) ([]string, error) { - paths := make([]string, len(inputs)) - for i, input := range inputs { - path := filepath.Join(tmp, fmt.Sprintf("input-%d.bin", i)) - if err := os.WriteFile(path, input.RawData, 0600); err != nil { - return nil, fmt.Errorf("write replay input %d: %w", i, err) - } - paths[i] = path - } - return paths, nil -} - -func replayDaveLuaEnv(opts replayOptions) []string { - sdkRoot := opts.CartesiSDKRoot - if sdkRoot == "" { - sdkRoot = detectCartesiSDKRoot(opts.CartesiMachine) - } - if sdkRoot == "" { - return nil - } - - luaPath := filepath.Join(sdkRoot, "share", "lua", "5.4", "?.lua") - luaCPath := filepath.Join(sdkRoot, "lib", "lua", "5.4", "?.so") - env := []string{ - "LUA_PATH_5_4=" + prependLuaSearchPath(os.Getenv("LUA_PATH_5_4"), luaPath), - "LUA_CPATH_5_4=" + prependLuaSearchPath(os.Getenv("LUA_CPATH_5_4"), luaCPath), - "LUA_PATH=" + prependLuaSearchPath(os.Getenv("LUA_PATH"), luaPath), - "LUA_CPATH=" + prependLuaSearchPath(os.Getenv("LUA_CPATH"), luaCPath), - } - if os.Getenv("CARTESI_IMAGES_PATH") == "" { - env = append(env, "CARTESI_IMAGES_PATH="+filepath.Join(sdkRoot, "share", "cartesi-machine", "images")) - } - return env -} - -func prependLuaSearchPath(current string, entry string) string { - if current == "" { - return entry + ";;" - } - if luaSearchPathContains(current, entry) { - return current - } - return entry + ";" + current -} - -func luaSearchPathContains(path string, entry string) bool { - for _, part := range strings.Split(path, ";") { - if part == entry { - return true - } - } - return false -} - -func detectCartesiSDKRoot(cartesiMachine string) string { - path, err := exec.LookPath(cartesiMachine) - if err != nil { - path = cartesiMachine - } - if resolved, err := filepath.EvalSymlinks(path); err == nil { - path = resolved - } - - if root := detectCartesiSDKRootFromFile(path); root != "" { - return root - } - for _, root := range []string{"/opt/cartesi-sdk21", "/opt/cartesi"} { - if fileExists(filepath.Join(root, "share", "lua", "5.4", "cartesi.lua")) { - return root - } - } - return "" -} - -var ( - cartesiMachineLuaPattern = regexp.MustCompile(`["']([^"']*/share/lua/5\.4/cartesi-machine\.lua)["']`) - cartesiLuaPathPattern = regexp.MustCompile(`["']?([^"'\s;]*/share/lua/5\.4)/\?\.lua`) -) - -func detectCartesiSDKRootFromFile(path string) string { - raw, err := os.ReadFile(path) //nolint:gosec - if err != nil { - return "" - } - text := string(raw) - if match := cartesiMachineLuaPattern.FindStringSubmatch(text); len(match) == 2 { - return strings.TrimSuffix(match[1], "/share/lua/5.4/cartesi-machine.lua") - } - if match := cartesiLuaPathPattern.FindStringSubmatch(text); len(match) == 2 { - return strings.TrimSuffix(match[1], "/share/lua/5.4") - } - return "" -} - -func fileExists(path string) bool { - info, err := os.Stat(path) - return err == nil && !info.IsDir() -} diff --git a/cmd/cartesi-rollups-machine-tool/replay_dave.lua b/cmd/cartesi-rollups-machine-tool/replay_dave.lua deleted file mode 100644 index dc957a628..000000000 --- a/cmd/cartesi-rollups-machine-tool/replay_dave.lua +++ /dev/null @@ -1,100 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -local cartesi = require("cartesi") - -local function usage(message) - if message then io.stderr:write(message, "\n") end - io.stderr:write( - "usage: replay_dave.lua --template --store --inputs-manifest \n" - ) - os.exit(2) -end - -local function parse_args(args) - local opts = {} - local i = 1 - while i <= #args do - local key = args[i] - local value = args[i + 1] - if key == "--template" then - opts.template = value - elseif key == "--store" then - opts.store = value - elseif key == "--inputs-manifest" then - opts.inputs_manifest = value - else - usage("unknown argument: " .. tostring(key)) - end - if not value then usage("missing value for " .. tostring(key)) end - i = i + 2 - end - if not opts.template then usage("missing --template") end - if not opts.store then usage("missing --store") end - if not opts.inputs_manifest then usage("missing --inputs-manifest") end - return opts -end - -local function read_all(path) - local file = assert(io.open(path, "rb")) - return assert(file:read("*a")) -end - -local function read_input_paths(path) - local inputs = {} - for line in io.lines(path) do - if line ~= "" then inputs[#inputs + 1] = line end - end - return inputs -end - -local function run_until_manual_yield(machine) - while true do - local break_reason = machine:run(math.maxinteger) - if break_reason == cartesi.BREAK_REASON_YIELDED_MANUALLY then - return - elseif break_reason == cartesi.BREAK_REASON_YIELDED_AUTOMATICALLY then - machine:receive_cmio_request() - elseif break_reason == cartesi.BREAK_REASON_HALTED then - error("machine halted before a manual yield") - elseif break_reason == cartesi.BREAK_REASON_FAILED then - error("machine failed before a manual yield") - elseif break_reason ~= cartesi.BREAK_REASON_YIELDED_SOFTLY then - error("unexpected machine break reason: " .. tostring(break_reason)) - end - end -end - -local function ensure_manual_yield(machine) - if machine:read_reg("iflags_Y") == 0 then - run_until_manual_yield(machine) - end -end - -local function advance_one(machine, input_path, input_number) - local checkpoint = machine:get_root_hash() - machine:send_cmio_response(cartesi.HTIF_YIELD_REASON_ADVANCE_STATE, read_all(input_path), checkpoint) - run_until_manual_yield(machine) - - local _, reason, data = machine:receive_cmio_request() - if reason == cartesi.HTIF_YIELD_MANUAL_REASON_RX_REJECTED then - error(string.format("Dave replay input %d was rejected", input_number)) - elseif reason == cartesi.HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION then - error(string.format("Dave replay input %d raised an exception", input_number)) - elseif reason ~= cartesi.HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED then - error(string.format("Dave replay input %d ended with unexpected yield reason %s", input_number, tostring(reason))) - end - if #data ~= 32 then - error(string.format("Dave replay input %d returned an invalid outputs hash length: %d", input_number, #data)) - end -end - -local opts = parse_args(arg) -local machine = cartesi.machine(opts.template) -ensure_manual_yield(machine) - -for index, input_path in ipairs(read_input_paths(opts.inputs_manifest)) do - advance_one(machine, input_path, index - 1) -end - -machine:store(opts.store) diff --git a/cmd/cartesi-rollups-machine-tool/replay_dave_test.go b/cmd/cartesi-rollups-machine-tool/replay_dave_test.go deleted file mode 100644 index bfc923d33..000000000 --- a/cmd/cartesi-rollups-machine-tool/replay_dave_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package main - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/cartesi/rollups-node/internal/model" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDetectCartesiSDKRootFromFile_WrapperWithCartesiMachineLua_ReturnsRoot(t *testing.T) { - t.Parallel() - - sdkRoot := filepath.Join(t.TempDir(), "cartesi-sdk") - luaDir := filepath.Join(sdkRoot, "share", "lua", "5.4") - wrapper := filepath.Join(t.TempDir(), "cartesi-machine") - contents := `#!/bin/sh -export LUA_PATH_5_4="` + luaDir + `/?.lua;${LUA_PATH_5_4:-;}" -lua5.4 "` + filepath.Join(luaDir, "cartesi-machine.lua") + `" "$@" -` - require.NoError(t, os.WriteFile(wrapper, []byte(contents), 0600)) - - assert.Equal(t, sdkRoot, detectCartesiSDKRootFromFile(wrapper)) -} - -func TestPrependLuaSearchPath_EmptyPath_KeepsLuaDefaultFallback(t *testing.T) { - t.Parallel() - - assert.Equal(t, "/opt/cartesi-sdk21/share/lua/5.4/?.lua;;", - prependLuaSearchPath("", "/opt/cartesi-sdk21/share/lua/5.4/?.lua")) -} - -func TestPrependLuaSearchPath_ExistingPath_PrependsWithoutDroppingExisting(t *testing.T) { - t.Parallel() - - got := prependLuaSearchPath("/custom/?.lua", "/opt/cartesi-sdk21/share/lua/5.4/?.lua") - - assert.Equal(t, "/opt/cartesi-sdk21/share/lua/5.4/?.lua;/custom/?.lua", got) -} - -func TestPrependLuaSearchPath_AlreadyPresent_DoesNotDuplicate(t *testing.T) { - t.Parallel() - - path := "/opt/cartesi-sdk21/share/lua/5.4/?.lua;/custom/?.lua" - - assert.Equal(t, path, prependLuaSearchPath(path, "/opt/cartesi-sdk21/share/lua/5.4/?.lua")) -} - -func TestReplayDaveLuaEnv_AutoDetectsSDKRootFromCartesiMachineWrapper(t *testing.T) { - sdkRoot := filepath.Join(t.TempDir(), "cartesi-sdk") - luaDir := filepath.Join(sdkRoot, "share", "lua", "5.4") - wrapper := filepath.Join(t.TempDir(), "cartesi-machine") - contents := `#!/bin/sh -export LUA_PATH_5_4="` + luaDir + `/?.lua;${LUA_PATH_5_4:-;}" -export LUA_CPATH_5_4="` + filepath.Join(sdkRoot, "lib", "lua", "5.4") + `/?.so;${LUA_CPATH_5_4:-;}" -lua5.4 "` + filepath.Join(luaDir, "cartesi-machine.lua") + `" "$@" -` - require.NoError(t, os.WriteFile(wrapper, []byte(contents), 0600)) - t.Setenv("LUA_PATH_5_4", "/custom/?.lua") - t.Setenv("LUA_CPATH_5_4", "") - t.Setenv("LUA_PATH", "") - t.Setenv("LUA_CPATH", "") - t.Setenv("CARTESI_IMAGES_PATH", "") - - env := replayDaveLuaEnv(replayOptions{CartesiMachine: wrapper}) - envByKey := envMap(env) - - assert.Equal(t, filepath.Join(luaDir, "?.lua")+";/custom/?.lua", envByKey["LUA_PATH_5_4"]) - assert.Equal(t, filepath.Join(sdkRoot, "lib", "lua", "5.4", "?.so")+";;", envByKey["LUA_CPATH_5_4"]) - assert.Equal(t, filepath.Join(luaDir, "?.lua")+";;", envByKey["LUA_PATH"]) - assert.Equal(t, filepath.Join(sdkRoot, "share", "cartesi-machine", "images"), envByKey["CARTESI_IMAGES_PATH"]) -} - -func TestWriteReplayInputManifest_WritesPayloadsAndManifest(t *testing.T) { - t.Parallel() - - tmp := t.TempDir() - inputs := []*model.Input{ - {RawData: []byte{0x01, 0x02}}, - {RawData: []byte("cartesi")}, - } - - manifest, err := writeReplayInputManifest(tmp, inputs) - require.NoError(t, err) - - raw, err := os.ReadFile(manifest) - require.NoError(t, err) - paths := strings.Split(strings.TrimSpace(string(raw)), "\n") - require.Len(t, paths, len(inputs)) - for i, path := range paths { - payload, err := os.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, inputs[i].RawData, payload) - } -} - -func envMap(env []string) map[string]string { - result := make(map[string]string, len(env)) - for _, item := range env { - key, value, _ := strings.Cut(item, "=") - result[key] = value - } - return result -} diff --git a/internal/advancer/advancer_test.go b/internal/advancer/advancer_test.go index 40efc5ff1..b38539519 100644 --- a/internal/advancer/advancer_test.go +++ b/internal/advancer/advancer_test.go @@ -2361,6 +2361,11 @@ func (m *MockMachineInstance) ProcessedInputs() uint64 { return m.machineImpl.processedInputs } +func (m *MockMachineInstance) HasRuntime() bool { + // Not used in advancer tests, but needed to satisfy the interface + return true +} + func (m *MockMachineInstance) StateProof(_ context.Context) (*StateProof, error) { if m.machineImpl.StateProofError != nil { return nil, m.machineImpl.StateProofError diff --git a/internal/advancer/determinism_test.go b/internal/advancer/determinism_test.go index f7effe6ea..2e25de457 100644 --- a/internal/advancer/determinism_test.go +++ b/internal/advancer/determinism_test.go @@ -767,6 +767,12 @@ func (m *determinismRuntime) StateProof(ctx context.Context) (*machine.StateProo }, nil } +func (m *determinismRuntime) GetProof(ctx context.Context, _ uint64, _, _ int32) (*machine.MemoryProof, error) { + m.mu.Lock() + defer m.mu.Unlock() + return nil, m.checkOpenLocked(ctx) +} + func (m *determinismRuntime) Advance( ctx context.Context, input []byte, diff --git a/internal/inspect/hardening_test.go b/internal/inspect/hardening_test.go index 6a40af983..0d2797519 100644 --- a/internal/inspect/hardening_test.go +++ b/internal/inspect/hardening_test.go @@ -109,6 +109,7 @@ func (m *erroringMachine) Advance(ctx context.Context, input []byte, a, b uint64 } func (m *erroringMachine) Application() *Application { return m.inner.Application() } func (m *erroringMachine) ProcessedInputs() uint64 { return m.inner.ProcessedInputs() } +func (m *erroringMachine) HasRuntime() bool { return m.inner.HasRuntime() } func (m *erroringMachine) StateProof(ctx context.Context) (*StateProof, error) { return m.inner.StateProof(ctx) } diff --git a/internal/inspect/inspect_test.go b/internal/inspect/inspect_test.go index a3729fed6..eb08c88b7 100644 --- a/internal/inspect/inspect_test.go +++ b/internal/inspect/inspect_test.go @@ -524,6 +524,11 @@ func (mock *MockMachine) ProcessedInputs() uint64 { return 0 } +// Not used in inspect tests, but needed to satisfy the interface +func (mock *MockMachine) HasRuntime() bool { + return true +} + func (mock *MockMachine) StateProof(_ context.Context) (*StateProof, error) { return nil, nil } diff --git a/internal/manager/instance.go b/internal/manager/instance.go index 530fce80c..76cdde1c9 100644 --- a/internal/manager/instance.go +++ b/internal/manager/instance.go @@ -192,6 +192,12 @@ func (m *MachineInstanceImpl) ProcessedInputs() uint64 { return m.processedInputs.Load() } +func (m *MachineInstanceImpl) HasRuntime() bool { + m.mutex.LLock() + defer m.mutex.Unlock() + + return m.runtime != nil +} // forkForAdvance creates a copy of the machine for advance operations // It verifies the input index and returns a forked machine func (m *MachineInstanceImpl) forkForAdvance(ctx context.Context, index uint64) (machine.Machine, error) { diff --git a/internal/manager/instance_test.go b/internal/manager/instance_test.go index abfb22982..0c6e46146 100644 --- a/internal/manager/instance_test.go +++ b/internal/manager/instance_test.go @@ -1449,6 +1449,8 @@ type MockRollupsMachine struct { StateProofReturn *machine.StateProof StateProofError error StateProofFunc func(context.Context) (*machine.StateProof, error) + GetProofReturn *machine.MemoryProof + GetProofError error AdvanceError error LastAdvanceComputeHashes bool @@ -1480,6 +1482,10 @@ func (m *MockRollupsMachine) StateProof(ctx context.Context) (*machine.StateProo return m.StateProofReturn, m.StateProofError } +func (m *MockRollupsMachine) GetProof(_ context.Context, _ uint64, _, _ int32) (*machine.MemoryProof, error) { + return m.GetProofReturn, m.GetProofError +} + func (m *MockRollupsMachine) Advance(_ context.Context, _ []byte, _ machine.Hash, computeHashes bool) (*machine.AdvanceResponse, error) { m.LastAdvanceComputeHashes = computeHashes if m.AdvanceError != nil { diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go index 726b14fe9..193841b9c 100644 --- a/internal/manager/manager_test.go +++ b/internal/manager/manager_test.go @@ -1928,6 +1928,10 @@ func (m *DummyMachineInstanceMock) ProcessedInputs() uint64 { return m.processedInputs } +func (m *DummyMachineInstanceMock) HasRuntime() bool { + return true +} + func (m *DummyMachineInstanceMock) StateProof(_ context.Context) (*model.StateProof, error) { return nil, nil } diff --git a/internal/manager/types.go b/internal/manager/types.go index 2acf2d72b..68d56eb1c 100644 --- a/internal/manager/types.go +++ b/internal/manager/types.go @@ -31,6 +31,7 @@ type MachineInstance interface { inputIndex uint64, computeHashes bool, ) (*model.AdvanceResult, error) + HasRuntime() bool Inspect(ctx context.Context, query []byte) (*InspectResult, error) CreateSnapshot(ctx context.Context, processedInputs uint64, path string) error ProcessedInputs() uint64 diff --git a/pkg/machine/implementation.go b/pkg/machine/implementation.go index 1fddb5e5d..43ac7e91f 100644 --- a/pkg/machine/implementation.go +++ b/pkg/machine/implementation.go @@ -194,6 +194,19 @@ func (m *machineImpl) StateProof(ctx context.Context) (*StateProof, error) { }, nil } +// GetProof returns a Merkle proof for the memory span starting at address +// with log2TargetSize, rooted at the machine memory tree of log2RootSize. +func (m *machineImpl) GetProof(ctx context.Context, address uint64, log2TargetSize, log2RootSize int32) (*MemoryProof, error) { + if err := checkContext(ctx); err != nil { + return nil, err + } + proof, err := m.backend.GetProof(address, log2TargetSize, log2RootSize, m.params.LoadDeadline) + if err != nil { + return nil, errors.Join(ErrMachineInternal, fmt.Errorf("could not get memory proof: %w", err)) + } + return &proof, nil +} + // ValidateAcceptedState checks the state semantics required when an epoch // is published through the released v3 contracts. StateProof itself remains // generic so the exact post-run proof can also be persisted for terminal diff --git a/pkg/machine/machine.go b/pkg/machine/machine.go index 793d78473..32db795ac 100644 --- a/pkg/machine/machine.go +++ b/pkg/machine/machine.go @@ -148,6 +148,10 @@ type Machine interface { // machine root and the three state leaves used by the rollups contracts. StateProof(ctx context.Context) (*StateProof, error) + // GetProof returns a Merkle proof for the memory span starting at address + // with log2TargetSize, rooted at the machine memory tree of log2RootSize. + GetProof(ctx context.Context, address uint64, log2TargetSize, log2RootSize int32) (*MemoryProof, 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. diff --git a/pkg/machine/machine_test.go b/pkg/machine/machine_test.go index a8469ee8e..bc5fc51dd 100644 --- a/pkg/machine/machine_test.go +++ b/pkg/machine/machine_test.go @@ -5,15 +5,19 @@ package machine import ( "context" + "encoding/binary" + "encoding/json" "errors" "fmt" "io" "log/slog" + "path/filepath" "testing" "time" "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/emulator" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" ) @@ -193,6 +197,90 @@ func (s *MachineSuite) TestLoad() { mockBackend.AssertExpectations(s.T()) } +// getProofYieldProgram writes the prepared manual yield request to HTIF +// tohost, leaving the machine at the accepted manual yield that Load requires. +var getProofYieldProgram = []uint32{ + 0x400082b7, // lui t0,0x40008: load the HTIF base address + 0x0002b423, // sd zero,8(t0): clear fromhost + 0x0132b023, // sd x19,0(t0): write the prepared request to tohost +} + +const ( + getProofRAMStart = uint64(0x80000000) // RISC-V RAM base for the test guest + getProofRAMLength = 4096 +) + +// TestGetProof proves a 32-byte memory span of a real emulator machine and +// verifies the proof against the machine root and the span's content. +func (s *MachineSuite) TestGetProof() { + require := s.Require() + ctx := context.Background() + + config, err := json.Marshal(map[string]any{ + "processor": map[string]any{"registers": map[string]any{"pc": getProofRAMStart}}, + "ram": map[string]any{"length": getProofRAMLength}, + "cmio": map[string]any{ + "rx_buffer": map[string]any{}, + "tx_buffer": map[string]any{}, + }, + }) + require.NoError(err) + + emu, err := emulator.CreateMachine(string(config), "", "") + require.NoError(err) + defer emu.Delete() + defer func() { _ = emu.Destroy() }() + + program := make([]byte, 4*len(getProofYieldProgram)) + for i, instruction := range getProofYieldProgram { + binary.LittleEndian.PutUint32(program[4*i:], instruction) + } + require.NoError(emu.WriteMemory(getProofRAMStart, program)) + + // The pre-set registers place the machine at the accepted manual yield + // that Load requires, without running the guest. + request := htifDeviceYield< Date: Tue, 22 Sep 2026 09:08:20 -0300 Subject: [PATCH 2/2] fix(machine-tool): reject replay ranges ending at a terminal input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A replay whose target is the application's terminal input ends in a terminal machine state that CreateSnapshot and Hash cannot read — MachineInstanceImpl disposes the runtime on terminal completion — so the store step failed with an opaque ErrMachineClosed only after a full replay. Check the last processed input's persisted status before creating the machine instance. If it is terminal and the requested range reaches it, fail fast and hint at replaying up to the preceding input. replay.Run's contradiction check remains the backstop for a terminal input that is not the application's last. Drop the HasRuntime method (MachineInstanceImpl, the MachineInstance interface, and the test mocks) that the previous after-the-fact check used. --- cmd/cartesi-rollups-machine-tool/main.go | 15 +++++++++++---- internal/advancer/advancer_test.go | 5 ----- internal/inspect/hardening_test.go | 1 - internal/inspect/inspect_test.go | 5 ----- internal/manager/instance.go | 6 ------ internal/manager/manager_test.go | 4 ---- internal/manager/types.go | 1 - 7 files changed, 11 insertions(+), 26 deletions(-) diff --git a/cmd/cartesi-rollups-machine-tool/main.go b/cmd/cartesi-rollups-machine-tool/main.go index 6885ad3b8..be33790e0 100644 --- a/cmd/cartesi-rollups-machine-tool/main.go +++ b/cmd/cartesi-rollups-machine-tool/main.go @@ -108,6 +108,17 @@ func runReplay(ctx context.Context, logger *slog.Logger, opts replayOptions) err return err } + lastInput, err := repo.GetLastProcessedInput(ctx, opts.Application) + if err != nil { + return fmt.Errorf("get last processed input: %w", err) + } + if lastInput != nil && lastInput.Status.IsTerminal() && toInputExclusive > lastInput.Index { + return fmt.Errorf( + "cannot snapshot: replay range reaches the application's terminal input 0x%x (status %s). Replay up to input 0x%x instead", + lastInput.Index, lastInput.Status, lastInput.Index, + ) + } + instance, err := manager.NewMachineInstance(ctx, app, logger, true) if err != nil { return fmt.Errorf("create machine instance: %w", err) @@ -124,10 +135,6 @@ func runReplay(ctx context.Context, logger *slog.Logger, opts replayOptions) err if err != nil { return fmt.Errorf("replay: %w", err) } - if !instance.HasRuntime() { - return fmt.Errorf("replay completed in a terminal machine state; snapshotting is unsupported") - } - if err := instance.CreateSnapshot(ctx, instance.ProcessedInputs(), opts.Store); err != nil { return fmt.Errorf("store machine: %w", err) } diff --git a/internal/advancer/advancer_test.go b/internal/advancer/advancer_test.go index b38539519..40efc5ff1 100644 --- a/internal/advancer/advancer_test.go +++ b/internal/advancer/advancer_test.go @@ -2361,11 +2361,6 @@ func (m *MockMachineInstance) ProcessedInputs() uint64 { return m.machineImpl.processedInputs } -func (m *MockMachineInstance) HasRuntime() bool { - // Not used in advancer tests, but needed to satisfy the interface - return true -} - func (m *MockMachineInstance) StateProof(_ context.Context) (*StateProof, error) { if m.machineImpl.StateProofError != nil { return nil, m.machineImpl.StateProofError diff --git a/internal/inspect/hardening_test.go b/internal/inspect/hardening_test.go index 0d2797519..6a40af983 100644 --- a/internal/inspect/hardening_test.go +++ b/internal/inspect/hardening_test.go @@ -109,7 +109,6 @@ func (m *erroringMachine) Advance(ctx context.Context, input []byte, a, b uint64 } func (m *erroringMachine) Application() *Application { return m.inner.Application() } func (m *erroringMachine) ProcessedInputs() uint64 { return m.inner.ProcessedInputs() } -func (m *erroringMachine) HasRuntime() bool { return m.inner.HasRuntime() } func (m *erroringMachine) StateProof(ctx context.Context) (*StateProof, error) { return m.inner.StateProof(ctx) } diff --git a/internal/inspect/inspect_test.go b/internal/inspect/inspect_test.go index eb08c88b7..a3729fed6 100644 --- a/internal/inspect/inspect_test.go +++ b/internal/inspect/inspect_test.go @@ -524,11 +524,6 @@ func (mock *MockMachine) ProcessedInputs() uint64 { return 0 } -// Not used in inspect tests, but needed to satisfy the interface -func (mock *MockMachine) HasRuntime() bool { - return true -} - func (mock *MockMachine) StateProof(_ context.Context) (*StateProof, error) { return nil, nil } diff --git a/internal/manager/instance.go b/internal/manager/instance.go index 76cdde1c9..530fce80c 100644 --- a/internal/manager/instance.go +++ b/internal/manager/instance.go @@ -192,12 +192,6 @@ func (m *MachineInstanceImpl) ProcessedInputs() uint64 { return m.processedInputs.Load() } -func (m *MachineInstanceImpl) HasRuntime() bool { - m.mutex.LLock() - defer m.mutex.Unlock() - - return m.runtime != nil -} // forkForAdvance creates a copy of the machine for advance operations // It verifies the input index and returns a forked machine func (m *MachineInstanceImpl) forkForAdvance(ctx context.Context, index uint64) (machine.Machine, error) { diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go index 193841b9c..726b14fe9 100644 --- a/internal/manager/manager_test.go +++ b/internal/manager/manager_test.go @@ -1928,10 +1928,6 @@ func (m *DummyMachineInstanceMock) ProcessedInputs() uint64 { return m.processedInputs } -func (m *DummyMachineInstanceMock) HasRuntime() bool { - return true -} - func (m *DummyMachineInstanceMock) StateProof(_ context.Context) (*model.StateProof, error) { return nil, nil } diff --git a/internal/manager/types.go b/internal/manager/types.go index 68d56eb1c..2acf2d72b 100644 --- a/internal/manager/types.go +++ b/internal/manager/types.go @@ -31,7 +31,6 @@ type MachineInstance interface { inputIndex uint64, computeHashes bool, ) (*model.AdvanceResult, error) - HasRuntime() bool Inspect(ctx context.Context, query []byte) (*InspectResult, error) CreateSnapshot(ctx context.Context, processedInputs uint64, path string) error ProcessedInputs() uint64