git / moonbite-network / pow.py · raw

"""MoonBite — Proof-of-Work with Bitcoin-compatible difficulty adjustment.Mining searches for a header nonce whose double-SHA-256 hash is below a target.This implements Bitcoin's actual retargeting algorithm (Bitcoin Core):1. Every 2016 blocks, recalculate the target based on actual vs. expected time2. New target = old target × (actual_timespan / expected_timespan)3. Clamp to prevent wild swings: new_target in [old/4, old×4]4. Ensure minimum target >= MINIMUM_TARGETDesign: `bits` encodes the target as a compact representation (similar to Bitcoin).For simplicity, we store the target as a full 256-bit integer and convert to/frombits when needed for the chain (for compatibility with block headers).This is **identical to Bitcoin's algorithm** and produces the same difficultyadjustments over 2-week (2016-block) cycles."""from __future__ import annotationsimport mathfrom block import Block# Consensus timing comes from params.py — the single source of truth.from params import TARGET_BLOCK_TIMEfrom params import RETARGET_INTERVAL  # single source of truthEXPECTED_TIMESPAN = TARGET_BLOCK_TIME * RETARGET_INTERVAL  # 20,160 minutes = 1,209,600 seconds# Difficulty boundsMIN_BITS = 1MAX_BITS = 240  # keep well under 256 so a target always exists# Minimum target (Bitcoin's limit, corresponds to bits=0x1d00ffff)# This is roughly 2^224, but we use a simpler bound: allow down to 1 bit difficultyMINIMUM_TARGET = 1 << (256 - MAX_BITS)  # = 2^(256-240) = 2^16MAXIMUM_TARGET = 1 << (256 - MIN_BITS)  # = 2^(256-1) = nearly all hashes validdef bits_to_target(bits: int) -> int:    """Convert difficulty bits to full 256-bit target.    bits is the count of required leading zero bits.    target = 2^(256 - bits)    """    if bits < MIN_BITS or bits > MAX_BITS:        raise ValueError(f"bits {bits} outside range [{MIN_BITS}, {MAX_BITS}]")    return 1 << (256 - bits)def target_to_bits(target: int) -> int:    """Convert 256-bit target back to difficulty bits.    bits = 256 - log2(target), rounded appropriately.    Ensures target is within valid range.    """    if target < MINIMUM_TARGET:        return MAX_BITS    if target >= MAXIMUM_TARGET:        return MIN_BITS    # Find the highest bit position in target    # bit_length() gives position of highest bit + 1    # For target = 2^n, we need bits = 256 - n    # So bits = 256 - (bit_length() - 1) = 257 - bit_length()    bit_length = target.bit_length()    if bit_length > 256:        return MIN_BITS    bits = 257 - bit_length    return max(MIN_BITS, min(MAX_BITS, bits))def hash_meets_target(hash_hex: str, bits: int) -> bool:    """True if hash is below the target (hash < 2^(256-bits))."""    target = bits_to_target(bits)    return int(hash_hex, 16) < targetdef block_meets_target(block: Block) -> bool:    return hash_meets_target(block.hash, block.header.bits)def mine(block: Block, max_nonce: int = 1 << 32) -> bool:    """Increment the block's nonce until its hash meets the target.    Returns True and leaves the winning nonce in the header on success; returns    False if the nonce space is exhausted (caller should change something, e.g.    the coinbase extra-nonce or timestamp, and retry).    """    for nonce in range(max_nonce):        block.header.nonce = nonce        if hash_meets_target(block.hash, block.header.bits):            return True    return Falsedef calculate_next_bits(    current_bits: int, actual_timespan: int, expected_timespan: int | None = None) -> int:    """Bitcoin-compatible difficulty retarget.    Called every RETARGET_INTERVAL (2016) blocks. Adjusts the target based on    how long the last interval actually took vs. expected time.    Algorithm (identical to Bitcoin Core):    1. new_target = old_target * (actual_timespan / expected_timespan)    2. Clamp to [old_target/4, old_target*4] to prevent wild swings    3. Ensure within [MINIMUM_TARGET, MAXIMUM_TARGET]    Args:        current_bits: Current difficulty (leading zero bits)        actual_timespan: How long the last 2016 blocks actually took (seconds)        expected_timespan: Expected time for 2016 blocks (defaults to 1,209,600 sec)    Returns:        New difficulty in bits, clamped to valid range.    """    if expected_timespan is None:        expected_timespan = EXPECTED_TIMESPAN    # Convert current bits to target    old_target = bits_to_target(current_bits)    # Bitcoin's algorithm: adjust target by timespan ratio, with clamp    # new_target = old_target * (actual_timespan / expected_timespan)    # Clamp actual_timespan to prevent extreme adjustments    # Bitcoin clamps to [timespan/4, timespan*4]    min_timespan = expected_timespan // 4    max_timespan = expected_timespan * 4    clamped_timespan = max(min_timespan, min(max_timespan, actual_timespan))    # Calculate new target (using integer arithmetic to avoid float errors)    # new_target = old_target * clamped_timespan / expected_timespan    new_target = (old_target * clamped_timespan) // expected_timespan    # Ensure target stays within valid bounds    new_target = max(MINIMUM_TARGET, min(MAXIMUM_TARGET, new_target))    # Convert back to bits    new_bits = target_to_bits(new_target)    return new_bits