Repository navigation
Expand file tree
/
Copy pathf10_receipts.py
More file actions
49 lines (47 loc) · 2.5 KB
/
Copy pathf10_receipts.py
File metadata and controls
49 lines (47 loc) · 2.5 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
from f10_reference import Sig, Chain, Witness, verify_receipt, canon, GENESIS_SEED
class GossipClient:
'''Collects receipts from roster witnesses per (chain_id, event_id),
merges via gossip, reports conflict evidence without claiming completeness.'''
def __init__(self, client_id):
self.client_id = client_id
self.receipts = {} # (chain_id, event_id) -> list of receipts
self.assurance = {} # (chain_id, event_id) -> witness_key_id -> ok/absent
def _verify(self, r, witness_pub, writer_registry):
'''Verify writer sig over checkpoint, then witness sig over full signed writer-envelope.'''
Sig('v', GENESIS_SEED).verify(canon(r['checkpoint']), r['writer_sig'],
writer_registry[r['writer_key_id']])
verify_receipt(r, witness_pub)
def collect(self, chain_id, event_id, roster, writer_registry):
for w, pub in roster:
rs = w.query_all(chain_id, event_id)
status = 'ok' if rs else 'absent'
self.assurance.setdefault((chain_id, event_id), {})[w.sig.key_id] = status
if rs:
for r in rs:
self._verify(r, pub, writer_registry)
self.merge(rs)
def merge(self, receipts):
for r in receipts:
cp = r['checkpoint']
k = (cp['chain_id'], cp['event_id'])
bucket = self.receipts.setdefault(k, [])
if not any(canon(x) == canon(r) for x in bucket):
bucket.append(r)
def conflict_report(self, chain_id, event_id, witness_pubs, writer_registry):
'''Returns report; NEVER claims absence of conflict elsewhere.'''
rs = self.receipts.get((chain_id, event_id), [])
for r in rs:
self._verify(r, witness_pubs[r['witness_key_id']], writer_registry)
heads = set(r['checkpoint']['head_hash'] for r in rs)
absent = sorted(k for k, a in self.assurance.get((chain_id, event_id), {}).items() if a == 'absent')
if len(heads) > 1:
return {'status': 'FORK_EVIDENCE', 'heads': sorted(heads), 'receipts': rs}
return {'status': 'NO_CONFLICT_OBSERVED', 'heads': sorted(heads), 'absent_witnesses': absent,
'note': 'no conflict observed; not a claim none exists'}
def gossip_merge(a, b):
'''Merge two clients' receipt sets: durable fork evidence.'''
for k, rs in b.receipts.items():
a.merge(rs)
for k, m in b.assurance.items():
a.assurance.setdefault(k, {}).update(m)
return a