def from_value(cls: Type[UInt32], value: Union[str, int]) -> UInt32: """ Construct a new UInt32 type from a number. Args: value: The number to construct a UInt32 from. Returns: The UInt32 constructed from value. Raises: XRPLBinaryCodecException: If a UInt32 could not be constructed from value. """ if not isinstance(value, (str, int)): raise XRPLBinaryCodecException( "Invalid type to construct a UInt32: expected str or int," " received {value.__class__.__name__}.") if isinstance(value, int): value_bytes = (value).to_bytes(_WIDTH, byteorder="big", signed=False) return cls(value_bytes) if isinstance(value, str) and value.isdigit(): value_bytes = (int(value)).to_bytes(_WIDTH, byteorder="big", signed=False) return cls(value_bytes) raise XRPLBinaryCodecException( "Cannot construct UInt32 from given value")
def verify_xrp_value(xrp_value: str) -> None: """ Validates the format of an XRP amount. Raises if value is invalid. Args: xrp_value: A string representing an amount of XRP. Returns: None, but raises if xrp_value is not a valid XRP amount. Raises: XRPLBinaryCodecException: If xrp_value is not a valid XRP amount. """ # Contains no decimal point if not _contains_decimal(xrp_value): raise XRPLBinaryCodecException( f"{xrp_value} is an invalid XRP amount.") # Within valid range decimal = Decimal(xrp_value) # Zero is less than both the min and max XRP amounts but is valid. if decimal.is_zero(): return if (decimal.compare(_MIN_XRP) == -1) or (decimal.compare(_MAX_DROPS) == 1): raise XRPLBinaryCodecException( f"{xrp_value} is an invalid XRP amount.")
def from_value(cls: Type[SerializedList], value: List[Any]) -> SerializedList: """ Create a SerializedList object from a dictionary. Args: value: The dictionary to construct a SerializedList from. Returns: The SerializedList object constructed from value. Raises: XRPLBinaryCodecException: If the provided value isn't a list or contains non-dict elements. """ if not isinstance(value, list): raise XRPLBinaryCodecException( "Invalid type to construct a SerializedList:" " expected list, received {value.__class__.__name__}.") if len(value) > 0 and not isinstance(value[0], dict): raise XRPLBinaryCodecException( ("Cannot construct SerializedList from a list of non-dict" " objects")) bytestring = b"" for obj in value: transaction = SerializedDict.from_value(obj) bytestring += bytes(transaction) bytestring += _ARRAY_END_MARKER return SerializedList(bytestring)
def from_value(cls: Type[PathSet], value: List[List[Dict[str, str]]]) -> PathSet: """ Construct a PathSet from a List of Lists representing paths. Args: value: The List to construct a PathSet object from. Returns: The PathSet constructed from value. Raises: XRPLBinaryCodecException: If the PathSet representation is invalid. """ if not isinstance(value, list): raise XRPLBinaryCodecException( "Invalid type to construct a PathSet: expected list," f" received {value.__class__.__name__}." ) if _is_path_set(value): buffer: List[bytes] = [] for path_dict in value: path = Path.from_value(path_dict) buffer.append(bytes(path)) buffer.append(bytes([_PATH_SEPARATOR_BYTE])) buffer[-1] = bytes([_PATHSET_END_BYTE]) return PathSet(b"".join(buffer)) raise XRPLBinaryCodecException("Cannot construct PathSet from given value")
def from_value(cls: Type[UInt16], value: int) -> UInt16: """ Construct a new UInt16 type from a number. Args: value: The value to consutrct a UInt16 from. Returns: The UInt16 constructed from value. Raises: XRPLBinaryCodecException: If a UInt16 can't be constructed from value. """ if not isinstance(value, int): raise XRPLBinaryCodecException( "Invalid type to construct a UInt16: expected int, " "received {value.__class__.__name__}.") if isinstance(value, int): value_bytes = (value).to_bytes(_WIDTH, byteorder="big", signed=False) return cls(value_bytes) raise XRPLBinaryCodecException( "Cannot construct UInt16 from given value")
def read_field_header(self: BinaryParser) -> FieldHeader: """ Reads field ID from BinaryParser and returns as a FieldHeader object. Returns: The field header. Raises: XRPLBinaryCodecException: If the field ID cannot be read. """ type_code = self.read_uint8() field_code = type_code & 15 type_code >>= 4 if type_code == 0: type_code = self.read_uint8() if type_code == 0 or type_code < 16: raise XRPLBinaryCodecException( "Cannot read field ID, type_code out of range." ) if field_code == 0: field_code = self.read_uint8() if field_code == 0 or field_code < 16: raise XRPLBinaryCodecException( "Cannot read field ID, field_code out of range." ) return FieldHeader(type_code, field_code)
def from_value(cls: Type[Amount], value: Union[str, Dict[str, str]]) -> Amount: """ Construct an Amount from an issued currency amount or (for XRP), a string amount. See `Amount Fields <https://xrpl.org/serialization.html#amount-fields>`_ Args: value: The value from which to construct an Amount. Returns: An Amount object. Raises: XRPLBinaryCodecException: if an Amount cannot be constructed. """ if isinstance(value, str): return cls(_serialize_xrp_amount(value)) if IssuedCurrencyAmount.is_dict_of_model(value): return cls(_serialize_issued_currency_amount(value)) raise XRPLBinaryCodecException( "Invalid type to construct an Amount: expected str or dict," f" received {value.__class__.__name__}.")
def _read_length_prefix(self: BinaryParser) -> int: """ Reads a variable length encoding prefix and returns the encoded length. The formula for decoding a length prefix is described in: `Length Prefixing <https://xrpl.org/serialization.html#length-prefixing>`_ """ byte1 = self.read_uint8() # If the field contains 0 to 192 bytes of data, the first byte defines # the length of the contents if byte1 <= _MAX_SINGLE_BYTE_LENGTH: return byte1 # If the field contains 193 to 12480 bytes of data, the first two bytes # indicate the length of the field with the following formula: # 193 + ((byte1 - 193) * 256) + byte2 if byte1 <= _MAX_SECOND_BYTE_VALUE: byte2 = self.read_uint8() return ((_MAX_SINGLE_BYTE_LENGTH + 1) + ((byte1 - (_MAX_SINGLE_BYTE_LENGTH + 1)) * _MAX_BYTE_VALUE) + byte2) # If the field contains 12481 to 918744 bytes of data, the first three # bytes indicate the length of the field with the following formula: # 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3 if byte1 <= 254: byte2 = self.read_uint8() byte3 = self.read_uint8() return (_MAX_DOUBLE_BYTE_LENGTH + ((byte1 - (_MAX_SECOND_BYTE_VALUE + 1)) * _MAX_DOUBLE_BYTE_VALUE) + (byte2 * _MAX_BYTE_VALUE) + byte3) raise XRPLBinaryCodecException( "Length prefix must contain between 1 and 3 bytes.")
def _decode_field_id(field_id: str) -> FieldHeader: """ Returns a FieldHeader object representing the type code and field code of a decoded field ID. """ byte_array = bytes.fromhex(field_id) if len(byte_array) == 1: high_bits = byte_array[0] >> 4 low_bits = byte_array[0] & 0x0F return FieldHeader(high_bits, low_bits) if len(byte_array) == 2: first_byte = byte_array[0] second_byte = byte_array[1] first_byte_high_bits = first_byte >> 4 first_byte_low_bits = first_byte & 0x0F if (first_byte_high_bits == 0 ): # next 4 bits are field code, second byte is type code return FieldHeader(second_byte, first_byte_low_bits) # Otherwise, next 4 bits are type code, second byte is field code return FieldHeader(first_byte_high_bits, second_byte) if len(byte_array) == 3: return FieldHeader(byte_array[1], byte_array[2]) raise XRPLBinaryCodecException( "Field ID must be between 1 and 3 bytes. " f"This field ID contained {len(byte_array)} bytes.")
def verify_iou_value(issued_currency_value: str) -> None: """ Validates the format of an issued currency amount value. Raises if value is invalid. Args: issued_currency_value: A string representing the "value" field of an issued currency amount. Returns: None, but raises if issued_currency_value is not valid. Raises: XRPLBinaryCodecException: If issued_currency_value is invalid. """ decimal_value = Decimal(issued_currency_value) if decimal_value.is_zero(): return exponent = decimal_value.as_tuple().exponent if ((_calculate_precision(issued_currency_value) > _MAX_IOU_PRECISION) or (exponent > _MAX_IOU_EXPONENT) or (exponent < _MIN_IOU_EXPONENT)): raise XRPLBinaryCodecException( "Decimal precision out of range for issued currency value.") _verify_no_decimal(decimal_value)
def from_value(cls: Type[PathStep], value: Dict[str, str]) -> PathStep: """ Construct a PathStep object from a dictionary. Args: value: The dictionary to construct a PathStep object from. Returns: The PathStep constructed from value. Raises: XRPLBinaryCodecException: If the supplied value is of the wrong type. """ if not isinstance(value, dict): raise XRPLBinaryCodecException( "Invalid type to construct a PathStep: expected dict," f" received {value.__class__.__name__}." ) data_type = 0x00 buffer = b"" if "account" in value: account_id = AccountID.from_value(value["account"]) buffer += bytes(account_id) data_type |= _TYPE_ACCOUNT if "currency" in value: currency = Currency.from_value(value["currency"]) buffer += bytes(currency) data_type |= _TYPE_CURRENCY if "issuer" in value: issuer = AccountID.from_value(value["issuer"]) buffer += bytes(issuer) data_type |= _TYPE_ISSUER return PathStep(bytes([data_type]) + buffer)
def __ge__(self: UInt, other: object) -> bool: """Determine whether one UInt object is greater than or equal to another.""" if isinstance(other, int): return self.value >= other if isinstance(other, UInt): return self.value >= other.value raise XRPLBinaryCodecException( f"Cannot compare UInt and {type(other)}")
def __ne__(self: UInt, other: object) -> bool: """Determine whether two UInt objects are unequal.""" if isinstance(other, int): return self.value != other if isinstance(other, UInt): return self.value != other.value raise XRPLBinaryCodecException( f"Cannot compare UInt and {type(other)}")
def _iso_code_from_hex(value: bytes) -> Optional[str]: candidate_iso = value.decode("ascii") if candidate_iso == "XRP": raise XRPLBinaryCodecException( "Disallowed currency code: to indicate the currency " "XRP you must use 20 bytes of 0s") if _is_iso_code(candidate_iso): return candidate_iso return None
def _serialize_issued_currency_value(value: str) -> bytes: """ Serializes the value field of an issued currency amount to its bytes representation. :param value: The value to serialize, as a string. :return: A bytes object encoding the serialized value. """ verify_iou_value(value) decimal_value = Decimal(value) if decimal_value.is_zero(): return _ZERO_CURRENCY_AMOUNT_HEX.to_bytes(8, byteorder="big") # Convert components to integers --------------------------------------- sign, digits, exp = decimal_value.as_tuple() mantissa = int("".join([str(d) for d in digits])) # Canonicalize to expected range --------------------------------------- while mantissa < _MIN_MANTISSA and exp > _MIN_IOU_EXPONENT: mantissa *= 10 exp -= 1 while mantissa > _MAX_MANTISSA: if exp >= _MAX_IOU_EXPONENT: raise XRPLBinaryCodecException( f"Amount overflow in issued currency value {str(value)}") mantissa //= 10 exp += 1 if exp < _MIN_IOU_EXPONENT or mantissa < _MIN_MANTISSA: # Round to zero _ZERO_CURRENCY_AMOUNT_HEX.to_bytes(8, byteorder="big", signed=False) if exp > _MAX_IOU_EXPONENT or mantissa > _MAX_MANTISSA: raise XRPLBinaryCodecException( f"Amount overflow in issued currency value {str(value)}") # Convert to bytes ----------------------------------------------------- serial = _ZERO_CURRENCY_AMOUNT_HEX # "Not XRP" bit set if sign == 0: serial |= _POS_SIGN_BIT_MASK # "Is positive" bit set serial |= (exp + 97) << 54 # next 8 bits are exponents serial |= mantissa # last 54 bits are mantissa return serial.to_bytes(8, byteorder="big", signed=False)
def __init__(self: Hash, buffer: Optional[bytes]) -> None: """ Construct a Hash. Args: buffer: The byte buffer that will be used to store the serialized encoding of this field. """ buffer = buffer if buffer is not None else bytes(self._get_length()) if len(buffer) != self._get_length(): raise XRPLBinaryCodecException("Invalid hash length {len(buffer)}") super().__init__(buffer)
def from_value(cls: Type[Blob], value: str) -> Blob: """ Create a Blob object from a hex-string. Args: value: The hex-encoded string to construct a Blob from. Returns: The Blob constructed from value. Raises: XRPLBinaryCodecException: If the Blob can't be constructed from value. """ if not isinstance(value, str): raise XRPLBinaryCodecException( "Invalid type to construct a Blob: expected str, received " f"{value.__class__.__name__}.") if isinstance(value, str): return cls(bytes.fromhex(value)) raise XRPLBinaryCodecException( "Cannot construct Blob from value given")
def from_value(cls: Type[UInt64], value: Union[str, int]) -> UInt64: """ Construct a new UInt64 type from a number. Args: value: The number to construct a UInt64 from. Returns: The UInt64 constructed from value. Raises: XRPLBinaryCodecException: If a UInt64 could not be constructed from value. """ if not isinstance(value, (str, int)): raise XRPLBinaryCodecException( "Invalid type to construct a UInt64: expected str or int," " received {value.__class__.__name__}.") if isinstance(value, int): if value < 0: raise XRPLBinaryCodecException( "{value} must be an unsigned integer") value_bytes = (value).to_bytes(_WIDTH, byteorder="big", signed=False) return cls(value_bytes) if isinstance(value, str): if not _HEX_REGEX.fullmatch(value): raise XRPLBinaryCodecException( "{value} is not a valid hex string") value = value.rjust(16, "0") value_bytes = bytes.fromhex(value) return cls(value_bytes) raise XRPLBinaryCodecException( "Cannot construct UInt64 from given value {value}")
def skip(self: BinaryParser, n: int) -> None: """ Consume the first n bytes of the BinaryParser. Args: n: The number of bytes to consume. Raises: XRPLBinaryCodecException: If n bytes can't be skipped. """ if n > len(self.bytes): raise XRPLBinaryCodecException( f"BinaryParser can't skip {n} bytes, only contains {len(self.bytes)}." ) self.bytes = self.bytes[n:]
def from_value(cls: Type[Currency], value: str) -> Currency: """ Construct a Currency object from a string representation of a currency. Args: value: The string to construct a Currency object from. Returns: A Currency object constructed from value. Raises: XRPLBinaryCodecException: If the Currency representation is invalid. """ if not isinstance(value, str): raise XRPLBinaryCodecException( "Invalid type to construct a Currency: expected str," f" received {value.__class__.__name__}.") if _is_iso_code(value): return Currency(_iso_to_bytes(value)) if _is_hex(value): return cls(bytes.fromhex(value)) raise XRPLBinaryCodecException( "Unsupported Currency representation: {value}")
def _verify_no_decimal(decimal: Decimal) -> None: """ Ensure that the value after being multiplied by the exponent does not contain a decimal. :param decimal: A Decimal object. """ actual_exponent = decimal.as_tuple().exponent exponent = Decimal("1e" + str(-(int(actual_exponent) - 15))) if actual_exponent == 0: int_number_string = "".join( [str(d) for d in decimal.as_tuple().digits]) else: # str(Decimal) uses sci notation by default... get around w/ string format int_number_string = "{:f}".format(decimal * exponent) if not _contains_decimal(int_number_string): raise XRPLBinaryCodecException("Decimal place found in int_number_str")
def from_value(cls: Type[Hash], value: str) -> Hash: """ Construct a Hash object from a hex string. Args: value: The value to construct a Hash from. Returns: The Hash object constructed from value. Raises: XRPLBinaryCodecException: If the supplied value is of the wrong type. """ if not isinstance(value, str): raise XRPLBinaryCodecException( f"Invalid type to construct a {cls.__name__}: expected str," f" received {value.__class__.__name__}." ) return cls(bytes.fromhex(value))
def get_field_type_code(field_name: str) -> int: """ Returns the type code associated with the given field. `Serialization Type Codes <https://xrpl.org/serialization.html#type-codes>`_ Args: field_name: The name of the field get a type code for. Returns: The type code associated with the given field name. Raises: XRPLBinaryCodecException: If definitions.json is invalid. """ field_type_name = get_field_type_name(field_name) field_type_code = _TYPE_ORDINAL_MAP[field_type_name] if not isinstance(field_type_code, int): raise XRPLBinaryCodecException( "Field type codes in definitions.json must be ints.") return field_type_code
def _encode_field_id(field_header: FieldHeader) -> bytes: """ Returns the unique field ID for a given field header. This field ID consists of the type code and field code, in 1 to 3 bytes depending on whether those values are "common" (<16) or "uncommon" (>=16) """ type_code = field_header.type_code field_code = field_header.field_code if not 0 < field_code <= 255 or not 0 < type_code <= 255: raise XRPLBinaryCodecException( "Codes must be nonzero and fit in 1 byte.") if type_code < 16 and field_code < 16: # high 4 bits is the type_code # low 4 bits is the field code combined_code = (type_code << 4) | field_code return _uint8_to_bytes(combined_code) if type_code >= 16 and field_code < 16: # first 4 bits are zeroes # next 4 bits is field code # next byte is type code byte1 = _uint8_to_bytes(field_code) byte2 = _uint8_to_bytes(type_code) return byte1 + byte2 if type_code < 16 and field_code >= 16: # first 4 bits is type code # next 4 bits are zeroes # next byte is field code byte1 = _uint8_to_bytes(type_code << 4) byte2 = _uint8_to_bytes(field_code) return byte1 + byte2 else: # both are >= 16 # first byte is all zeroes # second byte is type code # third byte is field code byte2 = _uint8_to_bytes(type_code) byte3 = _uint8_to_bytes(field_code) return bytes(1) + byte2 + byte3
def read_field_value(self: BinaryParser, field: FieldInstance) -> SerializedType: """ Read value of the type specified by field from the BinaryParser. Args: field: The FieldInstance specifying the field to read. Returns: A SerializedType read from the BinaryParser. Raises: XRPLBinaryCodecException: If a parser cannot be constructed from field. """ field_type = field.associated_type if field.is_variable_length_encoded: size_hint = self._read_length_prefix() value = field_type.from_parser(self, size_hint) else: value = field_type.from_parser(self, None) if value is None: raise XRPLBinaryCodecException( f"from_parser for {field.name}, {field.type} returned None." ) return value
def from_value(cls: Type[Path], value: List[Dict[str, str]]) -> Path: """ Construct a Path from an array of dictionaries describing PathSteps. Args: value: The array to construct a Path object from. Returns: The Path constructed from value. Raises: XRPLBinaryCodecException: If the supplied value is of the wrong type. """ if not isinstance(value, list): raise XRPLBinaryCodecException( "Invalid type to construct a Path: expected list, " f"received {value.__class__.__name__}." ) buffer: bytes = b"" for PathStep_dict in value: pathstep = PathStep.from_value(PathStep_dict) buffer += bytes(pathstep) return Path(buffer)
def _iso_to_bytes(iso: str) -> bytes: """ Convert an ISO code to a 160-bit (20 byte) encoded representation. See "Currency codes" subheading in `Amount Fields <https://xrpl.org/serialization.html#amount-fields>`_ """ if not _is_iso_code(iso): raise XRPLBinaryCodecException(f"Invalid ISO code: {iso}") if iso == "XRP": # This code (160 bit all zeroes) is used to indicate XRP in # rare cases where a field must specify a currency code for XRP. return bytes(_CURRENCY_CODE_LENGTH) iso_bytes = iso.encode("ASCII") # Currency Codes: https://xrpl.org/currency-formats.html#standard-currency-codes # 160 total bits: # 8 bits type code (0x00) # 88 bits reserved (0's) # 24 bits ASCII # 16 bits version (0x00) # 24 bits reserved (0's) return bytes(12) + iso_bytes + bytes(5)
def _handle_xaddress(field: str, xaddress: str) -> Dict[str, Union[str, int]]: """Break down an X-Address into a classic address and a tag. Args: field: Name of field xaddress: X-Address corresponding to the field Returns: A dictionary representing the classic address and tag. Raises: XRPLBinaryCodecException: field-tag combo is invalid. """ (classic_address, tag, is_test_network) = xaddress_to_classic_address(xaddress) if field == _DESTINATION: tag_name = _DEST_TAG elif field == _ACCOUNT: tag_name = _SOURCE_TAG elif tag is not None: raise XRPLBinaryCodecException(f"{field} cannot have an associated tag") if tag is not None: return {field: classic_address, tag_name: tag} return {field: classic_address}
for field in _DEFINITIONS["FIELDS"]: field_entry = _DEFINITIONS["FIELDS"][field] field_info = FieldInfo( field_entry["nth"], field_entry["isVLEncoded"], field_entry["isSerialized"], field_entry["isSigningField"], field_entry["type"], ) header = FieldHeader(_TYPE_ORDINAL_MAP[field_entry["type"]], field_entry["nth"]) _FIELD_INFO_MAP[field] = field_info _FIELD_HEADER_NAME_MAP[header] = field except KeyError as e: raise XRPLBinaryCodecException( f"Malformed definitions.json file. (Original exception: KeyError: {e})" ) def get_field_type_name(field_name: str) -> str: """ Returns the serialization data type for the given field name. `Serialization Type List <https://xrpl.org/serialization.html#type-list>`_ Args: field_name: The name of the field to get the serialization data type for. Returns: The serialization data type for the given field name. """ return _FIELD_INFO_MAP[field_name].type
def from_value( cls: Type[SerializedDict], value: Dict[str, Any], only_signing: bool = False ) -> SerializedDict: """ Create a SerializedDict object from a dictionary. Args: value: The dictionary to construct a SerializedDict from. only_signing: whether only the signing fields should be included. Returns: The SerializedDict object constructed from value. Raises: XRPLBinaryCodecException: If the SerializedDict can't be constructed from value. """ from xrpl.core.binarycodec.binary_wrappers.binary_serializer import ( BinarySerializer, ) serializer = BinarySerializer() xaddress_decoded = {} for (k, v) in value.items(): if isinstance(v, str) and is_valid_xaddress(v): handled = _handle_xaddress(k, v) if ( _SOURCE_TAG in handled and handled[_SOURCE_TAG] is not None and _SOURCE_TAG in value and value[_SOURCE_TAG] is not None ): raise XRPLBinaryCodecException( "Cannot have Account X-Address and SourceTag" ) if ( _DEST_TAG in handled and handled[_DEST_TAG] is not None and _DEST_TAG in value and value[_DEST_TAG] is not None ): raise XRPLBinaryCodecException( "Cannot have Destination X-Address and DestinationTag" ) xaddress_decoded.update(handled) else: xaddress_decoded[k] = _str_to_enum(k, v) sorted_keys: List[FieldInstance] = [] for field_name in xaddress_decoded: field_instance = get_field_instance(field_name) if ( field_instance is not None and xaddress_decoded[field_instance.name] is not None and field_instance.is_serialized ): sorted_keys.append(field_instance) sorted_keys.sort(key=lambda x: x.ordinal) if only_signing: sorted_keys = list(filter(lambda x: x.is_signing, sorted_keys)) for field in sorted_keys: try: associated_value = field.associated_type.from_value( xaddress_decoded[field.name] ) except XRPLBinaryCodecException as e: # mildly hacky way to get more context in the error # provides the field name and not just the type it's expecting # keeps the original stack trace e.args = (f"Error processing {field.name}: {e.args[0]}",) + e.args[1:] raise serializer.write_field_and_value(field, associated_value) if field.type == _SERIALIZED_DICT: serializer.append(_OBJECT_END_MARKER_BYTE) return SerializedDict(bytes(serializer))