{-# OPTIONS --safe #-}
open import Prelude
open import Hash
open import Jolteon.Assumptions

module Jolteon.Properties.Votes.Core (⋯ : _) (open Assumptions ⋯) where

open import Jolteon ⋯

-- ** Counting honest votes (≥ f + 1)

-- NB: might contain duplicates (e.g. from adversary replaying honest messages)

getHonestVoteFor : Block → Message → Maybe Pid
getHonestVoteFor b m = case m of λ where
  (Vote vs)    → M.when (isHonest (vs ∙pid) ∧ (vs ∙blockId == b ∙blockId)) (vs ∙pid)
  (Propose _)  → nothing
  (TCFormed _) → nothing
  (Timeout _)  → nothing

honestVotes' : Block → Messages → List Pid
honestVotes' b ms = flip L.mapMaybe ms $ getHonestVoteFor b

honestVotes : Block → Messages → List Pid
honestVotes = nub ∘₂ honestVotes'

MoreThanF-Honest : Block → GlobalState → Type
MoreThanF-Honest b s = MoreThanF $ honestVotes b (s .history)

Honest-honestVotes' : ∀ b ms → All Honest (honestVotes' b ms)
Honest-honestVotes' b [] = []
Honest-honestVotes' b (Propose _ ∷ ms) = Honest-honestVotes' b ms
Honest-honestVotes' b (Vote vs ∷ ms) with honest? (vs ∙pid) | vs ∙blockId ≟ b ∙blockId
... | no _   | _     = Honest-honestVotes' b ms
... | yes hp | no _  = Honest-honestVotes' b ms
... | yes hp | yes _ = hp ∷ Honest-honestVotes' b ms

Honest-honestVotes' b (TCFormed _ ∷ ms) = Honest-honestVotes' b ms
Honest-honestVotes' b (Timeout _ ∷ ms) = Honest-honestVotes' b ms

Honest-honestVotes : ∀ b ms → All Honest (honestVotes b ms)
Honest-honestVotes b ms = All-nub⁺ (Honest-honestVotes' b ms)

honestVotes-mono : ∀ ms m →
  length (honestVotes b ms) ≤ length (honestVotes b (m ∷ ms))
honestVotes-mono {b} ms m with getHonestVoteFor b m
... | nothing = Nat.≤-refl
... | just p = nub-length {xs = honestVotes' b ms}{p}

{-
The following lemma (which uses sublists):
  honestVotes⊆ : ms ⊆ ms′ → honestVotes b ms ⊆ honestVotes b ms′

is *not true* because sublists are not preserved by nub.

  FALSE -> nub-SubList : xs ⊆ ys → nub xs ⊆ nub ys

  [ 3 , 5 ] ⊆ [ 3 , 5 , 3 ] , but
  nub [ 3 , 5 ] ≡ [ 3 , 5 ] ¬⊆ [ 5 , 3 ] ≡ nub  [ 3 , 5 , 3 ]
-}

-- ** Counting votes (≥ 2*f + 1)

getVoteFor : Block → Message → Maybe Pid
getVoteFor b m = case m of λ where
  (Vote vs)    → M.when (vs ∙blockId == b ∙blockId) (vs ∙pid)
  (Propose _)  → nothing
  (TCFormed _) → nothing
  (Timeout _)  → nothing

-- NB: might contain duplicates (e.g. from adversary replaying honest messages)
votes' : Block → Messages → List Pid
votes' b ms = L.mapMaybe (getVoteFor b) ms

honestVotes'≡ : ∀ b ms → honestVotes' b ms ≡ filter honest? (votes' b ms)
honestVotes'≡ b [] = refl
honestVotes'≡ b (Propose _ ∷ ms) = honestVotes'≡ b ms
honestVotes'≡ b (Vote vs ∷ ms) with honest? (vs ∙pid) | vs ∙blockId ≟ b ∙blockId
... | no _   | no _ = honestVotes'≡ b ms
... | no ¬p  | yes _ =
  trans (honestVotes'≡ b ms)
        (sym $ L.filter-reject honest? ¬p)
... | yes hp | no _  = honestVotes'≡ b ms
... | yes hp | yes _ =
  trans (cong ((vs ∙pid) ∷_) $ honestVotes'≡ b ms)
        (sym $ L.filter-accept honest? hp)
honestVotes'≡ b (TCFormed _ ∷ ms) = honestVotes'≡ b ms
honestVotes'≡ b (Timeout _ ∷ ms) = honestVotes'≡ b ms

votes : Block → Messages → List Pid
votes = nub ∘₂ votes'

honestVotes≡ : ∀ b ms → honestVotes b ms ≡ filter honest? (votes b ms)
honestVotes≡ b ms
  rewrite sym $ nub-filter {xs = votes' b ms} honest?
  rewrite honestVotes'≡ b ms = refl

p∈⇒hp : ∀ b ms →
  p ∈ honestVotes b ms
  ────────────────────
  p ∈ votes b ms × Honest p
p∈⇒hp b ms p∈ rewrite honestVotes≡ b ms = L.Mem.∈-filter⁻ honest? p∈

MajorityVotes : Block → GlobalState → Type
MajorityVotes b s = IsMajority $ votes b (s .history)

Majority⇒MoreThanF : ∀ (ps : List Pid) →
  ∙ Unique ps
  ∙ IsMajority ps
    ─────────────────────────────
    MoreThanF (filter honest? ps)
Majority⇒MoreThanF ps u maj =
  *-cancelˡ-< 2 nodes (3 * hvs) $
  begin-strict
    2 * nodes
  ≤⟨ maj ⟩
    3 * _vs
  <⟨ *-monoʳ-< 3 vs<2*hvs ⟩
    3 * (2 * hvs)
  ≡⟨ (sym $ *-assoc 3 2 hvs) ⟩
    6 * hvs
  ≡⟨ *-assoc 2 3 hvs ⟩
    2 * (3 * hvs)
  ■
  where
    open Nat ; open ≤-Reasoning renaming (_∎ to _■)

    _vs = length $ ps
    hvs = length $ filter honest? ps

    vs<2*hvs : _vs < 2 * hvs
    vs<2*hvs = majority⇒honest-majority u maj

{- was Majority⇒MoreThanF
MajorityVotes⇒MoreThanF-Honest : ∀ {s} → Reachable s →
  MajorityVotes b s
  ────────────────────
  MoreThanF-Honest b s
MajorityVotes⇒MoreThanF-Honest {b} {s} Rs maj
  rewrite honestVotes≡ b (s .history)
  = let ps  = votes b (s .history)
        ps′ = votes' b (s .history)
    in Majority⇒MoreThanF ps (Unique-nub {xs = ps′}) maj
-}