"""Offline Litecoin mainnet retarget arithmetic; not a node or mining simulator.""" import json SPAN=302400 LIMIT=int("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16) MASK=(1<<256)-1 def decode(bits): exponent=bits>>24;mantissa=bits&0x007fffff if bits&0x00800000:raise ValueError("negative target") value=mantissa>>(8*(3-exponent)) if exponent<=3 else mantissa<<(8*(exponent-3)) if value<=0 or value>LIMIT:raise ValueError("target out of mainnet range") return value def encode(value): size=(value.bit_length()+7)//8 word=value<<(8*(3-size)) if size<=3 else value>>(8*(size-3)) if word&0x00800000:word>>=8;size+=1 return (size<<24)|(word&0x007fffff) def retarget(old_bits,elapsed): value=decode(old_bits);clamped=max(SPAN//4,min(SPAN*4,elapsed)) shifted=value.bit_length()>LIMIT.bit_length()-1 if shifted:value>>=1 value=((value*clamped)&MASK)//SPAN if shifted:value=(value<<1)&MASK value=min(value,LIMIT) return encode(value),clamped,shifted def lookback(next_height): if next_height<=0 or next_height%2016:raise ValueError("not an adjustment height") previous=next_height-1;steps=2015 if next_height==2016 else 2016 return dict(next_height=next_height,last_height=previous,first_height=previous-steps,steps=steps) VECTORS=[ dict(name="ordinary",old_bits=0x1c0ac141,first_time=1358118740,last_time=1358378777,expected=0x1c093f8d), dict(name="pow_limit",old_bits=0x1e0ffff0,first_time=1317972665,last_time=1318480354,expected=0x1e0fffff), dict(name="lower_timespan",old_bits=0x1b075cf1,first_time=1401682934,last_time=1401757934,expected=0x1b01d73c), dict(name="upper_timespan",old_bits=0x1b015318,first_time=1463690315,last_time=1464900315,expected=0x1b054c60)] def calculate(): vectors=[] for v in VECTORS: got,clamp,shift=retarget(v["old_bits"],v["last_time"]-v["first_time"]) assert got==v["expected"],(v["name"],hex(got)) vectors.append(dict(name=v["name"],old_bits=hex(v["old_bits"]),first_time=v["first_time"],last_time=v["last_time"],elapsed=v["last_time"]-v["first_time"],clamped=clamp,shift=shift,result_bits=hex(got),expected_bits=hex(v["expected"]))) models=[] for elapsed in [SPAN//8,SPAN//4,SPAN//2,SPAN,SPAN*2,SPAN*4,SPAN*8]: got,clamp,_=retarget(0x1b075cf1,elapsed) models.append(dict(elapsed=elapsed,clamped=clamp,target_factor_before_compact_rounding=clamp/SPAN,difficulty_factor_before_compact_rounding=SPAN/clamp,result_bits=hex(got))) assert lookback(2016)["first_height"]==0 and lookback(4032)["first_height"]==2015 assert lookback(6048)["first_height"]==4031 assert retarget(0x1b075cf1,SPAN)[0]==0x1b075cf1 assert retarget(0x1b075cf1,1)[0]==retarget(0x1b075cf1,SPAN//4)[0] assert retarget(0x1b075cf1,SPAN*8)[0]==retarget(0x1b075cf1,SPAN*4)[0] return dict(scope="Pinned Core0.21.5.8 mainnet arithmetic; four source-test vectors, seven chosen scenarios, no live chain replay or executable Core test.",core_vectors=vectors,synthetic_scenarios=models,lookback=[lookback(h) for h in [2016,4032,6048]],checks=dict(core_vectors_matched=4,synthetic_scenarios=7,lookback_boundaries=3)) if __name__=="__main__":print(json.dumps(calculate(),indent=2))