From d91659900206009ae24a2d1637fe8a38aaeb079d Mon Sep 17 00:00:00 2001 From: abel Date: Thu, 18 Apr 2024 15:17:16 -0300 Subject: [PATCH 1/3] (feat) Refactoring in Network class to support mixed secure and insecure endpoints --- CHANGELOG.md | 5 + pyinjective/async_client.py | 33 ++---- pyinjective/core/network.py | 111 ++++++++++++++++-- pyproject.toml | 2 +- .../core/test_network_deprecation_warnings.py | 28 +++++ .../test_async_client_deprecation_warnings.py | 18 +++ 6 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 tests/core/test_network_deprecation_warnings.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7844c23b..83970ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## [1.5.0] - 2024-04-19 +### Changed +- Refactoring in Network class to support mixed secure and insecure endpoints. +- Marked the Network parameter `use_secure_connection` as deprecated. + ## [1.4.2] - 2024-03-19 ### Changed - Updated `aiohttp` dependency version to ">=3.9.2" to solve a security vulnerability detected by Dependabot diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index d684a234..9d830006 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -88,7 +88,7 @@ def __init__( self, network: Network, insecure: Optional[bool] = None, - credentials=grpc.ssl_channel_credentials(), + credentials=None, ): # the `insecure` parameter is ignored and will be deprecated soon. The value is taken directly from `network` if insecure is not None: @@ -97,6 +97,13 @@ def __init__( DeprecationWarning, stacklevel=2, ) + # the `credentials` parameter is ignored and will be deprecated soon. The value is taken directly from `network` + if credentials is not None: + warn( + "credentials parameter in AsyncClient is no longer used and will be deprecated", + DeprecationWarning, + stacklevel=2, + ) self.addr = "" self.number = 0 @@ -105,11 +112,7 @@ def __init__( self.network = network # chain stubs - self.chain_channel = ( - grpc.aio.secure_channel(network.grpc_endpoint, credentials) - if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_endpoint) - ) + self.chain_channel = self.network.create_chain_grpc_channel() self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub(self.chain_channel) self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) @@ -121,11 +124,7 @@ def __init__( self.timeout_height = 1 # exchange stubs - self.exchange_channel = ( - grpc.aio.secure_channel(network.grpc_exchange_endpoint, credentials) - if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - ) + self.exchange_channel = self.network.create_exchange_grpc_channel() self.stubMeta = exchange_meta_rpc_grpc.InjectiveMetaRPCStub(self.exchange_channel) self.stubExchangeAccount = exchange_accounts_rpc_grpc.InjectiveAccountsRPCStub(self.exchange_channel) self.stubOracle = oracle_rpc_grpc.InjectiveOracleRPCStub(self.exchange_channel) @@ -138,18 +137,10 @@ def __init__( self.stubPortfolio = portfolio_rpc_grpc.InjectivePortfolioRPCStub(self.exchange_channel) # explorer stubs - self.explorer_channel = ( - grpc.aio.secure_channel(network.grpc_explorer_endpoint, credentials) - if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_explorer_endpoint) - ) + self.explorer_channel = self.network.create_explorer_grpc_channel() self.stubExplorer = explorer_rpc_grpc.InjectiveExplorerRPCStub(self.explorer_channel) - self.chain_stream_channel = ( - grpc.aio.secure_channel(network.chain_stream_endpoint, credentials) - if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.chain_stream_endpoint) - ) + self.chain_stream_channel = self.network.create_chain_stream_grpc_channel() self.chain_stream_stub = stream_rpc_grpc.StreamStub(channel=self.chain_stream_channel) self._timeout_height_sync_task = None diff --git a/pyinjective/core/network.py b/pyinjective/core/network.py index 93d006eb..e81eba70 100644 --- a/pyinjective/core/network.py +++ b/pyinjective/core/network.py @@ -4,6 +4,10 @@ from abc import ABC, abstractmethod from http.cookies import SimpleCookie from typing import Callable, Optional, Tuple +from warnings import warn + +import grpc +from grpc import ChannelCredentials class CookieAssistant(ABC): @@ -181,8 +185,20 @@ def __init__( fee_denom: str, env: str, cookie_assistant: CookieAssistant, - use_secure_connection: bool = False, + use_secure_connection: Optional[bool] = None, + grpc_channel_credentials: Optional[ChannelCredentials] = None, + grpc_exchange_channel_credentials: Optional[ChannelCredentials] = None, + grpc_explorer_channel_credentials: Optional[ChannelCredentials] = None, + chain_stream_channel_credentials: Optional[ChannelCredentials] = None, ): + # the `use_secure_connection` parameter is ignored and will be deprecated soon. + if use_secure_connection is not None: + warn( + "use_secure_connection parameter in Network is no longer used and will be deprecated", + DeprecationWarning, + stacklevel=2, + ) + self.lcd_endpoint = lcd_endpoint self.tm_websocket_endpoint = tm_websocket_endpoint self.grpc_endpoint = grpc_endpoint @@ -193,7 +209,10 @@ def __init__( self.fee_denom = fee_denom self.env = env self.cookie_assistant = cookie_assistant - self.use_secure_connection = use_secure_connection + self.grpc_channel_credentials = grpc_channel_credentials + self.grpc_exchange_channel_credentials = grpc_exchange_channel_credentials + self.grpc_explorer_channel_credentials = grpc_explorer_channel_credentials + self.chain_stream_channel_credentials = chain_stream_channel_credentials @classmethod def devnet(cls): @@ -219,6 +238,11 @@ def testnet(cls, node="lb"): if node not in nodes: raise ValueError("Must be one of {}".format(nodes)) + grpc_channel_credentials = grpc.ssl_channel_credentials() + grpc_exchange_channel_credentials = grpc.ssl_channel_credentials() + grpc_explorer_channel_credentials = grpc.ssl_channel_credentials() + chain_stream_channel_credentials = grpc.ssl_channel_credentials() + if node == "lb": lcd_endpoint = "https://testnet.sentry.lcd.injective.network:443" tm_websocket_endpoint = "wss://testnet.sentry.tm.injective.network:443/websocket" @@ -227,7 +251,6 @@ def testnet(cls, node="lb"): grpc_explorer_endpoint = "testnet.sentry.explorer.grpc.injective.network:443" chain_stream_endpoint = "testnet.sentry.chain.stream.injective.network:443" cookie_assistant = BareMetalLoadBalancedCookieAssistant() - use_secure_connection = True else: lcd_endpoint = "https://testnet.lcd.injective.network:443" tm_websocket_endpoint = "wss://testnet.tm.injective.network:443/websocket" @@ -236,7 +259,6 @@ def testnet(cls, node="lb"): grpc_explorer_endpoint = "testnet.explorer.grpc.injective.network:443" chain_stream_endpoint = "testnet.chain.stream.injective.network:443" cookie_assistant = DisabledCookieAssistant() - use_secure_connection = True return cls( lcd_endpoint=lcd_endpoint, @@ -249,7 +271,10 @@ def testnet(cls, node="lb"): fee_denom="inj", env="testnet", cookie_assistant=cookie_assistant, - use_secure_connection=use_secure_connection, + grpc_channel_credentials=grpc_channel_credentials, + grpc_exchange_channel_credentials=grpc_exchange_channel_credentials, + grpc_explorer_channel_credentials=grpc_explorer_channel_credentials, + chain_stream_channel_credentials=chain_stream_channel_credentials, ) @classmethod @@ -267,7 +292,10 @@ def mainnet(cls, node="lb"): grpc_explorer_endpoint = "sentry.explorer.grpc.injective.network:443" chain_stream_endpoint = "sentry.chain.stream.injective.network:443" cookie_assistant = BareMetalLoadBalancedCookieAssistant() - use_secure_connection = True + grpc_channel_credentials = grpc.ssl_channel_credentials() + grpc_exchange_channel_credentials = grpc.ssl_channel_credentials() + grpc_explorer_channel_credentials = grpc.ssl_channel_credentials() + chain_stream_channel_credentials = grpc.ssl_channel_credentials() return cls( lcd_endpoint=lcd_endpoint, @@ -280,7 +308,10 @@ def mainnet(cls, node="lb"): fee_denom="inj", env="mainnet", cookie_assistant=cookie_assistant, - use_secure_connection=use_secure_connection, + grpc_channel_credentials=grpc_channel_credentials, + grpc_exchange_channel_credentials=grpc_exchange_channel_credentials, + grpc_explorer_channel_credentials=grpc_explorer_channel_credentials, + chain_stream_channel_credentials=chain_stream_channel_credentials, ) @classmethod @@ -296,7 +327,6 @@ def local(cls): fee_denom="inj", env="local", cookie_assistant=DisabledCookieAssistant(), - use_secure_connection=False, ) @classmethod @@ -311,8 +341,20 @@ def custom( chain_id, env, cookie_assistant: Optional[CookieAssistant] = None, - use_secure_connection: bool = False, + use_secure_connection: Optional[bool] = None, + grpc_channel_credentials: Optional[ChannelCredentials] = None, + grpc_exchange_channel_credentials: Optional[ChannelCredentials] = None, + grpc_explorer_channel_credentials: Optional[ChannelCredentials] = None, + chain_stream_channel_credentials: Optional[ChannelCredentials] = None, ): + # the `use_secure_connection` parameter is ignored and will be deprecated soon. + if use_secure_connection is not None: + warn( + "use_secure_connection parameter in Network is no longer used and will be deprecated", + DeprecationWarning, + stacklevel=2, + ) + assistant = cookie_assistant or DisabledCookieAssistant() return cls( lcd_endpoint=lcd_endpoint, @@ -325,7 +367,37 @@ def custom( fee_denom="inj", env=env, cookie_assistant=assistant, - use_secure_connection=use_secure_connection, + grpc_channel_credentials=grpc_channel_credentials, + grpc_exchange_channel_credentials=grpc_exchange_channel_credentials, + grpc_explorer_channel_credentials=grpc_explorer_channel_credentials, + chain_stream_channel_credentials=chain_stream_channel_credentials, + ) + + @classmethod + def custom_chain_and_public_indexer_mainnet( + cls, + lcd_endpoint, + tm_websocket_endpoint, + grpc_endpoint, + chain_stream_endpoint, + cookie_assistant: Optional[CookieAssistant] = None, + ): + mainnet_network = cls.mainnet() + + return cls.custom( + lcd_endpoint=lcd_endpoint, + tm_websocket_endpoint=tm_websocket_endpoint, + grpc_endpoint=grpc_endpoint, + grpc_exchange_endpoint=mainnet_network.grpc_exchange_endpoint, + grpc_explorer_endpoint=mainnet_network.grpc_explorer_endpoint, + chain_stream_endpoint=chain_stream_endpoint, + chain_id="injective-1", + env="mainnet", + cookie_assistant=cookie_assistant, + grpc_channel_credentials=None, + grpc_exchange_channel_credentials=mainnet_network.grpc_exchange_channel_credentials, + grpc_explorer_channel_credentials=mainnet_network.grpc_explorer_channel_credentials, + chain_stream_channel_credentials=None, ) def string(self): @@ -336,3 +408,22 @@ async def chain_metadata(self, metadata_query_provider: Callable) -> Tuple[Tuple async def exchange_metadata(self, metadata_query_provider: Callable) -> Tuple[Tuple[str, str]]: return await self.cookie_assistant.exchange_metadata(metadata_query_provider=metadata_query_provider) + + def create_chain_grpc_channel(self) -> grpc.Channel: + return self._create_grpc_channel(self.grpc_endpoint, self.grpc_channel_credentials) + + def create_exchange_grpc_channel(self) -> grpc.Channel: + return self._create_grpc_channel(self.grpc_exchange_endpoint, self.grpc_exchange_channel_credentials) + + def create_explorer_grpc_channel(self) -> grpc.Channel: + return self._create_grpc_channel(self.grpc_explorer_endpoint, self.grpc_explorer_channel_credentials) + + def create_chain_stream_grpc_channel(self) -> grpc.Channel: + return self._create_grpc_channel(self.chain_stream_endpoint, self.chain_stream_channel_credentials) + + def _create_grpc_channel(self, endpoint: str, credentials: Optional[ChannelCredentials]) -> grpc.Channel: + if credentials is None: + channel = grpc.aio.insecure_channel(endpoint) + else: + channel = grpc.aio.secure_channel(endpoint, credentials) + return channel diff --git a/pyproject.toml b/pyproject.toml index 0aa3fd02..b148893b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "injective-py" -version = "1.4.2" +version = "1.5.0" description = "Injective Python SDK, with Exchange API Client" authors = ["Injective Labs "] license = "Apache-2.0" diff --git a/tests/core/test_network_deprecation_warnings.py b/tests/core/test_network_deprecation_warnings.py new file mode 100644 index 00000000..25a60b1f --- /dev/null +++ b/tests/core/test_network_deprecation_warnings.py @@ -0,0 +1,28 @@ +from warnings import catch_warnings + +from pyinjective.core.network import Network, DisabledCookieAssistant + + +class TestNetworkDeprecationWarnings: + def test_use_secure_connection_parameter_deprecation_warning(self): + with catch_warnings(record=True) as all_warnings: + Network( + lcd_endpoint="lcd_endpoint", + tm_websocket_endpoint="tm_websocket_endpoint", + grpc_endpoint="grpc_endpoint", + grpc_exchange_endpoint="grpc_exchange_endpoint", + grpc_explorer_endpoint="grpc_explorer_endpoint", + chain_stream_endpoint="chain_stream_endpoint", + chain_id="chain_id", + fee_denom="fee_denom", + env="env", + cookie_assistant=DisabledCookieAssistant(), + use_secure_connection=True, + ) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "use_secure_connection parameter in Network is no longer used and will be deprecated" + ) diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 2a6a1960..aa3952ca 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -1,5 +1,6 @@ from warnings import catch_warnings +import grpc import pytest from pyinjective.async_client import AsyncClient @@ -1682,3 +1683,20 @@ async def test_chain_stream_deprecation_warning( assert ( str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_chain_stream_updates instead" ) + + def test_credentials_parameter_deprecation_warning( + self, + auth_servicer, + ): + with catch_warnings(record=True) as all_warnings: + AsyncClient( + network=Network.local(), + credentials=grpc.ssl_channel_credentials() + ) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "credentials parameter in AsyncClient is no longer used and will be deprecated" + ) From 7b4e0b2c6a69565cfe379adc59cf655aafc96820 Mon Sep 17 00:00:00 2001 From: abel Date: Thu, 18 Apr 2024 17:18:20 -0300 Subject: [PATCH 2/3] (fix) Solve pre-commit issues --- pyinjective/core/network.py | 12 ++++++------ tests/core/test_network_deprecation_warnings.py | 2 +- tests/test_async_client_deprecation_warnings.py | 5 +---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pyinjective/core/network.py b/pyinjective/core/network.py index e81eba70..b6306586 100644 --- a/pyinjective/core/network.py +++ b/pyinjective/core/network.py @@ -375,12 +375,12 @@ def custom( @classmethod def custom_chain_and_public_indexer_mainnet( - cls, - lcd_endpoint, - tm_websocket_endpoint, - grpc_endpoint, - chain_stream_endpoint, - cookie_assistant: Optional[CookieAssistant] = None, + cls, + lcd_endpoint, + tm_websocket_endpoint, + grpc_endpoint, + chain_stream_endpoint, + cookie_assistant: Optional[CookieAssistant] = None, ): mainnet_network = cls.mainnet() diff --git a/tests/core/test_network_deprecation_warnings.py b/tests/core/test_network_deprecation_warnings.py index 25a60b1f..6dc7c76c 100644 --- a/tests/core/test_network_deprecation_warnings.py +++ b/tests/core/test_network_deprecation_warnings.py @@ -1,6 +1,6 @@ from warnings import catch_warnings -from pyinjective.core.network import Network, DisabledCookieAssistant +from pyinjective.core.network import DisabledCookieAssistant, Network class TestNetworkDeprecationWarnings: diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index aa3952ca..12b4d55d 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -1689,10 +1689,7 @@ def test_credentials_parameter_deprecation_warning( auth_servicer, ): with catch_warnings(record=True) as all_warnings: - AsyncClient( - network=Network.local(), - credentials=grpc.ssl_channel_credentials() - ) + AsyncClient(network=Network.local(), credentials=grpc.ssl_channel_credentials()) deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] assert len(deprecation_warnings) == 1 From c47c94efb0d96f7e26f8fc780cb0711227ba3541 Mon Sep 17 00:00:00 2001 From: abel Date: Thu, 18 Apr 2024 17:19:57 -0300 Subject: [PATCH 3/3] (fix) Renamed a deprecation test --- tests/test_async_client_deprecation_warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 12b4d55d..8ae39690 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -580,7 +580,7 @@ async def test_get_oracle_prices_deprecation_warning( assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_price instead" @pytest.mark.asyncio - async def test_stream_keepalive_deprecation_warning( + async def test_stream_oracle_prices_deprecation_warning( self, oracle_servicer, ):