Skip links

Building the Formal Model: Priority Resolution and Byzantine Tolerance in Isabelle/HOL

A still wave testing tank with a geometric test structure visible through crystal-clear water, evoking the transparency of formal Byzantine fault modeling in Isabelle/HOL

TL;DR

Property 2 modeling has begun. Priority_Resolution.thy defines three domain-independent locales (priority_system, deadlock_free_locking, fair_leader_system), and DQuencer_Instance.thy instantiates them into the regulatory domain. Five key design decisions shaped the implementation. (1) Using nat × nat × nat × nat tuples for priority keys, directly leveraging Isabelle’s built-in lexicographic order and avoiding manual linorder instance registration. (2) Sign inversion of timestamps and node_id to map “smaller is higher priority” onto nat’s default ordering. (3) Reusing Property 1’s code without modification through oss_message record inheritance. (4) Abstracting BFT consensus not as BLS signature cryptographic security but as the determinism property of consensus output. (5) Radically simplifying locale signatures from the initial design, reducing deadlock_free_locking to a single assumption (timeout > 0) that suffices to prove deadlock freedom. Currently 5 sorry remain in Priority_Resolution.thy and 0 in DQuencer_Instance.thy. The sorry elimination process will be covered in the next post.


From Property 1 to Property 2: What Is Being Extended

After Property 1’s proof was complete, we established the first principle for extension: add new structures on top of the existing model without modifying it. The four generic locales in State_Preservation.thy, along with Regulatory_Instance.thy‘s state transition model, global state, sync function, and valid_state invariant, all remain untouched.

What Property 2 adds is confined to four areas.

First, a priority system. A total order that determines which regulatory action to process first when conflicting actions arrive simultaneously. Property 1 handled concurrent arrivals through preemptive locking for sequential processing, but never defined the criteria for ordering.

Second, a Byzantine node model. Property 1 assumed all nodes honestly follow the protocol. Now we model an environment where up to f < n/3 malicious nodes exist.

Third, timeout-based deadlock freedom. Defense against Byzantine nodes intentionally refusing to release locks.

Fourth, fairness-based starvation freedom. Reducing VRF leader election’s probabilistic guarantee to a deterministic assumption for proof.

All four are defined in new files (Priority_Resolution.thy, DQuencer_Instance.thy), with no modifications to existing files.

Priority_Resolution.thy: Three Generic Locales in Isabelle/HOL

Priority_Resolution.thy is a standalone theory file that imports only Main. It never imports Regulatory_Instance.thy and knows nothing about the regulatory domain. The three locales it defines are reusable abstractions applicable across distributed systems.

locale priority_system: Deterministic Selection

This locale formalizes the problem of selecting the highest-priority element from a finite set. The required structure is minimal: a priority function mapping into a linear order (linorder), and that mapping must be injective.

locale priority_system =
  fixes priority :: "'m ⇒ 'k::linorder"
  assumes priority_injective:
    "⟦ priority m1 = priority m2 ⟧ ⟹ m1 = m2"

The injectivity assumption models tiebreaking. When a real system uses a composite key (authority level, timestamp, severity, node ID), two distinct messages sharing the same priority is structurally excluded.

The key theorem to prove within this locale is highest_priority_exists. Given an injective priority function over a finite non-empty set, the element with maximum priority exists uniquely.

lemma highest_priority_exists:
  assumes "finite S" and "S ≠ {}"
  shows "∃!m. m ∈ S ∧ (∀m' ∈ S. priority m' ≤ priority m)"

The proof has two steps. First, show the existence of a maximum element in a finite set (connecting to the appropriate lemma in Isabelle’s Finite_Set library), then derive uniqueness from injectivity. The uniqueness direction is already proven: if priority m = priority m2, injectivity gives m2 = m. One sorry remains on the existence direction, corresponding to the connection with Finite_Set‘s Max-related lemmas.

On top of this, the select_highest function is defined.

definition select_highest :: "'m set ⇒ 'm option" where
  "select_highest S =
    (if S = {} then None
     else Some (THE m. m ∈ S ∧ (∀m' ∈ S. priority m' ≤ priority m)))"

THE is Isabelle/HOL’s definite description operator, which selects the uniquely existing element. Since highest_priority_exists proves ∃!, the use of THE is justified. Three theorems, select_highest_deterministic, select_highest_in_set, and select_highest_is_max, are built on this definition and are currently sorry.

Where can this locale apply beyond regulatory systems? DeFi protocol transaction ordering, deterministic winner selection in MEV auctions, task priority resolution in distributed schedulers. Any system requiring deterministic selection based on total order over a finite set can instantiate this locale.

locale deadlock_free_locking: Timeout-Based Deadlock Freedom

This locale formalizes the minimal structure guaranteeing deadlock freedom in a resource locking system.

locale deadlock_free_locking =
  fixes timeout :: nat
  assumes timeout_positive: "timeout > 0"

A single assumption: the timeout is positive. On top of this, lock effectiveness is defined as a function of time.

definition lock_effective :: "nat ⇒ nat ⇒ bool" where
  "lock_effective lock_time current_time ⟷ current_time < lock_time + timeout"

A lock expires once timeout time units pass since the acquisition time (lock_time). Expired locks have no blocking effect regardless of whether the holder explicitly released them.

Two key theorems are proven in this locale. lock_eventually_expires shows that every lock eventually expires, and deadlock_freedom shows that any currently effective lock expires within lock_time + timeout. Both are proven without sorry.

theorem lock_eventually_expires:
  "∃t'. t' ≥ current_time ∧ ¬ lock_effective lock_time t'"

theorem deadlock_freedom:
  assumes "lock_effective lock_time current_time"
  shows "∃t'. t' ≤ lock_time + timeout ∧ ¬ lock_effective lock_time t'"

The proofs are constructive, directly presenting lock_time + timeout as the witness. Unfolding lock_effective_def shows that lock_time + timeout < lock_time + timeout is false, so the lock has expired, and auto handles the rest.

One design decision deserves attention here. Oraclizer’s locking structure uses single-resource locks (one lock per asset ID). The classical deadlock condition of “circular wait” is structurally impossible with single-resource locking. The situation where process A holds resource X while waiting for resource Y, and simultaneously process B holds Y while waiting for X, cannot occur when each process holds at most one lock. Timeouts provide additional defense on top of this structural guarantee, handling the case where a Byzantine node intentionally refuses to release its lock.

This locale’s generic applicability extends to distributed database row locks, file system locks, and distributed cache key locks, anywhere the same structure of “finite resources, locks, and timeouts” exists.

locale fair_leader_system: Starvation Freedom under Fair Leadership

This locale formalizes the minimal conditions guaranteeing starvation freedom in a leader-based consensus system.

locale fair_leader_system =
  fixes leader_at :: "nat ⇒ 'n"
    and is_honest :: "'n ⇒ bool"
    and pending :: "nat ⇒ nat"
    and fairness_bound :: nat
  assumes fairness_bound_positive: "fairness_bound > 0"
    and fair_leader:
      "∀epoch. ∃e. epoch ≤ e ∧ e < epoch + fairness_bound ∧ is_honest (leader_at e)"
    and honest_progress:
      "⟦ is_honest (leader_at e); pending e > 0 ⟧ ⟹ pending (Suc e) < pending e"
    and non_honest_bounded:
      "pending (Suc e) ≤ pending e"

Each of the four assumptions serves a specific role.

fair_leader is the fairness assumption. Starting from any epoch, at least one honest node is elected as leader within fairness_bound epochs. In D-quencer with VRF-based leader election, the probability of a Byzantine leader being elected k consecutive times is \((f/n)^k\), decreasing exponentially. Converting this probabilistic property into a deterministic assumption is what fair_leader does. This corresponds to the “assume” side of the assume-guarantee reasoning described in the previous post.

honest_progress is the crux. When an honest leader is elected and the pending queue is non-empty, the pending queue size strictly decreases in the next epoch. Since strict decrease on natural numbers guarantees reaching 0 within finite time, this forms the basis for the termination proof.

non_honest_bounded is a closed-system assumption. Even during dishonest leader epochs, the pending queue does not grow. This means no new requests arrive during the analysis interval. The limitation of this assumption must be explicitly acknowledged. If new request arrivals are modeled, the pending queue could grow unboundedly, and starvation freedom would require more complex conditions. The current scope is limited to “complete processing of a given finite queue.”

Two key theorems are proven within this locale. starvation_bound shows that if the pending queue is non-empty, at least one request is processed within fairness_bound epochs. This theorem is proven without sorry.

theorem starvation_bound:
  assumes "pending epoch > 0"
  shows "∃e. epoch ≤ e ∧ e < epoch + fairness_bound ∧ pending (Suc e) < pending e"

The proof obtains an honest leader epoch e from fair_leader, uses pending_monotone (monotonic non-increase of the pending queue) to show pending e > 0 at that epoch, then applies honest_progress.

eventual_completion shows that all pending requests are eventually processed. Using well-founded induction on the pending queue size (a natural number), repeated application of starvation_bound drives the queue to zero. One sorry remains on this theorem, corresponding to the well-founded induction construction.

theorem eventual_completion:
  shows "∃e_final. pending e_final = 0"
Priority_Resolution.thy Locale Architecture Three domain-independent locales defined in Priority_Resolution.thy: priority_system for deterministic selection, deadlock_free_locking for timeout-based deadlock freedom, and fair_leader_system for starvation freedom. PRIORITY_RESOLUTION.THY — THREE GENERIC LOCALES imports Main only — domain independent — reusable beyond regulatory systems priority_system FIXES priority :: ‘m ⇒ ‘k::linorder ASSUMES priority_injective DEFINITION select_highest :: ‘m set ⇒ ‘m option THEOREMS highest_priority_exists sorry select_highest_deterministic sorry select_highest_in_set sorry select_highest_is_max sorry REUSABLE FOR DeFi tx ordering, MEV auctions, distributed scheduler priority deadlock_free_locking FIXES timeout :: nat ASSUMES timeout_positive: timeout > 0 DEFINITION lock_effective :: nat ⇒ nat ⇒ bool THEOREMS lock_eventually_expires qed deadlock_freedom qed lock_expiry_bound qed lock_effective_before_timeout qed REUSABLE FOR Distributed DB locks, file system locks, distributed cache locks fair_leader_system FIXES leader_at, is_honest, pending, fairness_bound ASSUMES fair_leader (∃ honest within k) honest_progress (pending ↓) non_honest_bounded (≤ prev) THEOREMS pending_monotone qed starvation_bound qed eventual_completion sorry REUSABLE FOR Leader-based consensus (Raft, PBFT), round-robin schedulers 5 sorry remaining in Priority_Resolution.thy · 0 sorry in DQuencer_Instance.thy · All deadlock_free_locking theorems proven
PRIORITY_RESOLUTION.THY — THREE GENERIC LOCALES
priority_system
Fixes
priority :: ‘m ⇒ ‘k::linorder
Assumes
priority_injective
Theorems
highest_priority_exists sorry
select_highest_deterministic sorry
select_highest_in_set sorry
select_highest_is_max sorry
Reusable for: DeFi tx ordering, MEV auctions, distributed scheduler priority
deadlock_free_locking
Fixes
timeout :: nat
Assumes
timeout_positive: timeout > 0
Theorems
lock_eventually_expires qed
deadlock_freedom qed
lock_expiry_bound qed
lock_effective_before_timeout qed
Reusable for: Distributed DB locks, file system locks, distributed cache locks
fair_leader_system
Fixes
leader_at, is_honest, pending, fairness_bound
Assumes
fair_leader (∃ honest within k epochs)
honest_progress (pending ↓ strictly)
non_honest_bounded (pending ≤ prev)
Theorems
pending_monotone qed
starvation_bound qed
eventual_completion sorry
Reusable for: Leader-based consensus (Raft, PBFT), round-robin schedulers

Figure 1: Priority_Resolution.thy — Three Generic Locales with Assumptions, Theorems, and Proof Status

DQuencer_Instance.thy: Instantiation into the Regulatory Domain

DQuencer_Instance.thy is the junction where two worlds merge.

theory DQuencer_Instance
  imports Priority_Resolution Regulatory_Instance
begin

It imports the three generic locales from Priority_Resolution and the regulatory state transition model, global state, and sync function from Regulatory_Instance. This file serves three roles: (1) defining regulatory domain-specific types and functions, (2) instantiating the generic locales, and (3) combining Properties 1 and 2.

authority_level and action_severity

The regulatory authority hierarchy is formalized. The RCP framework‘s jurisdictional priority model, described in the previous post, is translated into code.

datatype authority_level = Regional | National | International

fun authority_rank :: "authority_level ⇒ nat" where
  "authority_rank Regional = 1"
| "authority_rank National = 2"
| "authority_rank International = 3"

authority_rank_injective is proven immediately by the cases tactic, since three enumeration values map to distinct nats.

Regulatory action severity is also formalized.

fun action_severity :: "reg_action ⇒ nat" where
  "action_severity UNRESTRICT = 1"
| "action_severity UNFREEZE = 2"
| "action_severity RELEASE = 3"
| "action_severity RESTRICT = 4"
| "action_severity FREEZE = 5"
| "action_severity SEIZE = 6"
| "action_severity CONFISCATE = 7"

This ordering reflects legal principles. Asset-protective actions (CONFISCATE, SEIZE, FREEZE) have higher priority than asset-releasing actions (UNRESTRICT, UNFREEZE, RELEASE). When concurrent conflicting regulatory orders arrive, the more conservative action prevails, consistent with legal practice.

Priority Key: Lexicographic Order on nat Tuples

A core design decision of Property 2 modeling lies here.

type_synonym priority_key = "nat × nat × nat × nat"

In Isabelle/HOL, nat × nat × nat × nat automatically has a linorder instance. The lexicographic order on product types is defined in the standard library. This decision matters because no separate linorder instance registration is required. Manually registering a linorder instance for a new datatype in Isabelle requires defining less_eq and less, then proving antisym, trans, and total, a non-trivial process. Using nat tuples provides all of this automatically.

One technical issue exists, however. In nat’s default order, “larger value = higher rank,” but for timestamps and node_id, “smaller value = higher priority” is needed. Earlier-issued regulatory orders take precedence, and lower-numbered nodes win tiebreakers.

The solution is sign inversion.

umber line inversion diagram drawn in marker on smoothed-out scrap paper lit only by laptop screen glow with a note questioning nat underflow

The sign inversion sketch that became max_time – msg_timestamp. Smaller timestamps produce larger priority keys.

definition make_priority_key ::
  "nat ⇒ nat ⇒ dq_message ⇒ priority_key"
where
  "make_priority_key max_time max_node msg =
    (authority_rank (dqm_authority_level msg),
     max_time - msg_timestamp msg,
     action_severity (msg_action msg),
     max_node - dqm_source_node msg)"

In max_time - msg_timestamp msg, max_time is the system-wide upper bound for timestamps. The smaller the timestamp (the earlier it arrived), the larger this value becomes, giving it higher rank in nat’s default order. The same logic applies to max_node.

Preventing nat underflow is essential in this construction. If max_time < msg_timestamp, the subtraction truncates to 0 in nat (floor truncation of nat subtraction). To prevent this, msg_timestamp msg ≤ max_time and dqm_source_node msg ≤ max_node are required as preconditions.

Message Extension through Record Inheritance

Property 1’s oss_message is extended without modification.

record dq_message = oss_message +
  dqm_authority_level :: authority_level
  dqm_source_node     :: nat

Isabelle’s record inheritance mechanism makes dq_message a subtype of oss_message. All fields defined on oss_message (msg_action, msg_asset_id, msg_authority, msg_timestamp, msg_source, msg_targets) are accessible on dq_message as well.

The practical value of this decision is that Property 1’s sync function only uses oss_message fields, so passing dq_message to sync requires no explicit conversion. The combined_safety_liveness theorem can directly invoke Property 1’s sync and valid_state_preservation.

Byzantine Node Model and dquencer_system Locale

Nodes are dichotomized into Honest and Byzantine.

datatype node_behavior = Honest | Byzantine

record node_info =
  ni_id       :: nat
  ni_behavior :: node_behavior

The dquencer_system locale encapsulates the system-wide BFT assumptions.

locale dquencer_system =
  fixes nodes :: "node_info set"
    and f_max :: nat
    and lock_timeout :: nat
    and fairness_bound :: nat
    and max_time :: nat
    and max_node :: nat
  assumes finite_nodes: "finite nodes"
    and bft_threshold: "card nodes ≥ 3 * f_max + 1"
    and byzantine_bound: "card (byzantine_nodes nodes) ≤ f_max"
    and nonempty_nodes: "nodes ≠ {}"
    and timeout_positive: "lock_timeout > 0"
    and fairness_positive: "fairness_bound > 0"

bft_threshold directly expresses \(n \geq 3f + 1\). From this, honest_majority is derived.

lemma honest_majority:
  "card (honest_nodes nodes) > 2 * f_max"

The proof combines honest_byzantine_partition (partition), honest_byzantine_disjoint (disjointness), and card_Un_disjoint (cardinality of disjoint union) through arithmetic reasoning. honest_nonempty follows immediately as a corollary: honest nodes exceed \(2f_{\max}\), hence exceed 0, hence the set is non-empty.

BFT Consensus Abstraction

Rather than formalizing BLS signature cryptographic security or the vote counting process, we model only the result property of consensus: under honest majority, the consensus output matches the highest-priority valid message.

definition bft_select ::
  "dq_message list ⇒ global_state ⇒ nat ⇒ nat ⇒ dq_message option"
where
  "bft_select msgs gs max_t max_n =
    (let valid_msgs = filter (valid_dq_message gs) msgs;
         prio_msgs = map (λm. (make_priority_key max_t max_n m, m)) valid_msgs;
         sorted = sort_key fst prio_msgs
     in if sorted = [] then None else Some (snd (last sorted)))"

valid_dq_message verifies that a message represents a valid transition in the current global state. The asset must exist, and the action must return Some from reg_transition given the current state.

The justification for this abstraction follows three steps: (1) with over 2/3 honest nodes, the BFT consensus output reflects honest majority agreement; (2) honest nodes follow the priority protocol, so the consensus result is the highest-priority message; (3) since priority is a total order, the result is deterministic.

Locale Instantiation

The deadlock_free_locking instantiation is the most direct.

context dquencer_system
begin

interpretation dq_locking: deadlock_free_locking lock_timeout
  by unfold_locales (rule timeout_positive)

The dquencer_system‘s timeout_positive assumption immediately satisfies deadlock_free_locking‘s sole assumption. After instantiation, dq_locking.deadlock_freedom and dq_locking.lock_eventually_expires become directly usable in the D-quencer context.

The fair_leader_system instantiation is more elaborate. A separate dquencer_liveness locale is defined, adding the leader schedule and pending queue function on top of dquencer_system.

locale dquencer_liveness = dquencer_system +
  fixes leader_schedule :: "nat ⇒ node_info"
    and pending_count :: "nat ⇒ nat"
  assumes fair_leader: "..."
    and honest_processes: "..."
    and pending_non_increasing: "..."
begin

interpretation dq_fair: fair_leader_system
  leader_schedule "λn. ni_behavior n = Honest" pending_count fairness_bound

The abstract is_honest function is concretized as λn. ni_behavior n = Honest, connecting the abstract notion of “honesty” to D-quencer’s node_behavior type.

DQuencer_Instance.thy — Locale Instantiation and Combined Theorem Shows how three generic locales from Priority_Resolution.thy are instantiated with regulatory domain types and combined with Property 1 results in DQuencer_Instance.thy DQUENCER_INSTANCE.THY — INSTANTIATION FLOW PRIORITY KEY CONSTRUCTION priority_key = nat × nat × nat × nat (Isabelle built-in lexicographic linorder) 1ST — HIGHEST authority_rank Int(3) > Nat(2) > Reg(1) Higher authority = higher 2ND max_time – timestamp Sign inversion: earlier = higher Requires: ts ≤ max_time 3RD action_severity CONFISCATE(7) > … > UNRESTRICT(1) Conservative action prevails 4TH — TIEBREAKER max_node – node_id Sign inversion: lower = higher Ensures global injectivity LOCALE INSTANTIATION interpretation: priority_system priority = make_priority_key max_t max_n Injectivity: 4-tuple uniqueness from authority + ts + act + node interpretation: dq_locking deadlock_free_locking lock_timeout Proof: unfold_locales (rule timeout_positive) interpretation: dq_fair fair_leader_system leader_schedule (λn. ni_behavior n = Honest) pending_count fairness_bound BFT SYSTEM MODEL locale dquencer_system: card nodes ≥ 3 * f_max + 1 → honest_majority: card (honest_nodes) > 2 * f_max → honest_nonempty: honest_nodes ≠ {} COMBINED THEOREM combined_safety_liveness: valid_state gs ∧ valid_transition ∧ ¬locked → ∃gs’. sync … gs = Some gs’ ∧ valid_state gs’
DQUENCER_INSTANCE.THY — INSTANTIATION FLOW
Priority Key Construction
priority_key = nat × nat × nat × nat
1
authority_rank
Int(3) > Nat(2) > Reg(1)
2
max_time – timestamp
Sign inversion: earlier = higher
3
action_severity
CONFISCATE(7) > … > UNRESTRICT(1)
4
max_node – node_id
Tiebreaker: lower ID = higher priority
Locale Instantiation
interpretation: priority_system
priority = make_priority_key max_t max_n
Injectivity from 4-tuple uniqueness
interpretation: dq_locking
deadlock_free_locking lock_timeout
Proof: unfold_locales (rule timeout_positive)
interpretation: dq_fair
fair_leader_system leader_schedule
(λn. ni_behavior n = Honest)
pending_count fairness_bound
BFT System Model
card nodes ≥ 3 * f_max + 1
→ honest_majority > 2 * f_max
→ honest_nonempty
COMBINED THEOREM
combined_safety_liveness:
valid_state gs ∧ valid_transition ∧ ¬locked
→ ∃gs’. sync … gs = Some gs’ ∧ valid_state gs’

Figure 2: DQuencer_Instance.thy — Priority Key Construction, Locale Instantiation, and Combined Theorem

Combining Properties 1 and 2

The combined_safety_liveness theorem connects both properties at the end of the file.

theorem combined_safety_liveness:
  assumes valid: "valid_state gs"
    and exists: "asset_exists gs source aid"
    and current: "get_reg_state gs source aid = Some s"
    and trans: "reg_transition s action = Some s'"
    and not_locked: "¬ is_locked gs aid"
    and fin: "finite (connected_chains gs aid)"
  shows "∃gs'. sync source action aid gs = Some gs' ∧ valid_state gs'"

This theorem directly invokes Property 1’s valid_state_preservation. If the preconditions are met (valid state, asset exists, valid transition, not locked, finite connected chains), sync succeeds and the resulting state preserves valid_state.

This theorem itself contains no Byzantine elements. Property 2’s Byzantine-related guarantees (deterministic selection, timeout-based lock release, starvation freedom) ensure that this theorem’s preconditions are actually met in practice. Even if a Byzantine node holds a lock, timeout releases it so the not_locked precondition is eventually satisfied; deterministic priority resolution ensures a valid action is selected; and starvation freedom ensures this process completes within finite time.

This is the formal meaning of “Property 2 satisfies in a Byzantine environment the if-condition that Property 1 defined.”

Current Sorry Status

The following table summarizes the remaining sorry obligations and their expected resolution strategies.

FileTheoremSorry ContentExpected Resolution
Priority_Resolution.thyhighest_priority_existsExistence directionConnect to Finite_Set max lemma
Priority_Resolution.thyselect_highest_deterministicTHE uniquenessFollows from highest_priority_exists
Priority_Resolution.thyselect_highest_in_setSet membershipUnfold definition + THE property
Priority_Resolution.thyselect_highest_is_maxMaximalityUnfold definition + THE property
Priority_Resolution.thyeventual_completionWell-founded inductionInduction on nat pending count
DQuencer_Instance.thy(none)

Of the 5 sorry, three (select_highest_*) are likely to resolve once highest_priority_exists is solved. eventual_completion requires a separate induction construction.

That DQuencer_Instance.thy has zero sorry is noteworthy. The instantiations and the combined theorem are all built on previously proven lemmas.

Design Decisions in the Modeling Process

Decision 1: nat Tuples vs Custom Datatype

The first fork was whether to use nat × nat × nat × nat for the priority key or define a dedicated datatype priority_key with manual linorder registration.

A dedicated datatype offers stronger type safety. nat × nat × nat × nat accepts any 4-tuple, while a dedicated datatype constrains construction to meaningful values only. However, the cost of linorder instance registration is substantial: defining less_eq and less, proving antisym, trans, and total, then proving that this order coincides with the intended lexicographic order. Property 2’s core contribution is not the order structure itself but the liveness proofs built on top of it, making excessive engineering effort on the order structure inefficient.

The choice of nat tuples was driven by three factors: automatic linorder provision, reduced modeling complexity, and focus on the essential contribution.

Decision 2: BFT Consensus Abstraction Level

Should we formalize BLS signatures? Model the vote counting process? Or assume only the determinism of consensus output?

Formal verification of consensus protocols is a complete research topic on its own. Tendermint’s TLA+ specification and Gasper’s Dafny verification were each independent research projects. Including consensus protocol verification in Property 2’s scope would expand the proof goal from “liveness under Byzantine faults” to “BFT consensus correctness + liveness under Byzantine faults,” significantly reducing feasibility.

The chosen abstraction: express that the consensus output is the highest-priority valid message as a structural property of the model, while leaving the fact that BFT consensus actually achieves this outside the proof scope. The bft_select function is this abstraction’s implementation.

Decision 3: Radical Simplification of Locale Signatures

In the initial design, the three locales’ signatures were far heavier than their final form. priority_system included messages :: "'m set" as a locale fix, making the message set itself a fixed parameter of the locale. deadlock_free_locking had six fixes: resources, is_locked, lock_holder, lock_time, timeout, and current_time.

During the actual .thy writing process, we drastically reduced these signatures. messages was removed from priority_system and converted to a theorem argument S. In deadlock_free_locking, only timeout :: nat remained, with all other parameters moved to definitions and theorem arguments.

The motivation for this change was locale reusability. The more parameters included in locale fixes, the more assumptions must be satisfied during instantiation, narrowing the locale’s scope. The core insight of deadlock_free_locking is that “if the timeout is positive, deadlock cannot occur,” and expressing this requires neither the resource set nor lock holder information. The single assumption timeout > 0 suffices to prove both lock_eventually_expires and deadlock_freedom.

The initial design also included a dquencer_state record (containing epoch, leader, pending queue, and lock timestamps for the complete D-quencer state). In the actual implementation, this was removed and distributed across locale parameters: dquencer_system for system-wide constants and dquencer_liveness for schedule-specific functions. A monolithic state record creates the burden of handling irrelevant fields in every proof step. Distribution means each theorem explicitly uses only the parameters it actually depends on, producing cleaner proof structures.

The planned to_oss_message conversion function from the design document also became unnecessary. Isabelle’s record inheritance makes dq_message a subtype of oss_message, so existing field accessors (msg_action, msg_asset_id, etc.) work directly on dq_message. Automatic subtyping replaced explicit conversion, making both code and proofs more concise.

These simplifications were not planned in advance but discovered during the actual writing of the .thy files. A similar experience occurred in Property 1, where the proving process revealed that separating consistent_state and no_locked_without_reason was more natural than the initial monolithic valid_state. In formal verification, the difference between the design document and the final code is not failure but evidence that the proof tool reveals better structure.

Decision 4: Accepting the Closed-System Assumption

The non_honest_bounded assumption (pending queue non-increase during dishonest leader epochs) presupposes a closed system. In an open system with continual request arrivals, this assumption does not hold, and starvation freedom would require modeling the relationship between arrival rate and throughput.

The reason for accepting this assumption: Property 2’s goal is to show that “a given finite set of regulatory requests is fully processed.” Arrival rate analysis belongs to queueing theory, requiring different methodological tools than formal verification. Recognizing the limitation, recording it explicitly, and providing strong guarantees within the current scope is the honest approach.

Next Steps: Sorry Elimination

Five sorry remain. The next post will document the process of eliminating them. From Property 1’s experience, sorry elimination was not mere debugging but an opportunity to improve the model structure itself. The redesign of fold to λ during the regulatory_homomorphism proof is one example.

We expect similar discoveries in Property 2. Whether the Finite_Set library connection in highest_priority_exists works as expected, and how directly the well-founded induction in eventual_completion can be constructed, can only be determined by attempting the actual proofs. Just as unexpected difficulties arose in Property 1 where they were least anticipated, the same will be true for Property 2.


References

[1]. Lochbihler, A., Schneider, J. (2021). Mechanizing Authenticated Data Structures in Isabelle/HOL. CPP 2021. https://dl.acm.org/doi/10.1145/3437992.3439921

[2]. Nipkow, T., Paulson, L., Wenzel, M. (2002). Isabelle/HOL: A Proof Assistant for Higher-Order Logic. Springer. https://isabelle.in.tum.de/doc/tutorial.pdf

[3]. Nipkow, T., Klein, G. (2014). Concrete Semantics with Isabelle/HOL. Springer. https://concrete-semantics.org/

Learn More

Defining D-quencer Liveness under Byzantine Faults

Cross-Domain State Preservation: What Was Proven and What It Means

Oraclizer Formal Verification (GitHub)

Read Next

Three Category Axioms, One Proved Functor
A research-journal account of mechanizing the functor laws and the degree-hierarchy natural transformation in Isabelle/HOL, where the obstacles were almost never the mathematics. The measure decrease I feared closed at once; a reserved keyword broke seven builds; a finiteness clash forced a redefinition; and the code overturned my belief that glue gives validity.
Oraclizer Core ⋅ Jun 06, 2026
Insurance and Recovery Economics: Preparing for Black Swan Events
Earlier designs cut node risk by 73%, but the unpredictable 27% needs different rules. This study fixes how a staking insurance pool is sized (15% of stake, not protected value), bootstrapped, and banded; why a reserve held in its own token collapses with it; and how session protection follows the sync-degree hierarchy when security breaks mid-session.
Oraclizer Core ⋅ May 29, 2026
Tokenized Securities Under the CLARITY Act: The Weight of Codification
The CLARITY Act tokenized securities clause settles a single proposition in statute: tokenization is a delivery method, not a new asset class. That one sentence codifies the regulatory status of tokenized securities in U.S. law for the first time and derives an entire infrastructure specification for boundaries the token crosses.
Oraclizer Core ⋅ May 23, 2026
Sync Degree Hierarchy: Classifying What Assets Demand from State Synchronization
Sync degree hierarchy turns sync requirement strength into a four-level classification axis for RWA assets. S₀ static through S₃ atomic state binding form a reduction relation where causal consistency separates S₁ from S₂. Existing oracles, structurally two independent channels, are capped at S₁ by definition. Regulatory action forces S₃.
Oraclizer Core ⋅ May 20, 2026