1515import json
1616
1717from pathlib import Path
18- from typing import Any
18+ from typing import Any , Iterable , List , Optional , Tuple , Union
19+
1920
2021def is_dir (dirpath : str | Path ):
2122 """Check if the directory is a directory."""
@@ -43,6 +44,21 @@ def compare_reg_error(reg:dict, error:dict) -> bool:
4344 except :
4445 return False
4546
47+ def index_processed_snapshot (snapshot ) -> dict :
48+ #
49+ indexed = {}
50+
51+ if isinstance (snapshot , list ):
52+ for rec in snapshot :
53+ indexed ["0x" + rec ["hir" ]["voting_key" ]] = rec
54+ else :
55+ # legacy snapshot
56+ print ("Legacy Snapshot not supported. Use the 'compare_snapshot.py' tool to compare a legacy with a full processed snapshot." )
57+ exit (1 )
58+
59+ return indexed
60+
61+
4662def analyze_snapshot (args : argparse .Namespace ):
4763 """Convert a snapshot into a format supported by SVE1."""
4864
@@ -61,25 +77,82 @@ def analyze_snapshot(args: argparse.Namespace):
6177 cip_36_single : list [dict [str , Any ]] = []
6278 cip_36_multi : list [dict [str , Any ]] = []
6379
80+ vkey_power : dict [str , list [int ]] = {}
81+
6482 total_rejects = 0
6583 total_registered_value = 0
6684
85+ rewards_payable = 0
86+ rewards_pointer = 0
87+ rewards_unpayable = 0
88+ rewards_invalid = 0
89+ rewards_types = {}
90+ unique_rewards = {}
91+
92+
6793 for registration in snapshot :
6894 # Index the registrations
6995 stake_pub_key = registration ["stake_public_key" ]
7096 snapshot_index [stake_pub_key ] = registration
7197
72- total_registered_value += registration ["voting_power" ]
98+ v_power = registration ["voting_power" ]
99+
100+ total_registered_value += v_power
101+
102+ rewards_addr = registration ["rewards_address" ]
103+
104+ long_addr_length = 116
105+ short_addr_length = 60
106+
107+ if len (rewards_addr ) > 4 and rewards_addr [0 :2 ] == "0x" and rewards_addr [2 ] in "01234567ef" and rewards_addr [3 ] == "1" :
108+ rewards_type = rewards_addr [2 ]
109+
110+ if rewards_type in "0123" :
111+ if len (rewards_addr ) == long_addr_length :
112+ rewards_payable += 1
113+ unique_rewards [rewards_addr ] = unique_rewards .get (rewards_addr , 0 ) + 1
114+ else :
115+ rewards_invalid += 1
116+ elif rewards_type in "45" :
117+ if len (rewards_addr ) == long_addr_length :
118+ rewards_pointer += 1
119+ unique_rewards [rewards_addr ] = unique_rewards .get (rewards_addr , 0 ) + 1
120+ else :
121+ rewards_invalid += 1
122+ elif rewards_type in "67" :
123+ if len (rewards_addr ) == short_addr_length :
124+ rewards_payable += 1
125+ unique_rewards [rewards_addr ] = unique_rewards .get (rewards_addr , 0 ) + 1
126+ else :
127+ rewards_invalid += 1
128+ elif rewards_type in "ef" :
129+ if len (rewards_addr ) == short_addr_length :
130+ rewards_unpayable += 1
131+ else :
132+ rewards_invalid += 1
133+
134+ rewards_types [rewards_type ] = rewards_types .get (rewards_type ,0 ) + 1
135+ else :
136+ rewards_invalid += 1
73137
74138 # Check if the delegation is a simple string.
75139 # If so, assume its a CIP-15 registration.
76140 delegation = registration ["delegations" ]
77141
78142 if isinstance (delegation , str ):
79143 cip_15_snapshot .append (registration )
144+
145+ if delegation not in vkey_power :
146+ vkey_power [delegation ] = []
147+ vkey_power [delegation ].append (v_power )
148+
80149 elif isinstance (delegation , list ):
81150 if len (delegation ) == 1 :
82151 cip_36_single .append (registration )
152+
153+ if delegation [0 ][0 ] not in vkey_power :
154+ vkey_power [delegation [0 ][0 ]] = []
155+ vkey_power [delegation [0 ][0 ]].append (v_power )
83156 else :
84157 cip_36_multi .append (registration )
85158 else :
@@ -89,6 +162,30 @@ def analyze_snapshot(args: argparse.Namespace):
89162 )
90163 total_rejects += 1
91164
165+ # Read the processed snapshot.
166+ total_processed_vpower = None
167+ processed_snapshot = None
168+ if args .processed is not None :
169+ processed_snapshot = index_processed_snapshot (json .loads (args .processed .read_text ()))
170+
171+ for rec in processed_snapshot .items ():
172+ rec_vpower = 0
173+ for contribution in rec [1 ]["contributions" ]:
174+
175+ if contribution ["stake_public_key" ] in snapshot_index :
176+ snap = snapshot_index [contribution ["stake_public_key" ]]
177+ if snap ["voting_power" ] != contribution ["value" ]:
178+ print (f"Mismatched Contribution Value for { contribution ['stake_public_key' ]} " )
179+ else :
180+ rec_vpower += contribution ["value" ]
181+ if rec_vpower != rec [1 ]["hir" ]["voting_power" ]:
182+ print (f"Mismatched Voting Power for { rec } " )
183+ else :
184+ if total_processed_vpower is None :
185+ total_processed_vpower = rec_vpower
186+ else :
187+ total_processed_vpower += rec_vpower
188+
92189 # Index Errors
93190 registration_obsolete : dict [str , Any ] = {}
94191 decode_errors : list [Any ] = []
@@ -121,8 +218,11 @@ def analyze_snapshot(args: argparse.Namespace):
121218 mismatched : dict [str , Any ] = {}
122219 equal_snapshots = 0
123220
221+
222+
124223 if args .compare is not None :
125224 raw_compare = json .loads (args .compare .read_text ())
225+
126226 for comp in raw_compare :
127227 # Index all records being compared.
128228 stake_pub_key = comp ["stake_public_key" ]
@@ -150,14 +250,21 @@ def analyze_snapshot(args: argparse.Namespace):
150250 missing_registrations .append (registration )
151251
152252 print ("Snapshot Analysis:" )
153- print (f" Total Registrations : { len (snapshot )} " )
154- print (f" Total CIP-15 : { len (cip_15_snapshot )} " )
155- print (f" Total CIP-36 Single : { len (cip_36_single )} " )
156- print (f" Total CIP-36 Multi : { len (cip_36_multi )} " )
157- print (f" Total Rejects : { total_rejects } " )
253+ print (f" Total Registrations : { len (snapshot ):10 } " )
254+ print (f" Total CIP-15 : { len (cip_15_snapshot ):10 } " )
255+ print (f" Total CIP-36 Single : { len (cip_36_single ):10 } " )
256+ print (f" Total CIP-36 Multi : { len (cip_36_multi ):10 } " )
257+ print (f" Total Rejects : { total_rejects :10 } " )
158258
159259 print ()
160- print ("Stake Address Types:" )
260+ print ("Reward Address Types:" )
261+ print (f" Total Payable : { rewards_payable :10} " )
262+ print (f" Total Pointer : { rewards_pointer :10} " )
263+ print (f" Total Unpayable : { rewards_unpayable :10} " )
264+ print (f" Total Invalid : { rewards_invalid :10} " )
265+ print (f" Total Types : { len (rewards_types ):10} " )
266+ print (f" Types = { ',' .join (rewards_types .keys ())} " )
267+ print (f" Total Unique Rewards : { len (unique_rewards ):10} " )
161268
162269 #if len(registration_errors) > 0:
163270 # print()
@@ -198,22 +305,62 @@ def analyze_snapshot(args: argparse.Namespace):
198305 for reg in missing_registrations :
199306 print (f" { reg } " )
200307
201- total_unregistered = len (snapshot_unregistered )
202- value_unregistered = 0
203- for value in snapshot_unregistered .values ():
204- value_unregistered += value
308+ total_unregistered = len (snapshot_unregistered )
309+ value_unregistered = 0
310+ for value in snapshot_unregistered .values ():
311+ value_unregistered += value
312+
313+ total_threshold_voting_power = 0
314+ total_threshold_registrations = 0
315+ multi_reg_voting_keys = 0
316+
317+ print ()
318+ print ("Multiple Registrations to same voting key" )
319+ for key in vkey_power :
320+ this_power = 0
321+ for v in vkey_power [key ]:
322+ this_power += v
323+ if this_power >= 450000000 :
324+ total_threshold_registrations += 1
325+ total_threshold_voting_power += this_power
326+
327+ if processed_snapshot is not None :
328+ if key not in processed_snapshot :
329+ print (f" Key { key } not in processed snapshot." )
330+ elif this_power != processed_snapshot [key ]["hir" ]["voting_power" ]:
331+ print (f" Key { key } voting power mismatch. Processed = { processed_snapshot [key ]['hir' ]['voting_power' ]} Actual = { this_power } " )
332+
205333
206- print ( f" Total Registrations = Total Voting Power : { len ( snapshot ):>10 } = { total_registered_value / 1000000 :>25 } ADA" )
207- print (f" Total Unregistered = Total Voting Power : { total_unregistered :>10 } = { value_unregistered / 1000000 :>25 } ADA " )
334+ elif key in processed_snapshot :
335+ print (f" Key { key } is in processed snapshot? " )
208336
209- staked_total = len (snapshot ) + total_unregistered
210- staked_total_value = total_registered_value + value_unregistered
337+ if len (vkey_power [key ]) > 1 :
338+ multi_reg_voting_keys += 1
339+ print (f" { multi_reg_voting_keys :3} { key } = { this_power / 1000000 :>25.6f} ADA" )
340+ powers = ", " .join ([f"{ x / 1000000 :0.6f} " .rstrip ("0" ).rstrip ("." ) for x in sorted (vkey_power [key ])])
341+ print (f" { len (vkey_power [key ])} Stake Addresses : ADA = { powers } " )
211342
212- reg_pct = 100.0 / staked_total * len (snapshot )
213- val_pct = 100.0 / staked_total_value * total_registered_value
214343
215- print (f" Registered% = VotingPower % : { reg_pct :>10.4 } % = { val_pct :>23.4 } % " )
344+ print (" " )
216345
346+ if total_processed_vpower is not None :
347+ print (f" Total Processed Registrations = Total Voting Power : { len (processed_snapshot .keys ()):>10} = { total_processed_vpower / 1000000 :>25.6f} ADA - Validates : { total_processed_vpower == total_threshold_voting_power } " )
348+ print (f" Total Threshold Registrations = Total Voting Power : { total_threshold_registrations :>10} = { total_threshold_voting_power / 1000000 :>25.6f} ADA" )
349+ print (f" Total Registrations = Total Voting Power : { len (snapshot ):>10} = { total_registered_value / 1000000 :>25.6f} ADA" )
350+ print (f" Total Unregistered = Total Voting Power : { total_unregistered :>10} = { value_unregistered / 1000000 :>25.6f} ADA" )
351+
352+ staked_total = len (snapshot ) + total_unregistered
353+ staked_total_value = total_registered_value + value_unregistered
354+
355+ reg_pct = 100.0 / staked_total * len (snapshot )
356+ val_pct = 100.0 / staked_total_value * total_registered_value
357+
358+ print (f" Registered% = VotingPower % : { reg_pct :>10.4f} % = { val_pct :>23.4f} %" )
359+
360+ thresh_reg_pct = 100.0 / staked_total * total_threshold_registrations
361+ thresh_val_pct = 100.0 / staked_total_value * total_threshold_voting_power
362+
363+ print (f" Threshold Registered% (450 A) = VotingPower % : { thresh_reg_pct :>10.4f} % = { thresh_val_pct :>23.4f} %" )
217364
218365
219366def main () -> int :
@@ -235,6 +382,13 @@ def main() -> int:
235382 type = is_file ,
236383 )
237384
385+ parser .add_argument (
386+ "--processed" ,
387+ help = "Processed Snapshot file to compare with." ,
388+ required = False ,
389+ type = is_file ,
390+ )
391+
238392 args = parser .parse_args ()
239393 analyze_snapshot (args )
240394 return 0
0 commit comments