From 0339e7adaa9f6f677840f9a5eedf5a7bac73a767 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 16 Feb 2023 21:56:36 -0500 Subject: [PATCH 01/14] update definitions file --- xrpl/core/binarycodec/definitions/definitions.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/xrpl/core/binarycodec/definitions/definitions.json b/xrpl/core/binarycodec/definitions/definitions.json index 317e1feb7..a59de992e 100644 --- a/xrpl/core/binarycodec/definitions/definitions.json +++ b/xrpl/core/binarycodec/definitions/definitions.json @@ -321,6 +321,16 @@ "type": "UInt16" } ], + [ + "NetworkID", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], [ "Flags", { @@ -2176,6 +2186,9 @@ "telCAN_NOT_QUEUE_BLOCKED": -389, "telCAN_NOT_QUEUE_FEE": -388, "telCAN_NOT_QUEUE_FULL": -387, + "telWRONG_NETWORK": -386, + "telREQUIRES_NETWORK_ID": -385, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, "temMALFORMED": -299, "temBAD_AMOUNT": -298, From 26c25075acf5b3c190a61a70bd124a5d9d8da998 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 16 Feb 2023 21:56:53 -0500 Subject: [PATCH 02/14] add network id to base transaction --- xrpl/models/transactions/transaction.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index 29fb557b6..dc0b55169 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -255,6 +255,13 @@ class Transaction(BaseModel): transaction. Automatically added during signing. """ + network_id: int = REQUIRED # type: ignore + """ + The network id of the transaction. Required. + + :meta hide-value: + """ + def _get_errors(self: Transaction) -> Dict[str, str]: errors = super()._get_errors() if self.ticket_sequence is not None and ( From 4041d7c7db45ea333c5ec25f0c04c90654f839d4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 23 Feb 2023 05:35:17 -0500 Subject: [PATCH 03/14] make network id optional --- xrpl/models/transactions/transaction.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index dc0b55169..2419c63ce 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -255,12 +255,8 @@ class Transaction(BaseModel): transaction. Automatically added during signing. """ - network_id: int = REQUIRED # type: ignore - """ - The network id of the transaction. Required. - - :meta hide-value: - """ + network_id: Optional[int] = None + """The network id of the transaction.""" def _get_errors(self: Transaction) -> Dict[str, str]: errors = super()._get_errors() From 776ccb98e88712f0ec6e16610073302254bd8fa3 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 24 Feb 2023 16:21:02 -0500 Subject: [PATCH 04/14] add get_network_id func --- xrpl/asyncio/ledger/__init__.py | 2 ++ xrpl/asyncio/ledger/main.py | 22 +++++++++++++++++++++- xrpl/ledger/__init__.py | 2 ++ xrpl/ledger/main.py | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/xrpl/asyncio/ledger/__init__.py b/xrpl/asyncio/ledger/__init__.py index 2d2ec70ac..c731cc9a1 100644 --- a/xrpl/asyncio/ledger/__init__.py +++ b/xrpl/asyncio/ledger/__init__.py @@ -3,10 +3,12 @@ get_fee, get_latest_open_ledger_sequence, get_latest_validated_ledger_sequence, + get_network_id, ) __all__ = [ "get_latest_validated_ledger_sequence", "get_fee", "get_latest_open_ledger_sequence", + "get_network_id", ] diff --git a/xrpl/asyncio/ledger/main.py b/xrpl/asyncio/ledger/main.py index f83a591c9..442631a12 100644 --- a/xrpl/asyncio/ledger/main.py +++ b/xrpl/asyncio/ledger/main.py @@ -5,7 +5,7 @@ from xrpl.asyncio.clients import Client, XRPLRequestFailureException from xrpl.asyncio.ledger.utils import calculate_fee_dynamically from xrpl.constants import XRPLException -from xrpl.models.requests import Fee, Ledger +from xrpl.models.requests import Fee, Ledger, ServerInfo from xrpl.utils import xrp_to_drops @@ -96,3 +96,23 @@ async def get_fee( if max_fee_drops < int(fee): fee = str(max_fee_drops) return fee + + +async def get_network_id(client: Client) -> int: + """ + Returns the network id of the connected server. + + Args: + client: The network client to use to send the request. + + Returns: + The network id of the connected server + + Raises: + XRPLRequestFailureException: if the rippled API call fails. + """ + response = await client._request_impl(ServerInfo()) + if response.is_successful(): + return cast(int, response.result["info"]["network_id"] or 1) + + raise XRPLRequestFailureException(response.result) diff --git a/xrpl/ledger/__init__.py b/xrpl/ledger/__init__.py index 756e1797d..1e53f35c8 100644 --- a/xrpl/ledger/__init__.py +++ b/xrpl/ledger/__init__.py @@ -3,10 +3,12 @@ get_fee, get_latest_open_ledger_sequence, get_latest_validated_ledger_sequence, + get_network_id, ) __all__ = [ "get_latest_validated_ledger_sequence", "get_fee", "get_latest_open_ledger_sequence", + "get_network_id", ] diff --git a/xrpl/ledger/main.py b/xrpl/ledger/main.py index 37a6b9673..0d3d45331 100644 --- a/xrpl/ledger/main.py +++ b/xrpl/ledger/main.py @@ -68,3 +68,18 @@ def get_fee( XRPLRequestFailureException: if the rippled API call fails. """ return asyncio.run(main.get_fee(client, max_fee=max_fee, fee_type=fee_type)) + + +def get_network_id( + client: SyncClient, +) -> int: + """ + Query the ledger for the current network id. + + Args: + client: the network client used to make network calls. + + Returns: + The network id as an integer + """ + return asyncio.run(main.get_network_id(client)) From e373ddba8fc992e268e59d9ee711340bedf789a9 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 24 Feb 2023 16:21:19 -0500 Subject: [PATCH 05/14] add network id to client --- xrpl/asyncio/clients/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xrpl/asyncio/clients/client.py b/xrpl/asyncio/clients/client.py index 54819ae4f..df4b7e36a 100644 --- a/xrpl/asyncio/clients/client.py +++ b/xrpl/asyncio/clients/client.py @@ -22,6 +22,7 @@ def __init__(self: Client, url: str) -> None: url: The url to which this client will connect """ self.url = url + self.network_id: int = 1 @abstractmethod async def _request_impl(self: Client, request: Request) -> Response: From 34ab46332aac232bc3ace6dc1d1f96004dfeb4a4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 24 Feb 2023 16:21:55 -0500 Subject: [PATCH 06/14] add network id to autofill --- xrpl/asyncio/transaction/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 803e887bf..782cd02e4 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -228,6 +228,8 @@ async def autofill( The autofilled transaction. """ transaction_json = transaction.to_dict() + if client.network_id > 1024 and not transaction_json["network_id"]: + transaction_json["network_id"] = client.network_id if "sequence" not in transaction_json: sequence = await get_next_valid_seq_number(transaction_json["account"], client) transaction_json["sequence"] = sequence From e6f5aa5504a6bc10b939dfe180c77263d22e3826 Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Tue, 6 Jun 2023 18:12:50 -0400 Subject: [PATCH 07/14] implement networkID inside autofill --- xrpl/asyncio/clients/client.py | 3 +- xrpl/asyncio/ledger/__init__.py | 2 - xrpl/asyncio/ledger/main.py | 20 ------ xrpl/asyncio/transaction/main.py | 102 +++++++++++++++++++++++++++++-- xrpl/ledger/__init__.py | 2 - xrpl/ledger/main.py | 15 ----- 6 files changed, 100 insertions(+), 44 deletions(-) diff --git a/xrpl/asyncio/clients/client.py b/xrpl/asyncio/clients/client.py index df4b7e36a..6f71ccebb 100644 --- a/xrpl/asyncio/clients/client.py +++ b/xrpl/asyncio/clients/client.py @@ -22,7 +22,8 @@ def __init__(self: Client, url: str) -> None: url: The url to which this client will connect """ self.url = url - self.network_id: int = 1 + self.network_id: int = None + self.build_version: str = None @abstractmethod async def _request_impl(self: Client, request: Request) -> Response: diff --git a/xrpl/asyncio/ledger/__init__.py b/xrpl/asyncio/ledger/__init__.py index c731cc9a1..2d2ec70ac 100644 --- a/xrpl/asyncio/ledger/__init__.py +++ b/xrpl/asyncio/ledger/__init__.py @@ -3,12 +3,10 @@ get_fee, get_latest_open_ledger_sequence, get_latest_validated_ledger_sequence, - get_network_id, ) __all__ = [ "get_latest_validated_ledger_sequence", "get_fee", "get_latest_open_ledger_sequence", - "get_network_id", ] diff --git a/xrpl/asyncio/ledger/main.py b/xrpl/asyncio/ledger/main.py index 442631a12..1a36f2110 100644 --- a/xrpl/asyncio/ledger/main.py +++ b/xrpl/asyncio/ledger/main.py @@ -96,23 +96,3 @@ async def get_fee( if max_fee_drops < int(fee): fee = str(max_fee_drops) return fee - - -async def get_network_id(client: Client) -> int: - """ - Returns the network id of the connected server. - - Args: - client: The network client to use to send the request. - - Returns: - The network id of the connected server - - Raises: - XRPLRequestFailureException: if the rippled API call fails. - """ - response = await client._request_impl(ServerInfo()) - if response.is_successful(): - return cast(int, response.result["info"]["network_id"] or 1) - - raise XRPLRequestFailureException(response.result) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 782cd02e4..96db1b15e 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -11,7 +11,7 @@ from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address from xrpl.core.binarycodec import encode, encode_for_multisigning, encode_for_signing from xrpl.core.keypairs.main import sign as keypairs_sign -from xrpl.models.requests import ServerState, SubmitOnly +from xrpl.models.requests import ServerInfo, ServerState, SubmitOnly from xrpl.models.response import Response from xrpl.models.transactions import EscrowFinish from xrpl.models.transactions.transaction import Signer, Transaction @@ -23,7 +23,11 @@ from xrpl.wallet.main import Wallet _LEDGER_OFFSET: Final[int] = 20 - +# Sidechains are expected to have network IDs above this. +# Mainnet and testnet are exceptions. More context: https://github.com/XRPLF/rippled/pull/4370 +_RESTRICTED_NETWORKS = 1024 +_REQUIRED_NETWORKID_VERSION = '1.11.0' +_HOOKS_TESTNET_ID = 21338 # TODO: make this dynamic based on the current ledger fee _ACCOUNT_DELETE_FEE: Final[int] = int(xrp_to_drops(2)) @@ -228,8 +232,10 @@ async def autofill( The autofilled transaction. """ transaction_json = transaction.to_dict() - if client.network_id > 1024 and not transaction_json["network_id"]: - transaction_json["network_id"] = client.network_id + if not client.network_id: + await _get_network_id_and_build_version(client) + if "network_id" not in transaction_json: + transaction_json["network_id"] = client.network_id if _tx_needs_networkID(client) else None if "sequence" not in transaction_json: sequence = await get_next_valid_seq_number(transaction_json["account"], client) transaction_json["sequence"] = sequence @@ -243,6 +249,94 @@ async def autofill( return Transaction.from_dict(transaction_json) +async def _get_network_id_and_build_version(client: Client) -> None: + """ + Get the network id and build version of the connected server. + + Args: + client: The network client to use to send the request. + + Raises: + XRPLRequestFailureException: if the rippled API call fails. + """ + response = await client._request_impl(ServerInfo()) + if response.is_successful(): + client.network_id = response.result["info"]["network_id"] or None + client.build_version = response.result["info"]["build_version"] or None + return + + raise XRPLRequestFailureException(response.result) + + +def _tx_needs_networkID(client: Client) -> bool: + """ + Determines whether the transactions required network ID to be valid. + More context: https://github.com/XRPLF/rippled/pull/4370 + + Args: + client (Client): The network client to use to send the request. + + Returns: + bool: whether the transactions required network ID to be valid + """ + if client.network_id and client.network_id > _RESTRICTED_NETWORKS: + # transaction needs networkID if either the network is hooks testnet or build version is >= 1.11.0 + # TODO: remove the buildVersion logic when 1.11.0 is out and widely used. + if ((client.build_version and _is_earlier_rippled_version(_REQUIRED_NETWORKID_VERSION, client.build_version)) + or client.network_id == _HOOKS_TESTNET_ID): + return True + return False + + +def _is_earlier_rippled_version(source: str, target: str) -> bool: + """ + Determines whether the source version is an earlier release than the target version. + + Args: + source: the source rippled version. + target: the target rippled version. + + Returns: + bool: true if source is earlier, false otherwise. + """ + if (source == target): + return False + source_decomp = source.split('.') + target_decomp = target.split('.') + source_major, source_minor = int(source_decomp[0]), int(source_decomp[1]) + target_major, target_minor = int(target_decomp[0]), int(target_decomp[1]) + + # Compare major version + if source_major != target_major: + return source_major < target_major + + # Compare minor version + if source_minor != target_minor: + return source_minor < target_minor + + source_patch, target_patch = source_decomp[2].split('-'), target_decomp[2].split('-') + source_patch_version, target_patch_version = int(source_patch[0]), int(target_patch[0]) + + # Compare patch version + if source_patch_version != target_patch_version: + return source_patch_version < target_patch_version + + # Compare release version + if len(source_patch) != len(target_patch): + return len(source_patch) > len(target_patch) + + if len(source_patch) == 2: + # Compare release types + if not source_patch[1][0].startswith(target_patch[1][0]): + return source_patch[1] < target_patch[1] + # Compare beta versions + if source_patch[1].startswith('b'): + return int(source_patch[1][1:]) < int(target_patch[1][1:]) + # Compare rc versions + return int(source_patch[1][2:]) < int(target_patch[1][2:]) + return False + + def _validate_account_xaddress( json: Dict[str, Any], account_field: str, tag_field: str ) -> None: diff --git a/xrpl/ledger/__init__.py b/xrpl/ledger/__init__.py index 1e53f35c8..756e1797d 100644 --- a/xrpl/ledger/__init__.py +++ b/xrpl/ledger/__init__.py @@ -3,12 +3,10 @@ get_fee, get_latest_open_ledger_sequence, get_latest_validated_ledger_sequence, - get_network_id, ) __all__ = [ "get_latest_validated_ledger_sequence", "get_fee", "get_latest_open_ledger_sequence", - "get_network_id", ] diff --git a/xrpl/ledger/main.py b/xrpl/ledger/main.py index 0d3d45331..37a6b9673 100644 --- a/xrpl/ledger/main.py +++ b/xrpl/ledger/main.py @@ -68,18 +68,3 @@ def get_fee( XRPLRequestFailureException: if the rippled API call fails. """ return asyncio.run(main.get_fee(client, max_fee=max_fee, fee_type=fee_type)) - - -def get_network_id( - client: SyncClient, -) -> int: - """ - Query the ledger for the current network id. - - Args: - client: the network client used to make network calls. - - Returns: - The network id as an integer - """ - return asyncio.run(main.get_network_id(client)) From 8c90e06dbd75166f75d83e0500e7073ccd47473b Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Tue, 6 Jun 2023 18:14:56 -0400 Subject: [PATCH 08/14] clean up unused import --- xrpl/asyncio/ledger/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xrpl/asyncio/ledger/main.py b/xrpl/asyncio/ledger/main.py index 1a36f2110..f83a591c9 100644 --- a/xrpl/asyncio/ledger/main.py +++ b/xrpl/asyncio/ledger/main.py @@ -5,7 +5,7 @@ from xrpl.asyncio.clients import Client, XRPLRequestFailureException from xrpl.asyncio.ledger.utils import calculate_fee_dynamically from xrpl.constants import XRPLException -from xrpl.models.requests import Fee, Ledger, ServerInfo +from xrpl.models.requests import Fee, Ledger from xrpl.utils import xrp_to_drops From 7ee830958ea8d2f5cac35e9c3125709bf2df6ca6 Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Tue, 6 Jun 2023 18:25:37 -0400 Subject: [PATCH 09/14] fix integration tests --- xrpl/asyncio/transaction/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 96db1b15e..c098af4c7 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -261,8 +261,8 @@ async def _get_network_id_and_build_version(client: Client) -> None: """ response = await client._request_impl(ServerInfo()) if response.is_successful(): - client.network_id = response.result["info"]["network_id"] or None - client.build_version = response.result["info"]["build_version"] or None + client.network_id = response.result["info"]["network_id"] if "network_id" in response.result["info"] else None + client.build_version = response.result["info"]["build_version"] if "build_version" in response.result["info"] else None return raise XRPLRequestFailureException(response.result) From a280cb04f4d4f8f2f86dcbfcbf0c4e0c893197f0 Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Wed, 7 Jun 2023 17:09:16 -0400 Subject: [PATCH 10/14] fix linting --- xrpl/asyncio/transaction/main.py | 54 +++++++++++++++++++------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index c098af4c7..32451f101 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -24,9 +24,10 @@ _LEDGER_OFFSET: Final[int] = 20 # Sidechains are expected to have network IDs above this. -# Mainnet and testnet are exceptions. More context: https://github.com/XRPLF/rippled/pull/4370 +# Mainnet and testnet are exceptions. +# More context: https://github.com/XRPLF/rippled/pull/4370 _RESTRICTED_NETWORKS = 1024 -_REQUIRED_NETWORKID_VERSION = '1.11.0' +_REQUIRED_NETWORKID_VERSION = "1.11.0" _HOOKS_TESTNET_ID = 21338 # TODO: make this dynamic based on the current ledger fee _ACCOUNT_DELETE_FEE: Final[int] = int(xrp_to_drops(2)) @@ -233,9 +234,9 @@ async def autofill( """ transaction_json = transaction.to_dict() if not client.network_id: - await _get_network_id_and_build_version(client) - if "network_id" not in transaction_json: - transaction_json["network_id"] = client.network_id if _tx_needs_networkID(client) else None + await _get_network_id_and_build_version(client) + if "network_id" not in transaction_json and _tx_needs_networkID(client): + transaction_json["network_id"] = client.network_id if "sequence" not in transaction_json: sequence = await get_next_valid_seq_number(transaction_json["account"], client) transaction_json["sequence"] = sequence @@ -261,8 +262,10 @@ async def _get_network_id_and_build_version(client: Client) -> None: """ response = await client._request_impl(ServerInfo()) if response.is_successful(): - client.network_id = response.result["info"]["network_id"] if "network_id" in response.result["info"] else None - client.build_version = response.result["info"]["build_version"] if "build_version" in response.result["info"] else None + if "network_id" in response.result["info"]: + client.network_id = response.result["info"]["network_id"] + if "build_version" in response.result["info"]: + client.build_version = response.result["info"]["build_version"] return raise XRPLRequestFailureException(response.result) @@ -280,10 +283,15 @@ def _tx_needs_networkID(client: Client) -> bool: bool: whether the transactions required network ID to be valid """ if client.network_id and client.network_id > _RESTRICTED_NETWORKS: - # transaction needs networkID if either the network is hooks testnet or build version is >= 1.11.0 + # transaction needs networkID if either the network is hooks testnet + # or build version is >= 1.11.0. # TODO: remove the buildVersion logic when 1.11.0 is out and widely used. - if ((client.build_version and _is_earlier_rippled_version(_REQUIRED_NETWORKID_VERSION, client.build_version)) - or client.network_id == _HOOKS_TESTNET_ID): + if ( + client.build_version + and _is_earlier_rippled_version( + _REQUIRED_NETWORKID_VERSION, client.build_version + ) + ) or client.network_id == _HOOKS_TESTNET_ID: return True return False @@ -295,42 +303,44 @@ def _is_earlier_rippled_version(source: str, target: str) -> bool: Args: source: the source rippled version. target: the target rippled version. - + Returns: bool: true if source is earlier, false otherwise. """ - if (source == target): + if source == target: return False - source_decomp = source.split('.') - target_decomp = target.split('.') + source_decomp = source.split(".") + target_decomp = target.split(".") source_major, source_minor = int(source_decomp[0]), int(source_decomp[1]) target_major, target_minor = int(target_decomp[0]), int(target_decomp[1]) - + # Compare major version if source_major != target_major: return source_major < target_major - + # Compare minor version if source_minor != target_minor: return source_minor < target_minor - source_patch, target_patch = source_decomp[2].split('-'), target_decomp[2].split('-') - source_patch_version, target_patch_version = int(source_patch[0]), int(target_patch[0]) - + source_patch = source_decomp[2].split("-") + target_patch = target_decomp[2].split("-") + source_patch_version = int(source_patch[0]) + target_patch_version = int(target_patch[0]) + # Compare patch version if source_patch_version != target_patch_version: return source_patch_version < target_patch_version - + # Compare release version if len(source_patch) != len(target_patch): return len(source_patch) > len(target_patch) - + if len(source_patch) == 2: # Compare release types if not source_patch[1][0].startswith(target_patch[1][0]): return source_patch[1] < target_patch[1] # Compare beta versions - if source_patch[1].startswith('b'): + if source_patch[1].startswith("b"): return int(source_patch[1][1:]) < int(target_patch[1][1:]) # Compare rc versions return int(source_patch[1][2:]) < int(target_patch[1][2:]) From dfb555bb3f673606a1d5b133728215a23a85133d Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Wed, 7 Jun 2023 17:15:53 -0400 Subject: [PATCH 11/14] fix type error --- xrpl/asyncio/clients/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xrpl/asyncio/clients/client.py b/xrpl/asyncio/clients/client.py index 6f71ccebb..15acadd2c 100644 --- a/xrpl/asyncio/clients/client.py +++ b/xrpl/asyncio/clients/client.py @@ -2,6 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import Optional from xrpl.models.requests.request import Request from xrpl.models.response import Response @@ -22,8 +23,8 @@ def __init__(self: Client, url: str) -> None: url: The url to which this client will connect """ self.url = url - self.network_id: int = None - self.build_version: str = None + self.network_id: Optional[int] = None + self.build_version: Optional[str] = None @abstractmethod async def _request_impl(self: Client, request: Request) -> Response: From 2b53407e8b3127de6b0f1861af43e85fb8878afe Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Mon, 12 Jun 2023 18:42:36 -0400 Subject: [PATCH 12/14] update tests --- .../unit/models/transactions/test_autofill.py | 66 +++++++++++++++++++ xrpl/asyncio/transaction/main.py | 10 +-- 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 tests/unit/models/transactions/test_autofill.py diff --git a/tests/unit/models/transactions/test_autofill.py b/tests/unit/models/transactions/test_autofill.py new file mode 100644 index 000000000..61b7ae530 --- /dev/null +++ b/tests/unit/models/transactions/test_autofill.py @@ -0,0 +1,66 @@ +from unittest import TestCase +from xrpl.asyncio.transaction.main import _RESTRICTED_NETWORKS +from xrpl.clients import JsonRpcClient, WebsocketClient + +from xrpl.models.transactions import AccountSet +from xrpl.transaction import autofill +from xrpl.wallet.wallet_generation import generate_faucet_wallet + +_FEE = "0.00001" + +class TestAutofill(TestCase): + # Autofill should override tx networkID for network with ID > 1024 and build_version from 1.11.0 or later. + def test_networkid_override(self): + client = JsonRpcClient('https://sidechain-net1.devnet.rippletest.net:51234') + wallet = generate_faucet_wallet(client, debug=True) + # Override client's build_version since 1.11.0 is not released yet. + client.build_version = '1.11.0' + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain='www.example.com', + ) + tx_autofilled = autofill(tx, client) + self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, client.network_id) + + # Autofill should ignore tx network_id for build version earlier than 1.11.0. + def test_networkid_ignore_early_version(self): + client = JsonRpcClient('https://sidechain-net1.devnet.rippletest.net:51234') + wallet = generate_faucet_wallet(client, debug=True) + # Override client's build_version since 1.11.0 is not released yet. + client.build_version = '1.10.0' + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain='www.example.com', + ) + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, None) + + # Autofill should ignore tx network_id for networks with ID <= 1024. + def test_networkid_ignore_restricted_networks(self): + client = JsonRpcClient('https://s.altnet.rippletest.net:51234') + wallet = generate_faucet_wallet(client, debug=True) + # Override client's build_version since 1.11.0 is not released yet. + client.build_version = '1.11.0' + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain='www.example.com', + ) + tx_autofilled = autofill(tx, client) + self.assertLessEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, None) + + # Autofill should override tx networkID for hooks-testnet. + def test_networkid_override(self): + with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client: + wallet = generate_faucet_wallet(client, debug=True) + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain='www.example.com', + ) + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, client.network_id) \ No newline at end of file diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 9a7d011f7..eef28793f 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -271,7 +271,7 @@ async def _get_network_id_and_build_version(client: Client) -> None: if response.is_successful(): if "network_id" in response.result["info"]: client.network_id = response.result["info"]["network_id"] - if "build_version" in response.result["info"]: + if not client.build_version and "build_version" in response.result["info"]: client.build_version = response.result["info"]["build_version"] return @@ -295,7 +295,7 @@ def _tx_needs_networkID(client: Client) -> bool: # TODO: remove the buildVersion logic when 1.11.0 is out and widely used. if ( client.build_version - and _is_earlier_rippled_version( + and _is_not_later_rippled_version( _REQUIRED_NETWORKID_VERSION, client.build_version ) ) or client.network_id == _HOOKS_TESTNET_ID: @@ -303,9 +303,9 @@ def _tx_needs_networkID(client: Client) -> bool: return False -def _is_earlier_rippled_version(source: str, target: str) -> bool: +def _is_not_later_rippled_version(source: str, target: str) -> bool: """ - Determines whether the source version is an earlier release than the target version. + Determines whether the source version is not a later release than the target version. Args: source: the source rippled version. @@ -315,7 +315,7 @@ def _is_earlier_rippled_version(source: str, target: str) -> bool: bool: true if source is earlier, false otherwise. """ if source == target: - return False + return True source_decomp = source.split(".") target_decomp = target.split(".") source_major, source_minor = int(source_decomp[0]), int(source_decomp[1]) From 4bd3cfe1219aa9db4b1a3c9bf5101e53bf879602 Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Mon, 12 Jun 2023 18:56:43 -0400 Subject: [PATCH 13/14] update docs --- xrpl/asyncio/transaction/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index eef28793f..6f926fd1f 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -24,6 +24,8 @@ _LEDGER_OFFSET: Final[int] = 20 # Sidechains are expected to have network IDs above this. +# Networks with ID above this restricted number are expected specify an accurate NetworkID field +# in every transaction to that chain to prevent replay attacks. # Mainnet and testnet are exceptions. # More context: https://github.com/XRPLF/rippled/pull/4370 _RESTRICTED_NETWORKS = 1024 @@ -281,6 +283,8 @@ async def _get_network_id_and_build_version(client: Client) -> None: def _tx_needs_networkID(client: Client) -> bool: """ Determines whether the transactions required network ID to be valid. + Transaction needs networkID if later than restricted ID and either the network is hooks testnet + or build version is >= 1.11.0. More context: https://github.com/XRPLF/rippled/pull/4370 Args: @@ -290,9 +294,8 @@ def _tx_needs_networkID(client: Client) -> bool: bool: whether the transactions required network ID to be valid """ if client.network_id and client.network_id > _RESTRICTED_NETWORKS: - # transaction needs networkID if either the network is hooks testnet - # or build version is >= 1.11.0. # TODO: remove the buildVersion logic when 1.11.0 is out and widely used. + # Issue: https://github.com/XRPLF/xrpl-py/issues/595 if ( client.build_version and _is_not_later_rippled_version( From a3b0f649bbd2f97e4a0cdf84574f9ffb45533238 Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Mon, 12 Jun 2023 20:16:33 -0400 Subject: [PATCH 14/14] fix lint --- .../unit/models/transactions/test_autofill.py | 94 ++++++++++--------- xrpl/asyncio/transaction/main.py | 11 ++- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/tests/unit/models/transactions/test_autofill.py b/tests/unit/models/transactions/test_autofill.py index 61b7ae530..491194808 100644 --- a/tests/unit/models/transactions/test_autofill.py +++ b/tests/unit/models/transactions/test_autofill.py @@ -1,66 +1,68 @@ from unittest import TestCase + from xrpl.asyncio.transaction.main import _RESTRICTED_NETWORKS from xrpl.clients import JsonRpcClient, WebsocketClient - from xrpl.models.transactions import AccountSet from xrpl.transaction import autofill from xrpl.wallet.wallet_generation import generate_faucet_wallet _FEE = "0.00001" + class TestAutofill(TestCase): - # Autofill should override tx networkID for network with ID > 1024 and build_version from 1.11.0 or later. - def test_networkid_override(self): - client = JsonRpcClient('https://sidechain-net1.devnet.rippletest.net:51234') - wallet = generate_faucet_wallet(client, debug=True) - # Override client's build_version since 1.11.0 is not released yet. - client.build_version = '1.11.0' - tx = AccountSet( + # Autofill should override tx networkID for network with ID > 1024 + # and build_version from 1.11.0 or later. + def test_networkid_override(self): + client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.11.0" + tx = AccountSet( account=wallet.classic_address, fee=_FEE, - domain='www.example.com', + domain="www.example.com", ) - tx_autofilled = autofill(tx, client) - self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS) - self.assertEqual(tx_autofilled.network_id, client.network_id) - - # Autofill should ignore tx network_id for build version earlier than 1.11.0. - def test_networkid_ignore_early_version(self): - client = JsonRpcClient('https://sidechain-net1.devnet.rippletest.net:51234') - wallet = generate_faucet_wallet(client, debug=True) - # Override client's build_version since 1.11.0 is not released yet. - client.build_version = '1.10.0' - tx = AccountSet( + tx_autofilled = autofill(tx, client) + self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, client.network_id) + + # Autofill should ignore tx network_id for build version earlier than 1.11.0. + def test_networkid_ignore_early_version(self): + client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.10.0" + tx = AccountSet( account=wallet.classic_address, fee=_FEE, - domain='www.example.com', + domain="www.example.com", ) - tx_autofilled = autofill(tx, client) - self.assertEqual(tx_autofilled.network_id, None) - - # Autofill should ignore tx network_id for networks with ID <= 1024. - def test_networkid_ignore_restricted_networks(self): - client = JsonRpcClient('https://s.altnet.rippletest.net:51234') - wallet = generate_faucet_wallet(client, debug=True) - # Override client's build_version since 1.11.0 is not released yet. - client.build_version = '1.11.0' - tx = AccountSet( + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, None) + + # Autofill should ignore tx network_id for networks with ID <= 1024. + def test_networkid_ignore_restricted_networks(self): + client = JsonRpcClient("https://s.altnet.rippletest.net:51234") + wallet = generate_faucet_wallet(client, debug=True) + # Override client build_version since 1.11.0 is not released yet. + client.build_version = "1.11.0" + tx = AccountSet( account=wallet.classic_address, fee=_FEE, - domain='www.example.com', + domain="www.example.com", ) - tx_autofilled = autofill(tx, client) - self.assertLessEqual(client.network_id, _RESTRICTED_NETWORKS) - self.assertEqual(tx_autofilled.network_id, None) + tx_autofilled = autofill(tx, client) + self.assertLessEqual(client.network_id, _RESTRICTED_NETWORKS) + self.assertEqual(tx_autofilled.network_id, None) - # Autofill should override tx networkID for hooks-testnet. - def test_networkid_override(self): - with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client: - wallet = generate_faucet_wallet(client, debug=True) - tx = AccountSet( - account=wallet.classic_address, - fee=_FEE, - domain='www.example.com', - ) - tx_autofilled = autofill(tx, client) - self.assertEqual(tx_autofilled.network_id, client.network_id) \ No newline at end of file + # Autofill should override tx networkID for hooks-testnet. + def test_networkid_override_hooks_testnet(self): + with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client: + wallet = generate_faucet_wallet(client, debug=True) + tx = AccountSet( + account=wallet.classic_address, + fee=_FEE, + domain="www.example.com", + ) + tx_autofilled = autofill(tx, client) + self.assertEqual(tx_autofilled.network_id, client.network_id) diff --git a/xrpl/asyncio/transaction/main.py b/xrpl/asyncio/transaction/main.py index 6f926fd1f..821ddedab 100644 --- a/xrpl/asyncio/transaction/main.py +++ b/xrpl/asyncio/transaction/main.py @@ -24,8 +24,8 @@ _LEDGER_OFFSET: Final[int] = 20 # Sidechains are expected to have network IDs above this. -# Networks with ID above this restricted number are expected specify an accurate NetworkID field -# in every transaction to that chain to prevent replay attacks. +# Networks with ID above this restricted number are expected to specify an +# accurate NetworkID field in every transaction to that chain to prevent replay attacks. # Mainnet and testnet are exceptions. # More context: https://github.com/XRPLF/rippled/pull/4370 _RESTRICTED_NETWORKS = 1024 @@ -283,8 +283,8 @@ async def _get_network_id_and_build_version(client: Client) -> None: def _tx_needs_networkID(client: Client) -> bool: """ Determines whether the transactions required network ID to be valid. - Transaction needs networkID if later than restricted ID and either the network is hooks testnet - or build version is >= 1.11.0. + Transaction needs networkID if later than restricted ID and either + the network is hooks testnet or build version is >= 1.11.0. More context: https://github.com/XRPLF/rippled/pull/4370 Args: @@ -308,7 +308,8 @@ def _tx_needs_networkID(client: Client) -> bool: def _is_not_later_rippled_version(source: str, target: str) -> bool: """ - Determines whether the source version is not a later release than the target version. + Determines whether the source version is not a later release than the + target version. Args: source: the source rippled version.