From 9dbdf9804f227e456c1838b448ea6018d6868bdd Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Fri, 24 Apr 2026 23:54:24 +0900 Subject: [PATCH 1/3] Fix(session): header handling in session request method Ensure headers are converted to a dictionary if provided. --- unshackle/core/session.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/unshackle/core/session.py b/unshackle/core/session.py index 2529934..81bc5ca 100644 --- a/unshackle/core/session.py +++ b/unshackle/core/session.py @@ -586,6 +586,9 @@ class RnetSession: # Skip retry for non-allowed methods if method_upper not in self.allowed_methods: + if "headers" in kwargs and kwargs["headers"] is not None: + kwargs["headers"] = dict(kwargs["headers"]) + raw_resp = client.request(rnet_method, url, **kwargs) return RnetResponse(raw_resp) @@ -594,6 +597,9 @@ class RnetSession: for attempt in range(self.max_retries + 1): try: + if "headers" in kwargs and kwargs["headers"] is not None: + kwargs["headers"] = dict(kwargs["headers"]) + raw_resp = client.request(rnet_method, url, **kwargs) response = RnetResponse(raw_resp) if response.status_code not in self.status_forcelist: From 831981a56e2dd0932827a6d2c4ba5a953e3ae520 Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Fri, 24 Apr 2026 23:57:24 +0900 Subject: [PATCH 2/3] Add(Base58): encoding and decoding functions Implement Base58 and Base58Check encoding and decoding. --- unshackle/utils/base58.py | 137 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 unshackle/utils/base58.py diff --git a/unshackle/utils/base58.py b/unshackle/utils/base58.py new file mode 100644 index 0000000..eae39fb --- /dev/null +++ b/unshackle/utils/base58.py @@ -0,0 +1,137 @@ +# Clone from https://github.com/keis/base58 + +"""Base58 encoding + +Implementations of Base58 and Base58Check encodings that are compatible +with the bitcoin network. +""" + +# This module is based upon base58 snippets found scattered over many bitcoin +# tools written in python. From what I gather the original source is from a +# forum post by Gavin Andresen, so direct your praise to him. +# This module adds shiny packaging and support for python3. + +from functools import lru_cache +from hashlib import sha256 +from typing import Mapping, Union + +__version__ = "2.1.1" + +# 58 character alphabet used +BITCOIN_ALPHABET = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +RIPPLE_ALPHABET = b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz" +XRP_ALPHABET = RIPPLE_ALPHABET + +# Retro compatibility +alphabet = BITCOIN_ALPHABET + + +def scrub_input(v: Union[str, bytes]) -> bytes: + if isinstance(v, str): + v = v.encode("ascii") + + return v + + +def b58encode_int(i: int, default_one: bool = True, alphabet: bytes = BITCOIN_ALPHABET) -> bytes: + """ + Encode an integer using Base58 + """ + if not i and default_one: + return alphabet[0:1] + string = b"" + base = len(alphabet) + while i: + i, idx = divmod(i, base) + string = alphabet[idx : idx + 1] + string + return string + + +def b58encode(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET) -> bytes: + """ + Encode a string using Base58 + """ + v = scrub_input(v) + + origlen = len(v) + v = v.lstrip(b"\0") + newlen = len(v) + + acc = int.from_bytes(v, byteorder="big") # first byte is most significant + + result = b58encode_int(acc, default_one=False, alphabet=alphabet) + return alphabet[0:1] * (origlen - newlen) + result + + +@lru_cache() +def _get_base58_decode_map(alphabet: bytes, autofix: bool) -> Mapping[int, int]: + invmap = {char: index for index, char in enumerate(alphabet)} + + if autofix: + groups = [b"0Oo", b"Il1"] + for group in groups: + pivots = [c for c in group if c in invmap] + if len(pivots) == 1: + for alternative in group: + invmap[alternative] = invmap[pivots[0]] + + return invmap + + +def b58decode_int(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *, autofix: bool = False) -> int: + """ + Decode a Base58 encoded string as an integer + """ + if b" " not in alphabet: + v = v.rstrip() + v = scrub_input(v) + + map = _get_base58_decode_map(alphabet, autofix=autofix) + + decimal = 0 + base = len(alphabet) + try: + for char in v: + decimal = decimal * base + map[char] + except KeyError as e: + raise ValueError("Invalid character {!r}".format(chr(e.args[0]))) from None + return decimal + + +def b58decode(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *, autofix: bool = False) -> bytes: + """ + Decode a Base58 encoded string + """ + v = v.rstrip() + v = scrub_input(v) + + origlen = len(v) + v = v.lstrip(alphabet[0:1]) + newlen = len(v) + + acc = b58decode_int(v, alphabet=alphabet, autofix=autofix) + + return acc.to_bytes(origlen - newlen + (acc.bit_length() + 7) // 8, "big") + + +def b58encode_check(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET) -> bytes: + """ + Encode a string using Base58 with a 4 character checksum + """ + v = scrub_input(v) + + digest = sha256(sha256(v).digest()).digest() + return b58encode(v + digest[:4], alphabet=alphabet) + + +def b58decode_check(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *, autofix: bool = False) -> bytes: + """Decode and verify the checksum of a Base58 encoded string""" + + result = b58decode(v, alphabet=alphabet, autofix=autofix) + result, check = result[:-4], result[-4:] + digest = sha256(sha256(result).digest()).digest() + + if check != digest[:4]: + raise ValueError("Invalid checksum") + + return result From bddb305c5d5d2db24a68d2e117dc41643ea6c446 Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Sat, 25 Apr 2026 14:53:07 +0900 Subject: [PATCH 3/3] feat(session): Optimize header handling in session requests Removed redundant conversion of headers to dict for requests. --- unshackle/core/session.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/unshackle/core/session.py b/unshackle/core/session.py index 81bc5ca..c51d45b 100644 --- a/unshackle/core/session.py +++ b/unshackle/core/session.py @@ -584,11 +584,12 @@ class RnetSession: if rnet_method is None: raise ValueError(f"Unsupported HTTP method: {method}") + # Convert headers to standard dict once to resolve PyO3 CaseInsensitiveDict rejection. + if kwargs.get("headers") is not None: + kwargs["headers"] = dict(kwargs["headers"]) + # Skip retry for non-allowed methods if method_upper not in self.allowed_methods: - if "headers" in kwargs and kwargs["headers"] is not None: - kwargs["headers"] = dict(kwargs["headers"]) - raw_resp = client.request(rnet_method, url, **kwargs) return RnetResponse(raw_resp) @@ -597,9 +598,6 @@ class RnetSession: for attempt in range(self.max_retries + 1): try: - if "headers" in kwargs and kwargs["headers"] is not None: - kwargs["headers"] = dict(kwargs["headers"]) - raw_resp = client.request(rnet_method, url, **kwargs) response = RnetResponse(raw_resp) if response.status_code not in self.status_forcelist: