github.com/aakash4dev/cometbft@v0.38.2/spec/mempool/quint/Mempool.qnt (about)

     1  // -*- mode: Bluespec; -*-
     2  // A mempool is a replicated set of transactions which is used as an input by a ledger.
     3  // Below, we follow the specification given here:
     4  // https://github.com/cometbft/knowledge-base/blob/main/protocols/mempool-overview.md
     5  
     6  module ABCI {
     7  
     8      import System.* from "System"
     9      import Ledger.* from "Ledger"
    10  
    11      pure def isValid(l: LedgerState, p: Process, t: Tx): bool = {
    12        true
    13      }
    14  
    15  }
    16  
    17  module Mempool {
    18  
    19      import Spells.* from "Spells"
    20      import System.* from "System"
    21      import Ledger.* from "Ledger"
    22      import ABCI.*
    23  
    24      type MempoolState = {
    25        mempool: Process -> Set[Tx],
    26        hmempool: Process -> Set[Tx], // history variable
    27        ledger: LedgerState
    28      }
    29  
    30      pure def newMempoolState(P: Set[Process]): MempoolState = {
    31        ledger: newLedger(P),
    32        mempool: P.mapBy(p => Set()),
    33        hmempool: P.mapBy(p => Set())
    34      }
    35  
    36      //// helpers
    37  
    38      pure def txsAvailable(st: MempoolState, p: Process): Set[Tx] = {
    39        st.mempool.get(p)
    40      }
    41  
    42      pure def txsSize(st: MempoolState, p: Process): int = {
    43        st.mempool.get(p).size()
    44      }
    45  
    46      pure def reap(st: MempoolState, p: Process, max: int): Set[Tx] = {
    47        setSubsetOfAtMost(st.mempool.get(p), max)
    48      }
    49  
    50      pure def txsOf(st: MempoolState, p: Process): Set[Tx] = {
    51        st.mempool.get(p)
    52      }
    53  
    54      pure def isValidAndNotCommittedFor(ledger: LedgerState, p: Process, tx: Tx): bool = {
    55        isValid(ledger, p, tx) and not(ledger.getCommittedFor(p).contains(tx))
    56      }
    57  
    58      //// conditions
    59  
    60      pure def mayRcvFromClientAt(st: MempoolState, p: Process, txs: Set[Tx]): bool = {
    61        and {
    62          not(txs.subseteq(st.txsOf(p))), // to avoid stuttering
    63          txs.forall(tx => isValidAndNotCommittedFor(st.ledger, p, tx))
    64        }
    65      }
    66  
    67      pure def mayRcvFromProcessAt(st: MempoolState, p: Process, q: Process, txs: Set[Tx]): bool = {
    68        and {
    69          p != q,
    70  	      not(txs.subseteq(st.txsOf(q))), // to avoid stuttering
    71          not(st.ledger.heightOf(p) < st.ledger.heightOf(q)),
    72        }
    73      }
    74  
    75      pure def maySubmitToLedger(st: MempoolState, p: Process, txs: Set[Tx]) : bool = {
    76        and {
    77          txs.forall(t => st.ledger.isValid(p, t)),
    78  	      st.ledger.maySubmit(p, txs)
    79        }
    80      }
    81  
    82      //// transitions
    83  
    84      pure def add(st: MempoolState, p: Process, txs: Set[Tx]): MempoolState = {
    85        val nmempool = st.mempool.set(p, st.mempool.get(p).union(txs))
    86        val nhmempool = st.hmempool.set(p, st.hmempool.get(p).union(txs))
    87        {mempool: nmempool, hmempool: nhmempool, ...st}
    88      }
    89  
    90      pure def commitThenUpdate(st: MempoolState, p: Process): MempoolState = {
    91        val nledger = st.ledger.commit(p)
    92        val nmempool = st.mempool.set(p, st.mempool.get(p).filter(tx => isValidAndNotCommittedFor(nledger, p, tx)))
    93        {mempool: nmempool, ledger: nledger, ...st}
    94      }
    95  
    96      //// state machine
    97  
    98      var _state: MempoolState
    99  
   100      action init : bool = all {
   101        all {
   102          _state' = newMempoolState(PROCESSES)
   103        }
   104      }
   105  
   106      action doClientThenAdd(p: Process, txs: Set[Tx]): bool = all {
   107        all {
   108          require(_state.mayRcvFromClientAt(p, txs)),
   109  	      _state' = _state.add(p, txs)        
   110        }
   111      }
   112  
   113      action doSubmit(p: Process): bool = all {
   114        val txs = reap(_state, p, 1)
   115        all {
   116          require(_state.maySubmitToLedger(p, txs)),
   117          _state' = {ledger: _state.ledger.submit(p, txs), ..._state}
   118        }
   119      }
   120  
   121      action doCommitThenUpdate(p: Process): bool = all {
   122        all {
   123          require(_state.ledger.mayCommit(p)),
   124  	      _state' = _state.commitThenUpdate(p)
   125        }
   126      }
   127  
   128      action doGossipThenAdd(p: Process, q: Process): bool = all {
   129        val txs = _state.txsOf(p) // all txs at once
   130        all {
   131          require(_state.mayRcvFromProcessAt(p, q, txs)),
   132  	       _state' = _state.add(p, txs)
   133        }
   134     }
   135  
   136      action step: bool = {
   137        nondet p = oneOf(PROCESSES)
   138        nondet q = oneOf(PROCESSES)
   139        nondet txs = Set(oneOf(TXS)) // one at a time
   140        any {
   141          doClientThenAdd(p,txs),
   142          doSubmit(p),
   143          doCommitThenUpdate(p),
   144          doGossipThenAdd(p,q)
   145        }	
   146      }
   147  
   148      //// invariants
   149      
   150      // INV1. the mempool is used as an input for the ledger
   151      val inv1 = {
   152        PROCESSES.forall(p => _state.ledger.getSubmittedFor(p).subseteq(_state.hmempool.get(p)))
   153      }
   154  
   155      // INV2. committed transactions are not in the mempool
   156      val inv2 = {
   157        PROCESSES.forall(p => 0.to(_state.ledger.heightOf(p)-1).forall(i =>  _state.ledger.entry(p, i).intersect(_state.txsOf(p)).size()==0))
   158      }
   159  
   160      // INV3. every transaction in the mempool is valid
   161      val inv3 = {
   162        PROCESSES.forall(p => _state.txsOf(p).forall(t => _state.ledger.isValid(p, t)))
   163      }
   164      
   165      // INV4. every transaction that appears in the mempool is eventually committed or forever invalid
   166      // temporal inv4 = {
   167      //   PROCESSES.forall(p => mempool.get(p).forall(tx => eventually(ledger.isCommittedFor(p, tx) or always(not(ledger.isValid(p, tx))))))
   168      // }
   169      // FIXME. cannot be verified yet
   170  
   171      // Instead, we use the (simpler) invariant below
   172      // INV4b. every transaction in hmempool is always committed or if valid still in the mempool
   173       val inv4() = {
   174         PROCESSES.forall(p => _state.hmempool.get(p).forall(tx => _state.ledger.isCommittedFor(p, tx) or not(_state.ledger.isValid(p, tx)) or _state.txsOf(p).contains(tx)))
   175       }
   176  
   177      val allInv = and {
   178        inv1,
   179        inv2,
   180        inv3,
   181        inv4
   182      }
   183  
   184      //// tests
   185  
   186      run moveHeightOnceTest = {
   187        nondet p = oneOf(PROCESSES)
   188        nondet txs = Set(oneOf(TXS))
   189        init
   190        .then(doClientThenAdd(p,txs))
   191        .then(doSubmit(p))
   192        .then(doCommitThenUpdate(p))
   193      }
   194  
   195  }