<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://systems-made-simple.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://systems-made-simple.dev/" rel="alternate" type="text/html" /><updated>2024-07-22T20:10:16+00:00</updated><id>https://systems-made-simple.dev/feed.xml</id><title type="html">Systems Made Simple</title><subtitle>Enhancing software development practices for better security and reliability. Subject to the Creative Commons Attribution 4.0 International License.</subtitle><author><name>Andrey Kuprianov</name><email>andrey@kuprum.xyz</email></author><entry><title type="html">The Rise of Model Checker: Verifying Blockchain Monitors In and Near Realtime</title><link href="https://systems-made-simple.dev/solarkraft/2024/07/04/solarkraft-monitor-verification.html" rel="alternate" type="text/html" title="The Rise of Model Checker: Verifying Blockchain Monitors In and Near Realtime" /><published>2024-07-04T00:00:00+00:00</published><updated>2024-07-04T00:00:00+00:00</updated><id>https://systems-made-simple.dev/solarkraft/2024/07/04/solarkraft-monitor-verification</id><content type="html" xml:base="https://systems-made-simple.dev/solarkraft/2024/07/04/solarkraft-monitor-verification.html"><![CDATA[<p><img src="/img/solarkraft.png" alt="Solarkraft" /></p>

<p><em>Solarkraft has been developed in collaboration by <a href="https://konnov.phd">Igor Konnov</a>, <a href="https://www.linkedin.com/in/jure-kukovec/">Jure Kukovec</a>, <a href="https://www.linkedin.com/in/andrey-kuprianov/">Andrey Kuprianov</a> and <a href="https://thpani.net">Thomas Pani</a>.</em></p>

<p><em>This is the fifth and last in a series of blog posts introducing <a href="https://github.com/freespek/solarkraft">Solarkraft</a>, a TLA+-based runtime monitoring solution for <a href="https://stellar.org/soroban">Soroban smart contracts</a>. The first post,</em> <a href="https://thpani.net/2024/06/why-smart-contract-bugs-matter-and-how-runtime-monitoring-saves-the-day-solarkraft-1/">“A New Hope – Why Smart Contract Bugs Matter and How Runtime Monitoring Saves the Day”</a> <em>gives an overview of smart contracts, explains how traditional security fails to address major challenges in securing crypto assets, and introduces runtime monitoring as a solution. The second post,</em> <a href="https://thpani.net/2024/06/small-and-modular-runtime-monitors-in-tla-for-soroban-smart-contracts-solarkraft-2/">“Guardians of the Blockchain: Small and Modular Runtime Monitors in TLA+ for Soroban Smart Contracts”</a> <em>introduces the language of Solarkraft monitors. The third post,</em> <a href="https://protocols-made-fun.com/solarkraft/2024/06/19/solarkraft-part3.html">“How to Run Solarkraft”</a> <em>gives an overview of the various features of Solarkraft, and explains how to use each one, step-by-step. The forth post,</em> <a href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html">“The Force Awakens: Hybrid Blockchain Runtime Monitors”</a> <em>defines and explores the distinctions between direct and reverse blockchain monitors, which together form what we call hybrid monitors.</em></p>

<p>In this post we first formally define what are hybrid blockchain runtime monitors (from the formal methods point of view), as then proceed to explore the far-reaching avenues of how to go from <em>offline monitoring</em>, as done now in Solarkraft, to truly <em>online monitoring</em> on the live blockchain.</p>

<h2 id="verifying-runtime-monitors-on-a-blockchain-">Verifying Runtime Monitors on a Blockchain 📒</h2>

<p>After reading the <a href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html">previous post on hybrid blockchain monitors</a> you may say: “All that is nice and good, but here are a few questions that still need to be addressed…” For people with different backgrounds these are probably the main ones:</p>

<ul>
  <li>🕴CEO / CTO: “Huh? Formal methods? <em>Why do I need yet another monitoring solution?</em> I already have the X/Y/Z system, and they send me real-time alerts!”</li>
  <li>🤓 Formal methods person: “How do you <em>verify</em> blockchain monitor? What are your <em>verification conditions</em>?”</li>
  <li>👨‍🏫 Mathematician: “What about <em>verification complexity</em>?”</li>
  <li>🧔 Software engineer: “How do you <em>practically check</em> them on the live blockchain?”</li>
</ul>

<p>This blog post outlines the answers to the above questions. <strong>TL;DR</strong>:</p>

<ul>
  <li>Formal methods-based blockchain monitors offer a unique combination of <em>conciseness</em> and <em>completeness</em>: formal monitor specifications are extremely compact, but, at the same time, they allow to completely specify and differentiate valid/invalid transactions, and to detect and prevent a wide range of potential errors or exploits, which are out of reach of traditional alert-based monitoring solutions.</li>
  <li>We verify blockchain monitors via a) producing verification conditions from each monitor specification; b) extracting pre- and post-states for every relevant blockchain transaction, as well as its parameters; c) validating each transaction against verification conditions using the <a href="https://konnov.phd/portfolio/apalache/">Apalache</a> model checker.</li>
  <li>Complexity of verifying blockchain monitors is <em>linear</em> wrt. the number of conditions in the specification and the number of transactions: each condition is checked <em>at most once</em> against every transaction (but many checks may be skipped/optimized away). On the other hand, the inherent logical complexity of checking <em>individual verification conditions</em> is highly dependent on their nature, and may be both very low and very high; <em>it depends</em>. We do propose below some ways to combat this complexity, exploiting for that the modular nature of our monitors.</li>
  <li>Practically, <em>in the current <a href="https://github.com/freespek/solarkraft">Solarkraft system</a></em>, we verify blockchain monitors in <em>offline mode</em> by first downloading transactions using <code class="language-plaintext highlighter-rouge">solarkraft fetch</code>, and then verifying them using <code class="language-plaintext highlighter-rouge">solarkraft verify</code>; as this doesn’t allow to execute preventive measures, we want to move eventually into verifying monitor specifications on the live blockchain, i.e. we want to do <em>online monitoring</em>. There may be several intermediate-strength solutions to that problem, which we outline below.</li>
</ul>

<p>Caught your attention? Do you want a monitoring solution for your blockchain project? <a href="mailto:andrey@kuprum.xyz">Give us a ping!</a> We are always happy to talk to you:)</p>

<p>Are you interested in more details? Then continue reading!</p>

<h2 id="formal-blockchain-monitors-are-super-powerful-">Formal Blockchain Monitors are Super-Powerful 🦸</h2>

<p>In this section we answer the question from an imaginary CEO/CTO:</p>
<blockquote>
  <p>“Huh? Formal methods? Why do I need yet another monitoring solution? I already have the X/Y/Z system, and they send me real-time alerts!”</p>
</blockquote>

<p>As a general intro into the usefulness of runtime monitoring for blockchains we recommend the first episode from our blockchain series: <a href="https://thpani.net/2024/06/why-smart-contract-bugs-matter-and-how-runtime-monitoring-saves-the-day-solarkraft-1/">“A New Hope – Why Smart Contract Bugs Matter and How Runtime Monitoring Saves the Day”</a>. But from the question we conclude that our CEO is already convinced that monitoring is useful, and is even using some other monitoring solution. So, <strong>why do we need formal methods-based blockchain monitors?</strong></p>

<p>To answer that question, <strong>let’s see what a typical blockchain monitoring solution offers:</strong></p>

<ul>
  <li>receive real-time alerts and notifications about blockchain events</li>
  <li>understand usage patterns and fund flows with customized dashboards</li>
  <li>visualize funding patterns, track wallets, report fraudulent activity</li>
  <li>understand the risk of a transaction; simulate its outputs in real time.</li>
</ul>

<p>Typically, some or all of the above activities can be parameterized, e.g. wrt. the addresses, or kinds of transactions, or amounts of funds, etc., which gives these systems a certain level of flexibility. Still, a typical monitoring system suffers from two main drawbacks:</p>

<ul>
  <li>Prevention techniques offered by typical  monitoring systems are most often incomplete: it is impossible to describe by any fixed set of rules the correctness conditions for an arbitrary smart contract.
    <ul>
      <li>When attempts are made within standard monitoring systems to improve their completeness, these attempts usually lead to proliferation of more and more complex pattern-based rules, which are cumbersome to create and maintain, while still never achieving the necessary completeness level.</li>
    </ul>
  </li>
  <li>“Real-time alerts” happen post-factum, when the transaction has already committed its changes. This is already too late: receiving a notification that funds have been withdrawn doesn’t help returning them.
    <ul>
      <li>Some systems try to prevent harmful events by using throttling, i.e. limiting the amounts of fund transfers within a period of time. While helping to mitigate the harmful effects to some degree, these solutions are also unsatisfactory for two reasons: a) they can still be side-stepped (e.g. by decreasing the withdrawal speed, or using intermediaries); b) throttling restrictions lead to frustrating experience for legitimate users.</li>
    </ul>
  </li>
</ul>

<p>Notice that the first problem (<em>monitoring incompleteness</em>) is exactly the reason for the second problem (<em>post-factum response</em>, <em>lack of harm prevention</em>): without being sure that we have described all possible valid/invalid cases, we can’t really be sure to revert a transaction, even if we suspect it being harmful.</p>

<p><strong>Here is where formal methods-based blockchain monitoring comes to save the day.</strong> Formal methods offer a mathematical logic-based solution which allows in many cases to <em>completely specify and differentiate valid/invalid transactions</em>. Moreover, using such decades-proven specification languages as <a href="https://en.wikipedia.org/wiki/TLA%2B">TLA+</a> helps to do it very compactly, and employing such powerful symbolic model checkers as <a href="https://konnov.phd/portfolio/apalache/">Apalache</a> allows us to check formal specifications extremely fast, in fractions of a second.</p>

<p><strong>We want to seamlessly integrate complete validation of transactions against monitor specifications directly into the transaction execution lifecycle.</strong> With our current <a href="https://github.com/freespek/solarkraft">Solarkraft system</a> we have made the first step towards this ultimate goal of <em>online blockchain monitoring</em>; in the subsequent sections we elaborate in more details about the technical details, as well as the next steps towards our goal.</p>

<h2 id="blockchain-monitors-in-formal-attire-">Blockchain Monitors in Formal Attire 👔</h2>

<p>In this section we define, using mathematical notation, what blockchain monitors are, and how to verify whether a blockchain transaction satisfies the conditions expressed by a monitor.</p>

<p>Formally, a blockchain is a sequence of <em>ledgers</em>, where each ledger is a snapshot of the blockchain <em>environment</em> and the blockchain <em>state</em>. States are partitioned: first into separate spaces per contract, and then into separate regions per contract variable. Blockchain states are mutated by <em>transactions</em>, where each transaction is an invocation of a certain contract <em>method</em> with the corresponding method parameters supplied. The invoked method modifies the states according to its logic. A successful transaction bring the blockchain from one environment/state to the next; a rejected/reverted transaction leaves the blockchain environment/state unchanged. We assume that unsuccessful transactions can be still observed.</p>

<p>We employ the following notation:</p>

<ul>
  <li>\(D\) is the set of all possible data values: strings, numbers, structs, etc. Mathematically we don’t distinguish between different data types (though practically we of course do).</li>
  <li>\(V\) is the set of typed contract variables. At this stage we don’t distinguish between states of different contracts: logical assertions may refer to the state of any contract (e.g. to token balances in other contracts).</li>
  <li>\(S = S_0, S_1, ...\) is a sequence of states.</li>
  <li>\(S_i \subseteq V \mapsto D\) is the \(i\)-th contract state, which is a partial mapping from variables to their data values. If a variable \(v\) is present in the mapping \(S_i\), we say that it is <em>defined</em> in this state.</li>
  <li>\(T = T_0, T_1, ...\) is a sequence of transactions. Each transaction brings the contract into its next state, which we denote by \(S_i \xrightarrow{T_i} S_{i+1}\).</li>
  <li>\(P_T\) is the set of all possible typed method parameters.</li>
  <li>\(T_i \subseteq P_T \mapsto D\): each transaction is a method invocation, represented by a partial mapping from method parameter names to their values; only the parameters specific to the invoked method are present in the mapping.</li>
  <li>\(E = E_0, E_1, ...\) is the blockchain environment, which is a sequence of environment states; each transaction executes in a specific environment state.</li>
  <li>\(P_E\) is the set of all typed environment parameters (such as <code class="language-plaintext highlighter-rouge">current_contract_address</code>, <code class="language-plaintext highlighter-rouge">ledger_timestamp</code>, or <code class="language-plaintext highlighter-rouge">method_name</code>).</li>
  <li>\(E_i: P_E \mapsto D\) is a mapping from environment parameters to their values, and defines the current blockchain environment, in which \(T_i\) executes.</li>
  <li>\(X_i \in \mathbb{B} = \{ \top, \bot \}\) is the result of executing the transaction \(T_i\): \(\top\) in case of success, \(\bot\) in case of failure.</li>
</ul>

<p>The above definitions describe the structure of the object to which we apply monitor specifications: a smart contract, executing on a blockchain. Now it’s time to define the structure of monitor specifications themselves. As checking each direct method specification or reverse effect specification is independent from others, we define only the structure for individual monitors.</p>

<ul>
  <li>\(M_D = \langle F, P, H \rangle\) is a direct method monitor specification, where the components are the finite sets of <code class="language-plaintext highlighter-rouge">MustFail</code>, <code class="language-plaintext highlighter-rouge">MustPass</code>, and <code class="language-plaintext highlighter-rouge">MustHold</code> conditions respectively.</li>
  <li>\(M_R = \langle C, A \rangle\) is a reverse effect monitor specification, where the components are the finite sets of <code class="language-plaintext highlighter-rouge">MonitorCheck</code> and <code class="language-plaintext highlighter-rouge">MonitorAssert</code> conditions respectively.</li>
</ul>

<p>In the above:</p>

<ul>
  <li>For any \(F_j \in F\), \(P_k \in P\) we have \(F_j, P_k: (E_i, S_i, T_i) \mapsto \mathbb{B}\) are boolean conditions of the environment state, the past contract state, and the method parameters.</li>
  <li>For any \(H_j \in H\) we have \(H_j: (E_i, S_i, T_i) \times S_{i+1} \mapsto \mathbb{B}\) are boolean conditions of the environment state, the past contract state, the method parameters, and the next contract state.</li>
  <li>For any \(C_j \in C\), \(A_k \in A\) we have \(C_j, A_k: (E_i, S_i) \times S_{i+1} \mapsto \mathbb{B}\) are boolean conditions defined over the environment state, the past contract state, and the next contract state.</li>
</ul>

<h3 id="verification-conditions-for-blockchain-monitors">Verification Conditions for Blockchain Monitors</h3>

<p><em>Verification conditions</em> are verifiable mathematical statements, which encode a certain aspect of the system correctness; in our case they encode whether the blockchain transaction is correct wrt. the blockchain monitor. Having formally defined what are blockchain states, transactions, and monitors, we are now in a position to specify monitor verification conditions.</p>

<p><strong>For a direct blockchain monitor</strong> \(M_D = \langle F, P, H \rangle\), we combine individual monitor conditions into larger ones:</p>

\[\mathbb{C}_{\mathit{Fail}} = \bigvee_{j}{F_j}\]

\[\mathbb{C}_{\mathit{Pass}} = \bigvee_{j}{P_j}\]

\[\mathbb{C}_{\mathit{Hold}} = \bigwedge_{j}{H_j}\]

<p>Given the above combined conditions, we check these verification conditions:</p>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Verification condition</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Must fail</td>
      <td>\(\mathbb{C}_{\mathit{Fail}}  \implies (X_i = \bot)\)</td>
    </tr>
    <tr>
      <td>Failure completeness</td>
      <td>\((X_i = \bot) \implies \mathbb{C}_{\mathit{Fail}}\)</td>
    </tr>
    <tr>
      <td>Must succeed</td>
      <td>\(\neg \mathbb{C}_{\mathit{Fail}} \wedge \mathbb{C}_{\mathit{Pass}} \implies (X_i = \top)\)</td>
    </tr>
    <tr>
      <td>Success completeness</td>
      <td>\((X_i = \top) \implies \neg \mathbb{C}_{\mathit{Fail}} \wedge \mathbb{C}_{\mathit{Pass}}\)</td>
    </tr>
    <tr>
      <td>Method correctness</td>
      <td>\((X_i = \top) \implies \mathbb{C}_{\mathit{Hold}}\)</td>
    </tr>
  </tbody>
</table>

<p>Compare these formal verification conditions with the <a href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html#direct-monitors">informal conditions from the previous post</a>, as well as with the <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/deposit.tla#L66-L104">TLA+ encoding of verification conditions for Timelock’s <code class="language-plaintext highlighter-rouge">deposit</code> method</a>. Notice also that the two implications from the pairs “Must fail”/”Failure completeness” and “Must succeed”/”Success completeness” encode together an equivalence between the checks and the transaction execution result. Nevertheless, we consider it a better strategy to treat these conditions separately, as this allows the developers to encode a more fine-grained monitor response. For example, a monitor may forcefully revert a transaction that violates the “Must fail” condition, but only issue a warning when “Failure completeness” is violated.</p>

<p><strong>For a reverse blockchain monitor</strong> \(M_R = \langle C, A \rangle\), we also combine individual monitor conditions into larger ones:</p>

\[\mathbb{C}_{\mathit{Check}} = \bigvee_{j}{C_j}\]

\[\mathbb{C}_{\mathit{Assert}} = \bigwedge_{j}{A_j}\]

<p>Reverse monitors encode only a single verification condition:</p>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Verification condition</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Effect correctness</td>
      <td>\((X_i = \top) \wedge \mathbb{C}_{\mathit{Check}} \implies \mathbb{C}_{\mathit{Assert}}\)</td>
    </tr>
  </tbody>
</table>

<p>You may compare the above verification condition with the <a href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html#reverse-monitors">informal condition from the previous post</a>, as well as with the <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/balance_record.tla#L31-L39">TLA+ encoding of verification conditions for the <code class="language-plaintext highlighter-rouge">BalanceRecord</code> monitor</a>.</p>

<h3 id="model-checking-blockchain-monitors">Model Checking Blockchain Monitors</h3>

<p><em>Model checking</em> is an automatic procedure of verifying mathematical specifications. Within <a href="https://github.com/freespek/solarkraft">Solarkraft</a>, we employ <a href="https://en.wikipedia.org/wiki/TLA%2B">TLA+</a> as our specification language, and <a href="https://konnov.phd/portfolio/apalache/">Apalache</a> as our model checker. Here are a few details worth noting:</p>

<ul>
  <li>Apalache is a <em>general purpose</em> model checker, in that it performs <em>invariant checking</em>: given an initial system state \(\mathit{Init}\), an encoding of the system transitions (the <em>next-state relation</em>) \(\mathit{Next}\), and an encoding of a supposed system invariant \(\mathit{Inv}\), it checks whether the invariant does indeed hold in all system states reachable from the initial one by executing system transitions.</li>
  <li>Apalache is a <em>bounded</em> model checker: it can check invariants only in states reachable in a certain number of transition steps (the execution bound \(\mathit{Length}\), say 1, 5, or 10) from the initial state.</li>
  <li>Apalache is a <em>symbolic</em> model checker, i.e. it encodes the the verification conditions symbolically, as formulas in certain logical theories, and passes the resulting encoding to <em>Satisfiability Modulo Theories (SMT) solvers</em>, which are specialized tools for solving massive volumes of math equations.</li>
</ul>

<p><em>In the current system</em> we employ Apalache by encoding monitor verification conditions as a <em>deadlock checking problem</em>: we encode the verification condition as part of the next-state relation. Thus, if the verification condition is violated, the system is unable to proceed (there is a deadlock), and this is detected by Apalache. Formally, for any given blockchain environment \(E_i\), the transaction pre-state \(S_i\), the transaction being executed \(T_i\), the transaction execution result \(X_i\), the transaction post-state \(S_{i+1}\), as well as any of the above verification conditions \(\mathit{VC}\), we execute Apalache using the following encoding:</p>

<ul>
  <li>Initial state: \(\mathit{Init} = E_i \wedge S_i\)</li>
  <li>Next-state relation: \(\mathit{Next} = T_i \wedge X_i \wedge S_{i+1} \wedge \mathit{VC}\)</li>
  <li>Invariant: \(\mathit{Inv} = \top\)</li>
  <li>Execution bound: \(\mathit{Length} = 1\)</li>
</ul>

<p>A few TLA+ tests for Apalache verification conditions using this encoding can be found e.g. in <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/deposit_test.tla">deposit_test.tla</a> (for the direct monitor of Timelock’s <code class="language-plaintext highlighter-rouge">deposit</code> method), or in <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/balance_record_test.tla">balance_record_test.tla</a> (for the reverse balance record monitor). In all cases Apalache is invoked in a similar fashion, e.g. like that for <code class="language-plaintext highlighter-rouge">deposit</code>’s first test:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apalache-mc check <span class="nt">--length</span><span class="o">=</span>1 <span class="nt">--init</span><span class="o">=</span>Init_1 <span class="nt">--next</span><span class="o">=</span>Next_1 deposit_test.tla
</code></pre></div></div>

<p>As explained above, Apalache is a <em>bounded</em> model checker: it can check execution traces up to a certain bound on the execution length. For most systems this restriction starts to manifest itself from the execution depth of around 7 steps: the model checker slows down substantially when exploring execution traces longer than that. But specifically for monitoring this restriction is irrelevant: with the execution length of 1 Apalache is blazing fast, and verifies the above formulas in fractions of a second, so it’s a perfect choice for monitoring applications.</p>

<p>In the above tests a monolithic encoding is used: all monitor conditions are encoded as a single invariant, and also included into the next-state relation. This encoding is the compromise we had to make due to the very limited project timeline, and has a few drawbacks:</p>

<ul>
  <li>All verification conditions are lumped together into a single invariant, and, moreover, the invariant is part of the next-state relation. As a result, when the invariant is violated, the feedback from the model checker is suboptimal: it reports only that the system is unable to proceed (deadlocked), but doesn’t explain the reason for that (as no invariant was violated).</li>
  <li>In cases more complex than Timelock, verifying a single large invariant may become way more time-consuming than the sum of times for verifying each individual invariant separately, due to ultimately exponential nature of the resulting logical problem.</li>
</ul>

<p>In general, we can be more flexible in encoding monitor verification conditions for model checking. E.g. in <a href="https://github.com/freespek/solarkraft/blob/f16a96e22c73aa4bbcb4a2fba56f8a61321db00f/doc/case-studies/timelock/timelock_mon_tests.tla">another version of Timelock’s monitors</a> we encoded one <em>combined monitor condition</em> per invariant. Finally, verification conditions can also be encoded very fine-grained, down to the smallest scale, when an invariant to be checked contains a single direct monitor condition (one of \(F_j\), \(P_j\), \(H_j\)), or a single reverse monitor condition (one of \(C_j\), \(A_j\)). In all those cases, we encode a verification condition \(\mathit{VC}\) as an <em>invariant checking problem</em> for Apalache in the following way:</p>

<ul>
  <li>Initial state: \(\mathit{Init} = E_i \wedge S_i\)</li>
  <li>Next-state relation: \(\mathit{Next} = T_i \wedge X_i \wedge S_{i+1}\)</li>
  <li>Invariant: \(\mathit{Inv} = \mathit{VC}\)</li>
  <li>Execution bound: \(\mathit{Length} = 1\)</li>
</ul>

<p>We then execute Apalache using the following command:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apalache-mc check <span class="nt">--length</span><span class="o">=</span>1 <span class="nt">--init</span><span class="o">=</span>Init <span class="nt">--next</span><span class="o">=</span>TxRes <span class="nt">--inv</span><span class="o">=</span>VC timelock_mon_tests.tla
</code></pre></div></div>

<p>This encoding solves the aforementioned problems wrt. monolithic encoding: the feedback from the model checker explains in details what is the problem when an invariant is violated; the encoding can also provide substantial improvements in terms of execution speed for monitors which are more complex than Timelock’s.</p>

<h2 id="practical-checking-of-blockchain-monitors-">Practical Checking of Blockchain Monitors 🛠</h2>

<p>In the present <a href="https://github.com/freespek/solarkraft">Solarkraft system</a> we do what’s called <em>offline monitoring</em>: we verify monitors <em>after</em> the state has already been committed to the blockchain. The delay between the action and the response can be made very small, a few seconds, but due to the final nature of the committed transactions this is not enough: the changes (such as balance transfer) can’t be undone. Our eventual goal is to perform <em>online monitoring</em>, i.e. to verify the monitors <em>before</em> the state has been committed, in order to be able to do preventive actions. This far-reaching goal is non-trivial, and has a few intermediate-strength solutions, which we are about to explore now.</p>

<p><strong>Offline monitoring</strong> is is the simplest blockchain monitoring solution, applied both by standard blockchain monitors, as well as by our <a href="https://github.com/freespek/solarkraft">current Solarkraft system</a>:</p>

<ul>
  <li>A transaction is committed on the blockchain;</li>
  <li>At some later time point, the transaction effects are observed: <code class="language-plaintext highlighter-rouge">solarkraft fetch</code>;</li>
  <li>Transaction is validated, and acted upon: <code class="language-plaintext highlighter-rouge">solarkraft verify --alert</code>.</li>
</ul>

<p>This approach is useful in that the reaction to the event (a transaction) may happen in <em>near real time</em>: a few seconds later. The problem is that for blockchain this is not enough: what matters is the logical state on the blockchain, which, when committed, is irreversible (except for hard forks). Thus, in many cases, the reaction can’t prevent the possible harm being done.</p>

<p>To better understand how preventive actions may be done, let’s take a look at <a href="https://developers.stellar.org/docs/learn/fundamentals/transactions/transaction-lifecycle">Stellar’s transaction lifecycle</a>. The important points where a monitoring system may intervene in the transaction lifecycle are the steps 3, 7, and 10:</p>

<blockquote>
  <ol>
    <li>Creation (Transaction Creator)</li>
    <li>Signing (Transaction Signers)</li>
    <li><strong>Submitting  (Transaction Submitter): After signing, the transaction can now be submitted to the Stellar network. If the transaction is invalid, it will be rejected immediately by Stellar Core.</strong></li>
    <li>Propagating (Validator)</li>
    <li>Crafting a candidate transaction set (Validator)</li>
    <li>Nominating a transaction set (Validator)</li>
    <li><strong>Stellar Consensus Protocol (SCP) determines the final transaction set (Validator Network). SCP resolves any differences between candidate transaction sets and ultimately determines a single transaction set to apply, the close time of the ledger, and any upgrades to the protocol that need to be applied network-wide at the apply time.</strong></li>
    <li>Transaction apply order is determined (Validator Network)</li>
    <li>Fees are collected (Validator)</li>
    <li><strong>Application (Validator): Each transaction is applied in the previously-determined order. For each transaction, the account’s sequence number is consumed (increased by 1), the transaction’s validity is rechecked, and each operation is applied in the order they occur in the transaction. Operations may fail at this stage due to errors that can occur outside of the transaction and operation validity checks. For example, an insufficient balance for a payment is not checked at submission and would fail at this time.</strong></li>
    <li>Protocol Upgrades (Validator)</li>
  </ol>
</blockquote>

<p>Why are these steps important? Because exactly at these steps <em>new information appears, which influences transaction validity</em>:</p>

<ul>
  <li>Step 3: the transaction \(T_i\) is determined: its parameters, signatures, etc.</li>
  <li>Step 7: the blockchain environment \(E_i\)  is determined, in which \(T_i\) will execute:
    <ul>
      <li>the set of transactions which will be executed together with \(T_i\);</li>
      <li>\(T_i\)’s ledger number / timestamp;</li>
      <li>the starting state for ledger’s transaction set, which is the end state of the previous ledger.</li>
    </ul>
  </li>
  <li>Step 10: the starting state \(S_i\) for transaction \(T_i\) is determined, which is the result of applying all other transactions preceding \(T_i\) in the apply order determined at step 8.</li>
</ul>

<p><em>It is worth noting that the order of application determined at step 8 is also a new piece of information, which influences transaction validity (and ultimately determines \(S_i\)). Nevertheless, as steps 8-10 happen essentially at the same time (see <a href="https://github.com/stellar/stellar-core/blob/2ba9f8de47faca0b9e3bf3da540f38f15665606b/src/ledger/LedgerManagerImpl.cpp#L894-L906">LedgerManagerImpl::closeLedger</a>), this difference in timing is immaterial. For conceptual reasons we prefer to focus on step 10.</em></p>

<p>When speaking about practicality, timing and throughput parameters start playing an important role:</p>

<ul>
  <li>Typical Stellar ledger close time: 5-6 seconds</li>
  <li>Stellar transaction throughput (transactions per second, TPS): up to 1000</li>
</ul>

<p>What does the above mean for validating blockchain monitors? Two things:</p>

<ul>
  <li>With each step, additional data becomes available; thus more monitor verification conditions (VCs) can be validated:
    <ul>
      <li>At step 3: <em>stateless</em> VCs can be validated, i.e. those depending only on \(T_i\);</li>
      <li>At step 7: <em>semi-stateful</em> VCs, depending only on \(T_i\) and \(E_i\) can be validated;</li>
      <li>At step 10: all <em>stateful</em> VCs can be validated.</li>
    </ul>
  </li>
  <li>With each step, the timing constraints become more strict (in order not to disrupt the core blockchain functionality):
    <ul>
      <li>At step 3: any reasonable time (e.g. up to 10 seconds) can be allocated to execute the transaction validity checks;</li>
      <li>At step 7: a small portion of the ledger close time (e.g. up to 1 second) can be allocated for checking all ledger’s transactions;</li>
      <li>At step 10: a tiny portion of ledger close time (e.g. up to 100 milliseconds) can be allocated for checking all ledger’s transactions.</li>
    </ul>
  </li>
</ul>

<p>What can <a href="https://konnov.phd/portfolio/apalache/">Apalache</a> model checker checker offer us in terms of validity checks execution time? For the Timelock example, the typical VC check time is around 1 second on a powerful laptop. Using such features as <em>Server Mode</em> (mostly implemented, see <a href="https://github.com/informalsystems/apalache/issues/730">[FEATURE] Server Mode</a> and <a href="https://apalache.informal.systems/docs/adr/010rfc-transition-explorer.html">RFC-010: Implementation of Transition Exploration Server</a>) we expect the startup time (runtime setup, parsing, typechecking, preprocessing) to be amortized for multiple queries, and validity checking time to be reduced to something like 100 milliseconds. This sounds good! But a few problems still exist, unfortunately:</p>

<ul>
  <li>This is the checking time for a single transaction; but for steps 7 and 10 <em>all ledger’s transactions</em> need to be checked. Taking into account the blockchain parameters, this means checking up to 5000 transactions in 1 second (for step 7), or in 100 milliseconds (for step 10).</li>
  <li>The Timelock example is one of the simplest imaginable in terms of its logical complexity. Thus, for more complex examples the checking time can be substantially higher.</li>
</ul>

<p>Taking all of the above in consideration we have two (mostly independent) strategies of how blockchain monitors can be integrated into the transaction lifecycle: one  from formal methods point of view, and another from blockchain engineering point of view.</p>

<h3 id="model-checking-improvements-for-blockchain-monitoring">Model Checking Improvements for Blockchain Monitoring</h3>

<p>As can be seen from the analysis above, <strong>model checking has to provide hard real time execution guarantees for validity checks</strong>, 
 such as <em>“up to 5000 transactions can be checked in 100 milliseconds”</em>. How can this be done? Below are a few ideas on how to achieve that.</p>

<p><strong>Software engineering improvements</strong>. Features such as Server Mode can substantially reduce startup times, giving up to 10x checking time reduction. This feature is mostly implemented, but still needs some polishing. Another useful feature would be efficient parallelization (also partially implemented): given 5000 transactions, each independently checkable in 100 ms, and being able to execute the checks in parallel, would allow us to execute all ledger’s transactions checks in 100 ms.</p>

<p><strong>Model checking problem decomposition.</strong> Our <a href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html">hybrid blockchain monitors</a> are already quite modular, in the sense that each monitor is expressed as a combination of simple conditions. As we explained in the previous sections, the verification conditions can be checked independently for each monitor condition, and then combined at the boolean level. Solving each of the resulting subproblem independently will allow both for parallelization (see above), as well as to use specialized solvers for each subproblem, with different complexity constraints (see below). We could employ the <a href="https://en.wikipedia.org/wiki/Three-valued_logic">three-valued logic</a> to describe the boolean structure of the overall problem, with the <em>Unknown</em> value expressing that the model checking is not possible with the available information, or didn’t terminate within the required hard time bound. Using then logical connectors from the three-valued logic would allow us to provide meaningful answers in some cases when the standard model checking procedure would not terminate.</p>

<p><strong>Theory-specific solvers for subproblems.</strong> Apalache reduces model checking problem to the QF_NIA logic (Quantifier-free theory of nonlinear integer arithmetic). While being very general and powerful, this theory is in the worst case undecidable. When looking at moderately large model checking problems as a whole (even at Timelock) at least QF_LIA (Quantifier-free theory of linear integer arithmetic) is required, which is a subtheory of QF_NIA with exponential complexity. When looking at subproblems though, simpler theories could be employed; examples of those are QF_EUF (Quantifier-free theory of equality and uninterpreted functions) with the worst-case \(n \cdot \mathit{log}(n)\) complexity, or QF_IDL (Quantifier-free theory of integer difference logic), with the worst-case cubic complexity. Putting aside record access (which can be abstracted away in some cases) examples of subproblems with reduced complexity in the Timelock case can be found in the <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/balance_record.tla#L10-L25">Balance Record monitor</a>, which falls under QF_EUF theory, or <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/claim.tla#L30-L38">Claim’s <code class="language-plaintext highlighter-rouge">MustHold</code> monitor conditions</a>, which is expressible in QF_IDL theory.</p>

<h3 id="blockchain-engineering-for-runtime-monitoring">Blockchain Engineering for Runtime Monitoring</h3>

<p>All of the above model checking improvements are useless if they can’t be applied at the right time and place. For that, a proper <strong>integration of monitoring into transaction lifecycle is necessary</strong>, specifically to be able to execute preventive measures when a violating transaction is detected. Based of the transaction lifecycle outlined above, here is how we see this can be done:</p>

<p><strong>Execute stateless validity checks at transaction submission time</strong>. At step 3, when a transaction is submitted to the blockchain, stateless checks (depending only in \(T_i\)) can be executed; this needs to be done at Stellar Core, as the single controllable point of entry for all incoming transactions. As timing requirements are not too strict at that point, Apalache can be employed as is (only the software engineering improvements would be useful for efficiency reasons).</p>

<p><strong>Execute semi-stateful validity checks when SCP decides ledger’s transaction set</strong>. Semi-stateful checks (depending on \(T_i\) and \(E_i\)) can be executed at step 7 by the validator network; the timing requirements become moderately strict, so model checking problem decomposition becomes necessary.</p>

<p><strong>Execute stateful checks when transactions are applied</strong>. This is done by a validator node at step 10, and the timing requirements are the most strict ones, so all model checking improvements become necessary. Inevitably there will be cases when model checking will exceed the timing requirements, returning the <em>Unknown</em> answer, so the monitoring system should be configurable with actions to be executed when this happens. E.g. in the most critical cases a transaction can be reverted; in less critical cases a transaction may be allowed to pass, but an alert will be issued.</p>

<p>All of the above requires integration of formal-methods based monitoring into the central blockchain components. If this isn’t possible for some reason for the whole blockchain, what can a project do in order to implement <strong>individual project monitoring</strong>? Though it’s less efficient than the whole-blockchain solution, but a lot can still be done:</p>

<p><strong>Perform stateless validation of user transactions via a dedicated service</strong>. A project may require its users to submit transactions using <code class="language-plaintext highlighter-rouge">Permit</code>s via a centralized service, which will perform transaction validation by interacting with the monitoring system. The service, in case of successful checks, will sign and submit transaction to the blockchain. The on-chain components of the system need to be restricted to accept only such transactions which are signed by the service, and also validate user’s <code class="language-plaintext highlighter-rouge">Permit</code> signatures.</p>

<p><strong>During transaction processing, perform stateful checks via on-chain monitoring system</strong>. An on-chain monitoring system can be implemented which will perform (limited) transaction validation. A project-specific contract, when receiving a transaction, will call into the monitoring system to perform transaction validation. This in turn can be done in two ways:</p>

<ul>
  <li>Implement on-chain solvers for simple theories such as as QF_EUF or QF_IDL, and validate the transaction within the same call. Some attempts in that direction have been undertaken already for EVM/Solidity, see e.g. the pilot project <a href="https://github.com/leonardoalt/dl_symb_exec_sol">EVM Symbolic Execution in Solidity</a>.</li>
  <li>Accept transaction for validation, log it on-chain, and wait for an off-chain component to validate it. The off-chain component will commit the validation results on-chain, and the on-chain component will forward the result to the project-specific contract. This happens with an inevitable delay of at least 1 ledger: e.g. a transaction is submitted at ledger \(n\), but validated and executed at ledger \(n+1\). While slightly less convenient for the user, this allows to side-step hard real time requirements wrt. model checker execution.</li>
</ul>

<hr />

<p>This post concludes our blog post series about the first phase of <a href="https://github.com/freespek/solarkraft">Solarkraft</a> development; we hope you’ve enjoyed it. Please don’t hesitate to <a href="mailto:andrey@kuprum.xyz">write to us</a>: we are happy to hear from you, and discuss everything concerning the fascinating topic of blockchain runtime monitoring!</p>

<hr />

<p><em>Development of Solarkraft was supported by the <a href="https://stellar.org/foundation">Stellar Development Foundation</a> with a generous Activation Award from the <a href="https://communityfund.stellar.org">Stellar Community Fund</a> of 50,000 USD in XLM.</em></p>]]></content><author><name>Andrey Kuprianov</name><email>andrey@kuprum.xyz</email></author><category term="solarkraft" /><category term="apalache" /><category term="formal-methods" /><category term="runtime-monitor" /><category term="smart-contracts" /><category term="solarkraft" /><category term="soroban" /><category term="specification" /><category term="stellar" /><category term="tla" /><category term="tlaplus" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The Force Awakens: Hybrid Blockchain Runtime Monitors</title><link href="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html" rel="alternate" type="text/html" title="The Force Awakens: Hybrid Blockchain Runtime Monitors" /><published>2024-06-24T00:00:00+00:00</published><updated>2024-06-24T00:00:00+00:00</updated><id>https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors</id><content type="html" xml:base="https://systems-made-simple.dev/solarkraft/2024/06/24/solarkraft-hybrid-monitors.html"><![CDATA[<p><img src="/img/solarkraft.png" alt="Solarkraft" /></p>

<p><em>Solarkraft has been developed in collaboration by <a href="https://konnov.phd">Igor Konnov</a>, <a href="https://www.linkedin.com/in/jure-kukovec/">Jure Kukovec</a>, <a href="https://www.linkedin.com/in/andrey-kuprianov/">Andrey Kuprianov</a> and <a href="https://thpani.net">Thomas Pani</a>.</em></p>

<p><em>This is the fourth in a series of blog posts introducing <a href="https://github.com/freespek/solarkraft">Solarkraft</a>, a TLA+-based runtime monitoring solution for <a href="https://stellar.org/soroban">Soroban smart contracts</a>. The first post,</em> <a href="https://thpani.net/2024/06/why-smart-contract-bugs-matter-and-how-runtime-monitoring-saves-the-day-solarkraft-1/">“A New Hope – Why Smart Contract Bugs Matter and How Runtime Monitoring Saves the Day”</a> <em>gives an overview of smart contracts, explains how traditional security fails to address major challenges in securing crypto assets, and introduces runtime monitoring as a solution. The second post,</em> <a href="https://thpani.net/2024/06/small-and-modular-runtime-monitors-in-tla-for-soroban-smart-contracts-solarkraft-2/">“Guardians of the Blockchain: Small and Modular Runtime Monitors in TLA+ for Soroban Smart Contracts”</a> <em>introduces the basic language of Solarkraft monitors. The third post,</em> <a href="https://protocols-made-fun.com/solarkraft/2024/06/19/solarkraft-part3.html">“How to Run Solarkraft”</a> <em>gives an overview of the various features of Solarkraft, and explains how to use each one, step-by-step.</em></p>

<p>While the previous posts explain the current state of the project, in this one we take one step further, and explore the directions in which we plan to evolve blockchain runtime monitoring with Solarkraft. Throughout the post we are using the same <a href="https://github.com/stellar/soroban-examples/blob/f595fb5df06058ec0b9b829e9e4d0fe0513e0aa8/timelock/src/lib.rs"><code class="language-plaintext highlighter-rouge">timelock</code> contract</a> from <code class="language-plaintext highlighter-rouge">soroban-examples</code> that was used in <a href="https://thpani.net/2024/06/small-and-modular-runtime-monitors-in-tla-for-soroban-smart-contracts-solarkraft-2/">Part 2: “Guardians of the Blockchain”</a>; please explore at least this post first to acquire the necessary context.</p>

<h2 id="blockchain-runtime-monitors">Blockchain Runtime Monitors</h2>

<p>Runtime monitoring, also known as <a href="https://runtime-verification.github.io/">runtime verification</a>, is a well-established field, where many practical approaches have been devised and applied successfully. Based on this heritage, we proposed the first version of a Web3 runtime monitoring system for the Stellar blockchain in <a href="https://thpani.net/2024/06/small-and-modular-runtime-monitors-in-tla-for-soroban-smart-contracts-solarkraft-2/">Part 2: “Guardians of the Blockchain”</a>. Our system is based on the <a href="https://en.wikipedia.org/wiki/TLA%2B">TLA+</a> specification language, a well-established formalism for specifying and verifying distributed systems.</p>

<p>Taking a step back from the concrete solution, let’s try to answer the more abstract question: <em>What do we want to achieve with runtime monitors in blockchains?</em> As runtime monitors are eventually going to be deployed and executed <em>on the live blockchain</em>, they should satisfy the following requirements:</p>

<ul>
  <li><strong>Prevent from safety violations</strong> (<em>safety</em>): bad things, such as your token balance being stolen, should not happen. This is the primary goal of runtime monitors: react preventively, and abort unwanted executions.</li>
  <li><strong>Detect liveness violations</strong> (<em>liveness</em>): good things should be able to happen! E.g. you, as an account owner, should be able to withdraw your own balance. If a legitimate transaction keeps reverting, that’s also a bug, not less severe than someone stealing your tokens.</li>
  <li><strong>Detect unexpected behaviors</strong> (<em>completeness</em>): same as code, specs are not perfect. If a spec author overlooked some behaviors, and they manifest themselves on the live blockchain, this may mean anything: from simple spec incompleteness, to an exploit being executed. Monitors should be configurable to either issue a simple warning, or to prevent such behaviors altogether.</li>
</ul>

<p>The problem we’ve observed with the previously employed approaches to formal specification is that the specs of <em>what</em> the method should do can easily be much larger than the actual implementation. So we would like to add to the above the following soft requirement:</p>

<ul>
  <li><strong>Specify behaviors compactly and independently</strong> (<em>compactness and modularity</em>): it is usually the case that a smart contract encompasses a lot of various aspects (e.g. authentication, authorization, storage management, math computations), and is written/employed/reasoned about by various roles (e.g. smart contract developer, mathematician, architect, UI developer). All of those roles should be able to specify various aspects of the smart contract behavior as easily and as independently as possible.</li>
</ul>

<p>So monitors should be able to specify both <em>safety</em> and <em>liveness</em> properties, be <em>complete</em> wrt. the current and future system behaviors, and, preferably, also be <em>compact and modular</em>. For that we propose a conceptual separation of monitors into <em>direct monitors</em> (those reasoning from cause to effect), and <em>reverse monitors</em> (those going from effect to cause). We can combine the two together in what we call <em>hybrid monitors</em>.</p>

<h2 id="direct-monitors">Direct Monitors</h2>

<p>Here we reason from the cause (method invocation) to the effect, but apply a structure which closely mimics, in formal semantics, what we expect to see when we program smart contracts. The essence of the structure is in the picture below:</p>

<p><img src="/img/DirectMonitors.png" alt="Direct monitor specs" /></p>

<p>In direct monitors, we distinguish three kinds of conditions:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">MustFail_i</code> is a condition under which the method is expected to fail. If <em>any</em> of those conditions hold, the monitor activates, and checks that the method does indeed fail;</li>
  <li><code class="language-plaintext highlighter-rouge">MustPass_i</code> is a condition, under which the method is expected to succeed, <em>provided that</em> none of the <code class="language-plaintext highlighter-rouge">MustFail_i</code> conditions hold. Each <code class="language-plaintext highlighter-rouge">MustPass_i</code> condition represents a separate happy path in the method invocation;</li>
  <li><code class="language-plaintext highlighter-rouge">MustHold_i</code> is a condition that should hold after the method invocation is successful (e.g. the tokens should be transferred). Unlike the previous two categories, which reason only about the state of the system before the method invocation, these properties may reference both the post-method state, and the pre-method state. <em>All</em> of <code class="language-plaintext highlighter-rouge">MustHold_i</code> should hold if the method is executed successfully.</li>
</ul>

<p>In the above, <code class="language-plaintext highlighter-rouge">Must&lt;Fail|Pass|Hold&gt;</code> is a prefix, which tells the monitor system how to interpret this predicate. The complete pattern for predicate names with these prefixes is as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Must&lt;Fail|Pass|Hold&gt;_&lt;Method&gt;_&lt;ConditionDescription&gt;
</code></pre></div></div>

<p>All predicates which refer to the same <code class="language-plaintext highlighter-rouge">&lt;Method&gt;</code> will be grouped, to create together one <em>method monitor</em>. Interpreted formally, the monitor should do the following when <code class="language-plaintext highlighter-rouge">&lt;Method&gt;</code> is invoked:</p>

<ol>
  <li>If any of <code class="language-plaintext highlighter-rouge">MustFail_i</code> conditions hold, check that method invocation reverts (otherwise, issue a warning / revert if configured to do so)</li>
  <li>If none of <code class="language-plaintext highlighter-rouge">MustFail_i</code> conditions hold, but method invocation reverted, issue a warning (incomplete spec)</li>
  <li>If none of  <code class="language-plaintext highlighter-rouge">MustFail_i</code> hold, and one of <code class="language-plaintext highlighter-rouge">MustPass_i</code> conditions hold, check that method invocation succeeds (otherwise, issue a warning)</li>
  <li>If none of  <code class="language-plaintext highlighter-rouge">MustFail_i</code> hold, and none of <code class="language-plaintext highlighter-rouge">MustPass_i</code> conditions hold, but method invocation succeeded, issue a warning of an incomplete spec (or revert if configured to do so)</li>
  <li>If method invocation succeeds, check that all of <code class="language-plaintext highlighter-rouge">MustHold_i</code> conditions hold on the pre- and post-states of the method invocation (otherwise, issue a warning / revert if configured to do so)</li>
</ol>

<p>Notice that above we apply <em>or</em> as default connector for preconditions (<code class="language-plaintext highlighter-rouge">MustFail_i</code> / <code class="language-plaintext highlighter-rouge">MustPass_i</code>), and we apply <em>and</em> as default connector for effects (<code class="language-plaintext highlighter-rouge">MustHold_i</code>). Thus, you may split preconditions/effects into separate predicates at your convenience, avoiding complicated logical structure inside predicates themselves.</p>

<h3 id="direct-monitors-for-the-timelock-contract">Direct monitors for the Timelock contract</h3>

<p>Having outlined the general structure of direct monitors, let’s apply it to the <a href="https://github.com/stellar/soroban-examples/blob/f595fb5df06058ec0b9b829e9e4d0fe0513e0aa8/timelock/src/lib.rs">Timelock contract</a>. Direct monitors for Timelock’s <code class="language-plaintext highlighter-rouge">deposit</code> and <code class="language-plaintext highlighter-rouge">claim</code> methods can be found in <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/deposit.tla">deposit.tla</a> and <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/claim.tla">claim.tla</a> respectively; below we depict only the structure of these monitor specifications (we omit <code class="language-plaintext highlighter-rouge">Must&lt;Fail|Pass|Hold&gt;</code> as well as the method names for clarity).</p>

<p><img src="/img/TimelockDirectMonitors.png" alt="Timelock direct monitors" /></p>

<p>As can be seen, a direct method monitor is decomposed into a collection of independent and small monitors, i.e. we did achieve our (soft) goal of <em>compactness and modularity</em>. Safety and liveness goals also seem to be satisfied:</p>

<ul>
  <li><em>Safety</em>: Timelock’s direct monitors guarantee numerous safety properties. A safety property is usually ensured by either <font style="background: #fd4d4d;">MustFail</font>, or <font style="background: #ffe342;">MustHold</font>, or a combination of both conditions. For example:
    <ul>
      <li>The property “only the approved claimant may claim the deposit” is ensured by the <font style="background: #fd4d4d;">NonClaimant</font> sub-monitor;</li>
      <li>The property “the Timelock contract receives the deposited funds from the claimant” is ensured by the combination of <font style="background: #fd4d4d;">NotEnoughBalance</font> and  <font style="background: #ffe342;">TokenTransferred</font> sub-monitors.</li>
    </ul>
  </li>
  <li><em>Liveness</em>: Timelock’s liveness properties are guaranteed by the <font style="background: #54c45e;">MustPass</font> conditions:
    <ul>
      <li>Implicit in case of <code class="language-plaintext highlighter-rouge">deposit</code> (whatever doesn’t fail, should succeed);</li>
      <li>Explicit in case of <code class="language-plaintext highlighter-rouge">claim</code>: a claim happening before the time bound, when its kind is <code class="language-plaintext highlighter-rouge">Before</code>, should succeed due to <font style="background: #54c45e;">BeforeTimeBound</font> (provided all other conditions are met); similarly, a claim happening after the time bound, when its kind is <code class="language-plaintext highlighter-rouge">After</code>, should succeed due to <font style="background: #54c45e;">AfterTimeBound</font>.</li>
    </ul>
  </li>
</ul>

<p>Can the described approach of direct monitors be considered satisfactory? Please stop to think about it for a sec, before opening our answer below.</p>

<details>
<summary> <b>Are direct monitors sufficient?</b></summary>

<p>You may have guessed the answer: we believe NO! And here is an (incomplete) list of reasons why:</p>

<ol>
<li> A method may have a side effect, which was overlooked by the spec author. E.g. a boolean flag gets set, which allows the redirection of funds to another account in a different method.</li>
<li> Code evolves, but the spec stays as is; as a result a side effect, like the one above, is introduced unintentionally, with the stale spec not accounting for it.</li>
<li> The same internal state component is modified by multiple methods, in different ways. The specification of how the component should be updated is scattered across multiple places, and loopholes may easily creep in.</li>
<li> An invariant which is preserved by the method of this contract, is violated by a method from another contract. As no specs are written or monitored for this other contract, no violation is detected.</li>
</ol>

What is fundamentally missing from direct monitors, is the guarantee of <i>completeness</i>. Thus, we proceed to explore <i>reverse monitors</i>, which are complementary to direct monitors, specifically for ensuring specification completeness.
</details>

<h2 id="reverse-monitors">Reverse Monitors</h2>

<p>With reverse reasoning we will try to patch the loopholes that were left by direct monitor specs above. To do so, we start with an <em>effect</em> (state was modified), and go back to its <em>cause</em> (what should have happened taking the effect into account). Here is the corresponding picture which puts a bit of structure into the reverse reasoning.</p>

<p><img src="/img/ReverseMonitors.png" alt="Reverse monitor specs" /></p>

<p>In reverse monitors, we distinguish two kinds of conditions:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">MonitorCheck_i</code> is a condition over past and next state variables, which activates the monitor. If <em>any</em> of those conditions hold, the monitor is activated. These conditions should not be confused with preconditions <code class="language-plaintext highlighter-rouge">MustFail/MustPass</code> from direct monitors: unlike those, a <code class="language-plaintext highlighter-rouge">MonitorCheck</code> can express conditions like <em>“a state variable has changed its value.”</em></li>
  <li><code class="language-plaintext highlighter-rouge">MonitorAssert_i</code> is a condition over past and next state variables, which the monitors requires . <em>All</em> of <code class="language-plaintext highlighter-rouge">MonitorAssert_i</code> should hold if the transaction is successful. These conditions are the same as <code class="language-plaintext highlighter-rouge">MustHold</code> in direct monitors, and express a post-condition.</li>
</ul>

<p><em>Thus, the main difference wrt. direct monitors is how monitor activation is expressed: while direct monitors are activated by observing the cause (a method invocation with specific parameters), reverse monitors are activated by observing the effect (a change in state variables). Another difference is that <code class="language-plaintext highlighter-rouge">Monitor&lt;Check|Assert&gt;</code> conditions are method-unspecific, and can refer only to the state variables and the environment, but not to the method parameters.</em></p>

<p>In the above, <code class="language-plaintext highlighter-rouge">Monitor&lt;Check|Assert&gt;</code> is a prefix, which tells the monitor system how to interpret this predicate. The complete pattern for predicate names with these prefixes is as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Monitor&lt;Check|Assert&gt;_&lt;Monitor&gt;_&lt;ConditionDescription&gt;
</code></pre></div></div>

<p>All predicates which refer to the same <code class="language-plaintext highlighter-rouge">&lt;Monitor&gt;</code> will be grouped, to create together one <em>effect monitor</em>. Interpreted formally, the monitor should do the following when activated:</p>

<ul>
  <li>If <em>any</em> of <code class="language-plaintext highlighter-rouge">MonitorCheck_i</code> conditions hold, ensure that <em>all</em> <code class="language-plaintext highlighter-rouge">MonitorAssert_i</code> hold over the past and next states (otherwise, issue a warning / revert if configured to do so)</li>
</ul>

<p>Again, imposing a simple structure which combines triggers with <em>or</em>, but effects with <em>and</em> allows you to avoid cumbersome logic inside monitor predicates.</p>

<p>Let’s take a look at how reverse monitor specs help us to patch the loopholes described above:</p>

<ol>
  <li>Overlooked side effects: a reverse monitor will detect <em>all</em> changes to the monitored state, no matter where they originate.</li>
  <li>Side effects introduced during system evolution: same as above. Additionally, if an effect monitor is configured to revert in case of unexpected effects taking place, the developers will be forced to keep the spec in sync with the implementation.</li>
  <li>Inconsistent / spread-out specs: An effect monitor may describe all effects that may happen wrt. a particular state component in one place. As this monitor will be triggered when any of the methods that modify the state is executed, this also brings us considerable savings in terms of spec size/complexity, as similar effects can be grouped together.</li>
  <li>Unrestricted access from other contracts/methods: as in 1. and 2., it doesn’t matter again where the modification comes from: if the state component we monitor is being changed, the monitor will detect it.</li>
</ol>

<h3 id="reverse-monitors-for-the-timelock-contract">Reverse monitors for the Timelock contract</h3>

<p>We apply the general structure of reverse monitors to the <a href="https://github.com/stellar/soroban-examples/blob/f595fb5df06058ec0b9b829e9e4d0fe0513e0aa8/timelock/src/lib.rs">Timelock contract</a>; below we depict only the structure of Timelock’s reverse monitors (we omit <code class="language-plaintext highlighter-rouge">Monitor&lt;Check|Assert&gt;</code> as well as the monitor names for clarity).</p>

<p><img src="/img/TimelockReverseMonitors.png" alt="Timelock reverse monitors" /></p>

<p>Timelock’s reverse monitors are not specific to the smart contract methods, but rather formulate two important completeness conditions:</p>

<ul>
  <li><em>“Timelock’s balance record is allowed to be updated only by <code class="language-plaintext highlighter-rouge">deposit</code> or <code class="language-plaintext highlighter-rouge">claim</code> methods”</em>: see the complete specification in file <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/balance_record.tla">balance_record.tla</a>.</li>
  <li><em>“Timelock’s contract balance in the deposited token is allowed to be reduced only by <code class="language-plaintext highlighter-rouge">claim</code> method”</em>: see the complete specification in file <a href="https://github.com/freespek/solarkraft/blob/cf26a544ab204220eab62a3545300cb689aa899b/doc/case-studies/timelock/token_balance.tla">token_balance.tla</a>.</li>
</ul>

<p>Having these conditions in place does indeed make the combination of Timelock’s direct and reverse monitors, which we call a <em>hybrid monitor</em>, complete both wrt. deficiencies in direct monitor specifications, and wrt. future code changes.</p>

<hr />

<p><em>Updated on 26.06.2024: replaced <code class="language-plaintext highlighter-rouge">Monitor&lt;Trigger|Effect&gt;</code> with <code class="language-plaintext highlighter-rouge">Monitor&lt;Check|Assert&gt;</code></em></p>

<p>Done reading? Then proceed to the final post of this blog post series, <a href="https://systems-made-simple.dev/solarkraft/2024/07/04/solarkraft-monitor-verification.html">“The Rise of Model Checker: Verifying Blockchain Monitors In and Near Realtime”</a>, where we address these important questions: <em>“How to verify monitor specs, and what is the verification complexity?”</em>, as well as <em>“How to practically check them on the live blockchain?”</em></p>

<hr />

<p><em>Development of Solarkraft was supported by the <a href="https://stellar.org/foundation">Stellar Development Foundation</a> with a generous Activation Award from the <a href="https://communityfund.stellar.org">Stellar Community Fund</a> of 50,000 USD in XLM.</em></p>]]></content><author><name>Andrey Kuprianov</name><email>andrey@kuprum.xyz</email></author><category term="solarkraft" /><category term="apalache" /><category term="formal-methods" /><category term="runtime-monitor" /><category term="smart-contracts" /><category term="solarkraft" /><category term="soroban" /><category term="specification" /><category term="stellar" /><category term="tla" /><category term="tlaplus" /><summary type="html"><![CDATA[]]></summary></entry></feed>