From ca261c049e24ac618bcb80631fe0afc396e5756a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 18:37:49 -0700 Subject: [PATCH 01/45] add BUILD_BINARY=0 --- tests/e2e_tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index b4770ed053..ba26c41e9c 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -66,8 +66,12 @@ def read_output(): reader_thread = threading.Thread(target=read_output, daemon=True) reader_thread.start() + env = os.environ.copy() + env["BUILD_BINARY"] = "0" + with subprocess.Popen( cmds, + env=env, start_new_session=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, From 0074c9b77456b1efc21412588c9cfe7a327542c6 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 18:38:45 -0700 Subject: [PATCH 02/45] replace devnet-ready to devnet-ready-with-nodes --- btwa_testing.py | 8 +++++ manual_testing.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 btwa_testing.py create mode 100644 manual_testing.py diff --git a/btwa_testing.py b/btwa_testing.py new file mode 100644 index 0000000000..a9e7f90b3b --- /dev/null +++ b/btwa_testing.py @@ -0,0 +1,8 @@ +from bittensor_wallet import Keypair, Keyfile, Wallet, Config + + + +wallet = Wallet(name="tmp") +wallet.coldkeyfile.save_to_env + +print(wallet.coldkey) diff --git a/manual_testing.py b/manual_testing.py new file mode 100644 index 0000000000..90b4e95b02 --- /dev/null +++ b/manual_testing.py @@ -0,0 +1,91 @@ +from bittensor.core.subtensor import Subtensor +from bittensor.utils.balance import tao +from bittensor_wallet.wallet import Wallet + +COLDKEY = "5G4T9VnZfUDD2vD3MdKsgmbSAsKQq5rtZ73JtQuFA8SRfW14" +HOTKEY = "5GQenM6A7sWGxgmPPAorKGnFQsBWczXDhGhZjkbZq3Wr9wtX" +HOTKEY_NETUIDS = [0, 4, 6] + +DEVNET = "wss://dev.chain.opentensor.ai:443" +wallet = Wallet(path="~/.bittensor/wallets", name="alice") + + +subtensor = Subtensor(network="test") + + +def main(): + # print(subtensor.get_hyperparameter("LastUpdate", 1)) + # print(subtensor.get_hyperparameter("MinDifficulty", 1)) # does not exist + # print(subtensor.all_subnets()) + # print(subtensor.blocks_since_last_update(1, 1)) + # print(subtensor.bonds(1)) # looking into + # print(subtensor.commit_reveal_enabled(1)) + # print(subtensor.difficulty(1)) + # print(subtensor.does_hotkey_exist(HOTKEY)) + # print(subtensor.get_all_subnets_info()) + # print(subtensor.get_balance(COLDKEY)) + # print(subtensor.get_balances(COLDKEY)) + + # print(current_block := subtensor.get_current_block()) + + # print(subtensor._get_block_hash(current_block)) + # print(subtensor.get_block_hash(current_block)) + # print(subtensor.get_children(HOTKEY, 1)) # maybe not working + # print(subtensor.get_metagraph_info(1)) + # print(subtensor.subnet(1)) + # print(subtensor.get_all_metagraphs_info()) + # print(subtensor.get_netuids_for_hotkey(HOTKEY)) + # print(subtensor.get_neuron_certificate()) # untested + # print(subtensor.get_neuron_for_pubkey_and_subnet(HOTKEY, HOTKEY_NETUIDS[1])) + # print(subtensor.get_stake(COLDKEY, HOTKEY, HOTKEY_NETUIDS[1])) + # print(subtensor.get_stake_for_coldkey_and_hotkey(COLDKEY, HOTKEY, HOTKEY_NETUIDS)) + # print(subtensor.get_stake_for_coldkey(COLDKEY)) + # print(type(subtensor.get_subnet_burn_cost())) + + # print(subtensor.get_subnet_hyperparameters(111)) + # print(subtensor.get_subnets(block=current_block-20)) + # print(subtensor.get_total_stake_for_hotkey(HOTKEY)) + # print(subtensor.get_total_subnets()) + # print(subtensor.get_transfer_fee(wallet=wallet, dest=COLDKEY, value=tao(1.0))) + # print(subtensor.get_uid_for_hotkey_on_subnet(HOTKEY, HOTKEY_NETUIDS[1])) + # print(subtensor.immunity_period(HOTKEY_NETUIDS[1])) + # print(subtensor.is_hotkey_delegate(COLDKEY)) + # print(subtensor.is_hotkey_registered(HOTKEY, HOTKEY_NETUIDS[1])) + # print(subtensor.is_hotkey_registered_any(HOTKEY)) + # print(subtensor.is_hotkey_registered_on_subnet(HOTKEY, HOTKEY_NETUIDS[1])) + # print(subtensor.last_drand_round()) + # print(subtensor.max_weight_limit(HOTKEY_NETUIDS[1])) + # print(subtensor.metagraph(HOTKEY_NETUIDS[1], lite=True)) + # print(subtensor.metagraph(HOTKEY_NETUIDS[1], lite=False)) + # print(subtensor.min_allowed_weights(HOTKEY_NETUIDS[1])) + # print(subtensor.neuron_for_uid(1, HOTKEY_NETUIDS[1])) + # print(subtensor.neurons(HOTKEY_NETUIDS[1])) + # print(subtensor.neurons_lite(HOTKEY_NETUIDS[1])) + # print(subtensor.query_identity(HOTKEY)) + # print(subtensor.recycle(HOTKEY_NETUIDS[1])) + # print(subtensor.subnet_exists(2)) + # print(subtensor.subnet_exists(200)) + # print(subtensor.subnetwork_n(HOTKEY_NETUIDS[1])) + # print(subtensor.tempo(HOTKEY_NETUIDS[1])) + # print(subtensor.tx_rate_limit()) + # print(subtensor.wait_for_block()) + # print(subtensor.wait_for_block(current_block+5)) + # for subnet in subtensor.get_subnets(): + # print(subtensor.weights(subnet)) + # print(subtensor.weights_rate_limit(HOTKEY_NETUIDS[1])) + print(block := subtensor.block) + # for uid in range(0, 7): + # print(subtensor.get_commitment(2, uid, block=block)) + # print(subtensor.get_current_weight_commit_info(1)) + # print(subtensor.get_delegate_by_hotkey(HOTKEY)) + # print(subtensor.get_delegate_identities()) + # print(subtensor.get_delegate_take("5GP7c3fFazW9GXK8Up3qgu2DJBk8inu4aK9TZy3RuoSWVCMi", block=block)) + # print(subtensor.get_delegated(COLDKEY, block=block)) + # print(subtensor.get_delegates(block=block)) + # print(subtensor.get_existential_deposit(block=block)) + # print(subtensor.get_hotkey_owner("5DcpkDYwWHgtkhLhKFCMqTQeU1ggRKjQEoLCf1e3X47VxZzw")) + # print(subtensor.get_minimum_required_stake()) + + +if __name__ == "__main__": + main() From a810aae2608c95f4e80018df7b9c10a178f97f50 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 18:52:05 -0700 Subject: [PATCH 03/45] trigger actions --- .github/workflows/e2e-subtensor-tests.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index a32971a918..5d61bcc466 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -23,6 +23,7 @@ env: CARGO_TERM_COLOR: always VERBOSE: ${{ github.event.inputs.verbose }} + # job to run tests in parallel jobs: # Job to find all test files @@ -90,7 +91,7 @@ jobs: - name: Setup subtensor repo working-directory: ${{ github.workspace }}/subtensor - run: git checkout devnet-ready + run: git checkout devnet-ready-with-nodes - name: Install uv uses: astral-sh/setup-uv@v4 From de812b9fe3487151ba2c906c597b2dd19fc88f1e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 18:58:16 -0700 Subject: [PATCH 04/45] add debug --- .github/workflows/e2e-subtensor-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 5d61bcc466..6705eea02d 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -101,6 +101,7 @@ jobs: - name: Run tests run: | + ls -lash ${{ github.workspace }}/subtensor/nodes LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s - name: Retry failed tests From e66c9ed97c15375861ff294aaf17efd7e93f0557 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 21:04:25 -0700 Subject: [PATCH 05/45] return proper runner --- .github/workflows/e2e-subtensor-tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 6705eea02d..27254dc51e 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -23,7 +23,6 @@ env: CARGO_TERM_COLOR: always VERBOSE: ${{ github.event.inputs.verbose }} - # job to run tests in parallel jobs: # Job to find all test files From 99390bac320a64bea667ff13d3c33c3dcf48fe47 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 21:59:06 -0700 Subject: [PATCH 06/45] get-latest-artifact-id --- .github/workflows/e2e-subtensor-tests.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 27254dc51e..7846528af8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -25,6 +25,28 @@ env: # job to run tests in parallel jobs: + + get-latest-artifact-id: + runs-on: ubuntu-latest + + steps: + - name: Fetch Artifacts List + id: fetch_artifacts + run: | + ARTIFACTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/opentensor/subtensor/actions/actions/artifacts) + echo "$ARTIFACTS" > artifacts.json + + - name: Parse Latest Artifact ID + id: parse_artifact + run: | + LATEST_ARTIFACT_ID=$(jq -r '.artifacts[0].id' artifacts.json) + echo "LATEST_ARTIFACT_ID=$LATEST_ARTIFACT_ID" >> $GITHUB_ENV + + - name: Print Latest Artifact ID + run: | + echo "Latest Artifact ID: ${{ env.LATEST_ARTIFACT_ID }}" + # Job to find all test files find-tests: runs-on: ubuntu-latest From 83ef99ffcaf7f3d8e8869f31eca2aa0d1bf08175 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 22:57:00 -0700 Subject: [PATCH 07/45] try tests with compatible architecture --- .github/workflows/e2e-subtensor-tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 7846528af8..2f377ba644 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -36,6 +36,9 @@ jobs: ARTIFACTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/opentensor/subtensor/actions/actions/artifacts) echo "$ARTIFACTS" > artifacts.json + + echo "Raw ARTIFACTS response:" + echo "$ARTIFACTS" - name: Parse Latest Artifact ID id: parse_artifact From a04018c289f4a356c4dbd0a330f8661f5c8d7088 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:03:39 -0700 Subject: [PATCH 08/45] use the same runner --- .github/workflows/e2e-subtensor-tests.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 2f377ba644..12a7e9b371 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -33,8 +33,7 @@ jobs: - name: Fetch Artifacts List id: fetch_artifacts run: | - ARTIFACTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - https://api.github.com/opentensor/subtensor/actions/actions/artifacts) + ARTIFACTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/opentensor/subtensor/actions/artifacts) echo "$ARTIFACTS" > artifacts.json echo "Raw ARTIFACTS response:" @@ -70,7 +69,7 @@ jobs: # Job to run tests in parallel run: needs: find-tests - runs-on: SubtensorCI + runs-on: ubuntu-latest timeout-minutes: 45 strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails From 3210071975b582ade852bd839ab1cef8c5311feb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:09:31 -0700 Subject: [PATCH 09/45] debug --- .github/workflows/e2e-subtensor-tests.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 12a7e9b371..0bbf631d53 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -82,12 +82,12 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - env: - RELEASE_NAME: development - RUSTV: ${{ matrix.rust-branch }} - RUST_BACKTRACE: full - RUST_BIN_DIR: target/${{ matrix.rust-target }} - TARGET: ${{ matrix.rust-target }} +# env: +# RELEASE_NAME: development +# RUSTV: ${{ matrix.rust-branch }} +# RUST_BACKTRACE: full +# RUST_BIN_DIR: target/${{ matrix.rust-target }} +# TARGET: ${{ matrix.rust-target }} steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 From 1ce957801082980d5ed3c41584c19631029844af Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:17:48 -0700 Subject: [PATCH 10/45] debug file --- .github/workflows/e2e-subtensor-tests.yaml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 0bbf631d53..e829d1e9eb 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -116,19 +116,22 @@ jobs: working-directory: ${{ github.workspace }}/subtensor run: git checkout devnet-ready-with-nodes - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --all-extras --dev + - name: Print file + working-directory: ${{ github.workspace }}/subtensor + run: | + apt install file + echo "File fast-blocks" + file ${{ github.workspace }}/subtensor/nodes/fast-blocks/release/node-subtensor + echo "File non-fast-blocks" + file ${{ github.workspace }}/subtensor/nodes/non-fast-blocks/release/node-subtensor - name: Run tests run: | ls -lash ${{ github.workspace }}/subtensor/nodes - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s + LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest ${{ matrix.test-file }} -s - name: Retry failed tests if: failure() run: | sleep 10 - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s + LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest ${{ matrix.test-file }} -s From b6716550c8b38a69865983feeb31b54292a9232f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:22:37 -0700 Subject: [PATCH 11/45] sudo --- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index e829d1e9eb..3aa09e481a 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -119,7 +119,7 @@ jobs: - name: Print file working-directory: ${{ github.workspace }}/subtensor run: | - apt install file + sudo apt install file echo "File fast-blocks" file ${{ github.workspace }}/subtensor/nodes/fast-blocks/release/node-subtensor echo "File non-fast-blocks" From f1f0898e1d67e6cdd014e5f8de154bc839bb9ef3 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:34:40 -0700 Subject: [PATCH 12/45] more debug info --- .github/workflows/e2e-subtensor-tests.yaml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 3aa09e481a..b81030a682 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -77,8 +77,8 @@ jobs: matrix: rust-branch: - stable - rust-target: - - x86_64-unknown-linux-gnu +# rust-target: +# - x86_64-unknown-linux-gnu os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} @@ -116,7 +116,7 @@ jobs: working-directory: ${{ github.workspace }}/subtensor run: git checkout devnet-ready-with-nodes - - name: Print file + - name: Print files and OS info working-directory: ${{ github.workspace }}/subtensor run: | sudo apt install file @@ -124,6 +124,21 @@ jobs: file ${{ github.workspace }}/subtensor/nodes/fast-blocks/release/node-subtensor echo "File non-fast-blocks" file ${{ github.workspace }}/subtensor/nodes/non-fast-blocks/release/node-subtensor + + echo "uname -m && uname -r" + uname -m && uname -r + + echo "ldd" + ldd ${{ github.workspace }}/subtensor/nodes/non-fast-blocks/release/node-subtensor + + echo "/lib/ld-linux-aarch64.so.1 exist" + ls /lib/ld-linux-aarch64.so.1 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: install dependencies + run: uv sync --all-extras --dev - name: Run tests run: | From d044f89f81544fd5751289e9e969503117a2f98a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:38:10 -0700 Subject: [PATCH 13/45] more debug info --- .github/workflows/e2e-subtensor-tests.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index b81030a682..c9b50630ba 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -128,9 +128,6 @@ jobs: echo "uname -m && uname -r" uname -m && uname -r - echo "ldd" - ldd ${{ github.workspace }}/subtensor/nodes/non-fast-blocks/release/node-subtensor - echo "/lib/ld-linux-aarch64.so.1 exist" ls /lib/ld-linux-aarch64.so.1 From 3007f679a78646fc22646ffa5023733accaff018 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:50:19 -0700 Subject: [PATCH 14/45] more debug info --- .github/workflows/e2e-subtensor-tests.yaml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index c9b50630ba..06ef6d1e1f 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -77,17 +77,17 @@ jobs: matrix: rust-branch: - stable -# rust-target: -# - x86_64-unknown-linux-gnu + rust-target: + - x86_64-unknown-linux-gnu os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} -# env: -# RELEASE_NAME: development -# RUSTV: ${{ matrix.rust-branch }} -# RUST_BACKTRACE: full -# RUST_BIN_DIR: target/${{ matrix.rust-target }} -# TARGET: ${{ matrix.rust-target }} + env: + RELEASE_NAME: development + RUSTV: ${{ matrix.rust-branch }} + RUST_BACKTRACE: full + RUST_BIN_DIR: target/${{ matrix.rust-target }} + TARGET: ${{ matrix.rust-target }} steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 @@ -127,9 +127,6 @@ jobs: echo "uname -m && uname -r" uname -m && uname -r - - echo "/lib/ld-linux-aarch64.so.1 exist" - ls /lib/ld-linux-aarch64.so.1 - name: Install uv uses: astral-sh/setup-uv@v4 From 336af8ec9638df1eeeae6ab2881f64f6eda64c5a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 12 Mar 2025 23:56:28 -0700 Subject: [PATCH 15/45] more debug info --- .github/workflows/e2e-subtensor-tests.yaml | 4 ++-- btwa_testing.py | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 btwa_testing.py diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 06ef6d1e1f..b79eaa2fa0 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -137,10 +137,10 @@ jobs: - name: Run tests run: | ls -lash ${{ github.workspace }}/subtensor/nodes - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest ${{ matrix.test-file }} -s + LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s - name: Retry failed tests if: failure() run: | sleep 10 - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest ${{ matrix.test-file }} -s + LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s diff --git a/btwa_testing.py b/btwa_testing.py deleted file mode 100644 index a9e7f90b3b..0000000000 --- a/btwa_testing.py +++ /dev/null @@ -1,8 +0,0 @@ -from bittensor_wallet import Keypair, Keyfile, Wallet, Config - - - -wallet = Wallet(name="tmp") -wallet.coldkeyfile.save_to_env - -print(wallet.coldkey) From ffe6a414c5c5eb9fe9d09951d97dbc115ef6f322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBy=C5=BAniewski?= Date: Thu, 13 Mar 2025 20:26:58 +0100 Subject: [PATCH 16/45] wait till start of the new epoch --- tests/e2e_tests/test_incentive.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 0467a0cd81..ac71a54fc2 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -5,6 +5,7 @@ from tests.e2e_tests.utils.chain_interactions import ( sudo_set_admin_utils, wait_epoch, + wait_interval, ) @@ -66,11 +67,15 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa assert bob_neuron.trust == 0 # update weights_set_rate_limit for fast-blocks + tempo = subtensor.tempo(netuid) status, error = sudo_set_admin_utils( local_chain, alice_wallet, call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": netuid, "weights_set_rate_limit": 10}, + call_params={ + "netuid": netuid, + "weights_set_rate_limit": tempo, + }, ) assert error is None @@ -82,8 +87,8 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa async with asyncio.timeout(60): await validator.set_weights.wait() - # Wait few epochs - await wait_epoch(subtensor, netuid, times=4) + # Wait till new epoch + await wait_interval(tempo, subtensor, netuid) # Refresh metagraph metagraph = subtensor.metagraph(netuid) From bb2d7952138683816e7c092f46dd21cdf7286afe Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:26:50 -0700 Subject: [PATCH 17/45] ruff --- tests/e2e_tests/conftest.py | 154 ++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 40 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index ba26c41e9c..b93c42daf2 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,27 +1,82 @@ import os import re +import shutil import shlex import signal import subprocess -import time +import sys import threading +import time +from bittensor.utils.btlogging import logging import pytest from async_substrate_interface import SubstrateInterface from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.subtensor import Subtensor -from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( Templates, setup_wallet, ) -# Fixture for setting up and tearing down a localnet.sh chain between tests +def wait_for_node_start(process, timestamp=None): + """Waits for node to start in the docker.""" + while True: + line = process.stdout.readline() + if not line: + break + + timestamp = timestamp or int(time.time()) + print(line.strip()) + # 10 min as timeout + if int(time.time()) - timestamp > 20 * 30: + print("Subtensor not started in time") + raise TimeoutError + + pattern = re.compile(r"Imported #1") + if pattern.search(line): + print("Node started!") + break + + # Start a background reader after pattern is found + # To prevent the buffer filling up + def read_output(): + while True: + if not process.stdout.readline(): + break + + reader_thread = threading.Thread(target=read_output, daemon=True) + reader_thread.start() + + @pytest.fixture(scope="function") def local_chain(request): - param = request.param if hasattr(request, "param") else None + """Determines whether to run the localnet.sh script in a subprocess or a Docker container.""" + args = request.param if hasattr(request, "param") else None + params = "" if args is None else f"{args}" + if shutil.which("docker"): + yield from docker_runner(params) + return + + if sys.platform.startswith("linux"): + docker_commend = ( + "Install docker with command " + "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]" + ) + elif sys.platform == "darwin": + docker_commend = "Install docker with command [blue]brew install docker[/blue]" + else: + docker_commend = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" + + logging.warning("Docker not found in the operating system!") + logging.warning(docker_commend) + logging.warning("Tests are run in legacy mode.") + yield from legacy_runner(request) + + +def legacy_runner(params): + """Runs the localnet.sh script in a subprocess and waits for it to start.""" # Get the environment variable for the script path script_path = os.getenv("LOCALNET_SH_PATH") @@ -31,54 +86,20 @@ def local_chain(request): pytest.skip("LOCALNET_SH_PATH environment variable is not set.") # Check if param is None, and handle it accordingly - args = "" if param is None else f"{param}" + args = "" if params is None else f"{params}" # Compile commands to send to process cmds = shlex.split(f"{script_path} {args}") - # Pattern match indicates node is compiled and ready - pattern = re.compile(r"Imported #1") - timestamp = int(time.time()) - - def wait_for_node_start(process, pattern): - while True: - line = process.stdout.readline() - if not line: - break - - print(line.strip()) - # 10 min as timeout - if int(time.time()) - timestamp > 20 * 60: - print("Subtensor not started in time") - raise TimeoutError - if pattern.search(line): - print("Node started!") - break - - # Start a background reader after pattern is found - # To prevent the buffer filling up - def read_output(): - while True: - line = process.stdout.readline() - if not line: - break - - reader_thread = threading.Thread(target=read_output, daemon=True) - reader_thread.start() - - env = os.environ.copy() - env["BUILD_BINARY"] = "0" - with subprocess.Popen( cmds, - env=env, start_new_session=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, text=True, ) as process: try: - wait_for_node_start(process, pattern) + wait_for_node_start(process) except TimeoutError: raise else: @@ -95,6 +116,59 @@ def read_output(): process.wait() +def docker_runner(params): + """Starts a Docker container before tests and gracefully terminates it after.""" + + container_name = f"test_local_chain_{str(time.time()).replace(".", "_")}" + image_name = "ghcr.io/opentensor/subtensor-localnet:latest" + + # Command to start container + cmds = [ + "docker", + "run", + "--rm", + "--name", + container_name, + "-p", + "9944:9944", + "-p", + "9945:9945", + image_name, + params, + ] + + # Start container + with subprocess.Popen( + cmds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + start_new_session=True, + ) as process: + try: + try: + wait_for_node_start(process, int(time.time())) + except TimeoutError: + raise + + result = subprocess.run( + ["docker", "ps", "-q", "-f", f"name={container_name}"], + capture_output=True, + text=True, + ) + if not result.stdout.strip(): + raise RuntimeError("Docker container failed to start.") + + yield SubstrateInterface(url="ws://127.0.0.1:9944") + + finally: + try: + subprocess.run(["docker", "kill", container_name]) + process.wait() + except subprocess.TimeoutExpired: + os.killpg(os.getpgid(process.pid), signal.SIGKILL) + + @pytest.fixture(scope="session") def templates(): with Templates() as templates: From 153815257f056d0f285ed8db64a5cf673fe92306 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:27:13 -0700 Subject: [PATCH 18/45] update e2e tests workflow --- .github/workflows/e2e-subtensor-tests.yaml | 114 ++++++--------------- 1 file changed, 33 insertions(+), 81 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 96bd4519dc..0106005179 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -26,30 +26,6 @@ env: # job to run tests in parallel jobs: - get-latest-artifact-id: - runs-on: ubuntu-latest - - steps: - - name: Fetch Artifacts List - id: fetch_artifacts - run: | - ARTIFACTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/opentensor/subtensor/actions/artifacts) - echo "$ARTIFACTS" > artifacts.json - - echo "Raw ARTIFACTS response:" - echo "$ARTIFACTS" - - - name: Parse Latest Artifact ID - id: parse_artifact - run: | - LATEST_ARTIFACT_ID=$(jq -r '.artifacts[0].id' artifacts.json) - echo "LATEST_ARTIFACT_ID=$LATEST_ARTIFACT_ID" >> $GITHUB_ENV - - - name: Print Latest Artifact ID - run: | - echo "Latest Artifact ID: ${{ env.LATEST_ARTIFACT_ID }}" - - # Job to find all test files find-tests: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} @@ -66,81 +42,57 @@ jobs: echo "::set-output name=test-files::$test_files" shell: bash + pull-docker-image: + runs-on: ubuntu-latest + steps: + - name: Log in to GitHub Container Registry (если образ приватный) + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin + + - name: Pull Docker Image + run: docker pull ghcr.io/opentensor/subtensor-localnet:latest + + - name: Save Docker Image to Cache + run: docker save -o subtensor-localnet.tar ghcr.io/opentensor/subtensor-localnet:latest + + - name: Upload Docker Image as Artifact + uses: actions/upload-artifact@v3 + with: + name: subtensor-localnet + path: subtensor-localnet.tar + retention-days: 1 + # Job to run tests in parallel run: - needs: find-tests + needs: + - find-tests + - pull-docker-image runs-on: ubuntu-latest timeout-minutes: 45 strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails max-parallel: 8 # Set the maximum number of parallel jobs matrix: - rust-branch: - - stable - rust-target: - - x86_64-unknown-linux-gnu os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - env: - RELEASE_NAME: development - RUSTV: ${{ matrix.rust-branch }} - RUST_BACKTRACE: full - RUST_BIN_DIR: target/${{ matrix.rust-target }} - TARGET: ${{ matrix.rust-target }} steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update && - sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler +# - name: Install uv +# uses: astral-sh/setup-uv@v4 +# +# - name: install dependencies +# run: uv sync --all-extras --dev - - name: Install Rust ${{ matrix.rust-branch }} - uses: actions-rs/toolchain@v1.0.6 + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 with: - toolchain: ${{ matrix.rust-branch }} - components: rustfmt - profile: minimal - - - name: Add wasm32-unknown-unknown target - run: | - rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu - rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu - - - name: Clone subtensor repo - run: git clone https://github.com/opentensor/subtensor.git + name: subtensor-localnet - - name: Setup subtensor repo - working-directory: ${{ github.workspace }}/subtensor - run: git checkout devnet-ready-with-nodes - - - name: Print files and OS info - working-directory: ${{ github.workspace }}/subtensor - run: | - sudo apt install file - echo "File fast-blocks" - file ${{ github.workspace }}/subtensor/nodes/fast-blocks/release/node-subtensor - echo "File non-fast-blocks" - file ${{ github.workspace }}/subtensor/nodes/non-fast-blocks/release/node-subtensor - - echo "uname -m && uname -r" - uname -m && uname -r - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --all-extras --dev + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar - name: Run tests run: | - ls -lash ${{ github.workspace }}/subtensor/nodes - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s - - - name: Retry failed tests - if: failure() - run: | - sleep 10 - LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" uv run pytest ${{ matrix.test-file }} -s + pytest ${{ matrix.test-file }} -s From 99aab1b4ca1aaac121029ca5adfa1aebf4e82dd0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:36:54 -0700 Subject: [PATCH 19/45] fix --- .github/workflows/e2e-subtensor-tests.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 0106005179..67041d1e6e 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -45,7 +45,7 @@ jobs: pull-docker-image: runs-on: ubuntu-latest steps: - - name: Log in to GitHub Container Registry (если образ приватный) + - name: Log in to GitHub Container Registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin - name: Pull Docker Image @@ -55,11 +55,10 @@ jobs: run: docker save -o subtensor-localnet.tar ghcr.io/opentensor/subtensor-localnet:latest - name: Upload Docker Image as Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: subtensor-localnet path: subtensor-localnet.tar - retention-days: 1 # Job to run tests in parallel run: From 8841166763104cf16e5c14923c1c9231047150bf Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:38:24 -0700 Subject: [PATCH 20/45] add reqs --- .github/workflows/e2e-subtensor-tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 67041d1e6e..f14e68fe92 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -94,4 +94,6 @@ jobs: - name: Run tests run: | + pip install -e .[dev] + pip install pytest pytest ${{ matrix.test-file }} -s From f42de89ea47aad04ef21fcf201f66f19aede5fd9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:56:35 -0700 Subject: [PATCH 21/45] try improvement --- .github/workflows/e2e-subtensor-tests.yaml | 46 +++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index f14e68fe92..e29773fd00 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -60,6 +60,30 @@ jobs: name: subtensor-localnet path: subtensor-localnet.tar + create_venv: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v4 + + - name: Create Python virtual environment + run: python -m venv ${{ github.workspace }}/venv + + - name: Install dependencies + run: | + ${{ github.workspace }}/venv/bin/pip install -e .[dev] + ${{ github.workspace }}/venv/bin/pip install pytest + + - name: Upload virtual environment as artifact + uses: actions/upload-artifact@v4 + with: + name: venv + path: ${{ github.workspace }}/venv + + # Job to run tests in parallel run: needs: @@ -69,15 +93,29 @@ jobs: timeout-minutes: 45 strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 8 # Set the maximum number of parallel jobs + max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in SubtensorCI runner) matrix: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} steps: - - name: Check-out repository under $GITHUB_WORKSPACE + - name: Check-out repository uses: actions/checkout@v4 + - name: Download virtual environment + uses: actions/download-artifact@v4 + with: + name: venv + path: venv + + - name: Install Python + uses: actions/setup-python@v4 + + - name: Install requirements + run: | + ${{ github.workspace }}/venv/bin/pip install -e .[dev] + ${{ github.workspace }}/venv/bin/pip install pytest + # - name: Install uv # uses: astral-sh/setup-uv@v4 # @@ -94,6 +132,4 @@ jobs: - name: Run tests run: | - pip install -e .[dev] - pip install pytest - pytest ${{ matrix.test-file }} -s + ${{ github.workspace }}/venv/bin/pytest ${{ matrix.test-file }} -s From c3595ea67cde6fcc24f1743029b8a3988303edcc Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 17:57:47 -0700 Subject: [PATCH 22/45] add needs --- .github/workflows/e2e-subtensor-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index e29773fd00..9ca618439b 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -89,6 +89,7 @@ jobs: needs: - find-tests - pull-docker-image + - create_venv runs-on: ubuntu-latest timeout-minutes: 45 strategy: From 93d8b1c94e54810cfeb259f89f6a0f953214ba3c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:04:29 -0700 Subject: [PATCH 23/45] remove venv job --- .github/workflows/e2e-subtensor-tests.yaml | 40 +++++----------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 9ca618439b..4f79c60ced 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -60,36 +60,11 @@ jobs: name: subtensor-localnet path: subtensor-localnet.tar - create_venv: - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v4 - - - name: Create Python virtual environment - run: python -m venv ${{ github.workspace }}/venv - - - name: Install dependencies - run: | - ${{ github.workspace }}/venv/bin/pip install -e .[dev] - ${{ github.workspace }}/venv/bin/pip install pytest - - - name: Upload virtual environment as artifact - uses: actions/upload-artifact@v4 - with: - name: venv - path: ${{ github.workspace }}/venv - - # Job to run tests in parallel run: needs: - find-tests - pull-docker-image - - create_venv runs-on: ubuntu-latest timeout-minutes: 45 strategy: @@ -111,11 +86,8 @@ jobs: - name: Install Python uses: actions/setup-python@v4 - - - name: Install requirements - run: | - ${{ github.workspace }}/venv/bin/pip install -e .[dev] - ${{ github.workspace }}/venv/bin/pip install pytest + with: + python-version: '3.12' # - name: Install uv # uses: astral-sh/setup-uv@v4 @@ -131,6 +103,10 @@ jobs: - name: Load Docker Image run: docker load -i subtensor-localnet.tar - - name: Run tests + - name: Install requirements run: | - ${{ github.workspace }}/venv/bin/pytest ${{ matrix.test-file }} -s + pip install -e .[dev] + pip install pytest + + - name: Run tests + run: pytest ${{ matrix.test-file }} -s From 096a312151a0d97fbd73763720157cbac32bb13b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:05:39 -0700 Subject: [PATCH 24/45] fix --- .github/workflows/e2e-subtensor-tests.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 4f79c60ced..013db0c44d 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -78,12 +78,6 @@ jobs: - name: Check-out repository uses: actions/checkout@v4 - - name: Download virtual environment - uses: actions/download-artifact@v4 - with: - name: venv - path: venv - - name: Install Python uses: actions/setup-python@v4 with: From 85067d2462cd085c35da417e3ca7bbc8e2b2a598 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:15:38 -0700 Subject: [PATCH 25/45] test time with uv --- .github/workflows/e2e-subtensor-tests.yaml | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 013db0c44d..bf76b57161 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -78,16 +78,16 @@ jobs: - name: Check-out repository uses: actions/checkout@v4 - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' +# - name: Install Python +# uses: actions/setup-python@v4 +# with: +# python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v4 -# - name: Install uv -# uses: astral-sh/setup-uv@v4 -# -# - name: install dependencies -# run: uv sync --all-extras --dev + - name: install dependencies + run: uv sync --all-extras --dev - name: Download Cached Docker Image uses: actions/download-artifact@v4 @@ -97,10 +97,10 @@ jobs: - name: Load Docker Image run: docker load -i subtensor-localnet.tar - - name: Install requirements - run: | - pip install -e .[dev] - pip install pytest +# - name: Install requirements +# run: | +# pip install -e .[dev] +# pip install pytest - name: Run tests run: pytest ${{ matrix.test-file }} -s From 049aeb8f5c95bfc45762534a78836202523e87e6 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:16:44 -0700 Subject: [PATCH 26/45] test time with uv --- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index bf76b57161..86c7f0f928 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -103,4 +103,4 @@ jobs: # pip install pytest - name: Run tests - run: pytest ${{ matrix.test-file }} -s + run: uv run pytest ${{ matrix.test-file }} -s From a594e7370cf429acd7a598261bb27ea74c8d374d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:21:15 -0700 Subject: [PATCH 27/45] remove debug file --- manual_testing.py | 91 ----------------------------------------------- 1 file changed, 91 deletions(-) delete mode 100644 manual_testing.py diff --git a/manual_testing.py b/manual_testing.py deleted file mode 100644 index 90b4e95b02..0000000000 --- a/manual_testing.py +++ /dev/null @@ -1,91 +0,0 @@ -from bittensor.core.subtensor import Subtensor -from bittensor.utils.balance import tao -from bittensor_wallet.wallet import Wallet - -COLDKEY = "5G4T9VnZfUDD2vD3MdKsgmbSAsKQq5rtZ73JtQuFA8SRfW14" -HOTKEY = "5GQenM6A7sWGxgmPPAorKGnFQsBWczXDhGhZjkbZq3Wr9wtX" -HOTKEY_NETUIDS = [0, 4, 6] - -DEVNET = "wss://dev.chain.opentensor.ai:443" -wallet = Wallet(path="~/.bittensor/wallets", name="alice") - - -subtensor = Subtensor(network="test") - - -def main(): - # print(subtensor.get_hyperparameter("LastUpdate", 1)) - # print(subtensor.get_hyperparameter("MinDifficulty", 1)) # does not exist - # print(subtensor.all_subnets()) - # print(subtensor.blocks_since_last_update(1, 1)) - # print(subtensor.bonds(1)) # looking into - # print(subtensor.commit_reveal_enabled(1)) - # print(subtensor.difficulty(1)) - # print(subtensor.does_hotkey_exist(HOTKEY)) - # print(subtensor.get_all_subnets_info()) - # print(subtensor.get_balance(COLDKEY)) - # print(subtensor.get_balances(COLDKEY)) - - # print(current_block := subtensor.get_current_block()) - - # print(subtensor._get_block_hash(current_block)) - # print(subtensor.get_block_hash(current_block)) - # print(subtensor.get_children(HOTKEY, 1)) # maybe not working - # print(subtensor.get_metagraph_info(1)) - # print(subtensor.subnet(1)) - # print(subtensor.get_all_metagraphs_info()) - # print(subtensor.get_netuids_for_hotkey(HOTKEY)) - # print(subtensor.get_neuron_certificate()) # untested - # print(subtensor.get_neuron_for_pubkey_and_subnet(HOTKEY, HOTKEY_NETUIDS[1])) - # print(subtensor.get_stake(COLDKEY, HOTKEY, HOTKEY_NETUIDS[1])) - # print(subtensor.get_stake_for_coldkey_and_hotkey(COLDKEY, HOTKEY, HOTKEY_NETUIDS)) - # print(subtensor.get_stake_for_coldkey(COLDKEY)) - # print(type(subtensor.get_subnet_burn_cost())) - - # print(subtensor.get_subnet_hyperparameters(111)) - # print(subtensor.get_subnets(block=current_block-20)) - # print(subtensor.get_total_stake_for_hotkey(HOTKEY)) - # print(subtensor.get_total_subnets()) - # print(subtensor.get_transfer_fee(wallet=wallet, dest=COLDKEY, value=tao(1.0))) - # print(subtensor.get_uid_for_hotkey_on_subnet(HOTKEY, HOTKEY_NETUIDS[1])) - # print(subtensor.immunity_period(HOTKEY_NETUIDS[1])) - # print(subtensor.is_hotkey_delegate(COLDKEY)) - # print(subtensor.is_hotkey_registered(HOTKEY, HOTKEY_NETUIDS[1])) - # print(subtensor.is_hotkey_registered_any(HOTKEY)) - # print(subtensor.is_hotkey_registered_on_subnet(HOTKEY, HOTKEY_NETUIDS[1])) - # print(subtensor.last_drand_round()) - # print(subtensor.max_weight_limit(HOTKEY_NETUIDS[1])) - # print(subtensor.metagraph(HOTKEY_NETUIDS[1], lite=True)) - # print(subtensor.metagraph(HOTKEY_NETUIDS[1], lite=False)) - # print(subtensor.min_allowed_weights(HOTKEY_NETUIDS[1])) - # print(subtensor.neuron_for_uid(1, HOTKEY_NETUIDS[1])) - # print(subtensor.neurons(HOTKEY_NETUIDS[1])) - # print(subtensor.neurons_lite(HOTKEY_NETUIDS[1])) - # print(subtensor.query_identity(HOTKEY)) - # print(subtensor.recycle(HOTKEY_NETUIDS[1])) - # print(subtensor.subnet_exists(2)) - # print(subtensor.subnet_exists(200)) - # print(subtensor.subnetwork_n(HOTKEY_NETUIDS[1])) - # print(subtensor.tempo(HOTKEY_NETUIDS[1])) - # print(subtensor.tx_rate_limit()) - # print(subtensor.wait_for_block()) - # print(subtensor.wait_for_block(current_block+5)) - # for subnet in subtensor.get_subnets(): - # print(subtensor.weights(subnet)) - # print(subtensor.weights_rate_limit(HOTKEY_NETUIDS[1])) - print(block := subtensor.block) - # for uid in range(0, 7): - # print(subtensor.get_commitment(2, uid, block=block)) - # print(subtensor.get_current_weight_commit_info(1)) - # print(subtensor.get_delegate_by_hotkey(HOTKEY)) - # print(subtensor.get_delegate_identities()) - # print(subtensor.get_delegate_take("5GP7c3fFazW9GXK8Up3qgu2DJBk8inu4aK9TZy3RuoSWVCMi", block=block)) - # print(subtensor.get_delegated(COLDKEY, block=block)) - # print(subtensor.get_delegates(block=block)) - # print(subtensor.get_existential_deposit(block=block)) - # print(subtensor.get_hotkey_owner("5DcpkDYwWHgtkhLhKFCMqTQeU1ggRKjQEoLCf1e3X47VxZzw")) - # print(subtensor.get_minimum_required_stake()) - - -if __name__ == "__main__": - main() From c117432e78535474c433826519af5b6859ce798c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 14 Mar 2025 18:33:13 -0700 Subject: [PATCH 28/45] clean up --- .github/workflows/e2e-subtensor-tests.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 86c7f0f928..97df3354be 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -78,11 +78,6 @@ jobs: - name: Check-out repository uses: actions/checkout@v4 -# - name: Install Python -# uses: actions/setup-python@v4 -# with: -# python-version: '3.12' - - name: Install uv uses: astral-sh/setup-uv@v4 @@ -97,10 +92,5 @@ jobs: - name: Load Docker Image run: docker load -i subtensor-localnet.tar -# - name: Install requirements -# run: | -# pip install -e .[dev] -# pip install pytest - - name: Run tests run: uv run pytest ${{ matrix.test-file }} -s From 0ac6ffc522731329e607fa829182369a543301a1 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Mar 2025 11:45:37 -0700 Subject: [PATCH 29/45] fix comments + new logic --- tests/e2e_tests/conftest.py | 84 +++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index b93c42daf2..a9037ee127 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,19 +1,19 @@ import os import re -import shutil import shlex +import shutil import signal import subprocess import sys import threading import time -from bittensor.utils.btlogging import logging import pytest from async_substrate_interface import SubstrateInterface from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.subtensor import Subtensor +from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( Templates, setup_wallet, @@ -55,24 +55,24 @@ def local_chain(request): """Determines whether to run the localnet.sh script in a subprocess or a Docker container.""" args = request.param if hasattr(request, "param") else None params = "" if args is None else f"{args}" - if shutil.which("docker"): + if shutil.which("docker") and not os.getenv("USE_DOCKER") == "0": yield from docker_runner(params) - return - - if sys.platform.startswith("linux"): - docker_commend = ( - "Install docker with command " - "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]" - ) - elif sys.platform == "darwin": - docker_commend = "Install docker with command [blue]brew install docker[/blue]" else: - docker_commend = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" - - logging.warning("Docker not found in the operating system!") - logging.warning(docker_commend) - logging.warning("Tests are run in legacy mode.") - yield from legacy_runner(request) + if not os.getenv("USE_DOCKER") == "0": + if sys.platform.startswith("linux"): + docker_command = ( + "Install docker with command " + "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]" + ) + elif sys.platform == "darwin": + docker_command = "Install docker with command [blue]brew install docker[/blue] or use documentation [blue]https://docs.docker.com/engine/install/[/blue]" + else: + docker_command = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" + + logging.warning("Docker not found in the operating system!") + logging.warning(docker_command) + logging.warning("Tests are run in legacy mode.") + yield from legacy_runner(request) def legacy_runner(params): @@ -103,7 +103,8 @@ def legacy_runner(params): except TimeoutError: raise else: - yield SubstrateInterface(url="ws://127.0.0.1:9944") + with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: + yield substrate finally: # Terminate the process group (includes all child processes) os.killpg(os.getpgid(process.pid), signal.SIGTERM) @@ -119,6 +120,44 @@ def legacy_runner(params): def docker_runner(params): """Starts a Docker container before tests and gracefully terminates it after.""" + def is_docker_running(): + """Check if Docker has been run.""" + try: + subprocess.run( + ["docker", "info"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + return True + except subprocess.CalledProcessError: + return False + + def try_start_docker(): + """Run docker based on OS.""" + try: + subprocess.run(["open", "-a", "Docker"], check=True) # macOS + except FileNotFoundError: + try: + subprocess.run(["systemctl", "start", "docker"], check=True) # Linux + except FileNotFoundError: + try: + subprocess.run( + ["sudo", "service", "docker", "start"], check=True + ) # Linux alternative + except FileNotFoundError: + print("Failed to start Docker. Manual start may be required.") + return False + + # Wait Docker run 10 attempts with 3 sec waits + for _ in range(10): + if is_docker_running(): + return True + time.sleep(3) + + print("Docker wasn't run. Manual start may be required.") + return False + container_name = f"test_local_chain_{str(time.time()).replace(".", "_")}" image_name = "ghcr.io/opentensor/subtensor-localnet:latest" @@ -137,6 +176,8 @@ def docker_runner(params): params, ] + try_start_docker() + # Start container with subprocess.Popen( cmds, @@ -147,7 +188,7 @@ def docker_runner(params): ) as process: try: try: - wait_for_node_start(process, int(time.time())) + wait_for_node_start(process, timestamp=int(time.time())) except TimeoutError: raise @@ -159,7 +200,8 @@ def docker_runner(params): if not result.stdout.strip(): raise RuntimeError("Docker container failed to start.") - yield SubstrateInterface(url="ws://127.0.0.1:9944") + with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: + yield substrate finally: try: From 9a2c53173d59267a54d28eb4d0313b611a83fa35 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Mar 2025 11:46:04 -0700 Subject: [PATCH 30/45] update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 109a321030..8de9667c7d 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,49 @@ The Python interpreter output will look like below. >>> ``` +### Testing +You can run integration and unit tests in interactive mode of IDE or in terminal mode using the command: +```bash +pytest tests/integration_tests +pytest tests/unit_tests +``` + +#### E2E tests have 2 options for launching (legacy runner): +- using a compiler based on the substrait code +- using an already built docker image (docker runner) + +#### Using `docker runner` (default for now): +- E2E tests with docker image do not require preliminary compilation +- are executed very quickly +- require docker installed in OS + +Ho to use: +```bash +pytest tests/e2e_tests +``` + +#### TUsing `legacy runner`: +- Will start compilation of the collected code in your subtensor repository +- you must provide the `LOCALNET_SH_PATH` variable in the local environment with the path to the file `/scripts/localnet.sh` in the cloned repository within your OS +- you can use the `BUILD_BINARY=0` variable, this will skip the copy step for each test. +- you can use the `USE_DOCKER=0` variable, this will run tests using the "legacy runner", even if docker is installed in your OS + +#### Ho to use: +Regular e2e tests run +```bash +LOCALNET_SH_PATH=/path/to/your/localnet.sh pytest tests/e2e_tests +``` + +If you want to skip re-build process for each e2e test +```bash +BUILD_BINARY=0 LOCALNET_SH_PATH=/path/to/your/localnet.sh pytest tests/e2e_tests +``` + +If you want to use legacy runner even with installed Docker in your OS +```bash +USE_DOCKER=0 BUILD_BINARY=0 LOCALNET_SH_PATH=/path/to/your/localnet.sh pytest tests/e2e_tests +``` + --- ## Release Guidelines From ef8798648076eac260ef333c5ed1ec69f95ff850 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Mar 2025 11:48:17 -0700 Subject: [PATCH 31/45] refactor --- tests/e2e_tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index a9037ee127..d0b270cf65 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -63,9 +63,10 @@ def local_chain(request): docker_command = ( "Install docker with command " "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]" + " or use documentation [blue]https://docs.docker.com/engine/install/[/blue]" ) elif sys.platform == "darwin": - docker_command = "Install docker with command [blue]brew install docker[/blue] or use documentation [blue]https://docs.docker.com/engine/install/[/blue]" + docker_command = "Install docker with command [blue]brew install docker[/blue]" else: docker_command = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" From 5022a5febc37ed274158ee5ccbb17d3cb7af0e82 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Mar 2025 11:48:25 -0700 Subject: [PATCH 32/45] ruff --- tests/e2e_tests/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index d0b270cf65..fd36947885 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -66,7 +66,9 @@ def local_chain(request): " or use documentation [blue]https://docs.docker.com/engine/install/[/blue]" ) elif sys.platform == "darwin": - docker_command = "Install docker with command [blue]brew install docker[/blue]" + docker_command = ( + "Install docker with command [blue]brew install docker[/blue]" + ) else: docker_command = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" From a23f2cd60be4fc47ab9fa55c38858f0b5b4fee03 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Mar 2025 11:51:38 -0700 Subject: [PATCH 33/45] fix except --- tests/e2e_tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index fd36947885..51a865b1b8 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -140,15 +140,15 @@ def try_start_docker(): """Run docker based on OS.""" try: subprocess.run(["open", "-a", "Docker"], check=True) # macOS - except FileNotFoundError: + except (FileNotFoundError, subprocess.CalledProcessError): try: subprocess.run(["systemctl", "start", "docker"], check=True) # Linux - except FileNotFoundError: + except (FileNotFoundError, subprocess.CalledProcessError): try: subprocess.run( ["sudo", "service", "docker", "start"], check=True ) # Linux alternative - except FileNotFoundError: + except (FileNotFoundError, subprocess.CalledProcessError): print("Failed to start Docker. Manual start may be required.") return False From f859a0a2500ed1efa7a67aaae669c5bf4d37fc1d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 11:59:50 -0700 Subject: [PATCH 34/45] Added to subtensor --- bittensor/core/subtensor.py | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 244080ff51..d8c4cc9f9f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1210,6 +1210,69 @@ def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) + def get_stake_fee( + self, + origin_hotkey_ss58: Optional[str], + origin_netuid: Optional[int], + origin_coldkey_ss58: str, + destination_hotkey_ss58: Optional[str], + destination_netuid: Optional[int], + destination_coldkey_ss58: str, + amount: Balance, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for a staking operation. + :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake) + :param origin_netuid: Netuid of source subnet (None for new stake) + :param origin_coldkey_ss58: SS58 address of source coldkey + :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake) + :param destination_netuid: Netuid of destination subnet (None for removing stake) + :param destination_coldkey_ss58: SS58 address of destination coldkey + :param amount: Amount of stake to transfer in RAO + :param block_hash: Optional block hash at which to perform the calculation + :return: The calculated stake fee as a Balance object + When to use None: + 1. Adding new stake (default fee): + - origin_hotkey_ss58 = None + - origin_netuid = None + - All other fields required + 2. Removing stake (default fee): + - destination_hotkey_ss58 = None + - destination_netuid = None + - All other fields required + For all other operations, no None values - provide all parameters: + 3. Moving between subnets + 4. Moving between hotkeys + 5. Moving between coldkeys + """ + + origin = None + if origin_hotkey_ss58 is not None and origin_netuid is not None: + origin = (origin_hotkey_ss58, origin_netuid) + + destination = None + if destination_hotkey_ss58 is not None and destination_netuid is not None: + destination = (destination_hotkey_ss58, destination_netuid) + + result = self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + origin, + origin_coldkey_ss58, + destination, + destination_coldkey_ss58, + amount, + ], + block=block, + ) + + if result is None: + raise Exception("Unable to retrieve stake fee.") + + return Balance.from_rao(result) + def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, From d32dc2c9b0bb7fc558f5b16ebe9497d5df6c300b Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 12:00:05 -0700 Subject: [PATCH 35/45] Added to async subtensor --- bittensor/core/async_subtensor.py | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 546be4a9d0..8c5b094192 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1575,6 +1575,72 @@ async def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) + async def get_stake_fee( + self, + origin_hotkey_ss58: Optional[str], + origin_netuid: Optional[int], + origin_coldkey_ss58: str, + destination_hotkey_ss58: Optional[str], + destination_netuid: Optional[int], + destination_coldkey_ss58: str, + amount: int, + block_hash: Optional[str] = None, + ) -> Balance: + """ + Calculates the fee for a staking operation. + + :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake) + :param origin_netuid: Netuid of source subnet (None for new stake) + :param origin_coldkey_ss58: SS58 address of source coldkey + :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake) + :param destination_netuid: Netuid of destination subnet (None for removing stake) + :param destination_coldkey_ss58: SS58 address of destination coldkey + :param amount: Amount of stake to transfer in RAO + :param block_hash: Optional block hash at which to perform the calculation + + :return: The calculated stake fee as a Balance object + + When to use None: + + 1. Adding new stake (default fee): + - origin_hotkey_ss58 = None + - origin_netuid = None + - All other fields required + + 2. Removing stake (default fee): + - destination_hotkey_ss58 = None + - destination_netuid = None + - All other fields required + + For all other operations, no None values - provide all parameters: + 3. Moving between subnets + 4. Moving between hotkeys + 5. Moving between coldkeys + """ + + origin = None + if origin_hotkey_ss58 is not None and origin_netuid is not None: + origin = (origin_hotkey_ss58, origin_netuid) + + destination = None + if destination_hotkey_ss58 is not None and destination_netuid is not None: + destination = (destination_hotkey_ss58, destination_netuid) + + result = await self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + origin, + origin_coldkey_ss58, + destination, + destination_coldkey_ss58, + amount, + ], + block_hash=block_hash, + ) + + return Balance.from_rao(result if result is not None else 0) + async def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, From 03cd1db81b81a537cee7d4c5a29215acdccecc82 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 12:00:18 -0700 Subject: [PATCH 36/45] adds unit test --- tests/unit_tests/test_subtensor.py | 91 ++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 2d7f1efb78..6be211a061 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2611,6 +2611,97 @@ def test_get_minimum_required_stake_query_failure(mocker, subtensor): ) +def test_get_stake_fee_parameter_handling(mocker, subtensor): + """Test get_stake_fee parameter handling, focusing on None cases and tuple creation.""" + # Mock data + fake_hotkey = "hk1" + fake_hotkey2 = "hk2" + fake_coldkey = "ck1" + fake_amount = Balance.from_tao(100) + netuid = 1 + fake_fee = 1_000_000 + + # Mock return fee + mock_query = mocker.patch.object( + subtensor, + "query_runtime_api", + side_effect=lambda runtime_api, method, params, block: ( + fake_fee + if runtime_api == "StakeInfoRuntimeApi" and method == "get_stake_fee" + else None + ), + ) + + # Test Cases + test_cases = [ + # 1. Adding new stake (origin is None) + { + "name": "new_stake", + "params": { + "origin_hotkey_ss58": None, + "origin_netuid": None, + "origin_coldkey_ss58": fake_coldkey, + "destination_hotkey_ss58": fake_hotkey, + "destination_netuid": netuid, + "destination_coldkey_ss58": fake_coldkey, + "amount": fake_amount, + }, + "expected_origin": None, + "expected_destination": (fake_hotkey, netuid), + }, + # 2. Removing stake (destination is None) + { + "name": "remove_stake", + "params": { + "origin_hotkey_ss58": fake_hotkey, + "origin_netuid": netuid, + "origin_coldkey_ss58": fake_coldkey, + "destination_hotkey_ss58": None, + "destination_netuid": None, + "destination_coldkey_ss58": fake_coldkey, + "amount": fake_amount, + }, + "expected_origin": (fake_hotkey, netuid), + "expected_destination": None, + }, + # 3. All parameters present + { + "name": "all_parameters", + "params": { + "origin_hotkey_ss58": fake_hotkey, + "origin_netuid": netuid, + "origin_coldkey_ss58": fake_coldkey, + "destination_hotkey_ss58": fake_hotkey2, + "destination_netuid": netuid, + "destination_coldkey_ss58": fake_coldkey, + "amount": fake_amount, + }, + "expected_origin": (fake_hotkey, netuid), + "expected_destination": (fake_hotkey2, netuid), + }, + ] + + for test_case in test_cases: + mock_query.reset_mock() + + result = subtensor.get_stake_fee(**test_case["params"]) + assert isinstance(result, Balance) + assert result == Balance.from_rao(fake_fee) + + mock_query.assert_called_once_with( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + test_case["expected_origin"], + test_case["params"]["origin_coldkey_ss58"], + test_case["expected_destination"], + test_case["params"]["destination_coldkey_ss58"], + test_case["params"]["amount"], + ], + block=None, + ) + + def test_get_minimum_required_stake_invalid_result(mocker, subtensor): """Test when the result cannot be decoded.""" # Mock data From 09ed453fbefc3cc6a219f70adcb4fba1e4ecba3c Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 12:19:05 -0700 Subject: [PATCH 37/45] added e2es --- tests/e2e_tests/test_stake_fee.py | 154 ++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 tests/e2e_tests/test_stake_fee.py diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py new file mode 100644 index 0000000000..dd5af0e39f --- /dev/null +++ b/tests/e2e_tests/test_stake_fee.py @@ -0,0 +1,154 @@ +import pytest +from bittensor import Balance + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +@pytest.mark.asyncio +async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): + """ + Tests the stake fee calculation mechanism for various staking operations + + Steps: + 1. Register a subnet through Alice + 2. Set up test parameters + 3. Test stake fees for different scenarios: + - Adding new stake + - Removing stake + - Moving between subnets + - Moving between hotkeys + - Moving between coldkeys + - Swapping between subnets + """ + MIN_STAKE_FEE = Balance.from_rao(50_000) + netuid = 2 + root_netuid = 0 + stake_amount = Balance.from_tao(100).rao # 100 TAO + + # Register subnet as Alice + assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" + assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + + # Add_stake (new stake) + stake_fee_0 = subtensor.get_stake_fee( + origin_hotkey_ss58=None, + origin_netuid=None, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, + destination_netuid=netuid, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object" + + # Remove stake + stake_fee_1 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=root_netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=None, + destination_netuid=None, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_1, Balance), "Stake fee should be a Balance object" + + # Move from root to non-root + stake_fee_2 = subtensor.get_stake_fee( + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=root_netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, + destination_netuid=netuid, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_2, Balance), "Stake fee should be a Balance object" + + # Move between hotkeys on root + stake_fee_3 = subtensor.get_stake_fee( + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=root_netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=root_netuid, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_3, Balance), "Stake fee should be a Balance object" + + # Move between coldkeys on root + stake_fee_4 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=root_netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=root_netuid, + destination_coldkey_ss58=bob_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_4, Balance), "Stake fee should be a Balance object" + + # Swap from non-root to root + stake_fee_5 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=root_netuid, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_5, Balance), "Stake fee should be a Balance object" + + # Move between hotkeys on non-root + stake_fee_6 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, + destination_netuid=netuid, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_6, Balance), "Stake fee should be a Balance object" + + # Move between coldkeys on non-root + stake_fee_7 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=netuid, + destination_coldkey_ss58=bob_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_7, Balance), "Stake fee should be a Balance object" + + # Swap from non-root to non-root (between subnets) + netuid2 = 3 + assert subtensor.register_subnet( + alice_wallet + ), "Unable to register the second subnet" + assert subtensor.subnet_exists(netuid2), "Second subnet wasn't created successfully" + + stake_fee_8 = subtensor.get_stake_fee( + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_netuid=netuid, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=netuid2, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + amount=stake_amount, + ) + assert isinstance(stake_fee_8, Balance), "Stake fee should be a Balance object" + + # Verify all fees are non-zero + assert stake_fee_0 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_1 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_2 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_3 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_4 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_5 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_6 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_7 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert stake_fee_8 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" From d4c3a163981ba3688988b9d7a09ed28da5538b8a Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 12:53:35 -0700 Subject: [PATCH 38/45] Bumps async substrate --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c95f3ba4f7..146da6265b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "websockets>=14.1", "bittensor-commit-reveal>=0.2.0", "bittensor-wallet>=3.0.4", - "async-substrate-interface>=1.0.5" + "async-substrate-interface>=1.0.8" ] [project.optional-dependencies] From 8016b02e66c28b1c35f4a7f6becec86cdf0870b7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 17 Mar 2025 22:16:04 +0200 Subject: [PATCH 39/45] Allows installation on 3.13 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c95f3ba4f7..14f0928d39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ {name = "bittensor.com"} ] license = { file = "LICENSE" } -requires-python = ">=3.9,<3.13" +requires-python = ">=3.9,<3.14" dependencies = [ "wheel", "setuptools~=70.0.0", @@ -38,7 +38,7 @@ dependencies = [ "websockets>=14.1", "bittensor-commit-reveal>=0.2.0", "bittensor-wallet>=3.0.4", - "async-substrate-interface>=1.0.5" + "async-substrate-interface>=1.0.8" ] [project.optional-dependencies] From a5d391dcb9fe56cbe94e7b332155f69724816251 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 13:45:52 -0700 Subject: [PATCH 40/45] improve e2e --- tests/e2e_tests/test_stake_fee.py | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index dd5af0e39f..1f97f6a785 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -39,6 +39,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_0 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Remove stake stake_fee_1 = subtensor.get_stake_fee( @@ -51,6 +54,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_1, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_1 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Move from root to non-root stake_fee_2 = subtensor.get_stake_fee( @@ -63,6 +69,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_2, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_2 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Move between hotkeys on root stake_fee_3 = subtensor.get_stake_fee( @@ -75,6 +84,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_3, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_3 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Move between coldkeys on root stake_fee_4 = subtensor.get_stake_fee( @@ -87,6 +99,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_4, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_4 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Swap from non-root to root stake_fee_5 = subtensor.get_stake_fee( @@ -99,6 +114,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_5, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_5 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Move between hotkeys on non-root stake_fee_6 = subtensor.get_stake_fee( @@ -111,6 +129,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_6, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_6 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Move between coldkeys on non-root stake_fee_7 = subtensor.get_stake_fee( @@ -123,6 +144,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_7, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee_7 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" # Swap from non-root to non-root (between subnets) netuid2 = 3 @@ -141,14 +165,6 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): amount=stake_amount, ) assert isinstance(stake_fee_8, Balance), "Stake fee should be a Balance object" - - # Verify all fees are non-zero - assert stake_fee_0 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_1 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_2 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_3 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_4 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_5 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_6 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_7 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" - assert stake_fee_8 >= MIN_STAKE_FEE, "Stake fee should be greater than 0" + assert ( + stake_fee_8 >= MIN_STAKE_FEE + ), "Stake fee should be greater than the minimum stake fee" From f7c22739c11cc60581e8f88141363d88ddb795a9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 15:26:10 -0700 Subject: [PATCH 41/45] Improves all apis --- bittensor/core/async_subtensor.py | 135 ++++++++++++++------- bittensor/core/subtensor.py | 138 ++++++++++++++------- tests/e2e_tests/test_stake_fee.py | 189 +++++++++++------------------ tests/unit_tests/test_subtensor.py | 181 ++++++++++++++------------- 4 files changed, 341 insertions(+), 302 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8c5b094192..7eb4f04eb2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1575,71 +1575,116 @@ async def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - async def get_stake_fee( + async def get_stake_add_fee( self, - origin_hotkey_ss58: Optional[str], - origin_netuid: Optional[int], - origin_coldkey_ss58: str, - destination_hotkey_ss58: Optional[str], - destination_netuid: Optional[int], - destination_coldkey_ss58: str, - amount: int, - block_hash: Optional[str] = None, + amount: Balance, + netuid: int, + coldkey_ss58: str, + hotkey_ss58: str, + block: Optional[int] = None, ) -> Balance: """ - Calculates the fee for a staking operation. - - :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake) - :param origin_netuid: Netuid of source subnet (None for new stake) - :param origin_coldkey_ss58: SS58 address of source coldkey - :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake) - :param destination_netuid: Netuid of destination subnet (None for removing stake) - :param destination_coldkey_ss58: SS58 address of destination coldkey - :param amount: Amount of stake to transfer in RAO - :param block_hash: Optional block hash at which to perform the calculation + Calculates the fee for adding new stake to a hotkey. - :return: The calculated stake fee as a Balance object + Args: + amount: Amount of stake to add in TAO + netuid: Netuid of subnet + coldkey_ss58: SS58 address of source coldkey + hotkey_ss58: SS58 address of destination hotkey + block: Block number at which to perform the calculation - When to use None: + Returns: + The calculated stake fee as a Balance object + """ + result = await self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + None, + coldkey_ss58, + (hotkey_ss58, netuid), + coldkey_ss58, + amount.rao, + ], + block=block, + ) + return Balance.from_rao(result) - 1. Adding new stake (default fee): - - origin_hotkey_ss58 = None - - origin_netuid = None - - All other fields required + async def get_unstake_fee( + self, + amount: Balance, + netuid: int, + coldkey_ss58: str, + hotkey_ss58: str, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for unstaking from a hotkey. - 2. Removing stake (default fee): - - destination_hotkey_ss58 = None - - destination_netuid = None - - All other fields required + Args: + amount: Amount of stake to unstake in TAO + netuid: Netuid of subnet + coldkey_ss58: SS58 address of source coldkey + hotkey_ss58: SS58 address of destination hotkey + block: Block number at which to perform the calculation - For all other operations, no None values - provide all parameters: - 3. Moving between subnets - 4. Moving between hotkeys - 5. Moving between coldkeys + Returns: + The calculated stake fee as a Balance object """ + result = await self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + None, + coldkey_ss58, + (hotkey_ss58, netuid), + coldkey_ss58, + amount.rao, + ], + block=block, + ) + return Balance.from_rao(result) - origin = None - if origin_hotkey_ss58 is not None and origin_netuid is not None: - origin = (origin_hotkey_ss58, origin_netuid) + async def get_stake_movement_fee( + self, + amount: Balance, + origin_netuid: int, + origin_hotkey_ss58: str, + origin_coldkey_ss58: str, + destination_netuid: int, + destination_hotkey_ss58: str, + destination_coldkey_ss58: str, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for moving stake between hotkeys/subnets/coldkeys. - destination = None - if destination_hotkey_ss58 is not None and destination_netuid is not None: - destination = (destination_hotkey_ss58, destination_netuid) + Args: + amount: Amount of stake to move in TAO + origin_netuid: Netuid of source subnet + origin_hotkey_ss58: SS58 address of source hotkey + origin_coldkey_ss58: SS58 address of source coldkey + destination_netuid: Netuid of destination subnet + destination_hotkey_ss58: SS58 address of destination hotkey + destination_coldkey_ss58: SS58 address of destination coldkey + block: Block number at which to perform the calculation + Returns: + The calculated stake fee as a Balance object + """ result = await self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", method="get_stake_fee", params=[ - origin, + (origin_hotkey_ss58, origin_netuid), origin_coldkey_ss58, - destination, + (destination_hotkey_ss58, destination_netuid), destination_coldkey_ss58, - amount, + amount.rao, ], - block_hash=block_hash, + block=block, ) - - return Balance.from_rao(result if result is not None else 0) + return Balance.from_rao(result) async def get_stake_for_coldkey_and_hotkey( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d8c4cc9f9f..e113c1b033 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1210,67 +1210,115 @@ def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - def get_stake_fee( + def get_stake_add_fee( self, - origin_hotkey_ss58: Optional[str], - origin_netuid: Optional[int], + amount: Balance, + netuid: int, + coldkey_ss58: str, + hotkey_ss58: str, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for adding new stake to a hotkey. + + Args: + amount: Amount of stake to add in TAO + netuid: Netuid of subnet + coldkey_ss58: SS58 address of coldkey + hotkey_ss58: SS58 address of hotkey + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ + result = self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + None, + coldkey_ss58, + (hotkey_ss58, netuid), + coldkey_ss58, + amount.rao, + ], + block=block, + ) + return Balance.from_rao(result) + + def get_unstake_fee( + self, + amount: Balance, + netuid: int, + coldkey_ss58: str, + hotkey_ss58: str, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for unstaking from a hotkey. + + Args: + amount: Amount of stake to unstake in TAO + netuid: Netuid of subnet + coldkey_ss58: SS58 address of coldkey + hotkey_ss58: SS58 address of hotkey + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ + result = self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + (hotkey_ss58, netuid), + coldkey_ss58, + None, + coldkey_ss58, + amount.rao, + ], + block=block, + ) + return Balance.from_rao(result) + + def get_stake_movement_fee( + self, + amount: Balance, + origin_netuid: int, + origin_hotkey_ss58: str, origin_coldkey_ss58: str, - destination_hotkey_ss58: Optional[str], - destination_netuid: Optional[int], + destination_netuid: int, + destination_hotkey_ss58: str, destination_coldkey_ss58: str, - amount: Balance, block: Optional[int] = None, ) -> Balance: """ - Calculates the fee for a staking operation. - :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake) - :param origin_netuid: Netuid of source subnet (None for new stake) - :param origin_coldkey_ss58: SS58 address of source coldkey - :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake) - :param destination_netuid: Netuid of destination subnet (None for removing stake) - :param destination_coldkey_ss58: SS58 address of destination coldkey - :param amount: Amount of stake to transfer in RAO - :param block_hash: Optional block hash at which to perform the calculation - :return: The calculated stake fee as a Balance object - When to use None: - 1. Adding new stake (default fee): - - origin_hotkey_ss58 = None - - origin_netuid = None - - All other fields required - 2. Removing stake (default fee): - - destination_hotkey_ss58 = None - - destination_netuid = None - - All other fields required - For all other operations, no None values - provide all parameters: - 3. Moving between subnets - 4. Moving between hotkeys - 5. Moving between coldkeys - """ - - origin = None - if origin_hotkey_ss58 is not None and origin_netuid is not None: - origin = (origin_hotkey_ss58, origin_netuid) - - destination = None - if destination_hotkey_ss58 is not None and destination_netuid is not None: - destination = (destination_hotkey_ss58, destination_netuid) + Calculates the fee for moving stake between hotkeys/subnets/coldkeys. + Args: + amount: Amount of stake to move in TAO + origin_netuid: Netuid of origin subnet + origin_hotkey_ss58: SS58 address of origin hotkey + origin_coldkey_ss58: SS58 address of origin coldkey + destination_netuid: Netuid of destination subnet + destination_hotkey_ss58: SS58 address of destination hotkey + destination_coldkey_ss58: SS58 address of destination coldkey + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ result = self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", method="get_stake_fee", params=[ - origin, + (origin_hotkey_ss58, origin_netuid), origin_coldkey_ss58, - destination, + (destination_hotkey_ss58, destination_netuid), destination_coldkey_ss58, - amount, + amount.rao, ], block=block, ) - - if result is None: - raise Exception("Unable to retrieve stake fee.") - return Balance.from_rao(result) def get_stake_for_coldkey_and_hotkey( diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 1f97f6a785..32062b6c6c 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -10,161 +10,108 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): Steps: 1. Register a subnet through Alice - 2. Set up test parameters - 3. Test stake fees for different scenarios: + 2. Test stake fees for: - Adding new stake - Removing stake - - Moving between subnets - - Moving between hotkeys - - Moving between coldkeys - - Swapping between subnets + - Moving stake between hotkeys/subnets/coldkeys """ - MIN_STAKE_FEE = Balance.from_rao(50_000) + netuid = 2 root_netuid = 0 - stake_amount = Balance.from_tao(100).rao # 100 TAO + stake_amount = Balance.from_tao(100) # 100 TAO + min_stake_fee = Balance.from_rao(50_000) # Register subnet as Alice assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" - # Add_stake (new stake) - stake_fee_0 = subtensor.get_stake_fee( - origin_hotkey_ss58=None, - origin_netuid=None, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, - destination_netuid=netuid, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + # Test add_stake fee + stake_fee_0 = subtensor.get_stake_add_fee( amount=stake_amount, + netuid=netuid, + coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object" assert ( - stake_fee_0 >= MIN_STAKE_FEE + stake_fee_0 >= min_stake_fee ), "Stake fee should be greater than the minimum stake fee" - # Remove stake - stake_fee_1 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, - origin_netuid=root_netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=None, - destination_netuid=None, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + # Test unstake fee + stake_fee_1 = subtensor.get_unstake_fee( amount=stake_amount, + netuid=root_netuid, + coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, ) assert isinstance(stake_fee_1, Balance), "Stake fee should be a Balance object" assert ( - stake_fee_1 >= MIN_STAKE_FEE + stake_fee_1 >= min_stake_fee ), "Stake fee should be greater than the minimum stake fee" - # Move from root to non-root - stake_fee_2 = subtensor.get_stake_fee( - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=root_netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, - destination_netuid=netuid, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_2, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_2 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" + # Test various stake movement scenarios + movement_scenarios = [ + # Move from root to non-root + { + "origin_netuid": root_netuid, + "origin_hotkey": alice_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": netuid, + "dest_hotkey": alice_wallet.hotkey.ss58_address, + "dest_coldkey": alice_wallet.coldkeypub.ss58_address, + }, + # Move between hotkeys on root + { + "origin_netuid": root_netuid, + "origin_hotkey": alice_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": root_netuid, + "dest_hotkey": bob_wallet.hotkey.ss58_address, + "dest_coldkey": alice_wallet.coldkeypub.ss58_address, + }, + # Move between coldkeys + { + "origin_netuid": root_netuid, + "origin_hotkey": bob_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": root_netuid, + "dest_hotkey": bob_wallet.hotkey.ss58_address, + "dest_coldkey": bob_wallet.coldkeypub.ss58_address, + }, + ] - # Move between hotkeys on root - stake_fee_3 = subtensor.get_stake_fee( - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=root_netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=root_netuid, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_3, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_3 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" + for scenario in movement_scenarios: + stake_fee = subtensor.get_stake_movement_fee( + amount=stake_amount, + origin_netuid=scenario["origin_netuid"], + origin_hotkey_ss58=scenario["origin_hotkey"], + origin_coldkey_ss58=scenario["origin_coldkey"], + destination_netuid=scenario["dest_netuid"], + destination_hotkey_ss58=scenario["dest_hotkey"], + destination_coldkey_ss58=scenario["dest_coldkey"], + ) + assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" + assert ( + stake_fee >= min_stake_fee + ), "Stake fee should be greater than the minimum stake fee" - # Move between coldkeys on root - stake_fee_4 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, - origin_netuid=root_netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=root_netuid, - destination_coldkey_ss58=bob_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_4, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_4 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" - - # Swap from non-root to root - stake_fee_5 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, - origin_netuid=netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=root_netuid, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_5, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_5 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" - - # Move between hotkeys on non-root - stake_fee_6 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, - origin_netuid=netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=alice_wallet.hotkey.ss58_address, - destination_netuid=netuid, - destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_6, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_6 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" - - # Move between coldkeys on non-root - stake_fee_7 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, - origin_netuid=netuid, - origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=netuid, - destination_coldkey_ss58=bob_wallet.coldkeypub.ss58_address, - amount=stake_amount, - ) - assert isinstance(stake_fee_7, Balance), "Stake fee should be a Balance object" - assert ( - stake_fee_7 >= MIN_STAKE_FEE - ), "Stake fee should be greater than the minimum stake fee" - - # Swap from non-root to non-root (between subnets) + # Test cross-subnet movement netuid2 = 3 assert subtensor.register_subnet( alice_wallet ), "Unable to register the second subnet" assert subtensor.subnet_exists(netuid2), "Second subnet wasn't created successfully" - stake_fee_8 = subtensor.get_stake_fee( - origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + stake_fee = subtensor.get_stake_movement_fee( + amount=stake_amount, origin_netuid=netuid, + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_netuid=netuid2, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, - amount=stake_amount, ) - assert isinstance(stake_fee_8, Balance), "Stake fee should be a Balance object" + assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" assert ( - stake_fee_8 >= MIN_STAKE_FEE + stake_fee >= min_stake_fee ), "Stake fee should be greater than the minimum stake fee" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 6be211a061..467652db27 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2611,97 +2611,6 @@ def test_get_minimum_required_stake_query_failure(mocker, subtensor): ) -def test_get_stake_fee_parameter_handling(mocker, subtensor): - """Test get_stake_fee parameter handling, focusing on None cases and tuple creation.""" - # Mock data - fake_hotkey = "hk1" - fake_hotkey2 = "hk2" - fake_coldkey = "ck1" - fake_amount = Balance.from_tao(100) - netuid = 1 - fake_fee = 1_000_000 - - # Mock return fee - mock_query = mocker.patch.object( - subtensor, - "query_runtime_api", - side_effect=lambda runtime_api, method, params, block: ( - fake_fee - if runtime_api == "StakeInfoRuntimeApi" and method == "get_stake_fee" - else None - ), - ) - - # Test Cases - test_cases = [ - # 1. Adding new stake (origin is None) - { - "name": "new_stake", - "params": { - "origin_hotkey_ss58": None, - "origin_netuid": None, - "origin_coldkey_ss58": fake_coldkey, - "destination_hotkey_ss58": fake_hotkey, - "destination_netuid": netuid, - "destination_coldkey_ss58": fake_coldkey, - "amount": fake_amount, - }, - "expected_origin": None, - "expected_destination": (fake_hotkey, netuid), - }, - # 2. Removing stake (destination is None) - { - "name": "remove_stake", - "params": { - "origin_hotkey_ss58": fake_hotkey, - "origin_netuid": netuid, - "origin_coldkey_ss58": fake_coldkey, - "destination_hotkey_ss58": None, - "destination_netuid": None, - "destination_coldkey_ss58": fake_coldkey, - "amount": fake_amount, - }, - "expected_origin": (fake_hotkey, netuid), - "expected_destination": None, - }, - # 3. All parameters present - { - "name": "all_parameters", - "params": { - "origin_hotkey_ss58": fake_hotkey, - "origin_netuid": netuid, - "origin_coldkey_ss58": fake_coldkey, - "destination_hotkey_ss58": fake_hotkey2, - "destination_netuid": netuid, - "destination_coldkey_ss58": fake_coldkey, - "amount": fake_amount, - }, - "expected_origin": (fake_hotkey, netuid), - "expected_destination": (fake_hotkey2, netuid), - }, - ] - - for test_case in test_cases: - mock_query.reset_mock() - - result = subtensor.get_stake_fee(**test_case["params"]) - assert isinstance(result, Balance) - assert result == Balance.from_rao(fake_fee) - - mock_query.assert_called_once_with( - runtime_api="StakeInfoRuntimeApi", - method="get_stake_fee", - params=[ - test_case["expected_origin"], - test_case["params"]["origin_coldkey_ss58"], - test_case["expected_destination"], - test_case["params"]["destination_coldkey_ss58"], - test_case["params"]["amount"], - ], - block=None, - ) - - def test_get_minimum_required_stake_invalid_result(mocker, subtensor): """Test when the result cannot be decoded.""" # Mock data @@ -3333,3 +3242,93 @@ def test_get_timestamp(mocker, subtensor): ) actual_result = subtensor.get_timestamp(block=fake_block) assert expected_result == actual_result + + +def test_stake_fee_methods(mocker, subtensor): + """Test the three stake fee calculation methods.""" + # Mock data + fake_hotkey = "hk1" + fake_coldkey = "ck1" + fake_amount = Balance.from_tao(100) + netuid = 1 + fake_fee = 1_000_000 + + # Mock return fee + mock_query = mocker.patch.object( + subtensor, + "query_runtime_api", + side_effect=lambda runtime_api, method, params, block: ( + fake_fee + if runtime_api == "StakeInfoRuntimeApi" and method == "get_stake_fee" + else None + ), + ) + + # get_stake_add_fee + result = subtensor.get_stake_add_fee( + amount=fake_amount, + netuid=netuid, + coldkey_ss58=fake_coldkey, + hotkey_ss58=fake_hotkey, + ) + assert isinstance(result, Balance) + assert result == Balance.from_rao(fake_fee) + mock_query.assert_called_with( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + None, + fake_coldkey, + (fake_hotkey, netuid), + fake_coldkey, + fake_amount.rao, + ], + block=None, + ) + + # get_unstake_fee + result = subtensor.get_unstake_fee( + amount=fake_amount, + netuid=netuid, + coldkey_ss58=fake_coldkey, + hotkey_ss58=fake_hotkey, + ) + assert isinstance(result, Balance) + assert result == Balance.from_rao(fake_fee) + mock_query.assert_called_with( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + None, + fake_coldkey, + (fake_hotkey, netuid), + fake_coldkey, + fake_amount.rao, + ], + block=None, + ) + + # get_stake_movement_fee + result = subtensor.get_stake_movement_fee( + amount=fake_amount, + origin_netuid=2, + origin_hotkey_ss58=fake_hotkey, + origin_coldkey_ss58=fake_coldkey, + destination_netuid=3, + destination_hotkey_ss58="hk2", + destination_coldkey_ss58=fake_coldkey, + ) + assert isinstance(result, Balance) + assert result == Balance.from_rao(fake_fee) + mock_query.assert_called_with( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_fee", + params=[ + (fake_hotkey, 2), + fake_coldkey, + ("hk2", 3), + fake_coldkey, + fake_amount.rao, + ], + block=None, + ) From 7b0c1eae1b2de599b0a4b9d98a1abbd29f20629c Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 15:35:52 -0700 Subject: [PATCH 42/45] Update test --- tests/unit_tests/test_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 467652db27..3af7f70072 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3299,10 +3299,10 @@ def test_stake_fee_methods(mocker, subtensor): runtime_api="StakeInfoRuntimeApi", method="get_stake_fee", params=[ - None, - fake_coldkey, (fake_hotkey, netuid), fake_coldkey, + None, + fake_coldkey, fake_amount.rao, ], block=None, From 64bd56b70d353787ee8ee938ef93679ed43d01e5 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 16:56:46 -0700 Subject: [PATCH 43/45] Make rao -> tao --- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/subtensor.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 7eb4f04eb2..e6747f5c5e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1608,7 +1608,7 @@ async def get_stake_add_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) async def get_unstake_fee( self, @@ -1643,7 +1643,7 @@ async def get_unstake_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) async def get_stake_movement_fee( self, @@ -1684,7 +1684,7 @@ async def get_stake_movement_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) async def get_stake_for_coldkey_and_hotkey( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e113c1b033..f7af1941fb 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1243,7 +1243,7 @@ def get_stake_add_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) def get_unstake_fee( self, @@ -1278,7 +1278,7 @@ def get_unstake_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) def get_stake_movement_fee( self, @@ -1319,7 +1319,7 @@ def get_stake_movement_fee( ], block=block, ) - return Balance.from_rao(result) + return Balance.from_tao(result) def get_stake_for_coldkey_and_hotkey( self, From acc66f83f852ceae0a50dd222c426329d3a67f84 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 16:58:31 -0700 Subject: [PATCH 44/45] Revert unneeded change --- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/subtensor.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e6747f5c5e..7eb4f04eb2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1608,7 +1608,7 @@ async def get_stake_add_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) async def get_unstake_fee( self, @@ -1643,7 +1643,7 @@ async def get_unstake_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) async def get_stake_movement_fee( self, @@ -1684,7 +1684,7 @@ async def get_stake_movement_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) async def get_stake_for_coldkey_and_hotkey( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f7af1941fb..e113c1b033 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1243,7 +1243,7 @@ def get_stake_add_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) def get_unstake_fee( self, @@ -1278,7 +1278,7 @@ def get_unstake_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) def get_stake_movement_fee( self, @@ -1319,7 +1319,7 @@ def get_stake_movement_fee( ], block=block, ) - return Balance.from_tao(result) + return Balance.from_rao(result) def get_stake_for_coldkey_and_hotkey( self, From 8d704ebfaebf97fb2713e43e035d34e03fe82ab7 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 17 Mar 2025 18:10:43 -0700 Subject: [PATCH 45/45] Bumps version and changelog --- CHANGELOG.md | 10 ++++++++++ VERSION | 2 +- bittensor/core/settings.py | 2 +- pyproject.toml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c88fa7c8..15214fcf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 9.2.0 /2025-03-18 + +## What's Changed +* Fix E2E test_incentive by waiting till start of the very next epoch by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2746 +* New era of e2e Tests Bittensor by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2743 +* Allow installation on Py 3.13 by @thewhaleking in https://github.com/opentensor/bittensor/pull/2756 +* Feat/dynamic stake prices by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2755 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.1.0...v9.2.0 + ## 9.1.0 /2025-03-12 ## What's Changed diff --git a/VERSION b/VERSION index e977f5eae6..85f864fe85 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.1.0 \ No newline at end of file +9.2.0 \ No newline at end of file diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 3e4174f950..8a53f6c423 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -1,4 +1,4 @@ -__version__ = "9.1.0" +__version__ = "9.2.0" import os import re diff --git a/pyproject.toml b/pyproject.toml index 14f0928d39..2c01c2858d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.1.0" +version = "9.2.0" description = "Bittensor" readme = "README.md" authors = [