Automatic Generation of Precise and Useful Commutativity Conditions

. Reasoning about commutativity between data-structure operations has been, and remains, an important problem with applications including parallelizing compilers, optimistic parallelization and, more recently, Ethereum smart contracts. There have been research results on automatic generation of commutativity conditions, yet we are unaware of any fully automated technique to generate conditions that are both sound and eﬀective (i.e., not overly conservative). We take a ﬁrst step in this direction. We have designed such a technique, driven by an algorithm that iteratively reﬁnes a conservative approximation of the commutativity (and non-commutativity) condition for a pair of methods into an increasingly precise version. The algorithm terminates if/when the entire state space has been considered, and can be aborted at any time to obtain a partial yet sound commutativity condition. We have generalized our work to left-/right-movers [25] and proved relative completeness. We describe aspects of our technique that lead to useful commutativity conditions, including how predicates are selected during reﬁnement and heuristics that impact the output shape of the condition. We have implemented our technique in a prototype open-source tool called Servois . Our algorithm produces quantiﬁer-free queries that are dispatched to a back-end SMT solver. We evaluate Servois through two case studies: (i) We synthesize commutativity conditions for a range of data structures including Set, HashTable, Accumulator, Counter, and Stack. (ii) We consider an Ethereum smart contract called BlockKing , and show that Servois can detect serious concurrency-related vulnerabilities and guide developers to construct robust and eﬃcient implementations.


Introduction
Reasoning about the conditions under which data-structure operations commute is an important problem. The ability to derive sound yet effective commutativity conditions unlocks the potential of multicore architectures, including parallelizing compilers [30,34], speculative execution (e.g. transactional memory [19]), peephole partial-order reduction [37], futures, etc. Another important application domain that has emerged recently is Ethereum [1] smart contracts: efficient execution of such contracts hinges on exploiting their commutativity [14] and block-wise concurrency can lead to vulnerabilities [31]. Intuitively, commutativity is an important property because linearizable data-structure operations that commute can be executed concurrently: their effects do not interfere with each other in an observable way. When using a linearizable HashTable, for example, knowledge that put(x,'a') commutes with get(y) provided that x = y enables significant parallelization opportunities. Indeed, it's important for the commutativity condition to be sufficiently granular so that parallelism can be exploited effectively [12]. At the same time, to make safe use of a commutativity condition, it must be sound [24,23]. Achieving both of these goals using manual reasoning is burdensome and error prone.
In light of that, researchers have investigated ways of verifying user-provided commutativity conditions [22] as well as synthesizing such conditions automatically, e.g. based on random interpretation [6], profiling [33] or sampling [18]. None of these approaches, however, meet the goal of computing a commutativity condition that is both sound and granular in a fully automated manner.
In this paper, we present a refinement-based technique for synthesizing commutativity conditions. Our technique builds on well-known descriptions and representations of abstract data types (ADTs) in terms of logical (Pre m , Post m ) specifications [20,16,17,10,28,26] for each method m. Our algorithm iteratively relaxes under-approximations of the commutativity and non-commutativity conditions of methods m and n, starting from false, into increasingly precise versions. At each step, we conjunctively subdivide the symbolic state space into regions, searching for areas where m and n commute and where they don't. Counterexamples to both the positive side and the negative side are used in the next symbolic subdivision. Throughout this recursive process, we accumulate the commutativity condition as a growing disjunction of these regions. The output of our procedure is a logical formula ϕ n m which specifies when method m commutes with method n. We have proven that the algorithm is sound, and can also be aborted at any time to obtain a partial, yet useful [33,19], commutativity condition. We show that, under certain conditions, termination is guaranteed (relative completeness).
We address several challenges that arise in using an iterative refinement approach to generating precise and useful commutativity conditions. First, we show how to pose the commutativity question in a way that does not introduce additional quantifiers. We also show how to generate the predicate vocabulary for expressing the condition ϕ n m , as well as how to choose the predicates throughout the refinement loop. A further question that we address is how predicate selection impacts the conciseness and readability of the generated commutativity conditions. Finally, we have generalized our algorithm to left-/right-movers [27], a more precise version of commutativity.
We have implemented our approach as the Servois tool, whose code and documentation are available online [2]. Servois is built on top of the CVC4 SMT solver [11]. We evaluate Servois through two case studies. First, we generate commutativity conditions for a collection of popular data structures, including Set, HashTable, Accumulator, Counter, and Stack. The conditions typically combine multiple theories, such as sets, integers, arrays, etc. We show the conditions to be comparable in granularity to manually specified conditions [22]. Second, we consider BlockKing [31], an Ethereum smart contract, with its known vulnerability. We demonstrate how a developer can be guided by Servois to create a more robust implementation.
Contributions. In summary, this paper makes the following contributions: -The first sound and precise technique to automatically generate commutativity conditions (Sec. 6). -Proof of soundness and relative completeness (Sec. 6).
-An implementation that takes an abstract code specification and automatically generates commutativity conditions using an SMT solver (Sec. 7). -A novel technique for selecting refinement predicates that improves scalability and the simplicity of the generated formulae (Sec. 7). -Demonstrated efficacy for several key data structures (Sec. ??) as well as the BlockKing Ethereum smart contract [31]. (Sec. ??).
This is an extended version of our paper [8].
Related work. The closest to our contribution in this paper is a recent technique by Gehr et al. [18] for learning, or inference, of commutativity conditions based on black-box sampling. They draw concrete arguments, extract relevant predicates from the sampled set of examples, and then search for a formula over the predicates. There are no soundness or completeness guarantees. Both Aleen and Clark [6] and Tripp et al. [33] identify sequences of actions that commute (via random interpretation and dynamic analysis, respectively). However, neither technique yields an explicit commutativity condition. Kulkarni et al. [25] point out that varying degrees of commutativity specification precision are useful. Kim and Rinard [22] use Jahob to verify manually specified commutativity conditions of several different linked data structures. Commutativity specifications are also found in dynamic analysis techniques [15]. More distantly related is work on synthesis of programs [32] and of synchronization [36,35].

Example
Specifying commutativity conditions is generally nontrivial, more importantly it is easy to miss subtle corner cases. Additionally, it has to be done pairwise for all methods. For ease of illustration, we will focus on the relatively simple Set ADT, whose state consists of a single set S that stores an unordered collection of unique elements. Let us consider one pair of operations: (i) contains(x)/bool, a side-effect-free check whether the element x is in S; and (ii) add(y)/bool adds y to S if it is not already there and returns true, or otherwise returns false. add and contains clearly commute if they refer to different elements in the set. There is another case that is less obvious: add and contains commute if they refer to the same element e, as long as in the pre-state e ∈ S. In this case, under both orders of execution, add and contains leave the set unmodified and return false and true, respectively. The algorithm we describe in this paper takes 3.6s to automatically produce a precise logical formula ϕ that captures this commutativity condition, i.e. the disjunction of the two cases above: ϕ ≡ x = y ∨ (x = y ∧ x ∈ S). The algorithm also generates the conditions under which the methods do not commute:φ ≡ x = y ∧ x / ∈ S. These are precise, since ϕ is the negation ofφ.
Capturing precise conditions such as these by hand, and doing so for many pairs of operations, is tedious and error prone. This paper instead presents a way to automate this. Our algorithm recursively subdivides the state space via predicates until, at the base case, regions are found that are either entirely commutative or else entirely non-commutative. Returning to our Set example, the conditions we incrementally generate are denoted ϕ andφ, respectively. The following diagram illustrates how our algorithm proceeds to generate the commutativity conditions for add and contains.
In this diagram, each subsequent panel depicts a partitioning of the state space into regions of commutativity (ϕ) or non-commutativity (φ). The counterexamples χ c , χ nc give values for the arguments x, y and the current state S. We denote by H the logical formula that describes the current state space at a given recursive call. We begin with H 0 = true, ϕ = false, andφ = false. There are three cases for a given H: (i) H describes a precondition for m and n in which they always commute; (ii) H describes a precondition for m and n in which they never commute; or (iii) neither of the above. The latter case drives the algorithm to subdivide the region by choosing a new predicate.
We now detail the run of this refinement loop on our earlier Set example. We elaborate on the other challenges that arise in later sections. At each step of the algorithm, we determine which case we are in via carefully designed validity queries to an SMT solver (Sec. 4). For H 0 , it returns the commutativity counterexample: χ c = {x = 0, y = 0, S = ∅} as well as the non-commutativity counterexample χ nc = {x = 0, y = 1, S = {0}}. Since, therefore, H 0 = true is neither a commutativity nor a non-commutativity condition, we must refine H 0 into regions (or stronger conditions). In particular, we would like to perform a useful subdivision: Divide H 0 into an H 1 that allows χ c but disallows χ nc , and an H ′ 1 that allows χ nc but not χ c . So we must choose a predicate p (from a suitable set of predicates P, discussed later), such that H 0 ∧ p ⇒ χ c while H 0 ∧ ¬p ⇒ χ nc (or vice versa). The predicate x = y satisfies this property. The algorithm then makes the next two recursive calls, adding p as a conjunct to H, as shown in the second column of the diagram above: one with H 1 ≡ true∧x = y and one with H ′ 1 ≡ true ∧ x = y. Taking the H ′ 1 case, our algorithm makes another SMT query and finds that x = y implies that add always commutes with contains. At this point, it can update the commutativity condition ϕ, letting ϕ := ϕ ∨ H ′ 1 , adding this H ′ 1 region to the growing disjunction. On the other hand, H 1 is neither a sufficient commutativity nor a sufficient non-commutativity condition, and so our algorithm, again, produces the respective counterexamples: χ c = {x = 0, y = 0, S = ∅} and χ nc = {x = 0, y = 0, S = {0}}. In this case, our algorithm selects the predicate x ∈ S, and makes two further recursive calls: one with H 2 ≡ x = y ∧ x ∈ S and another with H ′ 2 ≡ x = y ∧ x / ∈ S. In this case, it finds that H 2 is a sufficiently strong precondition for commutativity, while H ′ 2 is a strong enough precondition for non-commutativity. Consequently, H 2 is added as a new conjunct to ϕ, yielding ϕ ≡ x = y ∨ (x = y ∧ x ∈ S). Similarly,φ is updated to be:φ ≡ (x = y ∧ x / ∈ S). No further recursive calls are made so the algorithm terminates and we have obtained a precise (complete) commutativity/non-commutativity specification: ϕ ∨φ is valid (Lem. 2).
Challenges & outline. While the algorithm outlined so far is a relatively standard refinement, the above generated conditions were not immediate. We now discuss challenges involved in generating sound and useful conditions.
(Sec. 4) A first question is how to pose the underlying commutativity queries for each subsequent H in a way that avoids the introduction of additional quantifiers, so that we can remain in fragments for which the solver has complete decision procedures. Thus, if the data structure can be encoded using theories that are decidable, then the queries we pose to the SMT solver are guaranteed to be decidable as well. Pre m /Post m specifications that are partial would introduce quantifier alternation, but we show how this can be avoided by, instead, transforming them into total specifications.
(Sec. 6) We have proved that our algorithm is sound even if aborted or the ADT description involves undecidable theories. We further show that termination implies completeness, and specify broad conditions that imply termination.
(Sec. 7) Another challenge is to prioritize predicates during the refinement loop. This choice impacts not only the algorithm's performance, but also the quality/conciseness of the resulting conditions. Our choice of next predicate p is governed by two requirements. First, for progress, p/¬p must eliminate the counterexamples to commutativity/non-commutativity due to the last iteration. This may still leave multiple choices, and we propose two heuristics -called simple and poke-with different trade-offs to break ties.
(Sec. 8) We conclude with an evaluation on a range of popular data structures and a case study on boosting the security of an Ethereum smart contract.
An action α ∈ A is of the form m(x)/r, where m,x andr are called a method, arguments and return values respectively. As a convention, for actions corresponding to a method n, we useȳ for arguments ands for return values. The set of methods will be finite, inducing a finite partitioning of A. We refer to an action, say m(ā)/v, as corresponding to method m (whereā andv are vectors of values). The set of actions corresponding to a method m, denoted A m , might be infinite as arguments and return values may be from an infinite domain.
Since our approach works on deterministic transition systems, we have implemented an SMT-based check (Sec. 8) that ensures the input transition system is deterministic. Deterministic specifications were sufficient in our examples. This is unsurprising given the inherent difficulty of creating efficient concurrent implementations of nondeterministic operations, whose effects are hard to charac-terize. Reducing nondeterministic data-structure methods to deterministic ones through symbolic partial determinization [5,13] is left as future work.
Logical commutativity formulae. We will generate a commutativity condition for methods m and n as logical formulae over initial states and the arguments/return values of the methods. We denote a logical commutativity formula as ϕ and assume a decidable interpretation of formulae: (We tuple the arguments for brevity.) The first argument is the initial state. Commutativity post -and mid -conditions can also be written [22] but here, for simplicity, we focus on commutativity pre-conditions. We may write [[ϕ]] as ϕ when it is clear from context that ϕ is meant to be interpreted.
We say that ϕ n m is a sound commutativity condition, andφ n m a sound noncommutativity condition resp., for m and n provided that

Commutativity without quantifier alternation
Def. 1 requires showing equivalence between different compositions of potentially partial functions. That is, ( if and only if:

and a symmetric case for the other direction)
Even when the transition relation can be expressed in a decidable theory, because of ∀∃ quantifier alternation in the above encoding (which is undecidable in general), any procedure requiring such a check would be incomplete. SMT solvers are particularly poor at handling such constraints. We observe that when the transition system is specified as P re m and P ost m conditions, and the P ost m condition is consistent with P re m , then it is possible to avoid quantifier alternation. By consistent we mean that whenever P re m holds, there is always some state and return value for which P ost m holds.
This assumption holds for all of the specifications in the examples we considered (Sec. 8). This allows us to perform a simple transformation on transition systems to a lifted domain, and enforce a definition of commutativity in the lifted domain m⊲⊳ n that is equivalent to Def. 1. This new definition requires only universal quantification, and as such, is better suited to SMT-backed algorithms (Sec. 6).

Definition 2 (Lifted transition function). For
:Σ →Σ, as: Intuitively, (|]α[|) wraps (|α|) so that err loops back to err, and the (potentially partial) (|α|) is made to be total by mapping elements to err when they are undefined in (|α|). It is not necessary to lift the actions (or, indeed, the methods), but only the states and transition function. Once lifted, for a given stateσ 0 , the question of some successor state becomes equivalent to all successor states because there is exactly one successor state.
Abstraction. Pre-/post-conditions (Pre m , Post m ) are suitable for specifications of potentially partial transition systems. One can translate these into a new pair ( Pre m , Post m ) that induces a corresponding lifted transition system that is total and remains deterministic.
(We abuse notation, givingσ as an argument to Pre m , etc.) It is easy to see that the lifted transition system induced by this translation (Σ, (|] • [|)) is of the form given in Def. 2. In Apx. A.2, we show how our tool transforms a counter specification into an equivalent lifted version that is total.
We use the notation⊲⊳ to mean ⊲⊳ but over lifted transition systemT. Sincê ⊲⊳ is over total, determinsitic transition functions, α 1⊲ ⊳ α 2 is equivalent to: The equivalence above is in terms of state equality. Importantly, this is a universally quantified formula that translates to a ground satisfiability check in an SMT solver (modulo the theories used to model the data structure). In our refinement algorithm (Sec. 6), we will use this format to check whether candidate logical formulae describe commutative subregions. Proof. Follows from classical reasoning, functional extensionality and case analysis on totality-vs-partiality.

Right-/Left-movers
We now describe how the formalism presented thus far can be extend to a more fine-grained notion of commutativity: an asymmetric version called left-movers and right-movers [27], where a method commutes in one direction and not the other.
Definition 3 (Action right-mover [27]). We say that an action α 1 moves to the right of action α 2 commute, denoted Note that left-movers can be defined as right-movers, but with arguments swapped.
Definition 4 (Method right-mover). For m and n, m ⊲ n ≡ ∀xȳrs. m(x)/r ⊲ n(ȳ)/s A logical right-mover condition denoted Ψ n m has the same type as a commutativity condition and, again [[Ψ n m ]] denotes interpretations of Ψ n m . Moreover, we say that Ψ n m is a right-mover condition for m and n provided that ] σ 0 (m(x)/r) (n(ȳ)/s) = true ⇒ m ⊲ n and similar for a non-right-mover condition.
Checking whether H n m ⇒ m⊲ n. After performing the lifting transformation, we again are able to reduce the question of whether a formula H n m is a right-mover condition to a validity check that does not introduce quantifier alternation.
Notice that this is a generalization of the validity check for commutativity.

Iterative refinement
We now present an iterative refinement strategy that, when given a lifted abstract transition system, generates the commutativity and the non-commutativity conditions. We then discuss soundness and relative completeness and, in Secs. 7 and 8, challenges in generating precise and useful commutativity conditions.
The refinement algorithm symbolically searches the state space for regions where the operations commute (or do not commute) in a conjunctive manner, adding on one predicate at a time. We add each subregion H (described conjunctively) in which commutativity always holds to a growing disjunctive description of the commutativity condition ϕ, and each subregion H in which commutativity never holds to a growing disjunctive description of the non-commutativity conditionφ.
The algorithm in Fig. 1 begins by setting ϕ = false andφ = false. Refine begins a symbolic binary search through the state space H, starting from the entire state: H = true. It also may use a collection of predicates P (discussed later). At each iteration, Refine checks whether the current H represents a region of space for which m and n always commute: H ⇒ m⊲⊳ n (described below). If so, H can be disjunctively added to ϕ. It may, instead be the case that H represents a region of space for which m and n never commute: H ⇒ m \ ⊲⊳ n. If so, H can be disjunctively added toφ. If neither of these cases hold, we have two counterexamples. χ c is the counterexample to commutativity, returned if the validity check on Line 2 fails. χ nc is the counterexample to non-commutativity, returned if the validity check on Line 4 fails.
We now need to subdivide H into two regions. This is accomplished by selecting a new predicate p via the Choose method. For now, let the method Choose and the choice of predicate vocabulary P be parametric. Refine is sound regardless of the behavior of Choose. Below we give the conditions on Choose that ensure relative completeness, and in Sec. 8 we discuss our particular strategy. Regardless of what p is returned by Choose, two recursive calls are made to Refine, one with argument H ∧ p, and the other with argument H ∧ ¬p. The algorithm is exponential in the number of predicates. In Sec. 7 we discuss prioritizing predicates.
The refinement algorithm generates commutativity conditions in disjunctive normal form. Hence, any finite logical formula can be represented. This logical language is more expressive than previous commutativity logics that, because they were designed for run-time purposes, were restricted to conjunctions of inequalities [25] and boolean combinations of predicates over finite domains [15]. Above we assume as a black box an SMT solver providing valid. Here we have lifted the universal quantification within⊲⊳ outside the implication.
We can similarly check whether H n m is a condition under which m and n do not commute. First, we define negative analogs of commutativity: Proof. By induction. Initially, false is a suitable condition for when commutativity holds. false is also a suitable condition under which commutativity does not hold. At each iteration, ϕ orφ may be updated (not both, but for soundness this does not matter). Consider ϕ. It must also be the case that (ϕ ∨ H) ⇒ m⊲⊳ n because we know that ϕ ⇒ m⊲⊳ n (from the previous iteration) and that H ⇒ m⊲⊳ n (from the valid check at Line 2). Analogous reasoning forφ.
Soundness holds regardless of what Choose returns and even when the theories used to model the underlying data-structure are incomplete. Next we show termination implies completeness: Lemma 2. If Refine m n terminates, then ϕ ∨φ.
Proof. The recursive calls of the Refine algorithm induce a binary tree T , where nodes are labeled by the conjunction of predicates. If Refine terminates, then T is finite, and each node is labeled with a finite conjunction p 0 ∧ ... ∧ p n .
Claim. The disjunction of all leaf node labels is valid. Pf. By induction on the tree. Base case: a single-node tree has label true. Inductive case: for every new node created, labeled with a new conjunct ... ∧ p, there is a sibling node with label ... ∧ ¬p.
Each leaf node of tree T , labeled with conjunction γ, arises from Refine reaching a base case where, by construction, the conjunction γ is disjunctively added to either ϕ orφ. Since Refine terminates, all conjunctions are added to either ϕ orφ, and thus ϕ ∨φ must be valid.
Theorem 2 (Conditions for Termination). Refine m n terminates if 1. (expressiveness) the state space Σ is partitionable into a finite set of regions Σ 1 , ..., Σ N , each described by a finite conjunction of predicates ψ i , such that either ψ i ⇒ m⊲⊳ n or ψ i ⇒ m \ ⊲⊳ n; and 2. (fairness) for every p ∈ P, Choose eventually picks p (note that this does not imply that P is finite), Proof. By contradiction. As in the proof for Lemma 2, we represent the algorithm's execution as a binary tree T , induced by the recursive Refine calls, whose nodes are labeled by the conjunction of predicates (Lines 9 and 10 in Algorithm 1). Assume there exists an infinite path along T , and let its respective labels be π = p 0 , p 0 ∧ p 1 , p 0 ∧ p 1 ∧ p 2 , ....
Claim. There is no finite prefix of π that contains all the predicates ψ i . Pf. Had there been such a prefix ̟, by the expressiveness assumption the running condition H would satisfy one of the validity checks at lines 2 and 4 within, or immediately after, ̟. This is because H would be equal to, or stronger than, the conjunction of the predicates ψ i . This would have made π finite, as π is extended only if both of the validity checks fail, where we assume π is infinite.
By the above claim, at least one of the predicates ψ i is not contained in any finite prefix of π. This contradicts the fairness assumption, whereby any predicate p ∈ P is chosen after finitely many Choose invocations (provided the algorithm hasn't terminated).
Note that while these conditions ensure termination, the bound on the number of iterations depends on the predicate language and behavior of Choose.

The Servois tool and practical considerations
Input. We use an input specification language building on YAML (which has parser and printer support for all common programming languages) with SMTLIB as the logical language. This can be automatically generated relatively easily, thus enabling the integration with other tools [20,16,17,10,28,26]. In Apx. A.1, we show the Counter ADT specification, which was derived from the Pre and Post conditions used in earlier work [22]. The states of a transition system describing an ADT are encoded as list of variables (each as a name/type pair), and each method specification requires a list of argument types, return type, and Pre/Post conditions. Again, the Counter example can be seen in . A.1.

Implementation.
We have developed the open-source Servois tool [3], which implements Refine, Lift, predicate generation, and a method for selecting predicates (Choose) discussed below. Servois uses CVC4 [11] as a backend SMT solver. Servois begins by performing some pre-processing on the input transition system. It checks that the transition system is deterministic. Next, in case the transition system is partial, Servois performs the Lift transformation (Sec. 4). An example of Lift applied to Counter is in Apx. A.2.
Next, Servois automatically generates the predicate language (PGen) in addition to user-provided hints. If the predicate vocabulary is not sufficiently expressive, then the algorithm would not be able to converge on precise commutativity and non-commutativity conditions (Sec. 6). We generate predicates by using terms and operators that appear in the specification, and generating well-typed atoms not trivially true or false. As we demonstrate in Sec. 8, this strategy works well in practice. Intuitively, Pre and Post formulas suffice to express the footprint of an operation. So, the atoms comprising them are an effective vocabulary to express when operations do or do not interfere.
Predicate selection (Choose). Even though the number of computed predicates is relatively small, since our algorithm is exponential in number of predicates it is essential to be able to identify relevant predicates for the algorithm. To this end, in addition to filtering trivial predicates, we prioritize predicates based on the two counterexamples generated by the validity checks in Refine. Predicates that distinguish between the given counter examples are tried first (call these distinguishing predicates). Choose must return a predicate such that χ c ⇒ H ∧ p and χ nc ⇒ H ∧ ¬p. This guarantees progress on both recursive calls. When combined with a heuristic to favor less complex atoms, this ensured timely termination on our examples. We refer to this as the simple heuristic.
Though this produced precise conditions, they were not always very concise, which is desirable for human understanding, and inspection purposes. We thus introduced a new heuristic which significantly improves the qualitative aspect of our algorithm. We found that doing a lookahead (recurse on each predicate one level deep, or poke) and computing the number of distinguishing predicates for the two branches as a good indicator of importance of the predicate. More precisely, we pick the predicate with lowest sum of remaining number of distinguishing predicates by the two calls. As an aside, those familiar with decision tree learning, might see a connection with the notion of entropy gain. This requires more calls to the SMT solver at each call, but it cuts down the total number of branches to be explored. Also, all individual queries were relatively simple for CVC4. The heuristic converges much faster to the relevant predicates, and produces smaller, concise conditions.

Case studies
Common Data-Structures. We applied Servois to Set, HashTable, Accumulator, Counter, and Stack. The generated commutativity conditions for these data structures typically combine multiple theories, such as sets, integers and arrays. We used the quantifier-free integer theory in SMTLIB to encode the abstract state and contracts for the Counter and Accumulator ADTs. For Set, the theory of finite sets [9] for tracking elements along with integers to track size; for HashTable, finite sets to track keys, and arrays for the HashMap itself. For Stack, we observed that for the purpose of pairwise commutativity it is sufficient to track the behavior of boundedly many top elements. Since two operations can at most either pop the top two elements or push two elements, tracking four elements is sufficient. All evaluation data is available on our website [2].

Qs (time) Qs (time)
Counter decrement ⊲⊳ decrement 3 (0.1)  paren.) consumed by this heuristic. The experiments were run on a 2.53 GHz Intel Core 2 Duo machine with 8 GB RAM. The conditions in Fig.2 are those generated by the poke heuristic, and interested reader may compare them with the simple heuristic in [7]. On the theoretical side, our Choose implementation is fair (satisfies condition 2 of Thm. 2, as Lines 9-10 of the algorithm remove from P the predicate being tried). From our experiments we conclude that our choice of predicates satisfies condition 1 of Thm. 2.
Although our algorithm is sound, we manually validated the implementation of Servois by examining its output and comparing the generated commutativity conditions with those reported by prior studies. In the case of Accumulator and Counter, our commutativity conditions were identical to those given in [22]. For the Set data structure, the work of [22] used a less precise Set abstraction, so we instead validated against the conditions of [25]. As for HashTable, we validated that our conditions match those by Dimitrov et al. [15].
The BlockKing Ethereum smart contract. We further validated our approach by examining a real-world situation in which non-commutativity opens the door for attacks that exploit interleavings. We examined "smart contracts", which are programs written in the Solidity programming language [4] and executed on the Ethereum blockchain [1]. Eliding many details, smart contracts are like objects, and blockchain participants can invoke methods on these objects. Although the initial intuition is that smart contracts are executed sequentially, practitioners and academics [31] are increasingly realizing that the blockchain is a concurrent environment due to the fact the execution of one actor's smart contract can be split across multiple blocks, with other actors' smart contracts interleaved. Therefore, the execution model of the blockchain has been compared to that of concurrent objects [31]. Unfortunately, many smart contracts are not written with this in mind, and attackers can exploit interleavings to their benefit.
As an example, we study the BlockKing smart contract. Fig. 3 provides a simplification of its description, as discussed in [31]. This is a simple game in which the players-each identified by an address sendr-participate by making calls to BlockKing.enter(), sending money val to the contract. (The grey variables are external input that we have lifted to be parameters. bk reflects the caller's current block number and rnd is the outcome of a random number generation, described shortly.) The variables on Line 1 are globals, writable in any call to enter. On Line 3 there is a trivial case when the caller hasn't put enough value into the game, and the money is simply returned. Otherwise, the caller stores their address and value into the shared state. A random number is then generated and, since this requires complex algorithms, it is done via a remote procedure call to a third-party on Line 5, with a callback method provided on Line 7. If the randomly generated number is equal to a modulus of the current block number, then the caller is the winner, and warrior's (caller's) details are stored to king and kingBlock on Line 10.
Since random number generation is done via an RPC, players' invocations of enter can be interleaved. Moreover, these calls all write sendr and val to shared variables, so the RPC callback will always roll the dice for whomever most recently wrote to warriorBlock. An attacker can use this to leverage other players' investments to increase his/her own chance to win.
We now explore how Servois can aid a programmer in developing a more secure implementation. We observe that, as in traditional parallel programming contexts, if smart contracts are commutative then these interleavings are not problematic. Otherwise, there is cause for concern. To this end, we translated the BlockKing game into Servois format (see Apx. ??). Servois took 1.4s (on machine with 2.4 GHz Intel Core i5 processor and 8 GB RAM) and yielded the following non-commutativity condition for two calls to enter: This disjunction effectively enumerates cases under which they contract calls do not commute. Of particular note is the first disjunct. From this first disjunct, whenever sendr 1 = sendr 2 , the calls will not commute. Since in practice sendr 1 will always be different from sendr 2 (two different callers) and val 1 ≥ 50 ∧ val 2 ≥ 50 is the non-trivial case, the operations will almost never commute. This should be immediate cause for concern to the developer.
A commutative version of BlockKing would mean that there are no interleavings to be concerned about. Indeed, a simple way to improve commutativity is for each player to write their respective sendr and val to distinct shared state, perhaps via a hashtable keyed on sendr. To this end, we created a new version enter fixed, shown in Fig. 4. (YML versions of these two programs can be found in Appendix B and C.). Servois generated the following non-commutativity condition after 3.5s.
In the above non-commutativity condition, md is shorthand for modFun. In the first two disjuncts above, sendr 1 = sendr 2 which is, again, a case that will not occur in practice. All that remains is the third disjunct where md(bk 2 ) = rnd 2 and md(bk 1 ) = rnd 1 . This corresponds to the case where both players have won. In this case, it is acceptable for the operations to not commute, because whomever won more recently will store their address/block to the shared king/kingBlock.
In summary, if we assume that sendr 1 = sendr 2 , the non-commutativity of the original version is val 1 ≥ 50 ∨ val 2 ≥ 50 (very strong). By contrast, the non-commutativity of the fixed version is val 1 ≥ 50 ∧ val 2 ≥ 50 ∧ md(bk 2 ) = rnd 2 ∧ md(bk 1 ) = rnd 1 . We have thus demonstrated that the commutativity (and non-commutativity) conditions generated by Servois can help developers understand the model of interference between two concurrent calls.

Conclusions and future work
This paper demonstrates that it is possible to automatically generate sound and effective commutativity conditions, a task that has so far been done manually or without soundness. Our commutativity conditions are applicable in a variety of contexts including transactional boosting [19], open nested transactions [29], and other non-transactional concurrency paradigms such as race detection [15], parallelizing compilers [30,34], and, as we show, robustness of Ethereum smart contracts [31]. It has been shown that understanding the commutativity of datastructure operations provides a key avenue to improved performance [12] or ease of verification [24,23].