stf-analysis

star 4

Analyze State Transition Function implementations for correctness, threading issues, non-determinism, and simplification opportunities. Use when analyzing STF code, checking threading model, finding non-determinism sources, or reviewing execution layer.

evstack By evstack schedule Updated 2/1/2026

name: stf-analysis description: Analyze State Transition Function implementations for correctness, threading issues, non-determinism, and simplification opportunities. Use when analyzing STF code, checking threading model, finding non-determinism sources, or reviewing execution layer.

STF Analysis Skill

Use this skill to analyze State Transition Function implementations for:

  • Threading correctness (Send/Sync bounds)
  • Non-determinism sources
  • Simplification opportunities
  • Test coverage gaps

Analysis Checklist

1. Threading Model Analysis

Check for unnecessary Send/Sync bounds:

# Find all Send/Sync trait bounds
grep -rn "Send + Sync\|: Send\|: Sync" crates/

# Check if bounds are actually needed by looking for:
# - spawn, thread usage
# - Arc, Mutex, RwLock patterns
# - async/await with cross-task sharing

Questions to answer:

  • Is block execution single-threaded? (Should be for determinism)
  • Where does actual concurrency exist?
  • Are there bounds that propagate unnecessarily?

2. Non-Determinism Sources

Check for common non-determinism issues:

# HashMap iteration (non-deterministic order)
grep -rn "\.iter()\|\.into_iter()" --include="*.rs" crates/app/stf/

# Look for into_changes() and verify output is sorted or order-independent

Common sources:

  • HashMap/HashSet iteration without sorting
  • Floating point arithmetic
  • System time usage
  • Random number generation
  • Parallel execution without deterministic ordering

3. Atomicity Verification

Verify checkpoint/restore pattern:

// Every state-changing operation should follow this pattern:
let checkpoint = storage.checkpoint();
let result = do_operation();
if result.is_err() {
    storage.restore(checkpoint);
}

Check:

  • All do_invoke() calls have checkpoint before
  • All error paths restore checkpoint
  • Events are truncated on restore
  • Unique object counter is restored

4. Gas Configuration

Gas metering is configured via StorageGasConfig passed to the STF at construction:

use evolve_stf::{Stf, StorageGasConfig};

let gas_config = StorageGasConfig {
    storage_get_charge: 10,  // gas per storage read
    storage_set_charge: 10,  // gas per storage write
    storage_remove_charge: 10, // gas per storage delete
};

let stf = Stf::new(begin_blocker, end_blocker, tx_validator, post_tx, gas_config);

Key types:

  • StorageGasConfig - defines gas costs for storage operations
  • GasCounter - tracks gas during execution (Infinite or Finite mode)
  • ERR_OUT_OF_GAS - returned when gas limit exceeded

The gas config is stored in the STF and used in do_begin_block to configure gas metering for the block. This avoids the overhead of reading config from storage every block.

5. Resource Limits

Verify security limits exist:

Resource Expected Limit Location
Overlay entries ~100,000 execution_state.rs
Events per tx ~10,000 execution_state.rs
Key size ~256 bytes execution_state.rs
Value size ~1 MB execution_state.rs
Call depth ~64 invoker.rs

6. Test Coverage Gaps

Property tests that should exist:

// ExecutionState model test
#[test]
fn prop_execution_state_model() { /* overlay + undo = correct state */ }

// Gas counter model test
#[test]
fn prop_gas_counter_model() { /* no overflow, correct tracking */ }

// Gas calculation accuracy (uses correct charge per operation type)
#[test]
fn test_gas_calculation_accuracy() { /* set uses set_charge, remove uses remove_charge */ }

// Block failure invariance
#[test]
fn prop_apply_block_failure_invariance() { /* checkpoint/restore works */ }

// Determinism test (NEW - often missing)
#[test]
fn prop_determinism() { /* same input = same output, always */ }

7. Simplification Opportunities

Look for:

  1. Unused trait bounds

    // If ReadonlyKV: Send + Sync but only warm_cache needs Sync
    // Move the bound to warm_cache, not the trait
    
  2. Atomic overhead without threading

    // If metrics use AtomicU64 but execution is single-threaded
    // Consider Cell<u64> if Sync not required
    
  3. Dead code paths

    // Fields that exist but are never called
    // e.g., post_tx_handler that's never invoked
    

Output Template

After analysis, produce a report:

## STF Analysis Report

### Threading Model
- Execution model: [single-threaded / parallel]
- Concurrency locations: [list]
- Unnecessary bounds: [list]

### Non-Determinism Risks
- [x] HashMap iteration in into_changes() - NEEDS SORTING
- [ ] No floating point usage - OK
- [ ] No system time in execution - OK

### Atomicity
- [x] Checkpoints before state changes - OK
- [x] Restore on all error paths - OK
- [ ] Missing: [any gaps]

### Resource Limits
- [x] Overlay: 100,000 entries
- [x] Call depth: 64 levels
- [ ] Missing: [any gaps]

### Test Gaps
- [ ] Determinism test missing
- [ ] Call depth exhaustion test missing

### Simplification Recommendations
1. Remove Send+Sync from ReadonlyKV, add to warm_cache only
2. Replace AtomicU64 with Cell<u64> in metrics (if Sync not needed)
3. [other recommendations]

Quick Commands

# Check current Send/Sync usage
rg "Send \+ Sync|: Send|: Sync" crates/app/stf/src/

# Find HashMap iterations that might be non-deterministic
rg "\.into_iter\(\)|\.iter\(\)" crates/app/stf/src/ -A 2

# Check for checkpoint usage
rg "checkpoint\(\)|restore\(" crates/app/stf/src/

# Find all error returns without restore
rg "return Err" crates/app/stf/src/invoker.rs -B 5

# Check test coverage
cargo test -p evolve_stf -- --list 2>&1 | grep "test "
Install via CLI
npx skills add https://github.com/evstack/ev-rs --skill stf-analysis
Repository Details
star Stars 4
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator