Daine Yip is a
based in
currently building @

2025
Undergraduate research (CPSC 448) · Winter Term 1, 2025 · Supervised by Prof. Ivan Beschastnikh
Formal modelling of Byzantine fault tolerant consensus — a specification of PBFT written in TLA+/PlusCal, verified with the TLC model checker, and structured so it can eventually be compiled into Go by PGo.
A Byzantine fault tolerant system can maintain correctness even when participants lie: with n = 3f + 1 replicas, every correct replica must still reach the same
decision while up to f deviate arbitrarily. However, the implementations of BFT protocols in standard programming languages is prone to
concurrency bugs, race conditions, deadlocks, and
shared-memory corruption, none of which standard testing reliably surfaces. Therefore, bridging the gap between formal theory and executable code using formal verification tools such as TLA+ and PGo is critical for blockchain consensus algorithms.
TLA+ verifies the design mathematically; PGo compiles Modular PlusCal into runnable Go. Together they close the gap between a proof and the code that ships.
Complexity layering — start with the smallest thing that can be verified, then add one source of complexity at a time, so each bug surfaces in the simplest model that can still produce it:
2f + 1) that moves a replica between them.PBFTconcurrentClient.tla, where
multiple requests move through consensus interleaved.Head-of-line blocking. Strictly sequential processing deadlocked: a replica
awaiting a prepare message at the head of its inbox stalls forever if a slower peer's
commit arrives first. The fix was architectural — a message pump that drains the
inbox on arrival regardless of type into a local received log, with state transitions
recalculated by monitoring that log rather than reacting to whatever arrived next.
Shared-buffer corruption. A global msg_struct used for message assembly let one
replica overwrite another's payload mid-broadcast. Refactoring every temporary buffer
into a process-local variable restored memory isolation between threads.
Per-transaction state. With concurrent clients, a replica can be committed for sequence #1 while only pre-prepared for #2, which a single global state flag cannot represent. It became a sequence-indexed state map, so progress on one transaction can't disturb the finalization of another.
Verification completed at 2 replicas and 2 clients in roughly seven minutes. At three
or four, the reachability graph outgrew the machine and the TLA+ Toolbox exhausted RAM
and crashed. That boundary matters, so it's worth stating: the model is a
proof of the state machine and the concurrency logic, not of the Byzantine
threshold, which needs N = 4. It also exercises the happy flow rather than quorum
progress under silence, omits the view-change protocol, abstracts cryptography to
authenticated channels, and has no deterministic fault injection.
Simulation mode and a bigger TLC allocation to reach N = 4; refactoring the
specification into Modular PlusCal so PGo can generate a Go prototype; encoding the
invariants from the original OSDI '99 paper; and a fault-injection harness that forces
chosen replicas to misbehave on cue.
The full report is in this folder, and the specifications are on GitHub ↗.