Trillium: Higher-Order Concurrent and Distributed Separation Logic for Intensional Refinement

Expressive state-of-the-art separation logics rely on step-indexing to model semantically complex features and to support modular reasoning about imperative higher-order concurrent and distributed programs. Step- indexing comes, however, with an inherent cost: it restricts the adequacy theorem of program logics to a fairly simple class of safety properties. In this paper, we explore if and how intensional refinement is a viable methodology for strengthening higher-order concurrent (and distributed) separation logic to prove non-trivial safety and liveness properties. Specifically, we introduce Trillium, a language-agnostic separation logic framework for showing intensional refinement relations between traces of a program and a model. We instantiate Trillium with a concurrent language and develop Fairis, a concurrent separation logic, that we use to show liveness properties of concurrent programs under fair scheduling assumptions through a fair liveness-preserving refinement of a model. We also instantiate Trillium with a distributed language and obtain an extension of Aneris, a distributed separation logic, which we use to show refinement relations between distributed systems and TLA+ models.


INTRODUCTION
There is a tension between the expressivity of program logics and how much they say about the semantics of the programs being verified, that is, the strength of their adequacy theorems.As program Focus and Methodology.In this paper, we use Iris to establish intensional refinements between programs and labeled transition systems (LTSs), including the strong notion of liveness-preserving refinements, for concurrent and distributed programs.We develop Trillium, a language-agnostic generic program logic, whose adequacy theorem guarantees the existence of a refinement between the program and an LTS chosen by the prover.This is in addition to the usual properties enjoyed by program logics for safety reasoning as mentioned above, i.e., postconditions, progress, and preservation of invariants.The key insight is that, by showing an intensional refinement between a program and an LTS, we can-indirectly-establish non-trivial safety trace properties and liveness properties such as fair termination of concurrent programs.By proving that the LTS enjoys the property of interest (which is often, if not always, simpler than proving it for the program itself), we can use the refinement relation to "transport" the property to the program.For this reason, we will refer to the LTS as a model or specification of a program implementation.To the best of our knowledge, this is the first foundationally verified proof that a concrete implementation of a distributed protocol correctly implements an abstract TLA + specification.• We further show functional correctness and strong eventual consistency of a concrete implementation of a Conflict-Free Replicated Data Type (CRDT).The challenging part is incorporating the notion of fairness of the inter-replica communication; if messages from one replica are just ignored, then eventual consistency will never be reached.Moreover, the concurrent interactions with the user-exposed operations makes it non-trivial to reason about eventual consistency.To the best of our knowledge, this is the first such proof that takes into account the inter-replica communication at the level of the implementation.For the sake of space, we have relegated further details about this example to the accompanying appendix.• All the results that appear in the paper have been formalized in the Coq proof assistant using the Iris separation logic framework.

BACKGROUND AND KEY OBSERVATIONS
We will think of the operational semantics of a concurrent program as an LTS where the transition labels are thread identifiers corresponding to the thread taking the step.That an LTS refines another is a standard notion: two states are in a refinement relation if there exists a forward simulation relation  that relates them (see, e.g., Cleaveland and Sokolsky [2001]). 3The goal in this paper is to transport intensional safety and liveness properties of (possibly infinite) traces along such a refinement relation, e.g., transporting the property "the value of the counter increases (or stays the same) monotonically without skipping over any number" from the model Chain in Figure 1 to the program count _ up in Figure 2.
To transport intensional properties, we will work with intensional refinement, which is a lockstep relation where every step of the program is matched by a step of the model.This is, of course, too strong if taken literally: for example, the step of computation corresponding to a recursive call of count _ up does not increment the counter and hence does not correspond to a step in the model.For now, we will ignore this issue; in the following section we will present constructions on LTSs that will allow us to relax the correspondence between the program and the model, while still allowing intensional properties to be transported.
We recall the precise definitions of forward simulation and refinement.The definitions are relative to a parameter , a relation on traces, which provides for a bit of flexibility, by allowing one to restrict attention to traces satisfying .Here FAA is the (atomic) fetch-and-add operation which increments the integer stored in its first argument (a reference) by the given amount in the second argument.We assume that the value of  is zero at the beginning.
Definition 2.1 (-forward simulation).Let  be a binary relation on finite traces.A relation  is a -forward simulation, written ForSim  (), if: where last maps a trace to its end state.
Definition 2.2 (Intensional refinement).Let  be a binary relation on finite traces.A finite trace  is an intensional refinement of a finite trace  ′ , with respect to parameter , written  ≼   ′ , if there exists a -forward simulation relation  such that (,  ′ ).That is,  ≼   ′ ≜ ∃.ForSim  () ∧ (,  ′ ) In our running example, the parameter  is used to restrict our attention to traces where the value of the counter and the model state agree at all times.
The Trillium program logic is designed to establish (cf.Theorem 3.2) an intensional refinement  ≼   between singleton traces consisting of a program state  and a model state .From Lemma 2.4 below it then follows directly that any possibly-infinite execution of the program can be matched by a possibly-infinite trace of the model in such a way that all their corresponding finite prefixes are in the intensional refinement relation.

Definition 2.3 (Trace Relation Extension
).Given a relation  on finite traces, we lift  to possiblyinfinite traces, written R, by considering all finite prefixes as follows: where prefix (,  ′ ) means  is a prefix of  ′ .
Trillium: Higher-Order Concurrent and Distributed Separation Logic for Intensional Refinement 9:5

Program Steps That Do Not Correspond to Steps in the Model
The core idea of the methodology we propose in this paper is that the model refined by the program is more abstract, and hence simpler and easier to reason about, than the program.Thus, in general, there will be steps in the program that do not correspond to any step in the model.One way to reconcile this with our notion of refinement is to allow for stuttering.That is, allow the program to take a step while the model stays in the same state.We will support stuttering by lifting the model into an LTS that allows stuttering.We consider two kinds of lifting, one that is only sound for intensional safety trace properties, and one that is sound for liveness trace properties as well.
For the former, we show that it is sound in the sense that, if a program refines the lifted model, and the original model enjoys an intensional safety trace property, so does the program.Similarly, the latter lifting is sound with respect to liveness trace properties.
We remark on a subtle point here, namely that Trillium is a framework, which means that when we combine the base program logic of Trillium with one of the two aforementioned liftings, we obtain two different program logics.The choice of lifting presents a compromise between expressivity and simplicity of the derived logical principles of the program logic.In particular, for the lifting that is sound for intensional safety properties, we obtain a program logic that is conservative with respect to the ordinary program logic of Iris in that all the reasoning principles of the Iris program logic are still sound, and in addition one obtains simple reasoning principles that allow proving refinements (cf.§5).For the lifting that is sound with respect to liveness properties, we obtain a program logic that is more involved but allows proving liveness properties (cf.§4).
Stutter-Lifting: Sound for Intensional Safety Trace Properties.This lifting is very simple: it essentially amounts to adding self-loops, with a special label, to all states of the LTS.For example, the LTS Chain from Figure 1 would result in the following LTS, where dotted arrows are added to support stuttering: . . .
(Chain-Loops) Stutter-Lifting of the LTS Chain in Figure 1 The program count _ up in Figure 2 is an intensional refinement of the model Chain-Loops above if we take the parameter  to relate the value of memory location  with the state of the model-the recursive call then corresponds to taking the self-loop in the relevant state.As mentioned above, this stutter-lifting is sound for intensional safety trace properties.Hence, to show that "the value of the counter increases (or stays the same) monotonically without skipping over any number", it suffices to show that this property is enjoyed by Chain in Figure 1.
However, this lifting is not sound for liveness properties, because it is also refined by (using the same  parameter) the following program: which only increments  once and afterwards loops forever.To see this, take a very simple liveness property like "the value of the counter is eventually 3" which is trivially true for the LTS Chain in Figure 1, but not for the program above.The culprit here is unrestricted stuttering.
Fin-Stutter-Lifting: also Sound for Liveness Properties.To obtain soundness with respect to liveness properties, we define a fin-stutter-lifting construction, which only allows for finite stuttering.That is, instead of adding loops, it essentially adds finite unrollings of loops, by creating copies of each state, each of which allows at most a certain, fixed number of stuttering steps.For instance, the Fin-Stutter-Lifting of the LTS Chain in Figure 1 is given blow: The idea is to add states {  |  ∈ N} (for each state ), all of which intuitively correspond to state .From a state , we can either go to the state  + 1, or stutter to a state   from where we can at most stutter  times before going to state  + 1.The Fin-Stutter-Lifting construction is sound for liveness properties.In fact, it is the core idea of the so-called fuel construction we present in §4.3.Note that, as expected, the program above which increments the counter only once and then loops forever does not refine the LTS Chain-Fin-Stutter.

Step-Indexing and Finite Approximability
As mentioned in §1, step-indexing prevents direct reasoning about liveness properties because it restricts reasoning to finite prefixes of program execution.The same complication arises when establishing refinement relations.In this work, this issue shows up in Theorem 3.2 as the relative image-finiteness side-condition that we will discuss and formally define here.In order to see this issue concretely, revisit the LTS FinChains from Figure 1.FinChains has no infinite paths and hence any program that refines the fin-stutter-lifting of FinChains, regardless of the  parameter, must terminate.However, in a step-indexed logic, one can show a refinement relation between the program count _ up in Figure 2 and the fin-stutter-lifting of FinChains.To see this, simply take the  parameter for this step-indexed refinement relation to require that the value of l corresponds to the second component of the state in the fin-stutter-lifting of FinChains.In this case, for any finite trace of the program, there is a trace in the fin-stutter-lifting of FinChains that matches it according to this  relation.The key point here is that when the value of l goes from 0 to 1 in the program, on the model side we go from the state (∞, 0) to (, 1), where  is the number of steps of execution being considered.Hence, it would not be sound, in this case, to conclude an intensional refinement relation from the refinement relation established in the step-indexed logic.The crux of the issue here is the unbounded choice of transitions going out of the state (∞, 0) which allows us to pick a path based on the number of steps of execution that we are considering.Below we analyze this problem more formally, and conclude that a so-called relative image-finiteness side-condition suffices to circumvent the problem.(Note that the problem of unbounded branching is already present in the fin-stutter-lifting construction; we will discuss this issue further when we explain the fuel construction in §4.3.) We first define a notion of finite approximation, which intuitively corresponds to what guarded recursive predicates compute.Guarded recursive predicates are those defined as fixed points using the step-indexing technique, e.g., the weakest preconditions underlying the program logic of both Iris and Trillium.
Definition 2.5.Let  be a function on the space of binary relations on traces of LTSs  1 and  2 , i.e.,  : 2 tr ( 1 ) ×tr ( 2 ) → 2 tr ( 1 ) ×tr ( 2 ) , where 2  is the powerset of , and tr () is the set of all traces of the LTS .We define the finite approximation of  , written FinApprox ( ), as  The upshot of the limitation of step-indexed logics is that the best we can hope to conclude from a refinement relation defined in a step-indexed logic is a finite approximation of the refinement relation.Specifically, in our case we can conclude FinApprox ( ≼  ), where  ≼  is the function whose greatest fixed point (by the Knaster-Tarski fixed point theorem) is the intensional refinement relation in Definition 2.2: In other words, we can only conclude ≼  if ≼  is finitely approximable, i.e., if ≼  = FinApprox ( ≼  ), which is well-known not to be the case in general for refinement relations.Indeed, one frequently used condition for finite approximation is so-called image-finiteness [Cleaveland and Sokolsky 2001, Thm. 2.6].

Further Discussions
Coming up with the Appropriate Model.One natural question regarding the methodology that we present in this paper is "how does one come up with the appropriate model and the parameter  for the verification task at hand?"We argue that coming up with the appropriate model and  parameter is of the same nature, and indeed part of picking the appropriate specification, e.g., relevant preconditions and postconditions.Hence, there is no obvious, one-size-fits-all answer.Indeed, the model and the relation often need to be designed so as to facilitate establishing the safety trace property or liveness property of the program we wish to prove, e.g., the relation  and the model Chain in Figure 1 that we chose for the program count _ up in Figure 2, in order to establish that "the value of the counter increases monotonically without skipping over any number".Moreover, at a technical level, the limitation of relative-image-finiteness of the  relation restricts us in the choice of the model and the  parameter.For these reasons, the chosen model may not be arbitrarily abstract.It must reflect some of the core characteristics of the program, at least to the extent necessary for the trace property in question, and for the  to be relative image-finite.It is our hypothesis that coming up with appropriate models and  relations is feasible in most, if not all, interesting examples.However, in the present paper we only present the foundation and methodology of using intensional refinement to strengthen the expressivity of step-indexed higher-order concurrent (and distributed) separation logics, and support it with simple examples.A proper experimental evaluation of the hypothesis, using a wide range of more advanced examples, is beyond the scope of the current work, and we leave it for future work.
What about Transfinite Iris?Transfinite Iris [Spies et al. 2021] is a variant of Iris whose model is step-indexed over an arbitrary ordinal (as opposed to the natural numbers used in the model of ordinary Iris).The upshot of this change is that Transfinite Iris satisfies the so-called "existential property" [Spies et al. 2021] which in effect renders the (relative) image-finiteness side-condition unnecessary.We believe that the work of this paper could also be carried out on top of Transfinite Iris, dispensing with the relative image-finiteness side-condition, albeit at the cost of other possible complications in proofs. 4 Spies et al. [2021] prove termination and termination-preserving refinements of sequential programs but do not show any (preservation of) liveness properties beyond termination nor do they treat concurrent programs.
What about the Approaches for Contextual Refinement?There have been several works on establishing contextual refinement in Iris for complex sequential and concurrent programming languages [Frumin et al. 2018[Frumin et al. , 2020;;Georges et al. 2022;Gregersen et al. 2023;Jacobs et al. 2022Jacobs et al. , 2021;;Krebbers et al. 2017;Krogh-Jespersen et al. 2017;Timany and Birkedal 2019;Timany et al. 2018].In essence, contextual refinement boils down to showing that if one program terminates, so should the other.In these previous works, it is established by the use of invariants in Iris.This implies that the approach only allows one to show that for any finite prefix of execution of the first program (the implementation side) there exists a finite execution of the second program (the specification side) and the final states correspond.This notion of refinement is too weak for our purposes: (1) it says nothing about infinite executions and hence does not help us establish liveness properties, and (2) it does not allow us to transfer non-trivial safety trace properties.In the Appendix we discuss an illustrative example.

TRILLIUM: A TRACE PROGRAM LOGIC FRAMEWORK
In this section, we give a more detailed account of the general Trillium logic and a formal statement of its adequacy theorem.We first detail how we instrument the operational semantics of the domain programming language with "locale" transition identifiers (essentially thread id's), to facilitate thread-level properties such as fair scheduling.Second, we present the fundamentals of the Trillium logic, and its adequacy theorem (Theorem 3.2).We focus on the novelties of Trillium but will recount necessary constructions of the Iris base logic briefly at a high level.
Language Agnostic Framework.The Trillium program logic is language agnostic and is defined with respect to any programming language which comes with an operational semantics given by a notion of expression  ∈ Expr, value  ∈ Val ⊆ Expr, evaluation context  ∈ Ectx, program state  ∈ State (a model of, e.g., the heap and/or the network), and a primitive reduction relation  1 ,  1 ⇝  2 ,  2 ;   1 , • • • ,    that relates an expression  1 and a state  1 to an expression  2 , a state  2 , and a (possibly empty) list   1 , . . .,    of expressions, corresponding to the threads forked by the reduction.A value denotes an expression that has reached its final form and will no longer reduce.We write  [] for the result of replacing the hole in evaluation context  with .
The global state of the system is a configuration c = (tp, ), where the thread pool tp is a finite mapping from locales5 to expressions, each corresponding to an execution thread.We will write tp( ) for the expression whose locale is  in tp and use tp[ ↦ → ] for the corresponding update (which adds a new thread if  ∉ dom(tp)).We will write { ↦ → } for the singleton thread-pool consisting of a single thread .For a language with shared-memory concurrency a locale would simply be a thread identifier.For a distributed language, a natural definition would be a pair (, tid) of the name  of the node and the thread identifier tid of the thread in that node.Having explicit locales as part of the language definition will be beneficial when expressing, e.g., thread-level properties such as fair scheduling.
The primitive reduction relation is lifted to an LTS by a relation between configurations labeled by the step-taking locale as follows: ( 1 ,  1 ) ⇝ ( 2 ,  2 ; where fr ( , tp) is an infinite sequence of fresh locales (not in dom(tp)) derived from  , e.g., in case of distributed systems it would consist of fresh thread identifiers on the same node as  .
The Trillium Program Logic.The goal of the Trillium program logic is to establish an intensional refinement c ≾  m between singleton traces consisting of the initial program configuration c and a model state m.Proving this refinement is a matter of proving that  always holds, throughout the execution of the program starting in c, alongside a corresponding traversal of the model starting from m.This can be achieved by ensuring that the program execution makes progress in tandem with the model, in addition to the relation  being preserved throughout any such execution.
Conventional Iris-style weakest precondition predicates wp E   guarantee postcondition validity of terminated programs, progress of program executions, and preservation of the invariants whose names are in the mask E. This can be seen explicitly in its definition; a guarded fixpoint of the following equation [Jung et al. 2018] (which, unlike in Trillium, does not consider locales): The definition is by case distinction: either  is a value, in which case the postcondition should hold, or  is not a value, in which case there are two requirements (ignoring | ⇛ E 1 E 2 and  () for now).First, for the current state (captured by  ()), the program should be reducible, i.e., it can make progress.Second, for any program  ′ and forked threads    that  might reduce to, the weakest precondition must hold as well (with some post condition Ψ for the forked threads).The later modality ⊲ guarantees that the fixpoint is well-defined (the recursive occurrence is guarded).
The update modality | ⇛ E 1 E 2  enables a form of rely-guarantee reasoning regarding invariants: to establish  the prover can access the invariants in E 1 but they must also establish all the invariants in E 2 alongside proving .Hence, the definition of the weakest precondition preserves invariants in E by giving access to all of them (by going from E to ∅) but requires them to be preserved by asking them to be closed immediately after each program step (by going back from ∅ to E).The predicate  : State → iProp is the state interpretation predicate that reflects the state (e.g., the heap) of the program as resources in the logic and gives meaning to, e.g., the traditional separation logic connective ℓ ↦ →  for heap ownership.Note how the definition of weakest precondition enforces that the state interpretation, just like invariants, is preserved throughout program execution.We often write wp E   . ≜ wp E  . , and wp E   ≜ wp E  . = () *  .
Remark 3.1 (Invariants and ghost resources in Iris).That  is invariant in Iris is represented by the proposition  N which is annotated with a name N that identifies it.In order to work with invariants formally in Iris, the update modality is annotated with two masks: We write | ⇛ E when E 1 = E 2 = E and | ⇛ when E = ⊤, the set of all masks.The update modality allows us to update ghost resources as described by Iris's ghost resource theory [Jung et al. 2018] and to access invariants.Intuitively, the proposition | ⇛ E 1 E 2  holds if we can establish  and all invariants in E 2 through ghost updates, without violating the environment's resources (a "frame preserving update"), and all the invariants in E 1 .For weakest preconditions (in both the Iris and Trillium program logic) we can manipulate resources and invariants throughout the proof because weakest preconditions are closed under the update modality: The following rules allow us to create and access invariants and to manipulate the update modality.Note how accessing an invariant only makes its contents available one step later (under ⊲); similarly we only need to prove it one step later to (re)establish the invariant.The inv-access rule-alongside providing the contents of the invariant-also tells us how we can close/reestablish the invariant. Inv-alloc These rules, along with the definition of weakest preconditions above allow us to prove a rule for accessing invariants during an "atomic" step of computation, i.e., a program that reduces to a value in a single step of computation. wp-atomic Note how wp-atomic might appear to consume the invariant.This is not an issue, though, as invariants are persistent and hence freely duplicable and shareable [Jung et al. 2018].
In the rest of this paper we will make heavy use of one kind of ghost resource, which we explain in terms of abstract predicates.Given a set  we define two predicates •  () and •  (), for any  ∈ , respectively called the full part and the fragment of the resource;  is the name of the resource instance used for disambiguation.These predicates are defined in terms of Iris ghost resources internally and satisfy the following rules, which essentially say that the full part and the fragment must always agree: auth-agree We refer to Jung et al. [2018] for a more thorough treatment of how invariants, the update modality, the later modality, and ghost state is constructed in Iris.
To define the Trillium program logic, we enrich the Iris-style weakest precondition to consider (1) program traces, (2) locales, and (3) a lock-step relation between the program trace and the model trace.Formally, this is defined as a guarded fixpoint of the equation below: Instead of just program configurations, the definition now considers all program traces  where the expression  is about to make a step at the locale  , under an evaluation context ; and for all steps that  may take, there must exist a model state m ∈ M that the last state of the model trace  can step to.Moreover, instead of a state interpretation, it tracks a trace interpretation  of the program and model traces, which will allow us not only to interpret the state of the language and the current model state as resources.Note also that in the case where  is a value, in order to establish the postcondition, we can also access the trace interpretation.This change is required for the soundness of some of our proof rules which apply even when the program is a value but whose correctness relies on the resources in the trace interpretation-we will discuss this later on.
That the definition of the Trillium weakest precondition has the intended meaning is the content of the following general adequacy theorem.In particular, we must show that the trace interpretation holds for the initial singleton traces, that the weakest precondition holds, and that the predicate AlwaysHolds(, , m) holds.The predicate AlwaysHolds(, , m) is an Iris predicate that states that  (which is a relation on finite traces, not mentioning resources of the logic) does in fact always hold, assuming that all invariants hold, that postconditions hold (for any thread that has terminated up to that point in the trace), that the trace interpretation holds, and that the program never gets stuck.We refer to our accompanying Coq development for the precise definition of the AlwaysHolds predicate.In practice, designing program logics on top of Trillium is thus a matter of carefully picking a trace interpretation that admits user-friendly reasoning principles while allowing the user to conclude strong  relations. 6he adequacy theorem of Trillium is much stronger than that of the ordinary Iris program logic.Recall that the usual adequacy theorem only establishes postcondition validity, progress, and invariant preservation.Trillium's adequacy theorem, on the other hand, establishes intensional refinement for any relation  which in turn can include all three aforementioned guarantees.In addition, as per Lemma 2.4, it allows us to show that infinite traces of execution of the program also have corresponding infinite model traces.Consequently, the adequacy theorem of Trillium is much more complicated to establish.The proof is in two stages.We first show that the weakest precondition implies a finite approximation of intensional refinement stated in terms of guarded recursion of Iris's base logic.We then show that the aforementioned guarded recursive definition implies intensional refinement-this is where we exploit relative image-finiteness.We refer to the accompanying Coq development for the details of the proof.some cases, e.g., §4.2.3, we may be interested in proving a liveness property under an assumption stronger than fairness, i.e., that all fair traces that satisfy a certain property  satisfy the desired liveness property .In such cases the user of Fairis is responsible for proving that the property  can be transported from the program to Fuel(M) and from Fuel(M) to M-in case of the example in §4.2.3, we are interested in infinite fair traces; infinitude, like finiteness, trivially transports as it is preserved by intensional refinement and finite stuttering.The Fuel construction essentially augments the model LTS to add two pieces of information: (1) for each thread id  , the set of roles that are associated to  , and, (2) for each role , the value of its so-called fuel, i.e., a number which measures how long the program can still postpone taking a step in that role in .This allows us to assign obligations to threads, i.e., assign a set of live roles to a thread-a role is said to be live if it can still take steps.A thread that has a set  of roles assigned to it must eventually (restricted by fuel) take a step in each of those roles.This is enforced by mandating that to take a step of computation, a thread must have a non-empty set  of live roles, and that after the step all fuels in  are decreased, except if this step was matched by a non-stuttering step corresponding to a role  ∈  (one of its live roles) in the underlying LTS M. In that case, the thread may increase the fuel of role  (but the fuel for all other roles in  must decrease) as long as it remains under a certain global cap which we will denote with F cap .7Alternatively, a thread can delegate some of its obligations to a thread that it forks.The adequacy theorem of Fairis requires a proof in the program logic that enforces that threads may only stop if there are no live roles assigned to them anymore; this is encoded through threads' postconditions.
The Fairis adequacy theorem is a special case of the Trillium adequacy theorem.The theorem is shown below, and has differences from the Trillium adequacy theorem highlighted: Here, •  M (m) and tid ⇒ F init are initial resources used in the Fairis program logic rules.In particular, •  M (m) is an exclusive resource that tracks the current state of the user model, while  ⇒ fs associates the locale  with a partial map fs : Role(M) ⇀ N that associates model roles with their fuels.The authoritative part •  M (m) of the resource is kept as part of the trace interpretation; the name  M is a globally fixed name for the entire proof created as part of the proof of the adequacy theorem.The resource •  M (m) is available to the user of the Fairis program logic to be able to relate the state of the program to the state of the model (which always agrees with the one tracked in the trace interpretation as per resource rules), usually in an invariant.This relation in the logic is then what the user will use to establish the  relation as required-we will see this in examples below.Initially, all roles are assigned to the single thread in the thread-pool with fuels all being F cap .The last piece of resource the user acquires from the adequacy theorem is the full ownership of the entire initial state (heap)  in that we obtain points-to propositions for all locations in .The adequacy theorem, and the rule for forking threads as we will see later, enforce that upon termination (in the postcondition) the thread must own no live roles.Finally, note that even though the adequacy theorem establishes a ≾  fuel relation, the user only needs to prove AlwaysHolds for  and not for  fuel .
In the rest of this section we cover how we apply Fairis to prove various liveness properties.First, we present the key ideas of the Fairis program logic, along with its reasoning rules, used for proving the weakest precondition of programs §4.1.We then give a tour of how the Fairis program logic can be used to prove liveness properties of programs §4.2, including fair termination of the yes-no example discussed earlier.Finally, we outline some of the technical details involved in obtaining the Fairis adequacy theorem §4. 3.
The examples that we present in this section are simple examples designed to demonstrate viability of the Trillium approach to liveness properties under fair scheduling assumptions.As such, in all these examples the LTS model used includes the entire core functionality of the program; only the administrative program steps, e.g., beta-reduction, are not included which are taken into account via stuttering enabled by the fuel construction.Hence, in that sense, the LTS models of these examples are not very abstract.For more involved examples, we expect that the level of abstraction of the LTS model compared to the program should be similar to that of Aneris examples; c.f. the Paxos example of §5 where the model abstracts away a substantial amount of implementation details.However, to substantiate this claim one needs a proper experimental evaluation, using a wide range of more advanced examples, which is beyond the scope of the current work, and thus we leave it for future work.

The Fairis Program Logic
The Fairis program logic combines the reasoning principles of conventional Iris program logics with reasoning principles involving fuel and model resources.To properly compartmentalize these orthogonal reasoning principles, the program logic employs two layers of reasoning: an outer (model) layer and an inner (program) layer.
The intuition is that at every program step, the program will either do a fuel step, corresponding to stuttering, or a model step, corresponding to a step in the underlying model.Both of these are handled by the outer model layer.Regardless of which kind of step the program takes, the inner layer will then be used to reason about the actual program step.The rules of the inner program logic only concern the program on its own and hence very closely resemble those of the ordinary program logic of Iris.
The inner program logic layer is expressed in terms of a different weakest precondition wp E  ⟨⟩ (note the angle brackets instead of braces for the postcondition).This weakest precondition is a (heavily) simplified version of Trillium's weakest precondition where the state interpretation (it indeed only mentions the state and not the entire trace) only concerns the heap of the program.In  Additionally, the inner weakest precondition strictly captures a single step of reduction as for further steps the outer program logic must intervene again to manage the model-side details.As a result, the postcondition of the inner weakest preconditions are predicates over arbitrary expressions as opposed to values, i.e., a predicate  : Expr → iProp.Similar to the outer weakest precondition, we often write wp E  ⟨ ′ .⟩ ≜ wp E  ⟨ ′ .⟩, and wp E  ⟨⟩ ≜ wp E  ⟨ ′ . ′ = () * ⟩.Additionally, we often use  instead of  ′ for the return value binder, to denote that the returned expression is a value.The definition of the inner weakest precondition is given in the accompanying appendix.
An overview of the layers and their rules can be found in Figure 5.The rules of the inner program logic are entirely standard.We will thus not explain them in detail.The wp-step-fuel rule captures that we can take a fuel step whenever the fuel of all the roles of the non-empty fuel map is non-zero ( ⇒ fs ++ and fs ≠ ∅, where fs ++ denotes the map fs where all fuels are incremented by 1).We must then prove the inner weakest precondition, where the postcondition captures that we re-obtain the fuel map ( ⇒ fs), where all fuel has been decremented, for the remaining proof obligation.
The wp-step-model rule captures that we can take a model step whenever the underlying model can take a step over some role ; we have •  M (m) indicating the current model state, and m  − → m ′ indicating we can take a step.We must then show that the role  is associated with the current locale  , and that the fuel of all non- roles are non-zero ( ⇒ { := _} ⊎ fs ++ ).Just as in wp-step-fuel we must show the inner weakest precondition, which in turn gives us back the model and fuel resources.The model resource is updated to the new model state, •  M (m ′ ), and the fuel map has the fuel of  replenished to the maximum fuel cap, F cap , while all non- fuels have been decremented,  ⇒ { := F cap } ⊎ fs.The conjunction of these rules effectively enforce, that for any role  for which we can only take finitely many steps in the thread that  is assigned to, we are forced to take a step corresponding to .Otherwise, we would run out of fuel for the role  and hence not be able to take anymore steps, be it a stuttering step, or a step of any role other than .
The rules wp-role-dealloc and wp-role-fork allow us to discharge our obligations (roles we are responsible for).The rule wp-role-dealloc removes a role from the fuel map of the locale when all the obligations of that role are fulfilled, i.e., when the role "terminates" and cannot take any more steps.The rule wp-role-fork, on the other hand, allows the thread to delegate some of its obligations (roles) by forking a thread and passing those roles to the newly forked threads.Note how the fuel is decremented for all roles, including those that are passed to the newly forked threads.This restriction is necessary for the soundness of the logic as otherwise one could indefinitely postpone taking a step in role by repeatedly forking new threads.Furthermore, note how the rule wp-role-fork requires the postcondition of the forked thread to have a live role when it terminates, effectively forcing it to discharge all its obligations before termination.

Examples
In this section we cover a suite of examples to demonstrate how Fairis can be used to prove fairnessdependent liveness properties.In particular, we first prove fair termination of the yes-no example (Figure 3) discussed earlier.We will then present an example in §4.2.2 where the model LTS is not image-finite but still the  relation is relative image-finite cf.Definition 2.6.Finally, we demonstrate how Fairis can be used to show liveness properties beyond termination in §4.2.3.

4.2.1
Fair Termination: Yes-No.We initially consider the application of Fairis for proving fair termination, which we formally state as follows over the initial configuration  of the program: In particular, we prove fair termination for the yes-no program presented in Figure 3, using the associated model shown in Figure 4. Proving fair termination of the model is relatively straightforward.In our Coq formalization we apply the local criterion mentioned earlier; this criterion is explained in our accompanying appendix.Moreover, as discussed earlier, termination can be trivially transported from M to Fuel(M).Hence, to prove fair termination of the yes-no program, we just have to show that it is refined by the yes-no model using Fairis.
To do so, we apply the adequacy theorem of Fairis, using a trivial  relation, and an empty initial state  for the program.This choice means that AlwaysHolds(Fuel(), , m) holds trivially for any  and m.Since the yes-no model (Figure 4) is finitely branching, any  relation on it is trivially relative image-finite.What remains to prove is the weakest precondition of the yes-no program: where  is the state (, ⊤) in Figure 4, and 0 <  is any positive natural number.As for applying the rules updating the underlying model, the only steps in the program where this happens are the two cas operations in the yes and no threads.These are precisely the horizontal steps in Figure 4 when the cas operation succeeds and the loop when the cas operation fails-as cas is atomic, we can access the invariant (see the rest of the proof argument) during a cas operation.All the other steps in the program are stuttering steps.
The proof starts by using wp-alloc to resolve the allocation of the flag reference, obtaining  ↦ → true.As per conventional Iris methodology for concurrent programs, the proof employs an invariant to safely share the flag reference among the two threads.This invariant additionally ensures that the program behaves according to the model LTS by incorporating the •  M part of the model resource.Recall that  corresponds to how many times the model must cycle between flipping the flag  back and forth before termination.In order to tie the state of the model to the references managed by the two threads, we incorporate two additional resource predicates •  yes and •  no in the invariant, each tracking how many times each thread must still flip the flag -the fragmental parts of these resources will be passed to the two threads.The invariant used to prove the yes-no program, yesno_inv( yes ,  no , ), is as follows: Note how in the case the flag is false the value of the no thread may lag behind because it has not yet had the chance to flip the flag in this "round" to be able to catch up.Furthermore, note how this invariant asserts that the state (0, false) is not ever reached in the model when the program is refining the model.This is crucially the case because we only ever call yn_start with a strictly positive argument.It only remains to prove the following specifications of the two threads Note that the fuel of 42 is picked somewhat arbitrarily; it only needs to be below F cap (which we can arbitrarily pick) and large enough for the proof to go through.We use these specifications along with wp-role-fork to delegate the role obligations to the two threads, which completes the proof of the main thread which can (and must) now terminate as it has no roles associated to it anymore.We use wp-step-fuel to resolve all the stuttering steps in between model steps.The crux of the rest of the proof is the cas operations, where we update the model using the rule wp-step-model, regardless of it succeeding or not.In particular, we open the invariant around the operation using the rule inv-access, and then resolve the operation with wp-cas.Based on whether the cas operation succeeds, we either take the decrementing or the looping step in the model.In either case, the invariant is preserved.If we succeed, we update our model resource •  M () accordingly to preserve the invariant.If we fail, we simply do nothing and loop.In both cases, a model step has taken place in the underlying logic and hence the fuel for thread's role can be replenished.The program loops until we eventually hit 0, in which case we can use wp-role-dealloc to discharge the role obligation, as the role can no longer step in the model, thus fulfilling the postcondition.

Sound Infinite
Branching: Non-deterministic Nat.In §1 we remarked that the model should be relatively image-finite for the Trillium (and thereby Fairis) adequacy theorem to be sound.In this section we consider a model which is infinitely branching, while still being relative image-finite, by virtue of the user picked relation .
The program we consider can be seen in Figure 6.This program works on a pre-allocated reference ℓ as input.It then assigns a non-zero non-deterministically chosen natural number  to the reference, which it decrements until it hits zero, after which point it terminates.A specification for the non-deterministic number operation is simply wp E nondet ⟨ .∃. = ⟩.The model of the program can be seen in Figure 7.It starts in an initial state ∞, from which it can go to any natural number .While the model is terminating (all traces are finite), it is also infinitely branching to accommodate for the fact that the program may non-deterministically step to any natural number.
In order to prove relative image-finiteness we apply the Fairis adequacy theorem with the following user relation: That is, the state of the model always correspond to the number stored in the location, once it has been initialized-before that the location stores −1.This relation ensure relative image-finiteness, as for every program step, there is only one valid model step.Intuitively, the model side number we step to is uniquely (and hence finitely) determined by the program.
To be able to prove AlwaysHolds( nondet (ℓ), , m) we allocate a resource predicate, starting in the initial model state •  (∞) and •  (∞), and use the former establish the following invariant at the very beginning of our verification: This invariant enforces that the location points to a natural number corresponding to the model.Additionally, whenever the model is in the initial ∞ state, the location stores −1.Note that the relation  follows directly from the invariant.
To prove fair termination of the program, it only remains to prove the following specification: It is straightforward to prove that the invariant holds throughout the program, and that it implies the user defined relation.The only non-stuttering step in the proof is the assignment step in the then branch of decr _ loop which allows us to replenish the fuel in every loop iteration.An interesting aspect of this proof is that we use an invariant in the proof despite the fact that program is not concurrent.In this case, the invariant is not used to facilitate sharing between threads but rather to enforce an invariant of the program, i.e., that the value of the reference must always, after initialization, correspond to what is determined in the model LTS.

Liveness Properties
Beyond Termination: Even-Odd.In this section we demonstrate how Fairis can be used to prove liveness properties beyond termination.Consider the program shown in Figure 8.The program works on a location ℓ assumed to store 0 initially.It forks off two threads that each indefinitely increment the location whenever it is even or odd, respectively.The property that we prove is that the counter ℓ visits all natural numbers in order.This property only makes sense for maximal traces, i.e., traces that are either infinite or can take no further step-as we will discuss, all maximal traces of this program are infinite.Formally, the property we prove is the following: We prove the property above by relating the program in Figure 8 with the model LTS Figure 9.To this end, we prove the following property about the model Figure 9.
First, the property evenodd_prop_mdl(ℓ, m) is stable under finite stuttering-monotonicity is expressed using ≤ which is preserved by stuttering.Second, notice the discrepancy between this property and evenodd_prop(ℓ, c) where the latter only assumes maximality of the trace while the former requires infinitude.This property is easy to prove under fairness assumptions.A fair infinite trace cannot have a tail that just consists with one of the loops.Hence, it must visit all numbers.
Monotonicity follows rather trivially.
To finish the proof, we need to establish that (1) any maximal execution trace is infinite, and (2) that the liveness property can be transported from the fuel-instrumented model to the execution trace.To this end, we will pick  relations as follows: Note that, again, because the model in Figure 9 is finitely branching, the relation  even_odd is trivially relative image-finite.Moreover, if we have  ≼ ℓ even_odd , given the  steps () part of  ℓ even_odd together with maximality of , we can conclude that  must be infinite.This established by ( 1) above.The other part of the relation we pick,  ℓ match , simply expresses that the value stored in reference ℓ is always the same as the number in the model trace.In order to prove AlwaysHolds( ℓ even_odd , , m) we must show that  ℓ steps is preserved throughout program execution.This, however, is a simple consequence of post-condition validity and progress properties that we can assume as we prove AlwaysHolds.On the one hand, we know that the program cannot be stuck, that is each thread is either a value or it can take further steps.On the other hand, we cannot have that all threads are values, as if they were, then their postconditions would all hold.This would allow us to conclude that we have reached a state on the model side that has no live roles, which is in contradiction with the definition of the model as given in Figure 9. Now, for establishing the other part of AlwaysHolds( ℓ even_odd , , m), we follow an approach similar to the two preceding examples.We use resource predicates •  e (), •  e (), •  o (), and •  o (), and we will use the full parts in the invariant below while the fragment parts are passed to the two threads.The invariant below immediately allows us to establish that  ℓ match is preserved throughout program's execution.
This invariant combines the ideas of the invariants of the preceding two examples.In particular, it lets us modularly prove that the number stored in ℓ corresponds to the model state.Note how in each case, whether  is even or odd, the number tracked by the odd and even threads, respectively, are out of sync by being one ahead of .This very closely reflects the behavior of the program; the thread that is out of sync is waiting for the in-sync thread to perform its increment.
Finally, we need to prove the weakest precondition of the program. ( Fig. 10.Transitions between two states f and f ′ in Fuel(M) where th ≜ th f , th ′ ≜ th f ′ , etc.All conditions in the column must be satisfied for the transition in the top row to be valid.
We prove the two threads satisfy the following specifications for any : The proof follows similarly to the one of §4.2.1.We similarly open the invariant around the cas operation and in either case, whether it succeeds or fails, we take the appropriate model step.

Obtaining the Fairis Adequacy Theorem
This section describes in more detail how the Fairis logic and its adequacy theorem are obtained.In particular, we describe the critical Fuel-construction which, given a model M, yields a Trillium model Fuel(M) that handles fairness preservation and finite stuttering.The Fairis logic is then obtained by instantiating Trillium with this model and a well-chosen trace interpretation predicate  and relation  fuel .
The Fuel-construction.Recall that a state f of Fuel(M) has two components: a state f.m of the fairness model M, and a partial function f.map : ThreadId ⇀ (Role(M) ⇀ N) associating roles to thread ids and fuels (natural numbers) to roles.In actuality, not all such pairs are states of Fuel(M), as states must satisfy certain conditions.Each role must be associated to at most one thread id: and each live role must be associated to a thread id: where the live roles lroles(m) of a state m ∈ M are defined as the set { | ∃m ′ , m  − → m ′ } of roles that can take a step from m. Intuitively, to prove fairness of a model trace which refines a fair program execution, we need to show that any live role eventually takes a step, so we need to relate all live roles to locales.We write roles(f) for the set of roles allocated in f.map, formally, ⊎  ∈dom(f.map)dom(f.map()).
Roles that are in roles(f) \ lroles(f.m)have a specific purpose: threads must be associated to a role to be allowed to take steps.It is often the case that a thread has logically terminated (in that it took all the steps corresponding to steps in the model) but has not actually terminated.This flexibility allows the thread to terminate in a bounded number of steps without cluttering the model.
We now turn to the more delicate aspect of the Fuel-construction: the transitions.There are two types of transitions: silent stuttering steps are labeled with tau  , where  is meant to be the locale of the thread that takes the stuttering step, and visible steps, corresponding to the underlying model steps which are labeled with vis  () annotated with the local  taking the step, and the underlying role  in M. The locales in labels are necessary to relate Fuel(M) steps with the map f.th.
Figure 10 presents the list of conditions that define each type of transition.The conditions are stated using two auxiliary maps fm f : Roles ⇀ N associating role with its fuel, and th f : Roles ⇀ ThreadId associating each role to some thread id.These two maps are derived from f.map as follows: Condition st restricts the evolution of the underlying fairness model, th relates the thread with the state; dom restricts the evolution of the domain: the only new roles are the roles which became live in the new underlying state (if any); dec states that all threads associated to the thread that took the step must decrease their fuel, except possibly for the role that took a step, ni states that roles that changed theads must decrease their fuel; ni states that all other roles cannot increase their fuel, and finally ref restricts the new fuel of the new roles and of the role that took the step to the global bound F cap .This last condition is necessary to restrict the branching of the model Fuel(M), to be able to use the adequacy theorem of Trillium.It may not be obvious why condition ch is necessary: otherwise two threads could, at each step, take visible steps with roles  1 and  2 and, during that step, and change ownership of  3 , whose fuel could remain constant according to condition nd.
Logical Resources and the Trace Interpretation.The logical predicate  ⇒ fs reflects a coherent view of the data in the Fuel(M) model, as explained below.
To define the ⇒ predicate we use Iris's support for custom ghost state.These predicates satisfy, among other things, the following rules: The link between the state of the Fuel-model and these resources, as well as between locales recorded in the Fuel-model and the program's locales, is specified in the trace interpretation  (, ).First,  and  must have the same size, and their corresponding labels must match: the th label   of  is equal to locale(  ).Here   is the th label of , and locale extracts the locale from a label of Fuel(M), i.e., locale(tau  ) = locale(vis  ()) =  .Second, the respective last states (tp, ) and (m, fm) of  and  satisfy the following Iris predicate: where ownHeap() is the usual state interpretation of the Iris program logic reflecting the program's heap into Iris resources.Furthermore, coherent (FM, m, fm) is a predicate capturing that the ghost fuel map is coherent with respect to the model state.In particular, coherence captures that the ghost fuel map and model fuel map have the same locales, and that any role present in the former is also present in the latter with a larger or equal fuel.Additionally, coherence captures that any live role of the model exists in the ghost fuel map.
Adequacy.To use Trillium's adequacy theorem, the last missing piece is to choose the relation  fuel = Fuel().It turns out that, for our purpose, this relation can be rather weak.Given a finite program execution  = (tp 1 ,  1 ) , we define  fuel (, ) as follows: We can now use Trillium's adequacy theorem (Theorem 3.2) to prove Fairis's adequacy theorem (Theorem 4.1) given the following lemma.
Lemma 4.2.The following holds for the parameters we have chosen: AlwaysHolds(Fuel(), , m) =⇒ AlwaysHolds(, , ) Proof sketch.Let us consider a program execution  and a model trace .It suffices to consider their last states, which we write respectively as (tp, ) and f.That rng(th f ) ⊆ dom(tp) follows from coherent (FM, f.m, fm f ) and from the fact that dom(th f ) = dom(fm f ).We prove the contrapositive of the second conjunct of (2) above.Assume a locale  and a role  such that th f () =  and such that tp[ ] is a value.Since we know all the postconditions of terminated threads hold, we know  ⇒ ∅, which means, according to coherent (FM, f.m, fm f ), that  cannot be live in f.m.The condition (1) on labels follows directly from the trace interpretation.□ Fairness Preservation.The adequacy theorem of Fairis gives us that the intial states of the program and of the Fuel(M) model are related: (, ) ≾  fuel m.This refinement is useful because it has good properties: preservation of fairness and preservation of termination.To make things precise, we define some operations on traces: Any finite or infinite trace  of Fuel(M) induces a trace  in M which we obtain by removing tau transitions and projecting out the m component.Since there can only be finitely many tau transitions in a row (any such transition decreases the sum of all fuels),  is finite if and only if  is finite.And since ξfuel relates traces of the same length, if  is finite and if ξfuel (, ) holds, then  is finite.
Let us now explain why fairness is preserved in the other direction: if ξfuel (, ) and  is fair, then  is fair.We endow traces of Fuel(M) with the natural notion of fairness where we only look at the roles of the transitions, not the locales.It is obvious that if  is fair then  is fair, as tau-transitions do not play any role in fairness.Therefore, preservation of fairness boils down to: Lemma 4.3.If  is fair and if ξfuel (, ), then  is fair.
Proof sketch.The proof is quite technical and consists of two nested inductions, but the idea is the following: consider some state f of  and  which is live in that state, and call  = th f () its associated locale.We need to prove there eventually exists a  transition in .Since we have a fixed fair program trace , there exists  such that the next step of  is in at most  steps.We proceed by induction over (fm f (), ), ordered lexicographically.
Without loss of generality, we can assume it is the first state of .Write  for th f ().Consider the first step of the program with locale  ′ .There are two cases: (1)  ≠  ′ .Then we apply the induction hypothesis with the next state, the same fuel, and  − 1. (2)  =  ′ .If it is a -transition we conclude.Otherwise, the fuel associated to  decreases, and we apply the induction hypothesis with the next state, a smaller fuel and some arbitrary  ′ obtained as above. □

THE ANERIS LOGIC
To reason about distributed systems, we instantiate Trillium with AnerisLang, the programming language accompanying Aneris, a higher-order distributed separation logic [Krogh-Jespersen et al. 2020].AnerisLang is an OCaml-like programming language with network primitives for creating (socket) and binding (socketbind) network sockets as well as sending (sendto) and receiving (receivefrom) messages.The operational semantics of AnerisLang is designed so that the primitives closely model Unix sockets and UDP (unreliable) networking.The Aneris instantiation of Trillium is conceptually simple as we will target safety trace properties.This means that we can "bake-in" the reflexive closure of the model and hence freely allow model stuttering.The result is a program logic and reasoning principles that are virtually identical to the original (non-relational) Aneris program logic, where the only difference is the addition of a single rule (aneris-take-step) that allows us to relate an atomic step of the program to a corresponding step in the LTS model.In what follows, we will show how we use Aneris to transport safety (trace) properties of TLA + protocol models to distributed programs that implement them.In the Appendix we also show how we, under reasonable liveness assumptions, can prove eventual consistency [Vogels 2009] of a Conflict-Free Replicated Data Type [Shapiro et al. 2011].We leave a more principled approach to proving liveness properties of distributed systems as future work.
We use the Aneris instantiation of Trillium to show an intensional refinement between implementations of two classical distributed algorithms, Two-Phase Commit (TPC) [Gray 1978] and Single-Decree Paxos (SDP) [Lamport 1998[Lamport , 2001]], and their TLA + [Lamport 1992] models.As simple corollaries of the refinement, we show using a single modular specification (1) that clients are safe, i.e., they do not crash, (2) a formal proof that the implementation correctly implements a protocol, and (3) correctness of the implementation by leveraging existing correctness properties of the models.The TLA + specification of TPC and the TLA + specification of SDP can both be found in the official TLA + -examples repository on GitHub.In our formalization, we have manually translated the TLA + protocol specifications into STSs in Coq and proved their correctness properties. 8ote that correctness of the implementations can be established using regular Iris ghost resources and invariants, but doing so through a refinement has the immediate benefit that the necessary ghost theory is much simpler.The protocol logic is already encoded in the model and we "just" need to map the state of the model to the physical state of the distributed system.The only place we will need more sophisticated ghost theory is where the model is underspecified, e.g., in how SDP distributes ballots among proposers.Additionally, by showing an intensional refinement, we show that the implementation of, say, SDP actually implements the SDP protocol and not just any other consensus protocol.While we do not show this explicitly, it also means that it is possible to transfer other trace properties of the model to the implementation.
Both the implementation, the model, and the refinement proof for the TPC protocol can be found in the Appendix.The development follows the same methodology as for SDP, which we describe below; we omit network-and state-related Aneris resources and focus on the core parts relevant for showing the refinement.
Single-Decree Paxos.The Paxos algorithm is a consensus protocol and its single-decree version allows a set of distributed nodes to reach agreement on a single value by communicating through message-passing over an unreliable network.
In SDP, each node in the system adopts one or more of the responsibilities of either proposer, acceptor, or learner.A value is chosen when a learner learns that a quorum (e.g., a majority) of acceptors have accepted a value proposed by some proposer.The algorithm works in two phases: in the first phase, a proposer tries to convince a quorum of acceptors to promise that they will later accept its value.If it succeeds, it continues to the second phase where it asks the acceptors to fulfill their promise and accept its value.To satisfy the requirements of consensus, each attempt to decide a value is distinguished with a unique totally-ordered round number or ballot.Each acceptor stores its current ballot and the last value it might have accepted, if any.Acceptors will only give a promise to proposers with a ballot greater than their current one, and in that case they switch to the proposer's ballot; proposers only propose values that ensure consistency, if chosen.By observing that a quorum of acceptors have accepted a value for the same ballot, learners will learn that a value has been chosen.We refer to Lamport [2001] for an elaborate textual description of the protocol.The SDP-Phase1a transition adds a msg1a() message to the set of sent messages; this corresponds to the proposer asking the acceptors to not accept values for ballots smaller than .If a msg1a() message has been sent and  is greater than acceptor 's current ballot B () then the SDP-Phase1b transition updates 's state and sends a msg1b(, , ) message where  is 's last accepted value, if any.This corresponds to an acceptor responding to a proposer's promise request.
The second phase is initiated using the SDP-Phase2a transition that corresponds to the proposer proposing a value  for ballot  by sending a msg2a(, ) message.However, the transition can only be made if no value has previously been proposed for ballot  and if a quorum  of acceptors exists The proposer implementation receives as input a set of acceptor socket addresses, a bound socket, a ballot number and a value to (possibly) propose in the ballot.First phase is initiated by sending a message to all the acceptors and after receiving a response from a majority of the acceptors it continues to the second phase.In the second phase it picks the value of the maximum ballot among the responses; if no such value exist, it picks its own.The candidate is finally sent to all acceptors.
Note that this proposer implementation only proposes a value for a single ballot; typically, proposers will issue new ballots when learning that no decision has been reached due to messages being dropped or nodes crashing.Moreover, it is crucial that proposers do not issue proposals for the same ballot.In our Coq formalization, proposer  repeatedly issues new ballots of the form  • |Proposer| +  for  ∈ N by keeping track of the last issued  in a local reference.
Consensus by Refinement.To show that the SDP implementation refines the SDP model we instantiate the Aneris logic with the model; the key part of the proof is to keep the •  M () resource in a global invariant that ties together the model state and the physical state with enough information to verify the implementation and for the refinement relation established through the adequacy theorem to be strong enough for proving our final correctness theorem (Corollary 5.2).Under this invariant we will modularly verify each Paxos role and each component in isolation.
To state the invariant, we use three kinds of resources corresponding to: (1) sets of messages with predicates Msgs • (S) and Msgs • () such that Equipped with these resource we can state the invariant: The first part of the invariant ties the current state of the model (S, B, V) to its logical authoritative counterparts which means that by owning a fragmental part you own a piece of the model: e.g., by owning MaxBal • (, ) you may open the invariant and conclude B () =  where B is the current map of ballots.Intuitively, we will give acceptor  exclusive ownership of the parts of the model that should correspond to its local state (through resources MaxBal • (, ) and MaxVal • (, )).Similarly, by owning Msgs • () one may conclude that the message  has in fact been added to the set of messages in the model; this predicate we will transfer when sending physical messages corresponding to .
In the last part of the invariant, the BalCoh(S) predicate simply requires that if msg2a(, ) ∈ S then shot(, ) holds.This implies that by owning pending() you are the only entity that may propose a value for ballot  and it may never change.The MsgCoh(S) predicate ties the physical state of the program to the model using Aneris-specific predicates for tracking the state of the network.This, for instance, forces acceptors and proposers to also add to the model state S any message they send over the network.Hence, to verify a proposer or an acceptor that sends a message, the proof must open the invariant, use aneris-take-step to take a step in the model, and update the corresponding logical resources to close the invariant.Following this methodology, we give specifications of the following shape to the proposer and acceptor components: omitting Aneris-specific network connectives in the precondition; the postcondition for acceptor may be False as it does not terminate.We give a similar specification to the learner.Working in a modular program logic, we can compose these specifications to get a single specification for a distributed system with both proposers, acceptors, and learners.By applying the adequacy theorem to this specification we get that the implementation indeed refines the TLA + model of SDP. 9 Consensus for the Implementation.Given the specification has been established for the implementation, we can state and prove that the consistency property holds for all executions by transporting the consistency property of the model.Let where M is a set of physical messages and  ∼  holds when  is the serialization of the model message .By picking a trace relation  SDP that requires messages in the model to correspond to messages in the program state (as implied by MsgCoh(S)): we combine the adequacy theorem (Theorem 3.2) with our model correctness theorem (Theorem 5.1) to obtain the following corollary that only talks about the execution of the SDP implementation.
Corollary 5.2.Let  be a distributed system obtained by composing  proposers,  acceptors, and  learners.For any  and , if (; ∅) → * ( ; ) and both ChosenI (messages(),  1 ) and ChosenI (messages(),  2 ) hold then  1 =  2 .Functional Correctness.Corollary 5.2 is a meta-logic theorem (e.g., in Coq) that only talks about the program execution and it follows from the adequacy theorem and the model correctness theorem.However, it is not only in the meta-logic that we can exploit properties of the model to prove properties about programs as the model is also embedded as a resource in the logic.
Listing 3 shows a client application that receives a message from two different Paxos learners and asserts that the two values are equal; if the two values do not agree, the program crashes.We can prove a specification for the client of the shape  SDP * . . .− * wp client  . . . .From the adequacy theorem it follows that the program is safe, i.e., it does not crash, which means the asserted statement must always hold.In the proof of this specification, the client will receive ghost resources from the learners conveying that  1 and  2 have been chosen (i.e., that a quorum of acceptors have accepted   ).By opening the invariant  SDP and hence obtaining the model resource •  M (S, B, V), we can combine this knowledge with Theorem 5.1-a property exclusively of the model-and conclude that  1 =  2 .Naturally, we may still compose a distributed system containing the client together with proposers, acceptors, and learner nodes and derive a specification for the full system.This single specification for the full distributed system entails both the refinement of the TLA + model and the safety of the programs running on all nodes. 9The full Coq proof amounts to about 1100 lines of proof scripts.

RELATED WORK
Refinement-based Verification of Distributed Systems.We focus on works that, as ours, aim at proving that concrete implementations refine models.The most closely related works are IronFleet [?] and Igloo [Sprenger et al. 2020].In contrast to both, our approach is foundational: the operational semantics of the languages, the models, and the program logics are all formally defined in Coq, and through adequacy theorems of the program logic, the end result of a verification is a formal theorem expressed only in terms of the operational semantics of the programming language and the model.
IronFleet uses the Dafny verifier to verify implementations and encodes the refinement of an STS in preconditions and postconditions of programs but does not support node-local concurrency.IronFleet uses a pen-and-paper argument for proving liveness of simple programs (programs that consist of a simple event loop which calls terminating event handlers).
Igloo proves a particular kind of extensional safety properties.Igloo refines a high-level STS to a more low-level STS for each node of the system.STSs are annotated with IO operations which are used to generate IO specifications for network communications of the node in the style of Penninckx et al. [2015].Programs are subsequently verified against this generated specification.The relationship between the implementation and the model considered in Igloo is a fixed relation, i.e. producing the same IO behavior.In contrast, our work allows an arbitrary intensional refinement relation to be specified and established between the program and the model.

Refinement in Iris.
There has been earlier work on proving contextual refinements using Iris as discussed in the introduction.Additionally, Perennial [Chajed et al. 2019] defines correctness of a system using concurrent recovery refinement, requiring that the (possibly crashing) implementation and specification STS has the same external I/O.This notion of refinement is much coarser and does not allow you to prove, e.g., fair termination.Tassarotti and Harper [2019] relates concurrent probabilistic programs to abstract specifications denoting indexed valuations, exhibiting a probabilistic coupling when assuming that the implementation terminates.
Our approach to termination-preserving refinement is similar in spirit to the one of Tassarotti et al. [2017] but applies to reasoning about refinement of general concurrent programs with respect to abstract models, not just compilation of session-typed programs.To the best of our knowledge, the expressiveness of the logics is roughly similar.The main difference is that Tassarotti et al. [2017] augments the Iris base logic with linear propositions, which requires modifying the definition of resource algebra to add a transition relation.We achieve similar results without heavy modifications, using that the authoritative state of the model is threaded through the weakest precondition, and by putting an exclusive structure on the set of roles owned by a thread, which prevents arbitrary weakening of the role resource, a limited form of linearity.
Besides efforts in Iris, Liang andFeng [2016, 2018] have also used refinement to show a wider range of liveness properties of concurrent programs, including programs with partial methods, but focusing on first-order logic and first-order programs.It would be interesting to investigate if Trillium could serve as a basis for generalizing the verification methods of Liang andFeng [2016, 2018] to higher-order logic and higher-order programs.
Simuliris [Gäher et al. 2022] is a separation logic for fair termination-preserving contextual refinements for concurrent program transformations that can exploit undefined behavior.In contrast to both Iris and Trillium, Simuliris is not step-indexed, and thus does not support impredicative invariants or higher-order ghost state, which we crucially target and rely on. 10Certified Abstraction Layers.A related approach to verification is certified abstraction layers [Gu et al. 2015], in particular their concurrent variant [Gu et al. 2018], which are used to verify the CertiKOS verified kernel [Gu et al. 2016].Our approach is similar to that of CertiKOS in that both approaches use models to help verify programs.The main difference is that, in the CertiKOS approach, the person proving a refinement needs to work directly using the semantics of the program and of the model, which are both sets of traces.Our approach, on the other hand, is to use a program logic to do the heavy lifting of the refinement proof, which, we believe, lowers the proof burden dramatically.Another difference is that their notion of concurrent certified abstraction layers is more complex than our models, which are plain LTSs; but their models can be composed together.
Paxos Verification Efforts.Paxos and its multiple variants have been considered by many verification efforts using, e.g., automated theorem provers and model checkers [Chand et al. 2016;Jaskelioff and Merz 2005;Kellomäki 2004;Maric et al. 2017;Padon et al. 2017].These efforts all consider abstract models or specifications in high-level domain-specific languages of Paxos(-like) protocols and not actual implementations in a realistic and expressive programming language.Kragl et al. [2020] work in the Boogie verifier and programming language where they express their high-level model, their low-level (non-distributed) imperative implementation, and the layers in between, all in Boogie.By contrast, our effort is foundational (in the technical sense of being formalized in Coq as mentioned above) and the implementation is carried out in the OCaml-like programming language AnerisLang with UDP network primitives.García-Pérez et al. [2018] devise composable specifications for a pseudo-code implementation of Single-Decree Paxos and semantics-preserving optimizations to the protocol on pen-and-paper but without a formal connection to their implementation in Scala; it would be interesting future work to implement and verify the same optimizations in our setting.

CONCLUSION AND FUTURE WORK
In this paper, we explored how intensional refinement is indeed a viable methodology for strengthening higher-order concurrent and distributed separation logics to non-trivial safety and liveness properties using Trillium and its instantiations.We have developed Fairis, a higher-order concurrent separation logic, and we have shown how the logic gives us a methodology for proving liveness of concurrent programs under fair scheduling assumptions.Moreover, we instantiated Trillium with a distributed language and obtained an extension of Aneris, a distributed separation logic, that we have used to show refinement relations between distributed systems and their TLA + models.
Future work includes extending Trillium's support for modular reasoning to also allow specifications of library functions to be modular with respect to the model, such that a library function can be specified with respect to one model and client code can be specified with respect to another model in isolation.Currently, library functions can be reasoned about modularly in Trillium using higher-order specifications.For example, our Paxos implementation makes a call to the sendto all function, whose specification quantifies over arbitrary socket protocols (themselves higher-order predicates), and in turn the sendto all function internally makes a call to a function set iter, whose specification uses impredicative quantification to support arbitrary callback functions.An important point here, however, is that these library specifications do not interact with the model, i.e., execution corresponds to stuttering steps on the model side.A concrete goal would be to give a modular specification of a fair lock and then verify termination of a client that uses the lock, but only by relying on its specification.Data availability statement.The Coq formalization accompanying this work is available on Zenodo [Timany et al. 2023] and on GitHub at https://github.com/logsem/trillium.
Fig. 1.Two simple LTSs representing the infinite chain of natural numbers Chain, and all finite chains of natural numbers FinChains.
Fin-Stutter) Fin-Stutter-Lifting of the LTS Chain in Figure1 Theorem 3.2 (Adeqacy).Let  be a program,  a program state,  the locale of  in an otherwise empty thread pool, and  an Iris predicate on values.Let m ∈ M be a model state and  a relative image-finite relation on finite traces of the program and the model.Let  = ({ ↦ → } , ) be the initial configuration of the program.If  (, m) holds for the initial singleton traces, and furthermore we have | ⇛ ⊤  (, m) * wp  ⊤   * AlwaysHolds(, , m) then  ≾  m holds in the meta-logic.
Fig. 3.The Yes and No threads.
⊤ for true and ⊥ for false.
Theorem 4.1 (Fairis-Adeqacy).Let  be a program,  a program state,  the locale of  in an otherwise empty thread pool.Let m ∈ M be a model state, and  a relative image-finite relation on program traces and model traces.Let  = ({ ↦ → } , ) be the initial configuration of the program, and m be the initial state of Fuel(M) corresponding to m, i.e., m together with  assigned to a map F init of all roles with F cap fuel.If  (, m) and

Fig. 5 .
Fig. 5.The rules of the Fairis program logic

𝐼
SDP * MaxBal • (, None) * MaxVal • (, None) * . . .− * wp  ⊤ acceptor   False  SDP * pending() * . . .− * wp  ⊤ proposer     True An LTS is said to be image-finite if, for any state  and label , there exists only finitely many states  ′ such that   ′ .As we will discuss in §4.2.2, however, it is desirable to consider LTSs with infinite branching.For this reason we relax the image-finiteness condition by considering the weaker relative image-finiteness condition (a property of the  relation, not the LTS), defined below.For relative image-finiteness, it is sufficient that for any state  of the program and for any transition  Relative image-finiteness).Let  be a relation on traces of two LTSs.The relation  is relatively image-finite if, for any ,  ′ , and transition last () − →