From 19fe91e1568b9f9dd8643b72b8028d6757c57034 Mon Sep 17 00:00:00 2001 From: Zehui Zheng Date: Tue, 13 Jan 2026 17:48:55 -0800 Subject: [PATCH 1/3] feat: bump emulator to 0.20 chore: update emulator submodule, link omp chore: update step submodule and rename fields chore: update bindings, fix tests chore: update CHECKPOINT_ADDRESS --- .github/actions/cartesi-machine/action.yml | 2 +- .github/workflows/build.yml | 8 +- .../contracts/src/DaveConsensus.sol | 2 +- .../contracts/test/DaveAppFactory.t.sol | 2 +- .../node/blockchain-reader/src/lib.rs | 5 +- .../node/blockchain-reader/src/test_utils.rs | 9 +- .../src/persistent_state_access.rs | 5 +- .../node/state-manager/src/rollups_machine.rs | 6 +- .../node/state-manager/src/sql/test_helper.rs | 5 +- justfile | 4 +- machine/emulator | 2 +- machine/rust-bindings/Cargo.toml | 2 +- .../cartesi-machine-sys/build.rs | 24 +- .../cartesi-machine/src/config/machine.rs | 228 +++++++++++++++--- .../cartesi-machine/src/constants.rs | 18 +- .../cartesi-machine/src/machine.rs | 118 +++++---- machine/step | 2 +- prt/client-lua/computation/constants.lua | 2 +- prt/client-lua/computation/machine.lua | 2 +- prt/client-lua/cryptography/hash.lua | 3 +- prt/client-rs/core/src/machine/instance.rs | 13 +- .../CartesiStateTransition.sol | 2 +- .../state-transition/CmioStateTransition.sol | 4 +- prt/measure_constants/Dockerfile | 2 +- .../runners/helpers/patched_commitment.lua | 2 +- prt/tests/rollups/dave/node.lua | 10 +- prt/tests/rollups/justfile | 2 +- prt/tests/rollups/test_cases/simple.lua | 2 +- prt/tests/rollups/test_env.lua | 6 +- test/programs/justfile | 17 +- 30 files changed, 369 insertions(+), 140 deletions(-) diff --git a/.github/actions/cartesi-machine/action.yml b/.github/actions/cartesi-machine/action.yml index 8df8a10c..bc40b93b 100644 --- a/.github/actions/cartesi-machine/action.yml +++ b/.github/actions/cartesi-machine/action.yml @@ -4,7 +4,7 @@ inputs: version: description: 'Version of Cartesi Machine to install' required: false - default: 0.19.0 + default: 0.20.0 suffix-version: description: 'Suffix of Cartesi Machine to install' required: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d879e8fd..ebe9a738 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: - name: Install Cartesi Machine uses: ./.github/actions/cartesi-machine with: - version: 0.19.0 + version: 0.20.0 suffix-version: "" - name: Setup env @@ -71,7 +71,7 @@ jobs: - name: Install Cartesi Machine uses: ./.github/actions/cartesi-machine with: - version: 0.19.0 + version: 0.20.0 - name: Download PRT contracts working-directory: ./prt/contracts run: | @@ -138,8 +138,8 @@ jobs: working-directory: ./machine/emulator run: | make bundle-boost - wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/v0.19.0/add-generated-files.diff - echo "a892e2d9f5c331f5e80bcb5db4133e7db625aa4d14ffdf9467b75c4c34d1744f add-generated-files.diff" | sha256sum -c + wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/v0.20.0/add-generated-files.diff + echo "d9c2afcefc2759e7cd37bbedc83d54c81515f0fddb671103b489b8789aee33bb add-generated-files.diff" | sha256sum -c git apply add-generated-files.diff rm add-generated-files.diff make diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index eabf87f7..bde7f1ad 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -242,7 +242,7 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { require(proof.length == Memory.LOG2_MAX_SIZE, InvalidOutputsMerkleRootProofSize(proof.length)); bytes32 allegedStateHash = proof.merkleRootAfterReplacement( - EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, + EmulatorConstants.AR_CMIO_TX_BUFFER_START >> EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE, keccak256(abi.encode(outputsMerkleRoot)), LibKeccak256.hashPair ); diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index ff954c41..b67b71fa 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -167,7 +167,7 @@ contract DaveAppFactoryTest is Test { bytes32[] memory outputsMerkleRootProof = _randomProof(Memory.LOG2_MAX_SIZE); bytes32 machineMerkleRoot = outputsMerkleRootProof.merkleRootAfterReplacement( - EmulatorConstants.PMA_CMIO_TX_BUFFER_START >> EmulatorConstants.TREE_LOG2_WORD_SIZE, + EmulatorConstants.AR_CMIO_TX_BUFFER_START >> EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE, keccak256(abi.encode(outputsMerkleRoot)) ); diff --git a/cartesi-rollups/node/blockchain-reader/src/lib.rs b/cartesi-rollups/node/blockchain-reader/src/lib.rs index ce95bf1c..e86472bd 100644 --- a/cartesi-rollups/node/blockchain-reader/src/lib.rs +++ b/cartesi-rollups/node/blockchain-reader/src/lib.rs @@ -572,7 +572,10 @@ mod blockchain_reader_tests { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index fc9c49ce..9b20bfce 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -13,7 +13,7 @@ use cartesi_rollups_contracts::i_input_box::IInputBox; use serde::Deserialize; use std::{ fs::{self, File}, - io::Read, + io::{Read, Seek}, path::PathBuf, }; @@ -77,8 +77,11 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let dave_app_factory = deployment_address("DaveAppFactory"); let initial_hash = { - // $ xxd -p -c32 test/programs/echo/machine-image/hash - let mut file = File::open(program_path.join("machine-image").join("hash")).unwrap(); + // Root hash is stored in hash_tree.sht at offset 0x60 (node 1's hash in sparse tree). + // Equivalent to: xxd -seek 0x60 -l 0x20 -c 0x20 -p .../machine-image/hash_tree.sht + let mut file = + File::open(program_path.join("machine-image").join("hash_tree.sht")).unwrap(); + file.seek(std::io::SeekFrom::Start(0x60)).unwrap(); let mut buffer = [0u8; 32]; file.read_exact(&mut buffer).unwrap(); buffer diff --git a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs index b11d772f..704d1a60 100644 --- a/cartesi-rollups/node/state-manager/src/persistent_state_access.rs +++ b/cartesi-rollups/node/state-manager/src/persistent_state_access.rs @@ -330,7 +330,10 @@ mod tests { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/cartesi-rollups/node/state-manager/src/rollups_machine.rs b/cartesi-rollups/node/state-manager/src/rollups_machine.rs index 8f013741..8c0a9d5f 100644 --- a/cartesi-rollups/node/state-manager/src/rollups_machine.rs +++ b/cartesi-rollups/node/state-manager/src/rollups_machine.rs @@ -10,7 +10,7 @@ use cartesi_prt_core::machine::constants::{ use crate::{CommitmentLeaf, Proof}; use cartesi_machine::{ config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::{break_reason, pma::TX_START}, + constants::{break_reason, machine::TREE_LOG2_ROOT_SIZE, pma::TX_START}, error::{MachineError, MachineResult}, machine::Machine, types::{ @@ -45,7 +45,7 @@ pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE); -pub const CHECKPOINT_ADDRESS: u64 = 0x7ffff000; +pub const CHECKPOINT_ADDRESS: u64 = 0xfe0; pub struct RollupsMachine { machine: Machine, @@ -88,7 +88,7 @@ impl RollupsMachine { } pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { - let proof = self.machine.proof(TX_START, 5)?; + let proof = self.machine.proof(TX_START, 5, TREE_LOG2_ROOT_SIZE)?; let siblings = Proof::new(proof.sibling_hashes); let output_merkle = self.machine.read_memory(TX_START, 32)?; diff --git a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs b/cartesi-rollups/node/state-manager/src/sql/test_helper.rs index 73758261..d849c6b0 100644 --- a/cartesi-rollups/node/state-manager/src/sql/test_helper.rs +++ b/cartesi-rollups/node/state-manager/src/sql/test_helper.rs @@ -21,7 +21,10 @@ pub fn setup_db() -> (TempDir, Connection) { let mut machine = Machine::create( &MachineConfig::new_with_ram(RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), + backing_store: cartesi_machine::config::machine::BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, }), &RuntimeConfig::default(), ) diff --git a/justfile b/justfile index 96d84b19..9cbb28fc 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ update-submodules: git submodule update --recursive --init -apply-generated-files-diff VERSION="v0.19.0" FILEHASH="a892e2d9f5c331f5e80bcb5db4133e7db625aa4d14ffdf9467b75c4c34d1744f": +apply-generated-files-diff VERSION="v0.20.0" FILEHASH="d9c2afcefc2759e7cd37bbedc83d54c81515f0fddb671103b489b8789aee33bb": cd machine/emulator && \ (wget -O add-generated-files.diff https://github.com/cartesi/machine-emulator/releases/download/{{VERSION}}/add-generated-files.diff && \ (echo "{{FILEHASH}} add-generated-files.diff" | sha256sum -c) && \ @@ -18,7 +18,7 @@ clean-contracts: clean-consensus-contracts clean-prt-contracts clean-bindings cl make -C machine/emulator clean depclean distclean setup: update-submodules clean-emulator clean-contracts bundle-boost apply-generated-files-diff - make -C machine/emulator # Requires docker, necessary for machine bindings + make -C machine/emulator -j8 # Requires docker, necessary for machine bindings # Run this once after cloning, if using a docker environment setup-docker: setup build-docker-image diff --git a/machine/emulator b/machine/emulator index ce402f96..8bfca691 160000 --- a/machine/emulator +++ b/machine/emulator @@ -1 +1 @@ -Subproject commit ce402f96d6757c8f4b2f08cba861cd80ab6bf834 +Subproject commit 8bfca6912f4849e03b7b55677e17e385c0b2dfbe diff --git a/machine/rust-bindings/Cargo.toml b/machine/rust-bindings/Cargo.toml index 91400111..57ea66af 100644 --- a/machine/rust-bindings/Cargo.toml +++ b/machine/rust-bindings/Cargo.toml @@ -8,7 +8,7 @@ members = [ [workspace.package] -version = "0.19.0" +version = "0.20.0" edition = "2021" license = "Apache-2.0" diff --git a/machine/rust-bindings/cartesi-machine-sys/build.rs b/machine/rust-bindings/cartesi-machine-sys/build.rs index 527c6543..bb922419 100644 --- a/machine/rust-bindings/cartesi-machine-sys/build.rs +++ b/machine/rust-bindings/cartesi-machine-sys/build.rs @@ -39,6 +39,28 @@ fn main() { } } + // OpenMP linker configuration (cross-platform) + if cfg!(target_os = "macos") { + // macOS: Try Homebrew first, then MacPorts + let homebrew_libomp = PathBuf::from("/opt/homebrew/opt/libomp"); + if homebrew_libomp.exists() { + println!("cargo:rustc-link-search={}/lib", homebrew_libomp.display()); + println!("cargo:rustc-link-lib=omp"); + } else { + let macports_libomp = PathBuf::from("/opt/local/lib/libomp"); + if macports_libomp.exists() { + println!("cargo:rustc-link-search=/opt/local/lib/libomp"); + println!("cargo:rustc-link-lib=gomp"); + } else { + // Fallback: let system linker find it + println!("cargo:rustc-link-lib=omp"); + } + } + } else { + // Linux and other Unix-like systems: libgomp comes with GCC + println!("cargo:rustc-link-lib=gomp"); + } + // // Generate bindings // @@ -197,7 +219,7 @@ mod build_cm { process::{Command, Stdio}, }; - const VERSION_STRING: &str = "v0.19.0"; + const VERSION_STRING: &str = "v0.20.0"; pub fn download(machine_dir_path: &Path) { let patch_file = machine_dir_path.join("add-generated-files.diff"); diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index e599ed6f..5d3b6dde 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -4,15 +4,43 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; +/// Backing store config; matches C++ backing_store_config (data_filename, etc.). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct BackingStoreConfig { + #[serde(default)] + pub shared: bool, + #[serde(default)] + pub create: bool, + #[serde(default)] + pub truncate: bool, + #[serde(default)] + pub data_filename: PathBuf, + #[serde(default)] + pub dht_filename: PathBuf, + #[serde(default)] + pub dpt_filename: PathBuf, +} + +/// Config with only backing_store; matches C++ backing_store_config_only. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct BackingStoreConfigOnly { + #[serde(default)] + pub backing_store: BackingStoreConfig, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MachineConfig { pub processor: ProcessorConfig, pub ram: RAMConfig, pub dtb: DTBConfig, pub flash_drive: FlashDriveConfigs, + #[serde(default)] pub tlb: TLBConfig, + #[serde(default)] pub clint: CLINTConfig, + #[serde(default)] pub plic: PLICConfig, + #[serde(default)] pub htif: HTIFConfig, pub uarch: UarchConfig, pub cmio: CmioConfig, @@ -93,106 +121,206 @@ fn default_config() -> MachineConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProcessorConfig { + #[serde(default)] + pub backing_store: BackingStoreConfig, + #[serde(default)] pub x0: u64, + #[serde(default)] pub x1: u64, + #[serde(default)] pub x2: u64, + #[serde(default)] pub x3: u64, + #[serde(default)] pub x4: u64, + #[serde(default)] pub x5: u64, + #[serde(default)] pub x6: u64, + #[serde(default)] pub x7: u64, + #[serde(default)] pub x8: u64, + #[serde(default)] pub x9: u64, + #[serde(default)] pub x10: u64, + #[serde(default)] pub x11: u64, + #[serde(default)] pub x12: u64, + #[serde(default)] pub x13: u64, + #[serde(default)] pub x14: u64, + #[serde(default)] pub x15: u64, + #[serde(default)] pub x16: u64, + #[serde(default)] pub x17: u64, + #[serde(default)] pub x18: u64, + #[serde(default)] pub x19: u64, + #[serde(default)] pub x20: u64, + #[serde(default)] pub x21: u64, + #[serde(default)] pub x22: u64, + #[serde(default)] pub x23: u64, + #[serde(default)] pub x24: u64, + #[serde(default)] pub x25: u64, + #[serde(default)] pub x26: u64, + #[serde(default)] pub x27: u64, + #[serde(default)] pub x28: u64, + #[serde(default)] pub x29: u64, + #[serde(default)] pub x30: u64, + #[serde(default)] pub x31: u64, + #[serde(default)] pub f0: u64, + #[serde(default)] pub f1: u64, + #[serde(default)] pub f2: u64, + #[serde(default)] pub f3: u64, + #[serde(default)] pub f4: u64, + #[serde(default)] pub f5: u64, + #[serde(default)] pub f6: u64, + #[serde(default)] pub f7: u64, + #[serde(default)] pub f8: u64, + #[serde(default)] pub f9: u64, + #[serde(default)] pub f10: u64, + #[serde(default)] pub f11: u64, + #[serde(default)] pub f12: u64, + #[serde(default)] pub f13: u64, + #[serde(default)] pub f14: u64, + #[serde(default)] pub f15: u64, + #[serde(default)] pub f16: u64, + #[serde(default)] pub f17: u64, + #[serde(default)] pub f18: u64, + #[serde(default)] pub f19: u64, + #[serde(default)] pub f20: u64, + #[serde(default)] pub f21: u64, + #[serde(default)] pub f22: u64, + #[serde(default)] pub f23: u64, + #[serde(default)] pub f24: u64, + #[serde(default)] pub f25: u64, + #[serde(default)] pub f26: u64, + #[serde(default)] pub f27: u64, + #[serde(default)] pub f28: u64, + #[serde(default)] pub f29: u64, + #[serde(default)] pub f30: u64, + #[serde(default)] pub f31: u64, + #[serde(default)] pub pc: u64, + #[serde(default)] pub fcsr: u64, + #[serde(default)] pub mvendorid: u64, + #[serde(default)] pub marchid: u64, + #[serde(default)] pub mimpid: u64, + #[serde(default)] pub mcycle: u64, + #[serde(default)] pub icycleinstret: u64, + #[serde(default)] pub mstatus: u64, + #[serde(default)] pub mtvec: u64, + #[serde(default)] pub mscratch: u64, + #[serde(default)] pub mepc: u64, + #[serde(default)] pub mcause: u64, + #[serde(default)] pub mtval: u64, + #[serde(default)] pub misa: u64, + #[serde(default)] pub mie: u64, + #[serde(default)] pub mip: u64, + #[serde(default)] pub medeleg: u64, + #[serde(default)] pub mideleg: u64, + #[serde(default)] pub mcounteren: u64, + #[serde(default)] pub menvcfg: u64, + #[serde(default)] pub stvec: u64, + #[serde(default)] pub sscratch: u64, + #[serde(default)] pub sepc: u64, + #[serde(default)] pub scause: u64, + #[serde(default)] pub stval: u64, + #[serde(default)] pub satp: u64, + #[serde(default)] pub scounteren: u64, + #[serde(default)] pub senvcfg: u64, + #[serde(default)] pub ilrsc: u64, + #[serde(default)] pub iprv: u64, + #[serde(default)] #[serde(rename = "iflags_X")] pub iflags_x: u64, + #[serde(default)] #[serde(rename = "iflags_Y")] pub iflags_y: u64, + #[serde(default)] #[serde(rename = "iflags_H")] pub iflags_h: u64, + #[serde(default)] pub iunrep: u64, } @@ -205,7 +333,7 @@ impl Default for ProcessorConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RAMConfig { pub length: u64, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -213,7 +341,7 @@ pub struct DTBConfig { pub bootargs: String, pub init: String, pub entrypoint: String, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } impl Default for DTBConfig { @@ -224,18 +352,18 @@ impl Default for DTBConfig { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MemoryRangeConfig { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub start: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub length: Option, - pub image_filename: PathBuf, - pub shared: bool, + #[serde(default)] + pub read_only: bool, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct CmioBufferConfig { - pub image_filename: PathBuf, - pub shared: bool, + pub backing_store: BackingStoreConfig, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -272,91 +400,113 @@ pub struct VirtIODeviceConfig { pub type FlashDriveConfigs = Vec; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TLBConfig { - pub image_filename: PathBuf, -} - -impl Default for TLBConfig { - fn default() -> Self { - default_config().tlb - } + #[serde(default)] + pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct CLINTConfig { + #[serde(default)] pub mtimecmp: u64, } -impl Default for CLINTConfig { - fn default() -> Self { - default_config().clint - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct PLICConfig { + #[serde(default)] pub girqpend: u64, + #[serde(default)] pub girqsrvd: u64, } -impl Default for PLICConfig { - fn default() -> Self { - default_config().plic - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct HTIFConfig { + #[serde(default)] pub fromhost: u64, + #[serde(default)] pub tohost: u64, + #[serde(default)] pub console_getchar: bool, + #[serde(default)] pub yield_manual: bool, + #[serde(default)] pub yield_automatic: bool, } -impl Default for HTIFConfig { - fn default() -> Self { - default_config().htif - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UarchProcessorConfig { + #[serde(default)] + pub backing_store: BackingStoreConfig, + #[serde(default)] pub x0: u64, + #[serde(default)] pub x1: u64, + #[serde(default)] pub x2: u64, + #[serde(default)] pub x3: u64, + #[serde(default)] pub x4: u64, + #[serde(default)] pub x5: u64, + #[serde(default)] pub x6: u64, + #[serde(default)] pub x7: u64, + #[serde(default)] pub x8: u64, + #[serde(default)] pub x9: u64, + #[serde(default)] pub x10: u64, + #[serde(default)] pub x11: u64, + #[serde(default)] pub x12: u64, + #[serde(default)] pub x13: u64, + #[serde(default)] pub x14: u64, + #[serde(default)] pub x15: u64, + #[serde(default)] pub x16: u64, + #[serde(default)] pub x17: u64, + #[serde(default)] pub x18: u64, + #[serde(default)] pub x19: u64, + #[serde(default)] pub x20: u64, + #[serde(default)] pub x21: u64, + #[serde(default)] pub x22: u64, + #[serde(default)] pub x23: u64, + #[serde(default)] pub x24: u64, + #[serde(default)] pub x25: u64, + #[serde(default)] pub x26: u64, + #[serde(default)] pub x27: u64, + #[serde(default)] pub x28: u64, + #[serde(default)] pub x29: u64, + #[serde(default)] pub x30: u64, + #[serde(default)] pub x31: u64, + #[serde(default)] pub pc: u64, + #[serde(default)] pub cycle: u64, + #[serde(default)] pub halt_flag: bool, } @@ -368,9 +518,9 @@ impl Default for UarchProcessorConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UarchRAMConfig { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", default)] pub length: Option, - pub image_filename: PathBuf, + pub backing_store: BackingStoreConfig, } impl Default for UarchRAMConfig { diff --git a/machine/rust-bindings/cartesi-machine/src/constants.rs b/machine/rust-bindings/cartesi-machine/src/constants.rs index 9e8088af..fef0a375 100644 --- a/machine/rust-bindings/cartesi-machine/src/constants.rs +++ b/machine/rust-bindings/cartesi-machine/src/constants.rs @@ -6,19 +6,19 @@ pub mod machine { use cartesi_machine_sys::*; // pub const CYCLE_MAX: u64 = CM_MCYCLE_MAX as u64; - pub const HASH_SIZE: u32 = CM_HASH_SIZE; - pub const TREE_LOG2_WORD_SIZE: u32 = CM_TREE_LOG2_WORD_SIZE; - pub const TREE_LOG2_PAGE_SIZE: u32 = CM_TREE_LOG2_PAGE_SIZE; - pub const TREE_LOG2_ROOT_SIZE: u32 = CM_TREE_LOG2_ROOT_SIZE; + pub const HASH_SIZE: u32 = CM_HASH_SIZE as u32; + pub const TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; + pub const TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; + pub const TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; } pub mod pma { use cartesi_machine_sys::*; - pub const RX_START: u64 = CM_PMA_CMIO_RX_BUFFER_START as u64; - pub const RX_LOG2_SIZE: u64 = CM_PMA_CMIO_RX_BUFFER_LOG2_SIZE as u64; - pub const TX_START: u64 = CM_PMA_CMIO_TX_BUFFER_START as u64; - pub const TX_LOG2_SIZE: u64 = CM_PMA_CMIO_TX_BUFFER_LOG2_SIZE as u64; - pub const RAM_START: u64 = CM_PMA_RAM_START as u64; + pub const RX_START: u64 = CM_AR_CMIO_RX_BUFFER_START as u64; + pub const RX_LOG2_SIZE: u64 = CM_AR_CMIO_RX_BUFFER_LOG2_SIZE as u64; + pub const TX_START: u64 = CM_AR_CMIO_TX_BUFFER_START as u64; + pub const TX_LOG2_SIZE: u64 = CM_AR_CMIO_TX_BUFFER_LOG2_SIZE as u64; + pub const RAM_START: u64 = CM_AR_RAM_START as u64; } pub mod break_reason { diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 371ccb07..2bc75553 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -92,12 +92,14 @@ impl Machine { pub fn create(config: &MachineConfig, runtime_config: &RuntimeConfig) -> Result { let config_json = serialize_to_json!(&config); let runtime_config_json = serialize_to_json!(&runtime_config); + let dir_cstr = CString::new("").unwrap(); // in-memory machine let mut machine: *mut cartesi_machine_sys::cm_machine = ptr::null_mut(); let err_code = unsafe { cartesi_machine_sys::cm_create_new( config_json.as_ptr(), runtime_config_json.as_ptr(), + dir_cstr.as_ptr(), &mut machine, ) }; @@ -116,6 +118,7 @@ impl Machine { cartesi_machine_sys::cm_load_new( dir_cstr.as_ptr(), runtime_config_json.as_ptr(), + cartesi_machine_sys::CM_SHARING_CONFIG, &mut machine, ) }; @@ -125,9 +128,18 @@ impl Machine { } /// Stores a machine instance to a directory, serializing its entire state. + /// Uses CM_SHARING_ALL so that the current machine state is written for all + /// address ranges (required when storing in-memory machines that have no + /// backing files). pub fn store(&mut self, dir: &Path) -> Result<()> { let dir_cstr = path_to_cstring(dir); - let err_code = unsafe { cartesi_machine_sys::cm_store(self.machine, dir_cstr.as_ptr()) }; + let err_code = unsafe { + cartesi_machine_sys::cm_store( + self.machine, + dir_cstr.as_ptr(), + cartesi_machine_sys::CM_SHARING_ALL, + ) + }; check_err!(err_code)?; Ok(()) @@ -164,25 +176,20 @@ impl Machine { shared: bool, image_path: Option<&Path>, ) -> Result<()> { - let image_cstr = match image_path { - Some(path) => path_to_cstring(path), - None => CString::new("").unwrap(), - }; - - let image_ptr = if image_path.is_some() { - image_cstr.as_ptr() - } else { - ptr::null() - }; + let range_config = serde_json::json!({ + "start": start, + "length": length, + "read_only": false, + "backing_store": { + "data_filename": image_path.map(|p| p.to_string_lossy().to_string()).unwrap_or_default(), + "shared": shared + } + }); + + let range_json = serialize_to_json!(&range_config); let err_code = unsafe { - cartesi_machine_sys::cm_replace_memory_range( - self.machine, - start, - length, - shared, - image_ptr, - ) + cartesi_machine_sys::cm_replace_memory_range(self.machine, range_json.as_ptr()) }; check_err!(err_code)?; @@ -205,7 +212,7 @@ impl Machine { pub fn memory_ranges(&mut self) -> Result { let mut ranges_ptr: *const c_char = ptr::null(); let err_code = - unsafe { cartesi_machine_sys::cm_get_memory_ranges(self.machine, &mut ranges_ptr) }; + unsafe { cartesi_machine_sys::cm_get_address_ranges(self.machine, &mut ranges_ptr) }; check_err!(err_code)?; let ranges = parse_json_from_cstring!(ranges_ptr); @@ -223,13 +230,19 @@ impl Machine { } /// Obtains the proof for a node in the machine state Merkle tree. - pub fn proof(&mut self, address: u64, log2_size: u32) -> Result { + pub fn proof( + &mut self, + address: u64, + log2_target_size: u32, + log2_root_size: u32, + ) -> Result { let mut proof_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_proof( self.machine, address, - log2_size as i32, + log2_target_size as i32, + log2_root_size as ::std::os::raw::c_int, &mut proof_ptr, ) }; @@ -547,7 +560,6 @@ impl Machine { let mut break_reason = BreakReason::default(); let err_code = unsafe { cartesi_machine_sys::cm_verify_step( - ptr::null(), root_hash_before, log_filename_c.as_ptr(), mcycle_count, @@ -650,7 +662,7 @@ mod tests { use crate::{ config::{ - machine::{DTBConfig, MachineConfig, MemoryRangeConfig, RAMConfig}, + machine::{BackingStoreConfig, MachineConfig, MemoryRangeConfig, RAMConfig}, runtime::RuntimeConfig, }, constants, @@ -659,36 +671,46 @@ mod tests { }; fn make_basic_machine_config() -> MachineConfig { - MachineConfig::new_with_ram(RAMConfig { + let mut config = Machine::default_config().expect("failed to get default config"); + config.ram = RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), - }) - .dtb(DTBConfig { - entrypoint: "echo Hello from inside!".to_string(), - ..Default::default() - }) - .add_flash_drive(MemoryRangeConfig { - image_filename: "../../../test/programs/rootfs.ext2".into(), + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, + }; + config.dtb.entrypoint = "echo Hello from inside!".to_string(); + config.flash_drive = vec![MemoryRangeConfig { + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/rootfs.ext2".into(), + ..Default::default() + }, ..Default::default() - }) + }]; + config } fn make_cmio_machine_config() -> MachineConfig { - MachineConfig::new_with_ram(RAMConfig { + let mut config = Machine::default_config().expect("failed to get default config"); + config.ram = RAMConfig { length: 134217728, - image_filename: "../../../test/programs/linux.bin".into(), - }) - .dtb(DTBConfig { - entrypoint: - "echo '{\"domain\":16,\"id\":\"'$(echo -n Hello from inside! | hex --encode)'\"}' \ + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/linux.bin".into(), + ..Default::default() + }, + }; + config.dtb.entrypoint = + "echo '{\"domain\":16,\"id\":\"'$(echo -n Hello from inside! | hex --encode)'\"}' \ | rollup gio | grep -Eo '0x[0-9a-f]+' | tr -d '\\n' | hex --decode; echo" - .to_string(), + .to_string(); + config.flash_drive = vec![MemoryRangeConfig { + backing_store: BackingStoreConfig { + data_filename: "../../../test/programs/rootfs.ext2".into(), + ..Default::default() + }, ..Default::default() - }) - .add_flash_drive(MemoryRangeConfig { - image_filename: "../../../test/programs/rootfs.ext2".into(), - ..Default::default() - }) + }]; + config } fn create_machine(config: &MachineConfig) -> Result { @@ -918,7 +940,11 @@ mod tests { assert!(mem.iter().all(|x| *x == 1)); let log2_size = u64::BITS - range.length.leading_zeros(); - let proof: Proof = machine.proof(range.start, u64::BITS - range.length.leading_zeros())?; + let proof: Proof = machine.proof( + range.start, + u64::BITS - range.length.leading_zeros(), + constants::machine::TREE_LOG2_ROOT_SIZE, + )?; assert_eq!(proof.target_address, range.start); assert_eq!(proof.log2_target_size, log2_size as u64); diff --git a/machine/step b/machine/step index e8050ddf..3f5d163d 160000 --- a/machine/step +++ b/machine/step @@ -1 +1 @@ -Subproject commit e8050ddfdb986d6014bebdfc1d33e16cb3b2528a +Subproject commit 3f5d163df0f7564fef3345fc919252a371e5fb9f diff --git a/prt/client-lua/computation/constants.lua b/prt/client-lua/computation/constants.lua index 1a7c9f44..add83f6a 100644 --- a/prt/client-lua/computation/constants.lua +++ b/prt/client-lua/computation/constants.lua @@ -12,7 +12,7 @@ local log2_uarch_span_to_input = log2_uarch_span_to_barch + log2_barch_span_to_i local log2_uarch_span_to_epoch = log2_input_span_to_epoch + log2_barch_span_to_input + log2_uarch_span_to_barch -- Checkpoint address for machine state snapshots -local CHECKPOINT_ADDRESS = 0x7ffff000 +local CHECKPOINT_ADDRESS = 0xfe0 local constants = { log2_uarch_span_to_barch = log2_uarch_span_to_barch, diff --git a/prt/client-lua/computation/machine.lua b/prt/client-lua/computation/machine.lua index e292f87e..655d883a 100644 --- a/prt/client-lua/computation/machine.lua +++ b/prt/client-lua/computation/machine.lua @@ -334,7 +334,7 @@ function Machine:prove_read_leaf(address) return data end -local keccak = require "cartesi".keccak +local keccak = cartesi.keccak256 function Machine:prove_write_leaf(address) -- always write aligned 32 bytes (one leaf) diff --git a/prt/client-lua/cryptography/hash.lua b/prt/client-lua/cryptography/hash.lua index 30638be4..e334a9a9 100644 --- a/prt/client-lua/cryptography/hash.lua +++ b/prt/client-lua/cryptography/hash.lua @@ -1,4 +1,5 @@ -local keccak = require "cartesi".keccak +local cartesi = require "cartesi" +local keccak = cartesi.keccak256 local conversion = require "utils.conversion" local interned_hashes = {} diff --git a/prt/client-rs/core/src/machine/instance.rs b/prt/client-rs/core/src/machine/instance.rs index 74801ed6..71b4b5b5 100644 --- a/prt/client-rs/core/src/machine/instance.rs +++ b/prt/client-rs/core/src/machine/instance.rs @@ -9,6 +9,7 @@ use cartesi_dave_merkle::Digest; use cartesi_machine::{ cartesi_machine_sys, config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, + constants::machine::TREE_LOG2_ROOT_SIZE, machine::Machine, types::access_proof::AccessLog, types::{LogType, cmio::CmioResponseReason}, @@ -63,7 +64,7 @@ pub struct MachineInstance { pub snapshot_path: PathBuf, } -const CHECKPOINT_ADDRESS: u64 = 0x7ffff000; +const CHECKPOINT_ADDRESS: u64 = 0xfe0; impl MachineInstance { pub fn new_from_path(path: &str) -> Result { let runtime_config = RuntimeConfig { @@ -332,7 +333,9 @@ impl MachineInstance { // always read aligned 32 bytes (one leaf) let aligned_address = address & !0x1Fu64; let mut read = self.machine.read_memory(aligned_address, 32)?; - let proof = self.machine.proof(aligned_address, 5)?; + let proof = self + .machine + .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -350,7 +353,9 @@ impl MachineInstance { let aligned_address = address & !0x1Fu64; let mut read = self.machine.read_memory(aligned_address, 32)?; let read_hash = Digest::from_data(&read); - let proof = self.machine.proof(aligned_address, 5)?; + let proof = self + .machine + .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -370,7 +375,7 @@ impl MachineInstance { let read = self.machine.read_memory(address, 32)?; let read_hash = Digest::from_data(&read); // Get proof of write address - let proof = self.machine.proof(address, 5)?; + let proof = self.machine.proof(address, 5, TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); diff --git a/prt/contracts/src/state-transition/CartesiStateTransition.sol b/prt/contracts/src/state-transition/CartesiStateTransition.sol index eb6147bb..c3f2b713 100644 --- a/prt/contracts/src/state-transition/CartesiStateTransition.sol +++ b/prt/contracts/src/state-transition/CartesiStateTransition.sol @@ -111,7 +111,7 @@ contract CartesiStateTransition is IStateTransition { // * step // * reset // * advanceStatus - // * getCheckpointHash (only if needed) + // * getRevertRootHash (only if needed) AccessLogs.Context memory accessLogs = AccessLogs.Context(machineState, Buffer.Context(proofs, 0)); diff --git a/prt/contracts/src/state-transition/CmioStateTransition.sol b/prt/contracts/src/state-transition/CmioStateTransition.sol index a43cebc9..ff11277a 100644 --- a/prt/contracts/src/state-transition/CmioStateTransition.sol +++ b/prt/contracts/src/state-transition/CmioStateTransition.sol @@ -31,7 +31,7 @@ contract CmioStateTransition is ICmioStateTransition { pure returns (AccessLogs.Context memory) { - a.setCheckpointHash(checkpointState); + a.setRevertRootHash(checkpointState); return a; } @@ -41,7 +41,7 @@ contract CmioStateTransition is ICmioStateTransition { returns (AccessLogs.Context memory) { if (a.advanceStatus() == AdvanceStatus.Status.REJECTED) { - bytes32 checkpointState = a.getCheckpointHash(); + bytes32 checkpointState = a.getRevertRootHash(); a.currentRootHash = checkpointState; } diff --git a/prt/measure_constants/Dockerfile b/prt/measure_constants/Dockerfile index 09eace3f..6f3364b4 100644 --- a/prt/measure_constants/Dockerfile +++ b/prt/measure_constants/Dockerfile @@ -1,4 +1,4 @@ -FROM cartesi/machine-emulator:0.19.0 +FROM cartesi/machine-emulator:0.20.0 USER root RUN apt-get update && \ diff --git a/prt/tests/common/runners/helpers/patched_commitment.lua b/prt/tests/common/runners/helpers/patched_commitment.lua index 81820f40..a46bf4ae 100644 --- a/prt/tests/common/runners/helpers/patched_commitment.lua +++ b/prt/tests/common/runners/helpers/patched_commitment.lua @@ -32,7 +32,7 @@ local function filter_map_patches(patches, base_cycle, log2_stride, log2_stride_ local span = bint256.one() << (log2_stride_count + log2_stride) local mask = (bint256.one() << log2_stride) - 1 if (patch.meta_cycle & mask):iszero() and -- alignment; first bits are zero - patch.meta_cycle > base_cycle and -- meta_cycle is within lower bound + patch.meta_cycle > base_cycle and -- meta_cycle is within lower bound patch.meta_cycle <= base_cycle + span -- meta_cycle is within upper bounds then local position = ((patch.meta_cycle - base_cycle) >> log2_stride) - 1 diff --git a/prt/tests/rollups/dave/node.lua b/prt/tests/rollups/dave/node.lua index 7ff56ae7..b1d828e1 100644 --- a/prt/tests/rollups/dave/node.lua +++ b/prt/tests/rollups/dave/node.lua @@ -61,6 +61,7 @@ sqlite3 -readonly ./_state/%d/db \ 'SELECT repetitions, HEX(leaf) FROM leafs WHERE level=0 ORDER BY leaf_index ASC' 2>&1 ]] function Dave:root_commitment(epoch_index) + print(string.format("[Dave] root_commitment(epoch_index=%d) called", epoch_index)) local query = function() assert(db_exists(epoch_index), string.format("db %d doesn't exist ", epoch_index)) @@ -87,14 +88,21 @@ function Dave:root_commitment(epoch_index) builder:add(leaf, repetitions) end - return initial_state, builder:build(initial_state.root_hash) + local commitment = builder:build(initial_state.root_hash) + print(string.format("[Dave] root_commitment(epoch_index=%d) -> root=%s", epoch_index, commitment.root_hash:hex_string())) + return initial_state, commitment end local initial_state, commitment + local attempt = 0 time.sleep_until(function() + attempt = attempt + 1 self.sender:advance_blocks(1) local ok ok, initial_state, commitment = pcall(query) + if not ok and (attempt == 1 or attempt % 10 == 0) then + print(string.format("[Dave] root_commitment(epoch_index=%d) attempt %d failed: %s", epoch_index, attempt, tostring(initial_state))) + end return ok end, 5) diff --git a/prt/tests/rollups/justfile b/prt/tests/rollups/justfile index 260ed98c..5aa45d93 100644 --- a/prt/tests/rollups/justfile +++ b/prt/tests/rollups/justfile @@ -10,7 +10,7 @@ test PROGRAM SCRIPT: ANVIL_LOAD_PATH=`realpath {{ANVIL_LOAD_PATH}}` \ ANVIL_DUMP_PATH="anvil_{{PROGRAM}}_{{SCRIPT}}.json" \ TEMPLATE_MACHINE=`realpath ../../../test/programs/{{PROGRAM}}/machine-image` \ - TEMPLATE_MACHINE_HASH=0x`xxd -p -c32 ../../../test/programs/{{PROGRAM}}/machine-image/hash` \ + TEMPLATE_MACHINE_HASH=0x`xxd -seek 0x60 -l 0x20 -c 0x20 -p ../../../test/programs/{{PROGRAM}}/machine-image/hash_tree.sht` \ DAVE_APP_FACTORY=`jq -r .address {{DEPLOYMENTS_DIR}}/DaveAppFactory.json` \ INPUT_BOX=`jq -r .address {{DEPLOYMENTS_DIR}}/InputBox.json` \ ERC20_PORTAL=`jq -r .address {{DEPLOYMENTS_DIR}}/ERC20Portal.json` \ diff --git a/prt/tests/rollups/test_cases/simple.lua b/prt/tests/rollups/test_cases/simple.lua index 7f57553c..3c790697 100755 --- a/prt/tests/rollups/test_cases/simple.lua +++ b/prt/tests/rollups/test_cases/simple.lua @@ -5,7 +5,7 @@ local env = require "test_env" -- Main Execution -env.spawn_blockchain {env.sample_inputs[1]} +env.spawn_blockchain { env.sample_inputs[1] } local first_epoch = assert(env.reader:read_epochs_sealed()[1]) assert(first_epoch.input_upper_bound == 0) -- there's no input for epoch 0! diff --git a/prt/tests/rollups/test_env.lua b/prt/tests/rollups/test_env.lua index b34b1dee..a6d6a2fd 100644 --- a/prt/tests/rollups/test_env.lua +++ b/prt/tests/rollups/test_env.lua @@ -56,10 +56,12 @@ function Env.spawn_blockchain(inputs) local blockchain = Blockchain:new(ANVIL_LOAD_PATH, ANVIL_DUMP_PATH) Env.blockchain = blockchain - Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, SALT, blockchain.endpoint) + Env.reader = Reader:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, TEMPLATE_MACHINE_HASH, SALT, blockchain + .endpoint) Env.app_address = Env.reader.app_address Env.consensus_address = Env.reader.consensus_address - Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], blockchain.endpoint) + Env.sender = Sender:new(INPUT_BOX_ADDRESS, DAVE_APP_FACTORY_ADDRESS, Env.app_address, blockchain.pks[1], + blockchain.endpoint) Env.sender:tx_new_dave_app(TEMPLATE_MACHINE_HASH, SALT) Env.sender:tx_add_inputs(inputs) Env.sender:advance_blocks(2) diff --git a/test/programs/justfile b/test/programs/justfile index c5acdb2d..6f30ba3a 100644 --- a/test/programs/justfile +++ b/test/programs/justfile @@ -1,7 +1,7 @@ download-deps: clean-deps wget https://github.com/cartesi/image-kernel/releases/download/v0.20.0/linux-6.5.13-ctsi-1-v0.20.0.bin \ -O ./linux.bin - wget https://github.com/cartesi/machine-emulator-tools/releases/download/v0.17.1/rootfs-tools.ext2 \ + wget https://github.com/cartesi/machine-emulator-tools/releases/download/v0.17.2/rootfs-tools.ext2 \ -O ./rootfs.ext2 clean-deps: @@ -16,16 +16,16 @@ clean-program prog: # yield build-yield: clean-yield - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./yield/machine-image \ -- "while true; do yield manual rx-accepted; yield manual rx-rejected; done" clean-yield: (clean-program "yield") # echo build-echo: clean-echo - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./echo/machine-image \ -- "ioctl-echo-loop --vouchers=1 --notices=1 --reports=1 --verbose=1 --reject=2" clean-echo: (clean-program "echo") @@ -34,6 +34,9 @@ clean-echo: (clean-program "echo") build-honeypot-snapshot: clean-honeypot-snapshot clean-honeypot-project git clone https://github.com/cartesi/honeypot.git honeypot/project git -C honeypot/project reset --hard 34d00721a527eeb7ed8cce2a13a142e3d8de9aad # v3.0.0 + sed -i 's/,filename:/,data_filename:/g' honeypot/project/Makefile + sed -i 's/--append-bootargs=ro/--append-bootargs=rw/g' honeypot/project/Makefile + sed -i 's/label:state,length:4096,user:dapp/label:state,length:4096,user:dapp,mke2fs:false,mount:false/g' honeypot/project/Makefile mkdir -p honeypot/project/config/devnet ./honeypot/generate-devnet-honeypot-config.sh > honeypot/project/config/devnet/honeypot-config.hpp make -C honeypot/project snapshot HONEYPOT_CONFIG=devnet @@ -44,8 +47,8 @@ clean-honeypot-project: # compute build-compute: clean-compute - cartesi-machine --ram-image=./linux.bin \ - --flash-drive=label:root,filename:./rootfs.ext2 \ + cartesi-machine --ram-image=./linux.bin --final-hash \ + --flash-drive=label:root,data_filename:./rootfs.ext2 \ --no-rollback --store=./compute/machine-image \ --max-mcycle=0 \ -- "while dd if=/dev/zero bs=1M count=64 2>/dev/null | md5sum >/dev/null; do :; done" From f82fafc3488b314e79ca658d25ddd32da76b3376 Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Wed, 22 Apr 2026 15:54:36 -0300 Subject: [PATCH 2/3] fix: update to newest machine json schema --- .../cartesi-machine/src/config/machine.rs | 659 +++++++++--------- .../cartesi-machine/src/machine.rs | 16 +- 2 files changed, 340 insertions(+), 335 deletions(-) diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index 5d3b6dde..5fb93302 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -1,342 +1,239 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +//! Rust mirror of `cartesi::machine_config` and friends from the v0.20 +//! cartesi-machine C++ API. The field names and nesting match the JSON +//! emitted by `cm_get_default_config` / `cm_get_initial_config` and consumed +//! by `cm_create_new`. +//! +//! Invariants this module tries to enforce: +//! +//! 1. **Exact shape match with the C++ side.** Every struct carries +//! `#[serde(deny_unknown_fields)]` so that a future emulator release that +//! adds or renames a field causes a *loud* deserialization failure rather +//! than silent data loss. +//! 2. **No speculative `#[serde(default)]`.** The C++ `to_json` functions +//! always emit every field, so missing fields on deserialize indicate a +//! schema break, not a tolerable omission. Defaults are only applied on +//! types the user *constructs* from scratch (via `Default::default()`), +//! not on fields that participate in JSON round-trips. +//! 3. **Round-trip equality.** `serde_json::from_str::(raw) -> +//! serde_json::to_value` must equal `serde_json::from_str::(raw)` +//! for any JSON produced by the C library. Verified by +//! `test_default_config_json_roundtrip`. + use serde::{Deserialize, Serialize}; use std::path::PathBuf; -/// Backing store config; matches C++ backing_store_config (data_filename, etc.). -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Backing store +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::backing_store_config`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct BackingStoreConfig { - #[serde(default)] pub shared: bool, - #[serde(default)] pub create: bool, - #[serde(default)] pub truncate: bool, - #[serde(default)] pub data_filename: PathBuf, - #[serde(default)] pub dht_filename: PathBuf, - #[serde(default)] pub dpt_filename: PathBuf, } -/// Config with only backing_store; matches C++ backing_store_config_only. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::backing_store_config_only`. Used for memory +/// regions that have no extra per-range config (pmas, uarch ram, cmio rx/tx). +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct BackingStoreConfigOnly { - #[serde(default)] pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct MachineConfig { - pub processor: ProcessorConfig, - pub ram: RAMConfig, - pub dtb: DTBConfig, - pub flash_drive: FlashDriveConfigs, - #[serde(default)] - pub tlb: TLBConfig, - #[serde(default)] - pub clint: CLINTConfig, - #[serde(default)] - pub plic: PLICConfig, - #[serde(default)] - pub htif: HTIFConfig, - pub uarch: UarchConfig, - pub cmio: CmioConfig, - pub virtio: VirtIOConfigs, +// --------------------------------------------------------------------------- +// Register substructures (all nested inside RegistersConfig) +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::iflags_state`. The field names in JSON are the +/// uppercase single letters `X`, `Y`, `H`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct IFlagsConfig { + #[serde(rename = "X")] + pub x: u64, + #[serde(rename = "Y")] + pub y: u64, + #[serde(rename = "H")] + pub h: u64, } -impl MachineConfig { - pub fn new_with_ram(ram: RAMConfig) -> Self { - Self { - processor: ProcessorConfig::default(), - ram, - dtb: DTBConfig::default(), - flash_drive: FlashDriveConfigs::default(), - tlb: TLBConfig::default(), - clint: CLINTConfig::default(), - plic: PLICConfig::default(), - htif: HTIFConfig::default(), - uarch: UarchConfig::default(), - cmio: CmioConfig::default(), - virtio: VirtIOConfigs::default(), - } - } - - pub fn processor(mut self, processor: ProcessorConfig) -> Self { - self.processor = processor; - self - } - - pub fn dtb(mut self, dtb: DTBConfig) -> Self { - self.dtb = dtb; - self - } - - pub fn add_flash_drive(mut self, flash_drive: MemoryRangeConfig) -> Self { - self.flash_drive.push(flash_drive); - self - } - - pub fn tlb(mut self, tlb: TLBConfig) -> Self { - self.tlb = tlb; - self - } - - pub fn clint(mut self, clint: CLINTConfig) -> Self { - self.clint = clint; - self - } - - pub fn plic(mut self, plic: PLICConfig) -> Self { - self.plic = plic; - self - } - - pub fn htif(mut self, htif: HTIFConfig) -> Self { - self.htif = htif; - self - } - - pub fn uarch(mut self, uarch: UarchConfig) -> Self { - self.uarch = uarch; - self - } - - pub fn cmio(mut self, cmio: CmioConfig) -> Self { - self.cmio = cmio; - self - } +/// Mirror of C++ `cartesi::clint_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CLINTConfig { + pub mtimecmp: u64, +} - pub fn add_virtio(mut self, virtio_config: VirtIODeviceConfig) -> Self { - self.virtio.push(virtio_config); - self - } +/// Mirror of C++ `cartesi::plic_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct PLICConfig { + pub girqpend: u64, + pub girqsrvd: u64, } -fn default_config() -> MachineConfig { - crate::machine::Machine::default_config().expect("failed to get default config") +/// Mirror of C++ `cartesi::htif_state`. These are the five HTIF CSRs; the +/// old Rust binding used a different HTIF-runtime struct with feature flags +/// (`console_getchar`, `yield_manual`, `yield_automatic`), which belong to +/// `RuntimeConfig`, not `MachineConfig`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct HTIFConfig { + pub fromhost: u64, + pub tohost: u64, + pub ihalt: u64, + pub iconsole: u64, + pub iyield: u64, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ProcessorConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, - #[serde(default)] +/// Mirror of C++ `cartesi::registers_state`. This is the object emitted at +/// `processor.registers` in the config JSON. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct RegistersConfig { pub x0: u64, - #[serde(default)] pub x1: u64, - #[serde(default)] pub x2: u64, - #[serde(default)] pub x3: u64, - #[serde(default)] pub x4: u64, - #[serde(default)] pub x5: u64, - #[serde(default)] pub x6: u64, - #[serde(default)] pub x7: u64, - #[serde(default)] pub x8: u64, - #[serde(default)] pub x9: u64, - #[serde(default)] pub x10: u64, - #[serde(default)] pub x11: u64, - #[serde(default)] pub x12: u64, - #[serde(default)] pub x13: u64, - #[serde(default)] pub x14: u64, - #[serde(default)] pub x15: u64, - #[serde(default)] pub x16: u64, - #[serde(default)] pub x17: u64, - #[serde(default)] pub x18: u64, - #[serde(default)] pub x19: u64, - #[serde(default)] pub x20: u64, - #[serde(default)] pub x21: u64, - #[serde(default)] pub x22: u64, - #[serde(default)] pub x23: u64, - #[serde(default)] pub x24: u64, - #[serde(default)] pub x25: u64, - #[serde(default)] pub x26: u64, - #[serde(default)] pub x27: u64, - #[serde(default)] pub x28: u64, - #[serde(default)] pub x29: u64, - #[serde(default)] pub x30: u64, - #[serde(default)] pub x31: u64, - #[serde(default)] pub f0: u64, - #[serde(default)] pub f1: u64, - #[serde(default)] pub f2: u64, - #[serde(default)] pub f3: u64, - #[serde(default)] pub f4: u64, - #[serde(default)] pub f5: u64, - #[serde(default)] pub f6: u64, - #[serde(default)] pub f7: u64, - #[serde(default)] pub f8: u64, - #[serde(default)] pub f9: u64, - #[serde(default)] pub f10: u64, - #[serde(default)] pub f11: u64, - #[serde(default)] pub f12: u64, - #[serde(default)] pub f13: u64, - #[serde(default)] pub f14: u64, - #[serde(default)] pub f15: u64, - #[serde(default)] pub f16: u64, - #[serde(default)] pub f17: u64, - #[serde(default)] pub f18: u64, - #[serde(default)] pub f19: u64, - #[serde(default)] pub f20: u64, - #[serde(default)] pub f21: u64, - #[serde(default)] pub f22: u64, - #[serde(default)] pub f23: u64, - #[serde(default)] pub f24: u64, - #[serde(default)] pub f25: u64, - #[serde(default)] pub f26: u64, - #[serde(default)] pub f27: u64, - #[serde(default)] pub f28: u64, - #[serde(default)] pub f29: u64, - #[serde(default)] pub f30: u64, - #[serde(default)] pub f31: u64, - #[serde(default)] pub pc: u64, - #[serde(default)] pub fcsr: u64, - #[serde(default)] pub mvendorid: u64, - #[serde(default)] pub marchid: u64, - #[serde(default)] pub mimpid: u64, - #[serde(default)] pub mcycle: u64, - #[serde(default)] pub icycleinstret: u64, - #[serde(default)] pub mstatus: u64, - #[serde(default)] pub mtvec: u64, - #[serde(default)] pub mscratch: u64, - #[serde(default)] pub mepc: u64, - #[serde(default)] pub mcause: u64, - #[serde(default)] pub mtval: u64, - #[serde(default)] pub misa: u64, - #[serde(default)] pub mie: u64, - #[serde(default)] pub mip: u64, - #[serde(default)] pub medeleg: u64, - #[serde(default)] pub mideleg: u64, - #[serde(default)] pub mcounteren: u64, - #[serde(default)] pub menvcfg: u64, - #[serde(default)] pub stvec: u64, - #[serde(default)] pub sscratch: u64, - #[serde(default)] pub sepc: u64, - #[serde(default)] pub scause: u64, - #[serde(default)] pub stval: u64, - #[serde(default)] pub satp: u64, - #[serde(default)] pub scounteren: u64, - #[serde(default)] pub senvcfg: u64, - #[serde(default)] pub ilrsc: u64, - #[serde(default)] pub iprv: u64, - #[serde(default)] - #[serde(rename = "iflags_X")] - pub iflags_x: u64, - #[serde(default)] - #[serde(rename = "iflags_Y")] - pub iflags_y: u64, - #[serde(default)] - #[serde(rename = "iflags_H")] - pub iflags_h: u64, - #[serde(default)] + pub iflags: IFlagsConfig, pub iunrep: u64, + pub clint: CLINTConfig, + pub plic: PLICConfig, + pub htif: HTIFConfig, +} + +// --------------------------------------------------------------------------- +// Processor +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::processor_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ProcessorConfig { + pub registers: RegistersConfig, + pub backing_store: BackingStoreConfig, } impl Default for ProcessorConfig { fn default() -> Self { - default_config().processor + library_default().processor } } -#[derive(Clone, Debug, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// RAM and DTB +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::ram_config`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct RAMConfig { pub length: u64, pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::dtb_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct DTBConfig { pub bootargs: String, pub init: String, @@ -346,190 +243,174 @@ pub struct DTBConfig { impl Default for DTBConfig { fn default() -> Self { - default_config().dtb + library_default().dtb } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Memory range / flash drive +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::memory_range_config`. The C++ side uses the +/// sentinel `UINT64_MAX` for "auto-detect" on `start`/`length`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct MemoryRangeConfig { - #[serde(skip_serializing_if = "Option::is_none", default)] - pub start: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub length: Option, - #[serde(default)] + pub start: u64, + pub length: u64, pub read_only: bool, pub backing_store: BackingStoreConfig, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct CmioBufferConfig { - pub backing_store: BackingStoreConfig, +impl Default for MemoryRangeConfig { + /// Defaults match the C++ `memory_range_config` in-struct initializers: + /// `start` and `length` are `UINT64_MAX` to mean "auto-detect". + fn default() -> Self { + Self { + start: u64::MAX, + length: u64::MAX, + read_only: false, + backing_store: BackingStoreConfig::default(), + } + } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct VirtIOHostfwd { +pub type FlashDriveConfigs = Vec; + +// --------------------------------------------------------------------------- +// CMIO +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::cmio_config`. Both buffers are +/// `backing_store_config_only`. +pub type CmioBufferConfig = BackingStoreConfigOnly; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CmioConfig { + pub rx_buffer: CmioBufferConfig, + pub tx_buffer: CmioBufferConfig, +} + +// --------------------------------------------------------------------------- +// VirtIO +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::virtio_hostfwd_config`. Note that `host_port` +/// and `guest_port` are `uint16_t` on the C++ side. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct VirtIOHostfwdConfig { pub is_udp: bool, pub host_ip: u64, pub guest_ip: u64, - pub host_port: u64, - pub guest_port: u64, + pub host_port: u16, + pub guest_port: u16, } -pub type VirtIOHostfwdArray = Vec; +pub type VirtIOHostfwdArray = Vec; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum VirtIODeviceType { - #[default] +/// Mirror of C++ `cartesi::virtio_device_config` (a `std::variant`). The +/// JSON representation uses `"type"` as the discriminator, matching the +/// `to_json(virtio_device_config)` implementation. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "type", rename_all = "kebab-case", deny_unknown_fields)] +pub enum VirtIODeviceConfig { Console, - P9fs, + P9fs { + tag: String, + host_directory: String, + }, #[serde(rename = "net-user")] - NetUser, + NetUser { hostfwd: VirtIOHostfwdArray }, #[serde(rename = "net-tuntap")] - NetTuntap, + NetTuntap { iface: String }, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct VirtIODeviceConfig { - pub r#type: VirtIODeviceType, - pub tag: String, - pub host_directory: String, - pub hostfwd: VirtIOHostfwdArray, - pub iface: String, +impl Default for VirtIODeviceConfig { + fn default() -> Self { + VirtIODeviceConfig::Console + } } -pub type FlashDriveConfigs = Vec; - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct TLBConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, -} +pub type VirtIOConfigs = Vec; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct CLINTConfig { - #[serde(default)] - pub mtimecmp: u64, -} +// --------------------------------------------------------------------------- +// PMAS +// --------------------------------------------------------------------------- -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct PLICConfig { - #[serde(default)] - pub girqpend: u64, - #[serde(default)] - pub girqsrvd: u64, -} +/// Mirror of C++ `cartesi::pmas_config` (alias for `backing_store_config_only`). +pub type PmasConfig = BackingStoreConfigOnly; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct HTIFConfig { - #[serde(default)] - pub fromhost: u64, - #[serde(default)] - pub tohost: u64, - #[serde(default)] - pub console_getchar: bool, - #[serde(default)] - pub yield_manual: bool, - #[serde(default)] - pub yield_automatic: bool, -} +// --------------------------------------------------------------------------- +// Uarch +// --------------------------------------------------------------------------- -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UarchProcessorConfig { - #[serde(default)] - pub backing_store: BackingStoreConfig, - #[serde(default)] +/// Mirror of C++ `cartesi::uarch_registers_state`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct UarchRegistersConfig { pub x0: u64, - #[serde(default)] pub x1: u64, - #[serde(default)] pub x2: u64, - #[serde(default)] pub x3: u64, - #[serde(default)] pub x4: u64, - #[serde(default)] pub x5: u64, - #[serde(default)] pub x6: u64, - #[serde(default)] pub x7: u64, - #[serde(default)] pub x8: u64, - #[serde(default)] pub x9: u64, - #[serde(default)] pub x10: u64, - #[serde(default)] pub x11: u64, - #[serde(default)] pub x12: u64, - #[serde(default)] pub x13: u64, - #[serde(default)] pub x14: u64, - #[serde(default)] pub x15: u64, - #[serde(default)] pub x16: u64, - #[serde(default)] pub x17: u64, - #[serde(default)] pub x18: u64, - #[serde(default)] pub x19: u64, - #[serde(default)] pub x20: u64, - #[serde(default)] pub x21: u64, - #[serde(default)] pub x22: u64, - #[serde(default)] pub x23: u64, - #[serde(default)] pub x24: u64, - #[serde(default)] pub x25: u64, - #[serde(default)] pub x26: u64, - #[serde(default)] pub x27: u64, - #[serde(default)] pub x28: u64, - #[serde(default)] pub x29: u64, - #[serde(default)] pub x30: u64, - #[serde(default)] pub x31: u64, - #[serde(default)] pub pc: u64, - #[serde(default)] pub cycle: u64, - #[serde(default)] - pub halt_flag: bool, + /// `uint64_t` on the C++ side (shadow-uarch-state.h), not a C++ `bool`. + /// Used as a boolean flag (0 = not halted, non-zero = halted), but the + /// wire representation is an integer. + pub halt_flag: u64, } -impl Default for UarchProcessorConfig { - fn default() -> Self { - default_config().uarch.processor - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UarchRAMConfig { - #[serde(skip_serializing_if = "Option::is_none", default)] - pub length: Option, +/// Mirror of C++ `cartesi::uarch_processor_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct UarchProcessorConfig { + pub registers: UarchRegistersConfig, pub backing_store: BackingStoreConfig, } -impl Default for UarchRAMConfig { +impl Default for UarchProcessorConfig { fn default() -> Self { - default_config().uarch.ram + library_default().uarch.processor } } -#[derive(Clone, Debug, Serialize, Deserialize)] +/// Mirror of C++ `cartesi::uarch_ram_config` (alias for +/// `backing_store_config_only`). +pub type UarchRAMConfig = BackingStoreConfigOnly; + +/// Mirror of C++ `cartesi::uarch_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct UarchConfig { pub processor: UarchProcessorConfig, pub ram: UarchRAMConfig, @@ -537,40 +418,156 @@ pub struct UarchConfig { impl Default for UarchConfig { fn default() -> Self { - default_config().uarch + library_default().uarch } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CmioConfig { - pub rx_buffer: CmioBufferConfig, - pub tx_buffer: CmioBufferConfig, +// --------------------------------------------------------------------------- +// Hash tree +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::hash_function_type`. Serialized as a lower-case +/// string (`"keccak256"` / `"sha256"`). +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum HashFunctionType { + #[default] + Keccak256, + Sha256, +} + +/// Mirror of C++ `cartesi::hash_tree_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct HashTreeConfig { + pub shared: bool, + pub create: bool, + pub sht_filename: PathBuf, + pub phtc_filename: PathBuf, + pub phtc_size: u64, + pub hash_function: HashFunctionType, } -impl Default for CmioConfig { +impl Default for HashTreeConfig { fn default() -> Self { - default_config().cmio + library_default().hash_tree } } -pub type VirtIOConfigs = Vec; +// --------------------------------------------------------------------------- +// Top-level machine config +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::machine_config`. The field ordering matches the +/// order in which `to_json(machine_config)` emits them on the C++ side. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct MachineConfig { + pub processor: ProcessorConfig, + pub ram: RAMConfig, + pub dtb: DTBConfig, + pub flash_drive: FlashDriveConfigs, + pub virtio: VirtIOConfigs, + pub cmio: CmioConfig, + pub pmas: PmasConfig, + pub uarch: UarchConfig, + pub hash_tree: HashTreeConfig, +} + +impl MachineConfig { + /// Starts from the library's default config and overrides only the RAM + /// block. Useful for the common case where callers want the emulator's + /// baseline configuration plus a specific RAM image. + pub fn new_with_ram(ram: RAMConfig) -> Self { + let mut cfg = library_default(); + cfg.ram = ram; + cfg + } +} + +/// Fetches the emulator's built-in default config via `cm_get_default_config`. +/// All `Default` impls in this file delegate here rather than synthesizing +/// zeros in Rust, because C-side defaults carry non-trivial values like +/// `mvendorid`, `marchid`, initial `misa`, and the DTB `bootargs` string. +fn library_default() -> MachineConfig { + crate::machine::Machine::default_config() + .expect("failed to get default machine config from cartesi-machine library") +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; + use crate::machine::Machine; #[test] fn test_default_configs() { - default_config(); + library_default(); ProcessorConfig::default(); DTBConfig::default(); - TLBConfig::default(); + MemoryRangeConfig::default(); CLINTConfig::default(); PLICConfig::default(); HTIFConfig::default(); UarchProcessorConfig::default(); - UarchRAMConfig::default(); UarchConfig::default(); CmioConfig::default(); + HashTreeConfig::default(); + } + + /// Guardrail against silent schema drift between the Rust bindings and + /// the C++ `cartesi::machine_config`. Loads the default config as raw + /// JSON, deserializes it into `MachineConfig`, re-serializes, and + /// asserts structural equality with the original JSON. + /// + /// If this test fails after an emulator bump, do NOT add + /// `#[serde(default)]` to make it pass — the right fix is to update + /// this file's structs to match whatever the C++ side now emits. + #[test] + fn test_default_config_json_roundtrip() { + let raw_json = Machine::default_config_raw_json() + .expect("failed to fetch raw default config JSON"); + + let original: serde_json::Value = serde_json::from_str(&raw_json) + .expect("raw JSON from cm_get_default_config is not valid JSON"); + + let typed: MachineConfig = serde_json::from_str(&raw_json).unwrap_or_else(|e| { + panic!( + "failed to deserialize cm_get_default_config JSON into MachineConfig: {e}\n\ + (this usually means a schema drift between the emulator and these bindings)" + ); + }); + + let reserialized = serde_json::to_value(&typed).expect("re-serialization failed"); + + assert_eq!( + original, reserialized, + "MachineConfig round-trip lost or added data. Schema drift vs the C++ side." + ); + } + + /// Guardrail: makes sure an unknown field at the top level fails rather + /// than being silently dropped. Regression test for the bug this + /// refactor is fixing. + #[test] + fn test_unknown_field_is_rejected() { + let raw_json = Machine::default_config_raw_json() + .expect("failed to fetch raw default config JSON"); + + // Inject an unknown top-level field. + let mut value: serde_json::Value = serde_json::from_str(&raw_json).unwrap(); + value + .as_object_mut() + .unwrap() + .insert("something_new".to_string(), serde_json::json!(42)); + + let result = serde_json::from_value::(value); + assert!( + result.is_err(), + "deny_unknown_fields must reject previously-unseen top-level keys" + ); } } diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 2bc75553..2dd1502a 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -63,16 +63,24 @@ impl Machine { // API functions // ----------------------------------------------------------------------------- - /// Returns the default machine config. + /// Returns the default machine config as parsed by serde. pub fn default_config() -> Result { + let raw = Self::default_config_raw_json()?; + Ok(serde_json::from_str(&raw) + .expect("cm_get_default_config returned JSON that does not match MachineConfig")) + } + + /// Returns the raw JSON string produced by `cm_get_default_config`, + /// without deserializing into a typed struct. Primarily used by the + /// round-trip schema test. + pub fn default_config_raw_json() -> Result { let mut config_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_default_config(ptr::null(), &mut config_ptr) }; check_err!(err_code)?; - let config = parse_json_from_cstring!(config_ptr); - - Ok(config) + let cstr = unsafe { CStr::from_ptr(config_ptr) }; + Ok(cstr.to_string_lossy().into_owned()) } /// Gets the address of any x, f, or control state register. From d66006a24e87fd31e168c681c892caf9fd6554ce Mon Sep 17 00:00:00 2001 From: gcdepaula Date: Wed, 22 Apr 2026 20:12:10 -0300 Subject: [PATCH 3/3] refactor: harden v0.20 bindings against schema drift --- .../node/blockchain-reader/src/test_utils.rs | 29 +- .../node/state-manager/src/rollups_machine.rs | 18 +- .../cartesi-machine/src/config/machine.rs | 34 ++- .../cartesi-machine/src/config/runtime.rs | 261 ++++++++++++++++-- .../cartesi-machine/src/constants.rs | 25 +- .../rust-bindings/cartesi-machine/src/lib.rs | 19 ++ .../cartesi-machine/src/machine.rs | 163 +++++++++-- prt/client-lua/computation/constants.lua | 11 +- prt/client-rs/core/src/machine/constants.rs | 61 ++++ prt/client-rs/core/src/machine/instance.rs | 36 +-- prt/tests/rollups/justfile | 2 +- 11 files changed, 545 insertions(+), 114 deletions(-) diff --git a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs index 9b20bfce..e618dc02 100644 --- a/cartesi-rollups/node/blockchain-reader/src/test_utils.rs +++ b/cartesi-rollups/node/blockchain-reader/src/test_utils.rs @@ -9,13 +9,10 @@ use alloy::{ signers::{Signer, local::PrivateKeySigner}, }; use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory::{self, WithdrawalConfig}; +use cartesi_machine::{Machine, config::runtime::RuntimeConfig}; use cartesi_rollups_contracts::i_input_box::IInputBox; use serde::Deserialize; -use std::{ - fs::{self, File}, - io::{Read, Seek}, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; type Result = std::result::Result>; @@ -76,15 +73,19 @@ pub async fn spawn_anvil_and_provider() -> Result<(AnvilInstance, DynProvider, A let input_box = deployment_address("InputBox"); let dave_app_factory = deployment_address("DaveAppFactory"); - let initial_hash = { - // Root hash is stored in hash_tree.sht at offset 0x60 (node 1's hash in sparse tree). - // Equivalent to: xxd -seek 0x60 -l 0x20 -c 0x20 -p .../machine-image/hash_tree.sht - let mut file = - File::open(program_path.join("machine-image").join("hash_tree.sht")).unwrap(); - file.seek(std::io::SeekFrom::Start(0x60)).unwrap(); - let mut buffer = [0u8; 32]; - file.read_exact(&mut buffer).unwrap(); - buffer + // Load the stored machine through the emulator and ask it for the root + // hash, rather than reading the internal `hash_tree.sht` file directly. + // The file layout is an emulator implementation detail; going through + // `cm_load_new` + `cm_get_root_hash` is the only stable API. + let initial_hash: [u8; 32] = { + let mut machine = Machine::load( + &program_path.join("machine-image"), + &RuntimeConfig::quiet_console(), + ) + .expect("failed to load stored machine"); + machine + .root_hash() + .expect("failed to read machine root hash") }; let withdrawal_config = WithdrawalConfig { diff --git a/cartesi-rollups/node/state-manager/src/rollups_machine.rs b/cartesi-rollups/node/state-manager/src/rollups_machine.rs index 8c0a9d5f..2c6473b2 100644 --- a/cartesi-rollups/node/state-manager/src/rollups_machine.rs +++ b/cartesi-rollups/node/state-manager/src/rollups_machine.rs @@ -4,13 +4,14 @@ use std::path::{Path, PathBuf}; use cartesi_prt_core::machine::constants::{ - LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, + CHECKPOINT_ADDRESS, LOG2_BARCH_SPAN_TO_INPUT, LOG2_INPUT_SPAN_TO_EPOCH, + LOG2_UARCH_SPAN_TO_BARCH, }; use crate::{CommitmentLeaf, Proof}; use cartesi_machine::{ - config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::{break_reason, machine::TREE_LOG2_ROOT_SIZE, pma::TX_START}, + config::runtime::RuntimeConfig, + constants::{ar::TX_START, break_reason, machine::HASH_TREE_LOG2_ROOT_SIZE}, error::{MachineError, MachineResult}, machine::Machine, types::{ @@ -45,8 +46,6 @@ pub const STRIDE_COUNT_IN_EPOCH: u64 = 1 << (LOG2_INPUT_SPAN_TO_EPOCH + LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH - LOG2_STRIDE); -pub const CHECKPOINT_ADDRESS: u64 = 0xfe0; - pub struct RollupsMachine { machine: Machine, epoch_number: u64, @@ -59,12 +58,7 @@ impl RollupsMachine { epoch_number: u64, next_input_index_in_epoch: u64, ) -> MachineResult { - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let machine = Machine::load(path, &runtime_config)?; Ok(Self { @@ -88,7 +82,7 @@ impl RollupsMachine { } pub fn outputs_proof(&mut self) -> MachineResult<(Hash, Proof)> { - let proof = self.machine.proof(TX_START, 5, TREE_LOG2_ROOT_SIZE)?; + let proof = self.machine.proof(TX_START, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let siblings = Proof::new(proof.sibling_hashes); let output_merkle = self.machine.read_memory(TX_START, 32)?; diff --git a/machine/rust-bindings/cartesi-machine/src/config/machine.rs b/machine/rust-bindings/cartesi-machine/src/config/machine.rs index 5fb93302..0905483b 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/machine.rs @@ -322,9 +322,13 @@ pub enum VirtIODeviceConfig { host_directory: String, }, #[serde(rename = "net-user")] - NetUser { hostfwd: VirtIOHostfwdArray }, + NetUser { + hostfwd: VirtIOHostfwdArray, + }, #[serde(rename = "net-tuntap")] - NetTuntap { iface: String }, + NetTuntap { + iface: String, + }, } impl Default for VirtIODeviceConfig { @@ -502,6 +506,24 @@ fn library_default() -> MachineConfig { mod tests { use super::*; use crate::machine::Machine; + use crate::{EXPECTED_EMULATOR_VERSION, format_emulator_version}; + + /// Guardrail: the linked `libcartesi` must report the exact version these + /// bindings were written against. If this fails after an emulator bump, + /// update `EXPECTED_EMULATOR_VERSION` in `lib.rs` and rerun the config + /// round-trip tests to re-confirm the schema. + #[test] + fn test_emulator_version_pin() { + let linked = Machine::version(); + assert_eq!( + linked, + EXPECTED_EMULATOR_VERSION, + "cartesi-machine bindings were written for emulator version {}, but libcartesi reports {}. \ + Update EXPECTED_EMULATOR_VERSION after verifying the config schema still matches.", + format_emulator_version(EXPECTED_EMULATOR_VERSION), + format_emulator_version(linked), + ); + } #[test] fn test_default_configs() { @@ -528,8 +550,8 @@ mod tests { /// this file's structs to match whatever the C++ side now emits. #[test] fn test_default_config_json_roundtrip() { - let raw_json = Machine::default_config_raw_json() - .expect("failed to fetch raw default config JSON"); + let raw_json = + Machine::default_config_raw_json().expect("failed to fetch raw default config JSON"); let original: serde_json::Value = serde_json::from_str(&raw_json) .expect("raw JSON from cm_get_default_config is not valid JSON"); @@ -554,8 +576,8 @@ mod tests { /// refactor is fixing. #[test] fn test_unknown_field_is_rejected() { - let raw_json = Machine::default_config_raw_json() - .expect("failed to fetch raw default config JSON"); + let raw_json = + Machine::default_config_raw_json().expect("failed to fetch raw default config JSON"); // Inject an unknown top-level field. let mut value: serde_json::Value = serde_json::from_str(&raw_json).unwrap(); diff --git a/machine/rust-bindings/cartesi-machine/src/config/runtime.rs b/machine/rust-bindings/cartesi-machine/src/config/runtime.rs index 6f131d4a..2c7d191b 100644 --- a/machine/rust-bindings/cartesi-machine/src/config/runtime.rs +++ b/machine/rust-bindings/cartesi-machine/src/config/runtime.rs @@ -1,32 +1,251 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +//! Rust mirror of `cartesi::machine_runtime_config` from the v0.20 cartesi-machine +//! C++ API. Follows the same invariants as `config::machine`: +//! +//! 1. Every struct carries `#[serde(deny_unknown_fields)]` to surface future +//! schema additions as explicit deserialization failures. +//! 2. No speculative `#[serde(default)]` on fields the C++ `to_json` emits +//! unconditionally (all of them, here). +//! 3. A round-trip test (`test_runtime_config_schema_stability`) pins the +//! v0.20 shape so silent drift is impossible. + use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ConcurrencyRuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub update_merkle_tree: Option, +// --------------------------------------------------------------------------- +// Console configuration +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::console_output_destination`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleOutputDestination { + ToNull, + ToStdout, + ToStderr, + ToFd, + ToFile, + ToBuffer, +} + +impl Default for ConsoleOutputDestination { + /// Matches the C++ in-struct initializer (`console_output_destination::to_stdout`). + fn default() -> Self { + Self::ToStdout + } +} + +/// Mirror of C++ `cartesi::console_flush_mode`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleFlushMode { + WhenFull, + EveryChar, + EveryLine, +} + +impl Default for ConsoleFlushMode { + /// Matches the C++ in-struct initializer (`console_flush_mode::every_line`). + fn default() -> Self { + Self::EveryLine + } +} + +/// Mirror of C++ `cartesi::console_input_source`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsoleInputSource { + FromNull, + FromStdin, + FromFd, + FromFile, + FromBuffer, +} + +impl Default for ConsoleInputSource { + /// Matches the C++ in-struct initializer (`console_input_source::from_null`). + fn default() -> Self { + Self::FromNull + } +} + +/// Mirror of C++ `cartesi::console_runtime_config`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ConsoleRuntimeConfig { + pub output_destination: ConsoleOutputDestination, + pub output_flush_mode: ConsoleFlushMode, + pub output_buffer_size: u64, + pub output_fd: i32, + pub output_filename: String, + + pub input_source: ConsoleInputSource, + pub input_buffer_size: u64, + pub input_fd: i32, + pub input_filename: String, + + pub tty_cols: u16, + pub tty_rows: u16, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct HTIFRuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub no_console_putchar: Option, +impl Default for ConsoleRuntimeConfig { + /// Matches the in-struct initializers in `machine-runtime-config.h` and the + /// `os::TTY_DEFAULT_*` constants from v0.20 (`os.h`: cols=80, rows=25). + fn default() -> Self { + Self { + output_destination: ConsoleOutputDestination::default(), + output_flush_mode: ConsoleFlushMode::default(), + output_buffer_size: 4096, + output_fd: -1, + output_filename: String::new(), + + input_source: ConsoleInputSource::default(), + input_buffer_size: 4096, + input_fd: -1, + input_filename: String::new(), + + tty_cols: 80, + tty_rows: 25, + } + } } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +// --------------------------------------------------------------------------- +// Concurrency and top-level runtime configuration +// --------------------------------------------------------------------------- + +/// Mirror of C++ `cartesi::concurrency_runtime_config`. Note: v0.19's +/// `update_merkle_tree` field was renamed to `update_hash_tree` in v0.20. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct ConcurrencyRuntimeConfig { + pub update_hash_tree: u64, +} + +/// Mirror of C++ `cartesi::machine_runtime_config`. +/// +/// The v0.19 binding had top-level `htif`, `skip_root_hash_check`, and +/// `skip_root_hash_store` fields. None of those exist on the v0.20 +/// `machine_runtime_config`. The equivalent of v0.19's +/// `htif.no_console_putchar` is now `console.output_destination = ToNull`. +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct RuntimeConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub concurrency: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub htif: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_root_hash_check: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_root_hash_store: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_version_check: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub soft_yield: Option, + pub console: ConsoleRuntimeConfig, + pub concurrency: ConcurrencyRuntimeConfig, + pub skip_version_check: bool, + pub soft_yield: bool, + pub no_reserve: bool, +} + +impl RuntimeConfig { + /// Convenience for "run the machine without touching the host console" — + /// replaces the v0.19 pattern of setting `htif.no_console_putchar = true`. + pub fn quiet_console() -> Self { + Self { + console: ConsoleRuntimeConfig { + output_destination: ConsoleOutputDestination::ToNull, + input_source: ConsoleInputSource::FromNull, + ..ConsoleRuntimeConfig::default() + }, + ..Self::default() + } + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + /// Static schema-completeness test: pins the v0.20 `machine_runtime_config` + /// JSON shape directly against `RuntimeConfig`, so drift in either + /// direction surfaces as a test failure. + /// + /// The JSON below is constructed from `src/json-util.cpp::to_json + /// (machine_runtime_config)` and the default values in + /// `machine-runtime-config.h` / `os.h` (TTY_DEFAULT_COLS=80, + /// TTY_DEFAULT_ROWS=25). + #[test] + fn test_runtime_config_schema_stability() { + let v020_json = serde_json::json!({ + "console": { + "output_destination": "to_stdout", + "output_flush_mode": "every_line", + "output_buffer_size": 4096u64, + "output_fd": -1, + "output_filename": "", + "input_source": "from_null", + "input_buffer_size": 4096u64, + "input_fd": -1, + "input_filename": "", + "tty_cols": 80, + "tty_rows": 25, + }, + "concurrency": { "update_hash_tree": 0u64 }, + "skip_version_check": false, + "soft_yield": false, + "no_reserve": false, + }); + + let typed: RuntimeConfig = serde_json::from_value(v020_json.clone()) + .expect("v0.20 runtime JSON should parse into RuntimeConfig"); + let reserialized = serde_json::to_value(&typed).expect("re-serialization failed"); + + assert_eq!( + v020_json, reserialized, + "RuntimeConfig round-trip lost or added data. Schema drift vs the C++ side." + ); + } + + #[test] + fn test_runtime_config_default_round_trips() { + let cfg = RuntimeConfig::default(); + let json = serde_json::to_value(&cfg).expect("serialization should succeed"); + let back: RuntimeConfig = + serde_json::from_value(json).expect("deserialization should succeed"); + assert_eq!(cfg, back); + } + + #[test] + fn test_runtime_config_quiet_console() { + let cfg = RuntimeConfig::quiet_console(); + assert_eq!( + cfg.console.output_destination, + ConsoleOutputDestination::ToNull + ); + assert_eq!(cfg.console.input_source, ConsoleInputSource::FromNull); + } + + #[test] + fn test_runtime_config_unknown_field_rejected() { + let json = serde_json::json!({ + "console": { + "output_destination": "to_stdout", + "output_flush_mode": "every_line", + "output_buffer_size": 4096u64, + "output_fd": -1, + "output_filename": "", + "input_source": "from_null", + "input_buffer_size": 4096u64, + "input_fd": -1, + "input_filename": "", + "tty_cols": 80, + "tty_rows": 25, + }, + "concurrency": { "update_hash_tree": 0u64 }, + "skip_version_check": false, + "soft_yield": false, + "no_reserve": false, + "something_new": true, + }); + assert!( + serde_json::from_value::(json).is_err(), + "deny_unknown_fields must reject previously-unseen top-level keys" + ); + } } diff --git a/machine/rust-bindings/cartesi-machine/src/constants.rs b/machine/rust-bindings/cartesi-machine/src/constants.rs index fef0a375..92403e41 100644 --- a/machine/rust-bindings/cartesi-machine/src/constants.rs +++ b/machine/rust-bindings/cartesi-machine/src/constants.rs @@ -1,24 +1,39 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -//! Constants definitions from Cartesi Machine +//! Constants definitions from Cartesi Machine. +//! +//! The names in this module track the v0.20 emulator naming convention: +//! `HASH_TREE_LOG2_*` for hash-tree sizes (previously `TREE_LOG2_*`) and the +//! `ar` module for address ranges (previously `pma`). The numeric values are +//! unchanged from v0.19. pub mod machine { use cartesi_machine_sys::*; // pub const CYCLE_MAX: u64 = CM_MCYCLE_MAX as u64; pub const HASH_SIZE: u32 = CM_HASH_SIZE as u32; - pub const TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; - pub const TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; - pub const TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; + pub const HASH_TREE_LOG2_WORD_SIZE: u32 = CM_HASH_TREE_LOG2_WORD_SIZE as u32; + pub const HASH_TREE_LOG2_PAGE_SIZE: u32 = CM_HASH_TREE_LOG2_PAGE_SIZE as u32; + pub const HASH_TREE_LOG2_ROOT_SIZE: u32 = CM_HASH_TREE_LOG2_ROOT_SIZE as u32; } -pub mod pma { +pub mod ar { use cartesi_machine_sys::*; pub const RX_START: u64 = CM_AR_CMIO_RX_BUFFER_START as u64; pub const RX_LOG2_SIZE: u64 = CM_AR_CMIO_RX_BUFFER_LOG2_SIZE as u64; pub const TX_START: u64 = CM_AR_CMIO_TX_BUFFER_START as u64; pub const TX_LOG2_SIZE: u64 = CM_AR_CMIO_TX_BUFFER_LOG2_SIZE as u64; pub const RAM_START: u64 = CM_AR_RAM_START as u64; + /// Dedicated memory slot the off-chain client writes the pre-input root + /// hash to before sending a CMIO input, so that on-chain + /// `revertIfNeeded` can read it back after a rejected input. + /// + /// Canonical source is the emulator C++; the Solidity side mirrors it + /// through the auto-generated + /// `step/src/EmulatorConstants.sol::REVERT_ROOT_HASH_ADDRESS`, used + /// only from `EmulatorCompat.{set,get}RevertRootHash` wrappers — no + /// other Solidity file should reference the raw address. + pub const SHADOW_REVERT_ROOT_HASH_START: u64 = CM_AR_SHADOW_REVERT_ROOT_HASH_START as u64; } pub mod break_reason { diff --git a/machine/rust-bindings/cartesi-machine/src/lib.rs b/machine/rust-bindings/cartesi-machine/src/lib.rs index 06c0222a..b5b4cb62 100644 --- a/machine/rust-bindings/cartesi-machine/src/lib.rs +++ b/machine/rust-bindings/cartesi-machine/src/lib.rs @@ -13,3 +13,22 @@ pub use machine::Machine; // Reexport inner cartesi-machine-sys pub use cartesi_machine_sys; + +/// Emulator semantic version these bindings were written against, encoded per +/// the convention from `machine-c-api.h`: +/// `(major * 1000000) + (minor * 1000) + patch`. +/// +/// The `test_emulator_version_pin` test asserts at build time that the linked +/// `libcartesi` reports this exact version. Bumping the emulator requires +/// bumping this constant and re-running the config round-trip tests — any +/// schema drift will surface there. +pub const EXPECTED_EMULATOR_VERSION: u64 = 20_000; // 0.20.0 + +/// Formats an emulator version u64 (as returned by `cm_get_version`) as +/// `"major.minor.patch"`. +pub fn format_emulator_version(v: u64) -> String { + let major = v / 1_000_000; + let minor = (v / 1_000) % 1_000; + let patch = v % 1_000; + format!("{major}.{minor}.{patch}") +} diff --git a/machine/rust-bindings/cartesi-machine/src/machine.rs b/machine/rust-bindings/cartesi-machine/src/machine.rs index 2dd1502a..4e45f9f6 100644 --- a/machine/rust-bindings/cartesi-machine/src/machine.rs +++ b/machine/rust-bindings/cartesi-machine/src/machine.rs @@ -20,15 +20,28 @@ use crate::{ }, }; -/// Machine instance handle +/// Machine instance handle. +/// +/// Owns a `*mut cm_machine` and frees it on `Drop` via `cm_delete`. The raw +/// pointer is kept private — exposing it would let callers clone it and cause +/// a double-free when both `Machine`s get dropped. +/// +/// `Machine` is intentionally `!Send + !Sync` (the default, given the raw +/// pointer field). Do not add `unsafe impl Send for Machine` without auditing +/// `cm_get_last_error_message`: the C library threads error messages through +/// thread-local (or global) state with no machine-instance argument, so two +/// `Machine`s running on different threads could scramble each other's +/// `MachineError::message` fields. pub struct Machine { - pub machine: *mut cartesi_machine_sys::cm_machine, + machine: *mut cartesi_machine_sys::cm_machine, } impl Drop for Machine { fn drop(&mut self) { - unsafe { - cartesi_machine_sys::cm_delete(self.machine); + if !self.machine.is_null() { + unsafe { + cartesi_machine_sys::cm_delete(self.machine); + } } } } @@ -43,6 +56,16 @@ macro_rules! check_err { }; } +/// Both `serde_json::to_string` and `CString::new` below panic on failure +/// *by design*. They can only fail on: +/// - A Rust config type holding non-serializable state. Our types are plain +/// POD with derived `Serialize`, so this is statically impossible. +/// - A JSON string containing an interior NUL byte. `serde_json` escapes NUL +/// as `\u0000`, so the output is guaranteed NUL-free. +/// +/// If either panic ever fires, it indicates a bug in this crate or in +/// `serde_json` — there is no recovery, and a panic with a backtrace is more +/// debuggable than a bubbled-up `Result` that crashes at the caller anyway. macro_rules! serialize_to_json { ($src:expr) => { CString::new(serde_json::to_string($src).expect("failed serializing to json")) @@ -50,6 +73,13 @@ macro_rules! serialize_to_json { }; } +/// Panics on malformed JSON from the C library. This means either a bug in +/// `libcartesi` or a mismatch between the Rust struct shape and the +/// emulator's JSON schema — the round-trip tests in `config/machine.rs` and +/// `config/runtime.rs` are expected to catch the latter before production. +/// A `Result` return here would force every call site to propagate an error +/// variant for a condition with no meaningful recovery path; panicking gives +/// a clearer stacktrace. macro_rules! parse_json_from_cstring { ($src:expr) => {{ let cstr = unsafe { CStr::from_ptr($src) }; @@ -63,6 +93,13 @@ impl Machine { // API functions // ----------------------------------------------------------------------------- + /// Returns the emulator semantic version of the linked `libcartesi`, as + /// returned by `cm_get_version`. Encoded as + /// `(major * 1000000) + (minor * 1000) + patch`. + pub fn version() -> u64 { + unsafe { cartesi_machine_sys::cm_get_version() } + } + /// Returns the default machine config as parsed by serde. pub fn default_config() -> Result { let raw = Self::default_config_raw_json()?; @@ -118,7 +155,7 @@ impl Machine { /// Loads a new machine instance from a previously stored directory. pub fn load(dir: &Path, runtime_config: &RuntimeConfig) -> Result { - let dir_cstr = path_to_cstring(dir); + let dir_cstr = path_to_cstring(dir)?; let runtime_config_json = serialize_to_json!(&runtime_config); let mut machine: *mut cartesi_machine_sys::cm_machine = ptr::null_mut(); @@ -140,7 +177,7 @@ impl Machine { /// address ranges (required when storing in-memory machines that have no /// backing files). pub fn store(&mut self, dir: &Path) -> Result<()> { - let dir_cstr = path_to_cstring(dir); + let dir_cstr = path_to_cstring(dir)?; let err_code = unsafe { cartesi_machine_sys::cm_store( self.machine, @@ -164,19 +201,42 @@ impl Machine { Ok(()) } - /// Gets the machine runtime config. + /// Gets the machine runtime config as parsed by serde. pub fn runtime_config(&mut self) -> Result { + let raw = self.runtime_config_raw_json()?; + Ok(serde_json::from_str(&raw) + .expect("cm_get_runtime_config returned JSON that does not match RuntimeConfig")) + } + + /// Returns the raw JSON string produced by `cm_get_runtime_config`. Used + /// by the round-trip schema test. + pub fn runtime_config_raw_json(&mut self) -> Result { let mut rc_ptr: *const c_char = ptr::null(); let err_code = unsafe { cartesi_machine_sys::cm_get_runtime_config(self.machine, &mut rc_ptr) }; check_err!(err_code)?; - let runtime_config = parse_json_from_cstring!(rc_ptr); - - Ok(runtime_config) + let cstr = unsafe { CStr::from_ptr(rc_ptr) }; + Ok(cstr.to_string_lossy().into_owned()) } /// Replaces a memory range. + /// + /// Two intentional simplifications vs. the full JSON schema the C API + /// accepts: + /// + /// - `read_only` is hardcoded to `false`. The C++ + /// `machine_address_ranges::replace` explicitly rejects both a + /// read-only existing range and a replacement config with + /// `read_only: true` (see `machine-address-ranges.cpp`), so exposing a + /// `read_only` toggle here would always error. If that ever changes, + /// widen this API then. + /// - When `image_path` is `None`, `data_filename` is serialized as the + /// empty string. The C++ side treats empty `data_filename` as "no + /// backing store" (`backing_store_config::newly_created()` returns + /// true when `create || data_filename.empty()`), which is the same + /// semantics as the old API's `NULL` pointer: the range is + /// zero-filled in-memory. pub fn replace_memory_range( &mut self, start: u64, @@ -249,7 +309,7 @@ impl Machine { cartesi_machine_sys::cm_get_proof( self.machine, address, - log2_target_size as i32, + log2_target_size as ::std::os::raw::c_int, log2_root_size as ::std::os::raw::c_int, &mut proof_ptr, ) @@ -294,7 +354,7 @@ impl Machine { /// Reads a chunk of data from a machine memory range, by its physical address. pub fn read_memory(&mut self, address: u64, size: u64) -> Result> { - let mut buffer = vec![0u8; size as usize]; + let mut buffer = vec![0u8; u64_to_usize(size)?]; let err_code = unsafe { cartesi_machine_sys::cm_read_memory(self.machine, address, buffer.as_mut_ptr(), size) }; @@ -320,7 +380,7 @@ impl Machine { /// Reads a chunk of data from a machine memory range, by its virtual memory. pub fn read_virtual_memory(&mut self, address: u64, size: u64) -> Result> { - let mut buffer = vec![0u8; size as usize]; + let mut buffer = vec![0u8; u64_to_usize(size)?]; let err_code = unsafe { cartesi_machine_sys::cm_read_virtual_memory( self.machine, @@ -428,7 +488,10 @@ impl Machine { let mut reason: u16 = 0; let mut length: u64 = 0; - // if data is NULL, length will still be set without reading any data. + // First call with a NULL data pointer: the C API just writes the + // required length into `length` and returns, without reading any + // bytes. (See machine-c-api.h: "If NULL, length will still be set + // without reading any data.") let err_code = unsafe { cartesi_machine_sys::cm_receive_cmio_request( self.machine, @@ -440,7 +503,11 @@ impl Machine { }; check_err!(err_code)?; - let mut buffer = vec![0u8; length as usize]; + // `length` is in-out per the C API contract ("Must be initialized to + // the size of data buffer"). Sizing the buffer to exactly `length` + // and then passing the same value back in makes the buffer-size + // precondition and the required-length output coincide. + let mut buffer = vec![0u8; u64_to_usize(length)?]; let err_code = unsafe { cartesi_machine_sys::cm_receive_cmio_request( @@ -478,7 +545,7 @@ impl Machine { /// Runs the machine for the given mcycle count and generates a log of accessed pages and proof data. pub fn log_step(&mut self, mcycle_count: u64, log_filename: &Path) -> Result { let mut break_reason = BreakReason::default(); - let log_filename_c = path_to_cstring(log_filename); + let log_filename_c = path_to_cstring(log_filename)?; let err_code = unsafe { cartesi_machine_sys::cm_log_step( @@ -563,7 +630,7 @@ impl Machine { mcycle_count: u64, root_hash_after: &Hash, ) -> Result { - let log_filename_c = path_to_cstring(log_filename); + let log_filename_c = path_to_cstring(log_filename)?; let mut break_reason = BreakReason::default(); let err_code = unsafe { @@ -590,6 +657,9 @@ impl Machine { let err_code = unsafe { cartesi_machine_sys::cm_verify_step_uarch( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. (cm_verify_step itself doesn't take this + // argument — the asymmetry is intentional in the C API.) ptr::null(), root_hash_before, log_cstr.as_ptr(), @@ -610,6 +680,8 @@ impl Machine { let log_cstr = serialize_to_json!(&log); let err_code = unsafe { cartesi_machine_sys::cm_verify_reset_uarch( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. ptr::null(), root_hash_before, log_cstr.as_ptr(), @@ -633,6 +705,8 @@ impl Machine { let err_code = unsafe { cartesi_machine_sys::cm_verify_send_cmio_response( + // Optional `const cm_machine *m`; NULL means "local verification". + // See machine-c-api.h. ptr::null(), reason as u16, data.as_ptr(), @@ -660,8 +734,49 @@ impl Machine { } } -fn path_to_cstring(path: &Path) -> CString { - CString::new(path.to_string_lossy().as_bytes()).expect("CString::new failed") +/// Converts a `u64` byte count (as used by the C API) to a Rust `usize`, +/// erroring out if the value exceeds what the platform can address. Only +/// matters on 32-bit targets — on 64-bit, `usize` and `u64` are the same +/// width and this is a no-op. Guards against silent truncation that would +/// result in an undersized buffer being passed to a C function expecting +/// `size` bytes of space. +fn u64_to_usize(size: u64) -> Result { + usize::try_from(size).map_err(|_| MachineError { + code: constants::error_code::OUT_OF_RANGE, + message: format!("byte count {size} exceeds usize range on this platform"), + }) +} + +/// Converts a `Path` to a `CString` for the C API. +/// +/// On Unix, uses the raw `OsStr` bytes so that non-UTF-8 paths (which are +/// legal on the platform) are passed through verbatim instead of being +/// silently corrupted by `to_string_lossy` replacement. On other platforms, +/// falls back to UTF-8 conversion and errors out if the path is not valid +/// UTF-8. +/// +/// Returns `CM_ERROR_INVALID_ARGUMENT` on an interior NUL byte or, on +/// non-Unix, on a non-UTF-8 path. +fn path_to_cstring(path: &Path) -> Result { + #[cfg(unix)] + let bytes = { + use std::os::unix::ffi::OsStrExt; + path.as_os_str().as_bytes().to_vec() + }; + #[cfg(not(unix))] + let bytes = path + .to_str() + .ok_or_else(|| MachineError { + code: constants::error_code::INVALID_ARGUMENT, + message: format!("path is not valid UTF-8: {}", path.display()), + })? + .as_bytes() + .to_vec(); + + CString::new(bytes).map_err(|e| MachineError { + code: constants::error_code::INVALID_ARGUMENT, + message: format!("path contains NUL byte ({}): {}", e, path.display()), + }) } #[cfg(test)] @@ -722,13 +837,7 @@ mod tests { } fn create_machine(config: &MachineConfig) -> Result { - let runtime_config = RuntimeConfig { - htif: Some(config::runtime::HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; - Machine::create(config, &runtime_config) + Machine::create(config, &RuntimeConfig::quiet_console()) } #[test] @@ -951,7 +1060,7 @@ mod tests { let proof: Proof = machine.proof( range.start, u64::BITS - range.length.leading_zeros(), - constants::machine::TREE_LOG2_ROOT_SIZE, + constants::machine::HASH_TREE_LOG2_ROOT_SIZE, )?; assert_eq!(proof.target_address, range.start); assert_eq!(proof.log2_target_size, log2_size as u64); diff --git a/prt/client-lua/computation/constants.lua b/prt/client-lua/computation/constants.lua index add83f6a..5bcb878a 100644 --- a/prt/client-lua/computation/constants.lua +++ b/prt/client-lua/computation/constants.lua @@ -1,4 +1,5 @@ local arithmetic = require "utils.arithmetic" +local cartesi = require "cartesi" -- log2 value of the maximal number of micro instructions that emulates a big instruction local log2_uarch_span_to_barch = 20 @@ -11,8 +12,14 @@ local log2_uarch_span_to_input = log2_uarch_span_to_barch + log2_barch_span_to_i -- log2 value of the maximal number of meta instructions local log2_uarch_span_to_epoch = log2_input_span_to_epoch + log2_barch_span_to_input + log2_uarch_span_to_barch --- Checkpoint address for machine state snapshots -local CHECKPOINT_ADDRESS = 0xfe0 +-- Memory slot where the off-chain client writes the pre-input root hash +-- before sending a CMIO input, so that on-chain `revertIfNeeded` can read it +-- back after a rejected input. Sourced from the emulator directly (v0.20+ +-- `cartesi.AR_SHADOW_REVERT_ROOT_HASH_START`, currently 0xfe0); the Solidity +-- side mirrors this through step's auto-generated +-- `EmulatorConstants.REVERT_ROOT_HASH_ADDRESS`. +local CHECKPOINT_ADDRESS = cartesi.AR_SHADOW_REVERT_ROOT_HASH_START +assert(CHECKPOINT_ADDRESS, "emulator missing AR_SHADOW_REVERT_ROOT_HASH_START (expected v0.20+)") local constants = { log2_uarch_span_to_barch = log2_uarch_span_to_barch, diff --git a/prt/client-rs/core/src/machine/constants.rs b/prt/client-rs/core/src/machine/constants.rs index 97f6654e..6bc96821 100644 --- a/prt/client-rs/core/src/machine/constants.rs +++ b/prt/client-rs/core/src/machine/constants.rs @@ -14,3 +14,64 @@ pub const INPUT_SPAN_TO_EPOCH: u64 = arithmetic::max_uint(LOG2_INPUT_SPAN_TO_EPO // log2 value of the maximal number of micro instructions that executes an input pub const LOG2_UARCH_SPAN_TO_INPUT: u64 = LOG2_BARCH_SPAN_TO_INPUT + LOG2_UARCH_SPAN_TO_BARCH; + +/// Re-export of the emulator's dedicated memory slot for the pre-input root +/// hash (a.k.a. `CM_AR_SHADOW_REVERT_ROOT_HASH_START`, currently `0xfe0`). +/// +/// The off-chain client writes the current root hash to this address before +/// sending a CMIO input, so that on-chain `revertIfNeeded` can read it back +/// and restore the state after a rejected input. The Solidity side mirrors +/// the emulator through step's auto-generated +/// `EmulatorConstants.REVERT_ROOT_HASH_ADDRESS`; +/// `tests::test_emulator_and_step_agree_on_revert_address` asserts the two +/// stay in sync after any emulator or step bump. +pub use cartesi_machine::constants::ar::SHADOW_REVERT_ROOT_HASH_START as CHECKPOINT_ADDRESS; + +#[cfg(test)] +mod tests { + use super::CHECKPOINT_ADDRESS; + + /// Guardrail: step's `EmulatorConstants.sol` is auto-generated from the + /// emulator C++ source, and `REVERT_ROOT_HASH_ADDRESS` must equal the + /// emulator's `CM_AR_SHADOW_REVERT_ROOT_HASH_START` — otherwise the + /// off-chain client writes to one address while on-chain + /// `revertIfNeeded` reads from another, and any rejected-input dispute + /// mis-restores state. If this test fails after an emulator or step + /// bump, the step submodule is out of sync with the emulator version + /// these bindings link against: regenerate step's `EmulatorConstants.sol` + /// against the matching emulator and bump both submodule pointers + /// together. + #[test] + fn test_emulator_and_step_agree_on_revert_address() { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let emulator_constants_sol = manifest_dir + .join("../../..") + .join("machine/step/src/EmulatorConstants.sol"); + let source = std::fs::read_to_string(&emulator_constants_sol) + .unwrap_or_else(|e| panic!("failed to read {}: {e}", emulator_constants_sol.display())); + + // Find: `uint64 constant REVERT_ROOT_HASH_ADDRESS = 0x;` + let marker = "REVERT_ROOT_HASH_ADDRESS"; + let pos = source.find(marker).unwrap_or_else(|| { + panic!("{marker} not found in {}", emulator_constants_sol.display()) + }); + let after = &source[pos + marker.len()..]; + let eq = after.find('=').expect("expected `=` after constant name"); + let semi = after.find(';').expect("expected `;` after constant value"); + let value_str = after[eq + 1..semi].trim(); + let step_value = if let Some(hex) = value_str.strip_prefix("0x") { + u64::from_str_radix(hex, 16).expect("REVERT_ROOT_HASH_ADDRESS not valid hex") + } else { + value_str + .parse::() + .expect("REVERT_ROOT_HASH_ADDRESS not valid decimal") + }; + + assert_eq!( + CHECKPOINT_ADDRESS, step_value, + "Emulator CM_AR_SHADOW_REVERT_ROOT_HASH_START ({CHECKPOINT_ADDRESS:#x}) \ + does not match step's EmulatorConstants.REVERT_ROOT_HASH_ADDRESS ({step_value:#x}). \ + The off-chain client and on-chain verifier will disagree on the revert slot." + ); + } +} diff --git a/prt/client-rs/core/src/machine/instance.rs b/prt/client-rs/core/src/machine/instance.rs index 71b4b5b5..13fe5719 100644 --- a/prt/client-rs/core/src/machine/instance.rs +++ b/prt/client-rs/core/src/machine/instance.rs @@ -1,15 +1,15 @@ use crate::db::dispute_state_access::DisputeStateAccess; use crate::machine::constants::{ - BARCH_SPAN_TO_INPUT, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, LOG2_UARCH_SPAN_TO_INPUT, - UARCH_SPAN_TO_BARCH, + BARCH_SPAN_TO_INPUT, CHECKPOINT_ADDRESS, INPUT_SPAN_TO_EPOCH, LOG2_UARCH_SPAN_TO_BARCH, + LOG2_UARCH_SPAN_TO_INPUT, UARCH_SPAN_TO_BARCH, }; use crate::machine::error::Result; use cartesi_dave_arithmetic as arithmetic; use cartesi_dave_merkle::Digest; use cartesi_machine::{ cartesi_machine_sys, - config::runtime::{HTIFRuntimeConfig, RuntimeConfig}, - constants::machine::TREE_LOG2_ROOT_SIZE, + config::runtime::RuntimeConfig, + constants::machine::HASH_TREE_LOG2_ROOT_SIZE, machine::Machine, types::access_proof::AccessLog, types::{LogType, cmio::CmioResponseReason}, @@ -64,15 +64,9 @@ pub struct MachineInstance { pub snapshot_path: PathBuf, } -const CHECKPOINT_ADDRESS: u64 = 0xfe0; impl MachineInstance { pub fn new_from_path(path: &str) -> Result { - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let path = PathBuf::from(path); let mut machine = Machine::load(&path, &runtime_config)?; @@ -110,12 +104,7 @@ impl MachineInstance { // load inner machine with snapshot, update cycle, keep everything else the same pub fn load_snapshot(&mut self, snapshot_path: &Path, snapshot_cycle: u64) -> Result<()> { debug!("load snapshot from {}", snapshot_path.display()); - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); let mut machine = Machine::load(Path::new(snapshot_path), &runtime_config)?; let cycle = machine.mcycle()?; @@ -257,12 +246,7 @@ impl MachineInstance { != cartesi_machine::constants::cmio::tohost::manual::RX_ACCEPTED { trace!("Reject input,revert to previous snapshot"); - let runtime_config = RuntimeConfig { - htif: Some(HTIFRuntimeConfig { - no_console_putchar: Some(true), - }), - ..Default::default() - }; + let runtime_config = RuntimeConfig::quiet_console(); self.machine = Machine::load(&self.snapshot_path, &runtime_config)?; } @@ -335,7 +319,7 @@ impl MachineInstance { let mut read = self.machine.read_memory(aligned_address, 32)?; let proof = self .machine - .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -355,7 +339,7 @@ impl MachineInstance { let read_hash = Digest::from_data(&read); let proof = self .machine - .proof(aligned_address, 5, TREE_LOG2_ROOT_SIZE)?; + .proof(aligned_address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); @@ -375,7 +359,7 @@ impl MachineInstance { let read = self.machine.read_memory(address, 32)?; let read_hash = Digest::from_data(&read); // Get proof of write address - let proof = self.machine.proof(address, 5, TREE_LOG2_ROOT_SIZE)?; + let proof = self.machine.proof(address, 5, HASH_TREE_LOG2_ROOT_SIZE)?; let mut encoded: Vec = Vec::new(); diff --git a/prt/tests/rollups/justfile b/prt/tests/rollups/justfile index 5aa45d93..d58390e9 100644 --- a/prt/tests/rollups/justfile +++ b/prt/tests/rollups/justfile @@ -10,7 +10,7 @@ test PROGRAM SCRIPT: ANVIL_LOAD_PATH=`realpath {{ANVIL_LOAD_PATH}}` \ ANVIL_DUMP_PATH="anvil_{{PROGRAM}}_{{SCRIPT}}.json" \ TEMPLATE_MACHINE=`realpath ../../../test/programs/{{PROGRAM}}/machine-image` \ - TEMPLATE_MACHINE_HASH=0x`xxd -seek 0x60 -l 0x20 -c 0x20 -p ../../../test/programs/{{PROGRAM}}/machine-image/hash_tree.sht` \ + TEMPLATE_MACHINE_HASH=0x`cartesi-machine-stored-hash ../../../test/programs/{{PROGRAM}}/machine-image` \ DAVE_APP_FACTORY=`jq -r .address {{DEPLOYMENTS_DIR}}/DaveAppFactory.json` \ INPUT_BOX=`jq -r .address {{DEPLOYMENTS_DIR}}/InputBox.json` \ ERC20_PORTAL=`jq -r .address {{DEPLOYMENTS_DIR}}/ERC20Portal.json` \