Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

Last updated: 2026-04-17

Crate: rspace++ (Tuple Space Storage)

Path: rspace++/

High-performance, persistent tuple space engine. The core storage substrate for Rholang smart contract communication.

Core Concept

Processes communicate by placing data on channels and waiting for data with patterns:

  • Produce: Put data on a channel. If a waiting consumer matches, fire a COMM event.
  • Consume: Wait on channel(s) with pattern(s). If data already present matches, fire immediately.
  • COMM event: A matched produce+consume pair triggers continuation execution.

ISpace Trait (Core API)

All methods are async via #[async_trait], enabling cooperative concurrency with tokio::sync::Mutex per-channel locks (FIFO-ordered, matching Scala's Semaphore).

#[async_trait]
pub trait ISpace<C, P, A, K>: Send + Sync {
    // Tuple space operations
    async fn produce(&self, channel: C, data: A, persist: bool)
        -> Result<MaybeProduceResult<C, P, A, K>, RSpaceError>;
    async fn consume(&self, channels: Vec<C>, patterns: Vec<P>,
               continuation: K, persist: bool, peeks: BTreeSet<i32>)
        -> Result<MaybeConsumeResult<C, P, A, K>, RSpaceError>;
    async fn install(&self, channels: Vec<C>, patterns: Vec<P>, continuation: K)
        -> Result<Option<(K, Vec<A>)>, RSpaceError>;

    // Checkpointing
    async fn create_checkpoint(&self) -> Result<Checkpoint, RSpaceError>;
    async fn create_soft_checkpoint(&self) -> SoftCheckpoint<C, P, A, K>;
    async fn revert_to_soft_checkpoint(&self, cp: SoftCheckpoint<C, P, A, K>)
        -> Result<(), RSpaceError>;
    async fn reset(&self, root: &Blake2b256Hash) -> Result<(), RSpaceError>;

    // State inspection
    async fn get_data(&self, channel: &C) -> Vec<Datum<A>>;
    async fn get_waiting_continuations(&self, channels: Vec<C>)
        -> Vec<WaitingContinuation<P, K>>;
    async fn get_joins(&self, channel: C) -> Vec<Vec<C>>;
    async fn to_map(&self) -> HashMap<Vec<C>, Row<P, A, K>>;

    // Replay support
    async fn rig_and_reset(&self, start_root: Blake2b256Hash, log: Log)
        -> Result<(), RSpaceError>;
    async fn check_replay_data(&self) -> Result<(), RSpaceError>;
    async fn is_replay(&self) -> bool;
}

All methods take &self (interior mutability via RwLock/Mutex) instead of &mut self, enabling shared access from concurrent tokio::spawn tasks.

Core Data Types

pub struct Datum<A: Clone> {
    pub a: A,              // The data value
    pub persist: bool,     // Survives matching if true
    pub source: Produce,   // Hash reference to origin event
}

pub struct WaitingContinuation<P: Clone, K: Clone> {
    pub patterns: Vec<P>,
    pub continuation: K,
    pub persist: bool,
    pub peeks: BTreeSet<i32>,  // Non-consuming read indices
    pub source: Consume,
}

pub struct Checkpoint {
    pub root: Blake2b256Hash,  // Trie root hash
    pub log: Log,              // Event log from this checkpoint
}

Match Trait

pub trait Match<P, A>: Send + Sync {
    fn get(&self, p: P, a: A) -> Option<A>;  // None = no match
}

For Rholang, this implements spatial pattern matching on Par processes.

Implementations

Struct Purpose
RSpace<C,P,A,K> Primary implementation for block execution. is_replay() returns false. Uses per-channel two-phase locks (tokio::sync::Mutex) matching Scala's ConcurrentTwoStepLockF.
ReplayRSpace<C,P,A,K> Deterministic replay variant (tracks replay_data: MultisetMultiMap<IOEvent, COMM>). is_replay() returns true. Also has per-channel two-phase locks matching Scala's RSpaceOps.
ReportingRspace<C,P,A,K> Debugging variant that records all COMM events separated by soft checkpoints.

The is_replay() distinction is critical for non-deterministic system processes (e.g., external API calls to OpenAI, Ollama, gRPC services). When is_replay() returns true, these processes use cached output from the original execution stored in Produce::output_value instead of re-executing the external call, ensuring deterministic block replay.

Storage Architecture

HotStore (in-memory, HashMap)         <- Fast working set
    |
    | create_checkpoint()
    v
HistoryRepository                     <- Converts actions to trie mutations
    |
    v
RadixHistory (256-ary trie)           <- Immutable content-addressed trie
    |
    v
LMDB (3 databases per instance)       <- Durable persistence
  - {prefix}-history (trie nodes)
  - {prefix}-roots (checkpoint root hashes)
  - {prefix}-cold (leaf data: datums, continuations, joins)

Hot Store (InMemHotStore):

  • HotStoreState contains HashMaps (not DashMap): continuations, installed_continuations, data, joins, installed_joins
  • Interior mutability via std::sync::RwLock<HotStoreState> + separate std::sync::RwLock<HistoryStoreCache>
  • History reads cached back into hot store state for subsequent access

Cold Store (LMDB):

  • PersistedData enum: Data(DataLeaf), Continuations(ContinuationsLeaf), Joins(JoinsLeaf)
  • Each leaf stores serialized bytes

Radix Trie

256-ary trie for checkpointing. Each node has 256 slots (one per byte value):

pub enum Item {
    EmptyItem,
    Leaf { prefix: ByteVector, value: ByteVector },   // value = 32-byte hash
    NodePtr { prefix: ByteVector, ptr: ByteVector },   // ptr = 32-byte child hash
}
pub type Node = Vec<Item>;  // Always 256 items

Key paths: [PREFIX_DATUM|PREFIX_KONT|PREFIX_JOINS] + [hash_bytes]

Compact binary encoding: item index (1B) + type/prefix-length (1B) + prefix (0-127B) + value/ptr (32B).

Root hash = Blake2b256(encoded_root_node).

Event Tracking

pub struct Produce {
    pub channel_hash: Blake2b256Hash,
    pub hash: Blake2b256Hash,
    pub persistent: bool,
    pub is_deterministic: bool,
    pub output_value: Vec<Vec<u8>>,  // For non-deterministic processes
    pub failed: bool,
}

pub struct Consume {
    pub channel_hashes: Vec<Blake2b256Hash>,
    pub hash: Blake2b256Hash,
    pub persistent: bool,
}

pub struct COMM {
    pub consume: Consume,
    pub produces: Vec<Produce>,
    pub peeks: BTreeSet<i32>,
    pub times_repeated: BTreeMap<Produce, i32>,
    pub triggered_by_produce: bool,  // Replay cost determinism metadata
}

Produce Identity Semantics

Produce has custom PartialEq/Eq, Hash, and Ord/PartialOrd implementations that compare and order by the hash field only. Metadata fields (is_deterministic, output_value, failed) are set after creation (e.g., via mark_as_non_deterministic) and must NOT affect identity, which would break replay event matching in ReplayRSpace. The hash field is a cryptographic hash of the channel, data, and persist flag computed at creation time.

COMM Sort Order

COMM::new sorts produce_refs by (channel_hash, hash, persistent) for COMM event identity, which intentionally differs from Produce::Ord (hash-only). Do not replace with .sort().

COMM Trigger Direction

triggered_by_produce records whether a produce or consume triggered the COMM during play. This field is excluded from PartialEq and Hash -- it does not affect COMM identity or replay_data matching. It is used by ChargingRSpace to ensure the cost model's identity-based refund logic produces consistent results between play and replay, where tokio::spawn scheduling may cause a different operation to trigger the same COMM.

Merger (Consensus Support)

merger/merging_logic.rs analyzes event logs for conflicts:

  • depends(target, source) -> bool -- Checks if source events are prerequisites for target
  • are_conflicting(a, b) -> bool -- Detects races on non-persistent operations
  • conflict_reason(a, b) -> Option<String> -- Human-readable conflict explanation

ChannelChange<A> tracks added and removed items per channel for merge analysis.

FFI Sub-crate: rspace_rhotypes

Path: rspace++/libs/rspace_rhotypes/

C FFI bindings for Scala JNA interop:

Type aliases:
  Channel = Par
  Pattern = BindPattern
  Data = ListParWithRandom
  Continuation = TaggedContinuation

Exports: create_rspace(), produce(), consume(), install(), spatial_match_result(), reset_rspace(), free_space(), etc.

Memory management: Rust allocates via Box::leak(), returns raw pointer. Scala must call free_allocated_memory(). ALLOCATED_BYTES atomic counter tracks leaked memory.

Tests

Extensive test suites in tests/: hot_store_spec.rs (property-based), storage_actions_test.rs, replay_rspace_tests.rs, reporting_rspace_tests.rs, export_import_tests.rs, install_test.rs, plus history/ subdirectory tests.

See also: rspace++/ crate README

← Back to docs index