Deterministic Execution — Sealing Off Floating Point, Time, and Randomness TODO
Concept
Deterministic execution means that the same input and the same initial state always produce the same output and the same state transition. The usual culprits that break it are floating point (operation order, extended precision, FMA, differences between library implementations), wall-clock time and timeouts, randomness, thread scheduling, hash map iteration order, and external I/O such as files or the network. Sealing it off means turning every one of these nondeterminism sources into an injectable input: use integer or fixed-point arithmetic instead of floating point, pass time and the random seed in as arguments and record them, and fix iteration order with an explicit sort. Once that's done, replay verification, reproducible tests, and state machine replication all become possible. In consensus systems, determinism isn't a convenience — it's a safety requirement, because if nodes get different results from the same input, state has already forked.
Many irreproducible, intermittent bugs come from hidden nondeterminism like time, randomness, or iteration order, and replay debugging and replicated execution are only possible on top of determinism.
Code & Formula
# 결정론적 실행 — 부동소수점·시간·난수를 봉인해, 같은 입력이면 언제나 같은 결과가 나오게 만든다.
# float 대신 정수 고정소수점을, wall-clock 대신 주입된 시각을, os 난수 대신 시드 고정 PRNG 를 쓴다.
import random
import hashlib
SCALE = 10_000 # 고정소수점: 정수를 1/10000 단위로 취급 (부동소수점 연산 순서 의존성을 제거)
def fixed_add(a_scaled, b_scaled):
return a_scaled + b_scaled # 정수 덧셈은 결합/교환 법칙이 정확히 성립 — float 처럼 순서에 안 흔들림
def deterministic_run(seed, injected_time, events):
rng = random.Random(seed) # 벽시계 대신 시드로 재현 가능한 난수
balance = 0
log = []
for ev in events:
amount_scaled = int(round(ev * SCALE))
balance = fixed_add(balance, amount_scaled)
jitter = rng.randint(0, 99) # 진짜 os.urandom 대신 시드 기반 — 리플레이 가능
log.append((injected_time, balance, jitter))
injected_time += 1 # time.time() 대신 명시적으로 흘려보내는 논리 시계
return balance, log
events = [1.0001, 2.0002, -0.5, 3.3333]
# 같은 입력으로 3번 독립 실행 → 항상 같은 최종 잔고와 로그가 나와야 한다(결정론 검증).
runs = [deterministic_run(seed=42, injected_time=1000, events=events) for _ in range(3)]
digests = [hashlib.sha256(repr(r).encode()).hexdigest() for r in runs]
print("balance (scaled by 1e4):", runs[0][0])
print("balance (real value):", runs[0][0] / SCALE)
print("run 1 == run 2 == run 3 :", runs[0] == runs[1] == runs[2])
print("output hashes identical:", len(set(digests)) == 1, digests[0][:16])
docs/code/algorithms/algorithms-33.py
Exercise
Pick a small piece of computation logic that uses time, randomness, and map iteration, separate those three sources behind injectable interfaces, then run it 1000 times with the same seed and check that the output hashes all match.
Practical Connection
The EVM avoids floating point entirely and limits external input to things like block header values, which is exactly how it secures determinism — for the same reason, Verex's off-chain settlement calculations need integer arithmetic and fixed rounding rules to match on-chain results.
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.