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 }