-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathequivocation.py
More file actions
86 lines (71 loc) · 2.91 KB
/
Copy pathequivocation.py
File metadata and controls
86 lines (71 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""
orchestrator/equivocation.py — authenticated equivocation detection.
A contradiction is equivocation only when it is attributable to the same
observer identity in the same peer/epoch scope. Shared network provenance alone
is not sufficient because unrelated observers may legitimately share a relay,
NAT, or provenance bucket.
"""
from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Dict, List, Tuple
from orchestrator.membership import ObservationType, PeerObservation
MAX_OBSERVATIONS_PER_KEY = 50
@dataclass(frozen=True)
class EquivocationEvidence:
"""Forensic record of one authenticated contradiction."""
peer_id: str
epoch: int
observer_id: str
prior_observer_id: str
observer_provenance: str
contradicting_pair: Tuple[ObservationType, ObservationType]
timestamps: Tuple[float, float]
detected_at: float
class EquivocationLog:
"""Bounded observation log keyed by target peer and epoch.
Contradictions are emitted only when both the authenticated observer
identity and provenance match. Different observers sharing provenance are
ordinary witness disagreement, not evidence against either observer.
"""
def __init__(self) -> None:
self._observations: Dict[
Tuple[str, int], List[Tuple[str, ObservationType, float, str]]
] = {}
def record_observation(
self, observation: PeerObservation
) -> List[EquivocationEvidence]:
key = (observation.peer_id, observation.epoch)
provenance = observation.observer_provenance
new_entry: Tuple[str, ObservationType, float, str] = (
observation.observer_id,
observation.observation_type,
observation.timestamp,
provenance,
)
bucket = self._observations.setdefault(key, [])
if len(bucket) >= MAX_OBSERVATIONS_PER_KEY:
bucket.pop(0)
detected_at = time.time()
evidences: List[EquivocationEvidence] = []
for prior_observer_id, prior_type, prior_timestamp, prior_provenance in bucket:
if prior_observer_id != observation.observer_id:
continue
if prior_provenance != provenance:
continue
if prior_type == observation.observation_type:
continue
evidences.append(
EquivocationEvidence(
peer_id=observation.peer_id,
epoch=observation.epoch,
observer_id=observation.observer_id,
prior_observer_id=prior_observer_id,
observer_provenance=provenance,
contradicting_pair=(observation.observation_type, prior_type),
timestamps=(observation.timestamp, prior_timestamp),
detected_at=detected_at,
)
)
bucket.append(new_entry)
return evidences