"""Offline reconstruction of frozen Core data. No RPC, requests or trading.""" from pathlib import Path from decimal import Decimal,localcontext import csv,hashlib,json,math,statistics,sys D=Decimal def proof(bits): n=int(bits,16);exp=n>>24;mantissa=n&0x7fffff assert not n&0x800000 and mantissa>0 target=mantissa<<(8*(exp-3)) if exp>=3 else mantissa>>(8*(3-exp)) assert 00 and work>0 with localcontext() as ctx: ctx.prec=50;hps=D(work)/D(span) return {'window_blocks':n,'header_count':n+1,'start_height':rows[0]['height'],'end_height':rows[-1]['height'],'seconds_max_minus_min':span,'seconds_endpoint_difference':times[-1]-times[0],'work':str(work),'hps':str(hps),'phps':str(hps/D(10**15))} def analyze(package,output=None): package=Path(package);meta=json.loads((package/'capture.json').read_text());headers=json.loads((package/'headers.json').read_text());blocks=json.loads((package/'blocks.json').read_text());checks=0 def check(value): nonlocal checks assert value;checks+=1 for name,digest in meta['input_sha256'].items():check(hashlib.sha256((package/name).read_bytes()).hexdigest()==digest) check(meta['chain']=='main');check(len(headers)==601);check(len(blocks)==144);check(headers[-1]['height']==meta['anchor_height']);check(headers[-1]['hash']==meta['anchor_hash']) for a,b in zip(headers,headers[1:]): check(b['height']==a['height']+1 and b['previousblockhash']==a['hash']);check(int(b['chainwork'],16)-int(a['chainwork'],16)==proof(b['bits'])) rows=[] for b,h in zip(blocks,headers[-144:]): s=b['stats'];check((b['height'],b['hash'],b['time'])==(h['height'],h['hash'],h['time']));check(s['height']==b['height'] and s['blockhash']==b['hash']) subsidy=s['subsidy'];fees=s['totalfee'];claimed=sum(b['coinbase_output_litoshis']);available=subsidy+fees;mweb=sum(b['mweb_kernel_fee_litoshis']) ordinary=s['txs']-1-b['hogex_count'];base=fees-mweb check(subsidy==625000000);check(0<=claimed<=available);check(0<=mweb<=fees);check(b['hogex_count'] in [0,1]);check(ordinary>=0 and base>=0) rows.append({'height':b['height'],'hash':b['hash'],'header_time':b['time'],'subsidy_litoshis':subsidy,'fees_litoshis':fees,'available_litoshis':available,'claimed_litoshis':claimed,'unclaimed_litoshis':available-claimed,'mweb_kernel_fees_litoshis':mweb,'base_chain_fees_excluding_hogex_litoshis':base,'base_chain_entries_excluding_coinbase_hogex':ordinary,'rpc_txs':s['txs'],'hogex_count':b['hogex_count'],'mweb_kernels':len(b['mweb_kernel_fee_litoshis']),'rpc_maxfee_litoshis':s['maxfee'],'rpc_medianfee_litoshis':s['medianfee']}) fees=sum(r['fees_litoshis'] for r in rows);subsidy=sum(r['subsidy_litoshis'] for r in rows);mweb=sum(r['mweb_kernel_fees_litoshis'] for r in rows);count=sum(r['base_chain_entries_excluding_coinbase_hogex'] for r in rows);base=fees-mweb ordered=sorted(rows,key=lambda r:(-r['fees_litoshis'],r['height']));high=ordered[0];med=D(str(statistics.median([r['fees_litoshis'] for r in rows]))) fee_summary={'block_range':meta['fee_block_range'],'block_count':144,'header_time_first':rows[0]['header_time'],'header_time_last':rows[-1]['header_time'],'header_time_min':min(r['header_time'] for r in rows),'header_time_max':max(r['header_time'] for r in rows),'header_endpoint_span_seconds':rows[-1]['header_time']-rows[0]['header_time'],'subsidy_ltc':str(D(subsidy)/10**8),'fees_ltc':str(D(fees)/10**8),'available_reward_ltc':str(D(subsidy+fees)/10**8),'claimed_reward_ltc':str(D(sum(r['claimed_litoshis'] for r in rows))/10**8),'unclaimed_ltc':str(D(sum(r['unclaimed_litoshis'] for r in rows))/10**8),'full_claim_blocks':sum(r['unclaimed_litoshis']==0 for r in rows),'fee_share_of_available_pct':str(D(fees)*100/D(subsidy+fees)),'mweb_kernel_fees_ltc':str(D(mweb)/10**8),'mweb_fee_share_pct':str(D(mweb)*100/D(fees)) if fees else None,'mweb_kernel_count':sum(r['mweb_kernels'] for r in rows),'base_chain_entries_excluding_coinbase_hogex':count,'rpc_txs_including_coinbase_hogex':sum(r['rpc_txs'] for r in rows),'hogex_count':sum(r['hogex_count'] for r in rows),'base_chain_fees_excluding_hogex_ltc':str(D(base)/10**8),'mean_base_chain_fee_litoshis':str(D(base)/D(count)) if count else None,'mean_block_fee_litoshis':str(D(fees)/144),'median_block_fee_litoshis':str(med),'largest_fee_block':high,'largest_fee_share_pct':str(D(high['fees_litoshis'])*100/D(fees)) if fees else None,'top_10_block_fee_share_pct':str(D(sum(r['fees_litoshis'] for r in ordered[:10]))*100/D(fees)) if fees else None,'max_block_fee_vs_mean_multiple':str(D(high['fees_litoshis'])*144/D(fees)) if fees else None,'max_block_fee_vs_median_multiple':str(D(high['fees_litoshis'])/med) if med else None,'top_10_blocks':ordered[:10]} maximum=high['rpc_maxfee_litoshis'];check(high['mweb_kernel_fees_litoshis']==0 and high['fees_litoshis']<2*maximum);check(maximum==max(r['rpc_maxfee_litoshis'] for r in rows)) fee_summary['largest_entry_fee_ltc']=str(D(maximum)/10**8) fee_summary['largest_entry_fee_share_of_block_pct']=str(D(maximum)*100/D(high['fees_litoshis'])) fee_summary['largest_entry_fee_share_of_sample_pct']=str(D(maximum)*100/D(fees)) fee_summary['base_chain_mean_excluding_single_largest_fee_litoshis']=str(D(base-maximum)/D(count-1)) fee_summary['largest_block_base_chain_mean_litoshis']=str(D(high['fees_litoshis'])/D(high['base_chain_entries_excluding_coinbase_hogex'])) fee_summary['largest_block_base_chain_mean_excluding_max_litoshis']=str(D(high['fees_litoshis']-maximum)/D(high['base_chain_entries_excluding_coinbase_hogex']-1)) fee_summary['exclusion_scope']='Sensitivity: remove the one 0.2 LTC maximum-fee entry from the base-chain fee numerator and entry count, not an alternative blockchain. That block has no MWEB fees and total fees below 0.4 LTC, so only one entry can pay that maximum. Sender, intent and transaction ID were not reconstructed.' estimates=[estimate(headers,600,n) for n in [30,120,576]] for e in estimates: observed=D(str(meta['rpc_hashrate_estimates_hps'][str(e['window_blocks'])]));computed=D(e['hps']);check(abs(observed-computed)/computed2 else None) print(json.dumps(result,indent=2))