Repository: bitcoin/bitcoin
File: test/functional/test_framework/util.py (Python)

Code before:
        raise AssertionError("Objects were found %s" % (str(to_match)))


# Utility functions
###################


def check_json_precision():
    """Make sure json library being used does not lose precision converting BTC values"""
    n = Decimal("20000000.00000003")
    satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
    if satoshis != 2000000000000003:
        raise RuntimeError("JSON encode/decode loses precision")


def count_bytes(hex_string):
    return len(bytearray.fromhex(hex_string))


def str_to_b64str(string):
    return b64encode(string.encode('utf-8')).decode('ascii')


def ceildiv(a, b):
    """
    Divide 2 ints and round up to next int rather than round down
    Implementation requires python integers, which have a // operator that does floor division.
    Other types like decimal.Decimal whose // operator truncates towards 0 will not work.
    """
    assert isinstance(a, int)
    assert isinstance(b, int)
    return -(-a // b)


def random_bitflip(data):
    data = list(data)
    data[random.randrange(len(data))] ^= (1 << (random.randrange(8)))
    return bytes(data)


def get_fee(tx_size, feerate_btc_kvb):
    """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
    feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
    target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat
    return target_fee_sat / Decimal(1e8) # Return result in  BTC


def satoshi_round(amount):
    return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)



Review comment (bug):
nit:

```suggestion
SATS_PRECISION = Decimal('0.00000001')
...

def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal:
    """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode."""
    return Decimal(amount).quantize(SATS_PRECISION, rounding=rounding)
```

The `Decimal('0.00000001')` appears in a few places, would be good to extract it as a constant `SATS_PRECISION` (or whatever name you prefer) for readability and to make it less likely someone accidentally sets the wrong number of zeroes when making a change

Code after:
    return len(bytearray.fromhex(hex_string))


def str_to_b64str(string):
    return b64encode(string.encode('utf-8')).decode('ascii')


def ceildiv(a, b):
    """
    Divide 2 ints and round up to next int rather than round down
    Implementation requires python integers, which have a // operator that does floor division.
    Other types like decimal.Decimal whose // operator truncates towards 0 will not work.
    """
    assert isinstance(a, int)
    assert isinstance(b, int)
    return -(-a // b)


def get_fee(tx_size, feerate_btc_kvb):
    """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
    feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
    target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat
    return target_fee_sat / Decimal(1e8) # Return result in  BTC


def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal:
    """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode."""
    return Decimal(amount).quantize(SATOSHI_PRECISION, rounding=rounding)


def wait_until_helper_internal(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
    """Sleep until the predicate resolves to be True.

    Warning: Note that this method is not recommended to be used in tests as it is
    not aware of the context of the test framework. Using the `wait_until()` members
    from `BitcoinTestFramework` or `P2PInterface` class ensures the timeout is
    properly scaled. Furthermore, `wait_until()` from `P2PInterface` class in
    `p2p.py` has a preset lock.
    """
    if attempts == float('inf') and timeout == float('inf'):
        timeout = 60
    timeout = timeout * timeout_factor
    attempt = 0
    time_end = time.time() + timeout

    while attempt < attempts and time.time() < time_end:
        if lock:
            with lock:
                if predicate():
                    return
        else: