Skip to content

Commit c85bec9

Browse files
UlfNorellMaximilianAlgehedabailly
authored
Agda to QuickCheck demonstration (#86)
* start on small example of connecting a quickcheck model and agda spec * initial WIP on a Haskell-side model to use as the target to aim for from Agda * working quickCheck model of the super simple example protocol * Some thoughts about what happens when you add negative testing * some thinking and refining the protocol * trying to make the model match the implementation * modify the target model to align with outcome of meeting + stop using negative actions as these are only going to complicate things * a bit more cleanup * simplify the model * started on agda spec for the mini protocol * some form completed agda spec for the toy protocol * hook up agda2hs and get halfway on a soundness proof * finished soundness proof and made it nice(ish) * add dishonest actions to tests (not yet to soundness proof) * some proofs about dishonest actions * finished soundness proof for dishonest actions * some thoughts on completeness * some proof cleanup and soundness for sequences of actions * Rename modules, add Makefile and start on documentation * Test suite * Run fourmolu on all the Haskell code * Some cleanup of the agda code * Some embryo of contents in Overview.lagda.md * More documentation * Remove bound on q-d --------- Co-authored-by: Maximilian Algehed <m.algehed@gmail.com> Co-authored-by: Arnaud Bailly <arnaud.bailly@iohk.io>
1 parent 7298f3c commit c85bec9

16 files changed

+1367
-1
lines changed

cabal.project

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ repository cardano-haskell-packages
1111

1212
index-state: 2024-01-05T13:49:07Z
1313
index-state:
14-
, hackage.haskell.org 2023-12-15T16:32:49Z
14+
, hackage.haskell.org 2024-03-14T00:00:00Z
1515
, cardano-haskell-packages 2023-12-15T14:50:31Z
1616

1717
packages:
1818
peras-hs
1919
peras-iosim
2020
peras-quickcheck
2121
peras-delta-q
22+
test-demo
2223

2324
tests: True
2425
test-show-details: direct

test-demo/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Revision history for test-demo
2+
3+
## 0.1.0.0 -- YYYY-mm-dd
4+
5+
* First version. Released on an unsuspecting world.

test-demo/LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2024 Maximilian Algehed
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included
12+
in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

test-demo/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
default : typecheck
3+
4+
typecheck :
5+
agda2hs src/Overview.lagda.md
6+
@rm -f src/Overview.hs
7+
fourmolu -i src/*.hs
8+
9+
test : typecheck
10+
cabal test

test-demo/src/CommonTypes.agda

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
module CommonTypes where
3+
4+
open import Haskell.Prelude
5+
open import Data.Nat.Base
6+
open import Relation.Binary.PropositionalEquality
7+
8+
open import ProofPrelude
9+
10+
{-# FOREIGN AGDA2HS import GHC.Generics #-}
11+
12+
data Party : Set where
13+
Alice Bob : Party
14+
15+
{-# COMPILE AGDA2HS Party deriving (Eq, Ord, Show, Generic) #-}
16+
17+
BlockIndex =
18+
Slot =
19+
20+
{-# FOREIGN AGDA2HS
21+
type BlockIndex = Integer
22+
type Slot = Integer
23+
#-}
24+
25+
record Block : Set where
26+
constructor Blk
27+
field
28+
blockIndex : BlockIndex
29+
30+
open Block public
31+
32+
{-# COMPILE AGDA2HS Block deriving (Eq, Ord, Show, Generic) #-}
33+
34+
instance
35+
EqParty : Eq Party
36+
EqParty ._==_ Alice Alice = True
37+
EqParty ._==_ Alice Bob = False
38+
EqParty ._==_ Bob Alice = False
39+
EqParty ._==_ Bob Bob = True
40+
41+
EqBlock : Eq Block
42+
EqBlock ._==_ (Blk i) (Blk j) = i == j
43+
44+
eqParty-sound : {p q : Party} (p == q) ≡ True p ≡ q
45+
eqParty-sound {Alice} {Alice} _ = refl
46+
eqParty-sound {Bob} {Bob} _ = refl
47+
48+
eqParty-complete : {p q : Party} (p == q) ≡ False p ≢ q
49+
eqParty-complete {Alice} {Bob} h ()
50+
eqParty-complete {Bob} {Alice} h ()
51+
52+
eqBlock-sound : {b b₁ : Block} (b == b₁) ≡ True b ≡ b₁
53+
eqBlock-sound {Blk i} {Blk j} h = cong Blk (eqℕ-sound h)
54+
55+
eqBlock-complete : {b b₁ : Block} (b == b₁) ≡ False b ≢ b₁
56+
eqBlock-complete {Blk i} isF refl = eqℕ-complete {i} isF refl

test-demo/src/CommonTypes.hs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module CommonTypes where
2+
3+
import GHC.Generics
4+
5+
data Party
6+
= Alice
7+
| Bob
8+
deriving (Eq, Ord, Show, Generic)
9+
10+
type BlockIndex = Integer
11+
type Slot = Integer
12+
13+
data Block = Blk {blockIndex :: BlockIndex}
14+
deriving (Eq, Ord, Show, Generic)

test-demo/src/FormalSpec.agda

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
2+
-- The formal Agda specification with all the fancy types.
3+
4+
module FormalSpec where
5+
6+
open import Data.Nat
7+
open import Data.Nat.Properties
8+
open import Data.List.Base
9+
open import Data.Product.Base
10+
open import Relation.Binary.PropositionalEquality
11+
open import Relation.Nullary
12+
open import Function
13+
14+
open import CommonTypes
15+
16+
-- Super simple protocol:
17+
-- - The hosts take turns round robin to produce blocks.
18+
-- - `blockIndex` is incremented with each block on the chain.
19+
-- - If a node misses its window the other node should produce the missed block in its slot
20+
-- instead.
21+
-- Network model: π-calculus style instant delivery
22+
23+
record LocalState : Set where
24+
constructor _,_
25+
field
26+
lastBlock : Block
27+
lastBlockSlot : Slot
28+
29+
open LocalState public
30+
31+
record State : Set where
32+
constructor ⟦_,_,_⟧
33+
field
34+
clock : Slot
35+
aliceState : LocalState
36+
bobState : LocalState
37+
38+
open State public
39+
40+
initLocalState : LocalState
41+
initLocalState = Blk 0 , 0
42+
43+
initState : State
44+
initState =0 , initLocalState , initLocalState ⟧
45+
46+
-- State manipulation
47+
48+
getLocalState : Party State LocalState
49+
getLocalState Alice = aliceState
50+
getLocalState Bob = bobState
51+
52+
modifyLocalState : Party (LocalState LocalState) State State
53+
modifyLocalState Alice f ⟦ t , as , bs ⟧ = ⟦ t , f as , bs ⟧
54+
modifyLocalState Bob f ⟦ t , as , bs ⟧ = ⟦ t , as , f bs ⟧
55+
56+
setLocalState : Party LocalState State State
57+
setLocalState p ls = modifyLocalState p λ _ ls
58+
59+
-- Honesty models. Encodes who is allowed to perform adversarial actions.
60+
61+
data Honesty : Set where
62+
happyPath : Honesty
63+
badAlice : Honesty
64+
badBob : Honesty
65+
66+
-- Certificate that the given party is allowed to do bad things.
67+
data Dishonest : Honesty Party Set where
68+
badAlice : Dishonest badAlice Alice
69+
badBob : Dishonest badBob Bob
70+
71+
variable
72+
s s₀ s₁ s₂ s₃ s₄ : State
73+
ls ls₀ ls₁ : LocalState
74+
p q : Party
75+
b b₁ : Block
76+
i j : BlockIndex
77+
t t₁ now : Slot
78+
h : Honesty
79+
80+
-- Alice has even slots, Bob has odd slots.
81+
data SlotOf (t : Slot) : Party Set where
82+
AliceSlot : t % 20 SlotOf t Alice
83+
BobSlot : t % 21 SlotOf t Bob
84+
85+
-- `ValidBlock now ls p b`: At time `now` is it valid for a party `p`
86+
-- to send a block `b` from the point of view of a node with local
87+
-- state `ls`.
88+
data ValidBlock : Slot LocalState Party Block Set where
89+
valid : t < now
90+
SlotOf now p
91+
ValidBlock now (Blk i , t) p (Blk (suc i))
92+
93+
-- Local state update on receive
94+
data _⊢_[_,_]?_ : Slot LocalState Party Block LocalState Set where
95+
correctBlock : ValidBlock now ls p b
96+
now ⊢ ls [ p , b ]? (b , now)
97+
wrongBlock : ¬ ValidBlock now ls p b
98+
now ⊢ ls [ p , b ]? ls
99+
100+
-- Global state update on receive
101+
data _[_,_↦_]?_ : State Party Block Party State Set where
102+
receive : clock s ⊢ getLocalState q s [ p , b ]? ls
103+
s [ p , b ↦ q ]? setLocalState q ls s
104+
105+
-- Local state update on send
106+
data _,_,_⊢_[_↦_]!_ : Honesty Slot Party LocalState Block Party LocalState Set where
107+
correctBlock : ValidBlock now ls p b
108+
h , now , p ⊢ ls [ b ↦ q ]! (b , now)
109+
dishonestBlock : Dishonest h p
110+
h , now , p ⊢ ls [ b ↦ q ]! ls
111+
112+
-- Global state update on send
113+
data _⊢_[_,_↦_]!_ : Honesty State Party Block Party State Set where
114+
send : q b h , clock s , p ⊢ getLocalState p s [ b ↦ q ]! ls
115+
p ≢ q
116+
h ⊢ s [ p , b ↦ q ]! setLocalState p ls s
117+
118+
-- The top-level small-step semantics
119+
data _⊢_↝_ : Honesty State State Set where
120+
121+
-- π-calculus-style instant message delivery
122+
deliver : h ⊢ s₀ [ p , b ↦ q ]! s₁
123+
s₁ [ p , b ↦ q ]? s₂
124+
h ⊢ s₀ ↝ s₂
125+
126+
-- We can only tick if the party whose slot it is has produced (or
127+
-- pretends to have produced) their block.
128+
tick : SlotOf (clock s) p
129+
lastBlockSlot (getLocalState p s) ≡ clock s
130+
h ⊢ s ↝ record s { clock = suc (clock s) }
131+
132+
-- A dishonest party can update their local state to whatever they want.
133+
trickery : Dishonest h p ls h ⊢ s ↝ setLocalState p ls s
134+
135+
-- Standard reflexive-transitive closure.
136+
data _⊢_↝*_ : Honesty State State Set where
137+
[] : h ⊢ s ↝* s
138+
_∷_ : h ⊢ s₀ ↝ s₁ h ⊢ s₁ ↝* s₂ h ⊢ s₀ ↝* s₂
139+
140+
infixr 5 _<>_
141+
_<>_ : h ⊢ s₀ ↝* s₁ h ⊢ s₁ ↝* s₂ h ⊢ s₀ ↝* s₂
142+
[] <> tr = tr
143+
(r ∷ tr) <> tr₁ = r ∷ tr <> tr₁
144+
145+
-- Which blocks does Alice produce in a given trace?
146+
aliceBlocks : h ⊢ s₀ ↝* s₁ List (Slot × Block)
147+
aliceBlocks [] = []
148+
aliceBlocks {s₀ = s₀} (deliver {p = Alice} {b} _ _ ∷ tr) = (clock s₀ , b) ∷ aliceBlocks tr
149+
aliceBlocks (deliver _ _ ∷ tr) = aliceBlocks tr
150+
aliceBlocks (trickery _ _ ∷ tr) = aliceBlocks tr
151+
aliceBlocks (tick _ _ ∷ tr) = aliceBlocks tr
152+
153+
-- Some proofs
154+
155+
liftHonesty : happyPath ⊢ s ↝ s₁ h ⊢ s ↝ s₁
156+
liftHonesty (deliver (send q b (correctBlock v) !q) r) = deliver (send q b (correctBlock v) !q) r
157+
liftHonesty (tick sl sent) = tick sl sent
158+
159+
liftHonesty* : happyPath ⊢ s ↝* s₁ h ⊢ s ↝* s₁
160+
liftHonesty* [] = []
161+
liftHonesty* (r ∷ tr) = liftHonesty r ∷ liftHonesty* tr
162+
163+
sameAliceBlocks : (tr : happyPath ⊢ s ↝* s₁) aliceBlocks tr ≡ aliceBlocks (liftHonesty* {h = h} tr)
164+
sameAliceBlocks [] = refl
165+
sameAliceBlocks {h = h} (deliver {p = Alice} {b} (send _ _ (correctBlock _) _) _ ∷ tr)
166+
rewrite sameAliceBlocks {h = h} tr = refl
167+
sameAliceBlocks (deliver {p = Bob} (send _ _ (correctBlock _) _) _ ∷ tr) = sameAliceBlocks tr
168+
sameAliceBlocks (tick _ _ ∷ tr) = sameAliceBlocks tr
169+
170+
appendAliceBlocks : (tr : h ⊢ s₀ ↝* s₁) (tr₁ : h ⊢ s₁ ↝* s₂) aliceBlocks (tr <> tr₁) ≡ aliceBlocks tr ++ aliceBlocks tr₁
171+
appendAliceBlocks [] tr₁ = refl
172+
appendAliceBlocks (deliver {p = Alice} _ _ ∷ tr) tr₁ rewrite appendAliceBlocks tr tr₁ = refl
173+
appendAliceBlocks (deliver {p = Bob } _ _ ∷ tr) tr₁ = appendAliceBlocks tr tr₁
174+
appendAliceBlocks (trickery _ _ ∷ tr) tr₁ = appendAliceBlocks tr tr₁
175+
appendAliceBlocks (tick _ _ ∷ tr) tr₁ = appendAliceBlocks tr tr₁
176+
177+
-- Examples
178+
179+
_ : happyPath ⊢ initState ↝* ⟦ 2 , (Blk 2 , 2) , (Blk 2 , 2) ⟧
180+
_ = tick (AliceSlot refl) refl
181+
∷ deliver (send Alice (Blk 1) (correctBlock (valid ≤-refl (BobSlot refl))) λ())
182+
(receive (correctBlock (valid ≤-refl (BobSlot refl))))
183+
∷ tick (BobSlot refl) refl
184+
∷ deliver (send Bob (Blk 2) (correctBlock (valid ≤-refl (AliceSlot refl))) λ())
185+
(receive (correctBlock (valid ≤-refl (AliceSlot refl))))
186+
∷ []
187+
188+
_ : badBob ⊢ initState ↝* ⟦ 2 , (Blk 1 , 2) , (Blk 1 , 2) ⟧
189+
_ = tick (AliceSlot refl) refl
190+
∷ trickery badBob (Blk 0 , 1) -- Bob pretends to have sent a message (bumping lastBlkSlot)
191+
∷ tick (BobSlot refl) refl
192+
∷ deliver (send Bob (Blk 1) (correctBlock (valid (s≤s z≤n) (AliceSlot refl))) λ())
193+
(receive (correctBlock (valid ≤-refl (AliceSlot refl))))
194+
∷ []

0 commit comments

Comments
 (0)