diff --git a/CHANGELOG.md b/CHANGELOG.md index 011a1464d4..1922cf33f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 8.1.0 /2024-10-03 + +## What's Changed +* Implements new logging level 'warning' by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2323 +* Adds ConnectionRefusedError in-case of connection error by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2326 +* Subtensor verbose False by default, debug logging for subtensor connected by @thewhaleking in https://github.com/opentensor/bittensor/pull/2335 +* Fix tests to be ready for rust-based bittensor-wallet by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2336 + ## 8.0.0 /2024-09-25 ## What's Changed diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 5ddba2abe4..f4d8ee906a 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -22,27 +22,6 @@ from .utils.deprecated import * -# Logging helpers. -def trace(on: bool = True): - """ - Enables or disables trace logging. - - Args: - on (bool): If True, enables trace logging. If False, disables trace logging. - """ - logging.set_trace(on) - - -def debug(on: bool = True): - """ - Enables or disables debug logging. - - Args: - on (bool): If True, enables debug logging. If False, disables debug logging. - """ - logging.set_debug(on) - - def __getattr__(name): if name == "version_split": warnings.warn( diff --git a/bittensor/core/axon.py b/bittensor/core/axon.py index 63b79d49c8..ce967acb80 100644 --- a/bittensor/core/axon.py +++ b/bittensor/core/axon.py @@ -32,14 +32,15 @@ from typing import Any, Awaitable, Callable, Optional, Tuple import uvicorn -from bittensor_wallet import Wallet +from bittensor_wallet import Wallet, Keypair + from fastapi import APIRouter, Depends, FastAPI from fastapi.responses import JSONResponse from fastapi.routing import serialize_response from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.requests import Request from starlette.responses import Response -from substrateinterface import Keypair + from bittensor.core.chain_data import AxonInfo from bittensor.core.config import Config diff --git a/bittensor/core/dendrite.py b/bittensor/core/dendrite.py index 0b9bc5381f..f2d0a5f4d5 100644 --- a/bittensor/core/dendrite.py +++ b/bittensor/core/dendrite.py @@ -23,8 +23,7 @@ from typing import Any, AsyncGenerator, Optional, Union, Type import aiohttp -from bittensor_wallet import Wallet -from substrateinterface import Keypair +from bittensor_wallet import Keypair, Wallet from bittensor.core.axon import Axon from bittensor.core.chain_data import AxonInfo diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index cfccf362be..f1ab20c1ee 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -15,7 +15,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -__version__ = "8.0.0" +__version__ = "8.1.0" import os import re diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 5339645952..ca7397adb6 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -134,7 +134,7 @@ def __init__( network: Optional[str] = None, config: Optional["Config"] = None, _mock: bool = False, - log_verbose: bool = True, + log_verbose: bool = False, connection_timeout: int = 600, ) -> None: """ @@ -175,17 +175,18 @@ def __init__( logging.info( f"You are connecting to {self.network} network with endpoint {self.chain_endpoint}." ) - logging.warning( + logging.debug( "We strongly encourage running a local subtensor node whenever possible. " "This increases decentralization and resilience of the network." ) - logging.warning( + logging.debug( "In a future release, local subtensor will become the default endpoint. " "To get ahead of this change, please run a local subtensor node and point to it." ) self.log_verbose = log_verbose self._connection_timeout = connection_timeout + self.substrate: "SubstrateInterface" = None self._get_substrate() def __str__(self) -> str: @@ -201,7 +202,8 @@ def __repr__(self) -> str: def close(self): """Cleans up resources for this subtensor instance like active websocket connection and active extensions.""" - self.substrate.close() + if self.substrate: + self.substrate.close() def _get_substrate(self): """Establishes a connection to the Substrate node using configured parameters.""" @@ -214,7 +216,7 @@ def _get_substrate(self): type_registry=settings.TYPE_REGISTRY, ) if self.log_verbose: - logging.info( + logging.debug( f"Connected to {self.network} network and {self.chain_endpoint}." ) @@ -223,14 +225,15 @@ def _get_substrate(self): except (AttributeError, TypeError, socket.error, OSError) as e: logging.warning(f"Error setting timeout: {e}") - except ConnectionRefusedError: + except ConnectionRefusedError as error: logging.error( f"Could not connect to {self.network} network with {self.chain_endpoint} chain endpoint.", ) logging.info( "You can check if you have connectivity by running this command: nc -vz localhost " - f"{self.chain_endpoint.split(':')[2]}" + f"{self.chain_endpoint}" ) + raise ConnectionRefusedError(error.args) @staticmethod def config() -> "Config": diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 58a7e0a7c2..6239d89808 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -16,10 +16,10 @@ # DEALINGS IN THE SOFTWARE. import hashlib -from typing import List, Dict, Literal, Union, Optional, TYPE_CHECKING +from typing import Literal, Union, Optional, TYPE_CHECKING import scalecodec -from substrateinterface import Keypair +from bittensor_wallet import Keypair from substrateinterface.utils import ss58 from bittensor.core.settings import SS58_FORMAT @@ -245,7 +245,7 @@ def _is_valid_ed25519_pubkey(public_key: Union[str, bytes]) -> bool: else: raise ValueError("public_key must be a string or bytes") - keypair = Keypair(public_key=public_key, ss58_format=SS58_FORMAT) + keypair = Keypair(public_key=public_key) ss58_addr = keypair.ss58_address return ss58_addr is not None diff --git a/bittensor/utils/btlogging/loggingmachine.py b/bittensor/utils/btlogging/loggingmachine.py index b2cfb2918e..abc4758bf8 100644 --- a/bittensor/utils/btlogging/loggingmachine.py +++ b/bittensor/utils/btlogging/loggingmachine.py @@ -69,26 +69,48 @@ class LoggingMachine(StateMachine, Logger): Debug = State() Trace = State() Disabled = State() + Warning = State() enable_default = ( Debug.to(Default) | Trace.to(Default) | Disabled.to(Default) | Default.to(Default) + | Warning.to(Default) ) + enable_info = enable_default + enable_trace = ( - Default.to(Trace) | Debug.to(Trace) | Disabled.to(Trace) | Trace.to(Trace) + Default.to(Trace) + | Debug.to(Trace) + | Disabled.to(Trace) + | Trace.to(Trace) + | Warning.to(Trace) ) enable_debug = ( - Default.to(Debug) | Trace.to(Debug) | Disabled.to(Debug) | Debug.to(Debug) + Default.to(Debug) + | Trace.to(Debug) + | Disabled.to(Debug) + | Debug.to(Debug) + | Warning.to(Debug) + ) + + enable_warning = ( + Default.to(Warning) + | Trace.to(Warning) + | Disabled.to(Warning) + | Debug.to(Warning) + | Warning.to(Warning) ) disable_trace = Trace.to(Default) disable_debug = Debug.to(Default) + disable_warning = Warning.to(Default) + disable_logging = ( Trace.to(Disabled) | Debug.to(Disabled) @@ -301,16 +323,36 @@ def after_transition(self, event, state): # Default Logging def before_enable_default(self): """Logs status before enable Default.""" - self._logger.info(f"Enabling default logging.") + self._logger.info("Enabling default logging.") self._logger.setLevel(stdlogging.WARNING) for logger in all_loggers(): if logger.name in self._primary_loggers: continue logger.setLevel(stdlogging.CRITICAL) + def before_enable_info(self): + """Logs status before enable Default.""" + self._logger.info("Enabling default logging.") + self._logger.setLevel(stdlogging.INFO) + for logger in all_loggers(): + if logger.name in self._primary_loggers: + continue + logger.setLevel(stdlogging.CRITICAL) + def after_enable_default(self): pass + def before_enable_warning(self): + """Logs status before enable Warning.""" + self._logger.info("Enabling warning.") + self._stream_formatter.set_trace(True) + for logger in all_loggers(): + logger.setLevel(stdlogging.WARNING) + + def after_enable_warning(self): + """Logs status after enable Warning.""" + self._logger.info("Warning enabled.") + # Trace def before_enable_trace(self): """Logs status before enable Trace.""" @@ -325,7 +367,7 @@ def after_enable_trace(self): def before_disable_trace(self): """Logs status before disable Trace.""" - self._logger.info(f"Disabling trace.") + self._logger.info("Disabling trace.") self._stream_formatter.set_trace(False) self.enable_default() @@ -449,6 +491,25 @@ def set_trace(self, on: bool = True): if self.current_state_value == "Trace": self.disable_trace() + def set_warning(self, on: bool = True): + """Sets Warning state.""" + if on and not self.current_state_value == "Warning": + self.enable_warning() + elif not on: + if self.current_state_value == "Warning": + self.disable_warning() + + def set_default(self): + """Sets Default state.""" + if not self.current_state_value == "Default": + self.enable_default() + + # as an option to be more obvious. `bittensor.logging.set_info()` is the same `bittensor.logging.set_default()` + def set_info(self): + """Sets Default state.""" + if not self.current_state_value == "Default": + self.enable_info() + def get_level(self) -> int: """Returns Logging level.""" return self._logger.level diff --git a/bittensor/utils/deprecated.py b/bittensor/utils/deprecated.py index 6075a93d8f..146e8395d0 100644 --- a/bittensor/utils/deprecated.py +++ b/bittensor/utils/deprecated.py @@ -42,7 +42,7 @@ Keyfile, ) from bittensor_wallet.wallet import display_mnemonic_msg, Wallet # noqa: F401 -from substrateinterface import Keypair # noqa: F401 +from bittensor_wallet import Keypair # noqa: F401 from bittensor.core import settings from bittensor.core.axon import Axon @@ -112,6 +112,7 @@ ) from bittensor.utils.balance import Balance as Balance # noqa: F401 from bittensor.utils.mock.subtensor_mock import MockSubtensor as MockSubtensor # noqa: F401 +from bittensor.utils.btlogging import logging from bittensor.utils.subnets import SubnetsAPI # noqa: F401 # Backwards compatibility with previous bittensor versions. @@ -148,3 +149,35 @@ # Makes the `bittensor.core.extrinsics` subpackage available as `bittensor.extrinsics` for backwards compatibility. extrinsics_subpackage = importlib.import_module("bittensor.core.extrinsics") sys.modules["bittensor.extrinsics"] = extrinsics_subpackage + + +# Logging helpers. +def trace(on: bool = True): + """ + Enables or disables trace logging. + Args: + on (bool): If True, enables trace logging. If False, disables trace logging. + """ + logging.set_trace(on) + + +def debug(on: bool = True): + """ + Enables or disables debug logging. + Args: + on (bool): If True, enables debug logging. If False, disables debug logging. + """ + logging.set_debug(on) + + +def warning(on: bool = True): + """ + Enables or disables warning logging. + Args: + on (bool): If True, enables warning logging. If False, disables warning logging and sets default (INFO) level. + """ + logging.set_warning(on) + + +# set Warning logging level for bittensor SDK +warning() diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index d7c86bcdca..f004af446c 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -26,7 +26,7 @@ from numpy.typing import NDArray from scalecodec import U16, ScaleBytes, Vec -from substrateinterface import Keypair +from bittensor_wallet import Keypair from bittensor.utils.btlogging import logging from bittensor.utils.registration import legacy_torch_api_compat, torch, use_torch diff --git a/requirements/prod.txt b/requirements/prod.txt index 631f949be3..9e21dafbc5 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -20,4 +20,4 @@ python-Levenshtein scalecodec==1.2.11 substrate-interface~=1.7.9 uvicorn -bittensor-wallet==1.0.0 \ No newline at end of file +bittensor-wallet==2.0.0 \ No newline at end of file diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index d82944afd5..5f562039ee 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -45,7 +45,10 @@ def local_chain(request): # install neuron templates logging.info("downloading and installing neuron templates from github") - templates_dir = clone_or_update_templates() + # commit with subnet-template-repo changes for rust wallet + templates_dir = clone_or_update_templates( + "334d3da101279218b3a4c9d72a12d517f6e39be3" + ) install_templates(templates_dir) timestamp = int(time.time()) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index ba662647a4..2568908bea 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -3,7 +3,7 @@ import subprocess import sys -from substrateinterface import Keypair +from bittensor_wallet import Keypair import bittensor diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 8539839ccc..e252cb63f1 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -18,10 +18,12 @@ import unittest from unittest.mock import MagicMock, patch -from substrateinterface import Keypair +import pytest +from bittensor_wallet import Keypair import bittensor from bittensor.core import settings +from bittensor.core.extrinsics import transfer from bittensor.utils.balance import Balance from bittensor.utils.mock import MockSubtensor from tests.helpers import ( @@ -30,7 +32,6 @@ get_mock_keypair, get_mock_wallet, ) -from bittensor.core.extrinsics import transfer class TestSubtensor(unittest.TestCase): @@ -171,7 +172,7 @@ def test_transfer_invalid_dest(self): ) self.assertFalse(fail, msg="Transfer should fail because of invalid dest") - def test_transfer_dest_as_bytes(self): + def test_transfer_dest_as_bytes_fails(self): fake_coldkey = get_mock_coldkey(1) with patch( "bittensor.core.extrinsics.transfer.do_transfer", @@ -183,13 +184,14 @@ def test_transfer_dest_as_bytes(self): self.subtensor.get_balance = MagicMock(return_value=self.balance) dest_as_bytes: bytes = Keypair(fake_coldkey).public_key - success = self.subtensor.transfer( - self.wallet, - dest_as_bytes, # invalid dest - amount=200, - wait_for_inclusion=True, - ) - self.assertTrue(success, msg="Transfer should succeed") + + with pytest.raises(TypeError): + self.subtensor.transfer( + self.wallet, + dest_as_bytes, # invalid dest + amount=200, + wait_for_inclusion=True, + ) def test_set_weights(self): chain_weights = [0] diff --git a/tests/unit_tests/test_axon.py b/tests/unit_tests/test_axon.py index 689d217379..9c473665fe 100644 --- a/tests/unit_tests/test_axon.py +++ b/tests/unit_tests/test_axon.py @@ -380,10 +380,16 @@ def test_to_string(info_return, expected_output, test_id): ) def test_valid_ipv4_and_ipv6_address(ip, port, expected_ip_type, test_id): # Arrange + hotkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP") + coldkey = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP") + coldkeypub = MockHotkey("5EemgxS7cmYbD34esCFoBgUZZC8JdnGtQvV5Qw3QFUCRRtGP") + wallet = MockWallet(hotkey, coldkey, coldkeypub) + axon = Axon() axon.ip = ip axon.external_ip = ip axon.port = port + axon.wallet = wallet # Act ip_type = axon.info().ip_type @@ -586,7 +592,7 @@ async def test_ping__no_dendrite(self, http_client): assert (response.status_code, response.json()) == ( 401, { - "message": "Not Verified with error: No SS58 formatted address or public key provided" + "message": "Not Verified with error: No SS58 formatted address or public key provided." }, ) diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py index 3150aaf648..d1f9327649 100644 --- a/tests/unit_tests/test_dendrite.py +++ b/tests/unit_tests/test_dendrite.py @@ -73,7 +73,8 @@ def axon_info(): @pytest.fixture(scope="session") def setup_axon(): - axon = Axon() + wallet = get_mock_wallet() + axon = Axon(wallet) axon.attach(forward_fn=dummy) axon.start() yield axon