Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Determinism

All module code must be deterministic. Given identical inputs, execution must produce identical outputs on all nodes. Violating determinism causes consensus failures.

Banned Patterns

1. Non-Deterministic Collections

NEVER use HashMap or HashSet - iteration order varies by platform/run.

// BAD - iteration order is non-deterministic
use std::collections::HashMap;
let map = HashMap::new();
for (k, v) in &map { /* order varies! */ }
 
// GOOD - use BTreeMap for deterministic ordering
use std::collections::BTreeMap;
let map = BTreeMap::new();
for (k, v) in &map { /* order is consistent */ }
 
// BEST - use Evolve's storage collections
use evolve_collections::map::Map;
let map: Map<Key, Value> = Map::new(0);

2. System Time

NEVER use std::time - system clocks vary between nodes.

// BAD - non-deterministic
use std::time::{Instant, SystemTime};
let now = SystemTime::now();
 
// GOOD - use block time from environment
let block_time = env.block_time_ms();

3. Random Number Generation

NEVER use rand or any RNG without deterministic seeding from chain state.

// BAD - non-deterministic
use rand::Rng;
let random = rand::thread_rng().gen::<u64>();
 
// GOOD - derive randomness from block hash or chain state
let seed = block_hash.as_bytes();

4. Floating Point Arithmetic

NEVER use f32 or f64 - floating point operations can vary across platforms.

// BAD - platform-dependent results
let result = 0.1 + 0.2; // might not equal 0.3 exactly
 
// GOOD - use fixed-point arithmetic
use evolve_math::FixedPoint;
let result = FixedPoint::from_decimal(1, 1) + FixedPoint::from_decimal(2, 1);

5. External I/O

NEVER perform I/O operations - file access, network calls, or environment variables.

// BAD - all of these
std::fs::read("file.txt");
std::net::TcpStream::connect("...");
std::env::var("SECRET");

6. Threading and Async

NEVER spawn threads or use async - execution order is non-deterministic.

// BAD
std::thread::spawn(|| { /* ... */ });
 
// Modules execute synchronously within the STF

Compile-Time Protection

Configure clippy to catch common issues. Add to .clippy.toml:

disallowed-types = [
    { path = "std::collections::HashMap", reason = "Non-deterministic iteration order" },
    { path = "std::collections::HashSet", reason = "Non-deterministic iteration order" },
    { path = "std::time::Instant", reason = "Non-deterministic - use env.block_time_ms()" },
    { path = "std::time::SystemTime", reason = "Non-deterministic - use env.block_time_ms()" },
]

Safe Patterns

Using Evolve Collections

All Evolve collections are deterministic by design:

CollectionDescription
Item<T>Single value storage
Map<K, V>Key-value with deterministic iteration
Vector<T>Ordered sequence
Queue<T>FIFO queue
UnorderedMap<K, V>Deterministic within a session

Deriving Values from Chain State

When you need "randomness" or unique values:

// Use a counter stored in your module for unique IDs
let unique_id = self.counter.update(|c| Ok(c + 1), env)?;
 
// Use block info from environment for time-based logic
let block_height = env.block_height();
let block_time = env.block_time_ms();

Testing for Determinism

Run the same test multiple times and verify identical results:

#[test]
fn test_determinism() {
    for _ in 0..100 {
        let result = execute_module_logic();
        assert_eq!(result, EXPECTED_VALUE);
    }
}

Debugging Consensus Failures

If nodes diverge:

  1. Check for HashMap/HashSet usage
  2. Look for floating point arithmetic
  3. Verify no time-dependent logic
  4. Check for uninitialized memory access
  5. Run with RUST_BACKTRACE=1 to find the divergence point