Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added e2e test for CRv3 + enhancements #2532

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,40 @@ def neurons(self, netuid: int, block: Optional[int] = None) -> list["NeuronInfo"

return neurons

def last_drand_round(
self,
) -> Optional[int]:
"""
Retrieves the last drand round emitted in bittensor. This corresponds when committed weights will be revealed.

Returns:
int: The latest Drand round emitted in bittensor.
"""
result = self.substrate.query(
module="Drand", storage_function="LastStoredRound"
)
return getattr(result, "value", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a note that we'll need to update this after #2526 is merged


def get_weight_commits(self, netuid: int, block: Optional[int] = None) -> list:
"""
Retrieves CRV3 weight commit information for a specific subnet.

Args:
netuid (int): The unique identifier of the subnet.
block (Optional[int]): The blockchain block number for the query.

Returns:
list: A list of commit details, where each entry is a dictionary with keys 'who',
'serialized_commit', and 'reveal_round', or an empty list if no data is found.
"""
result = self.query_map(
module="SubtensorModule",
name="CRV3WeightCommits",
params=[netuid],
block=block,
)
return result.records[0][1].value if result and result.records else []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here


def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]:
"""
Retrieves the total number of subnets within the Bittensor network as of a specific blockchain block.
Expand Down
188 changes: 188 additions & 0 deletions tests/e2e_tests/test_commit_reveal_v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import re

import numpy as np
import pytest
from bittensor.utils.btlogging import logging
from bittensor.core.subtensor import Subtensor
from bittensor.utils.balance import Balance
from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit
from tests.e2e_tests.utils.chain_interactions import (
add_stake,
register_subnet,
sudo_set_hyperparameter_bool,
sudo_set_hyperparameter_values,
wait_interval,
sudo_set_admin_utils,
next_tempo,
)
from tests.e2e_tests.utils.e2e_test_utils import setup_wallet


# Skipping till we have CRV3 on testnet
@pytest.mark.skip
@pytest.mark.parametrize("local_chain", [False], indirect=True)
@pytest.mark.asyncio
async def test_commit_and_reveal_weights_cr3(local_chain):
"""
Tests the commit/reveal weights mechanism (CR3)

Steps:
1. Register a subnet through Alice
2. Register Alice's neuron and add stake
3. Enable commit-reveal mechanism on the subnet
4. Lower weights rate limit
5. Change the tempo for subnet 1
5. Commit weights and ensure they are committed.
6. Wait interval & reveal weights and verify
Raises:
AssertionError: If any of the checks or verifications fail
"""
netuid = 1
logging.console.info("Testing test_commit_and_reveal_weights")

# Register root as Alice
keypair, alice_wallet = setup_wallet("//Alice")
assert register_subnet(local_chain, alice_wallet), "Unable to register the subnet"

# Verify subnet 1 created successfully
assert local_chain.query(
"SubtensorModule", "NetworksAdded", [1]
).serialize(), "Subnet wasn't created successfully"

logging.console.info("Subnet 1 is registered")

subtensor = Subtensor(network="ws://localhost:9945")

# Register Alice to the subnet
assert subtensor.burned_register(
alice_wallet, netuid
), "Unable to register Alice as a neuron"
logging.console.info("Registered Alice to subnet 1")

# Stake to become to top neuron after the first epoch
add_stake(local_chain, alice_wallet, Balance.from_tao(100_000))
logging.console.info("Stake added by Alice")

# Enable commit_reveal on the subnet
assert sudo_set_hyperparameter_bool(
local_chain,
alice_wallet,
"sudo_set_commit_reveal_weights_enabled",
True,
netuid,
), "Unable to enable commit reveal on the subnet"

# Verify commit_reveal was enabled
assert subtensor.get_subnet_hyperparameters(
netuid=netuid,
).commit_reveal_weights_enabled, "Failed to enable commit/reveal"
logging.console.info("Commit reveal enabled")

# Change the weights rate limit on the subnet
assert sudo_set_hyperparameter_values(
local_chain,
alice_wallet,
call_function="sudo_set_weights_set_rate_limit",
call_params={"netuid": netuid, "weights_set_rate_limit": "0"},
return_error_message=True,
)

# Verify weights rate limit was changed
assert (
subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0
), "Failed to set weights_rate_limit"
assert subtensor.weights_rate_limit(netuid=netuid) == 0
logging.console.info("sudo_set_weights_set_rate_limit executed: set to 0")

# Change the tempo of the subnet from default 360
# Since this is in normal blocks, this is necessary
tempo_set = 10
assert sudo_set_admin_utils(
local_chain,
alice_wallet,
call_function="sudo_set_tempo",
call_params={"netuid": netuid, "tempo": tempo_set},
return_error_message=True,
)
tempo = subtensor.get_subnet_hyperparameters(netuid=netuid).tempo
assert tempo_set == tempo
logging.console.info(f"sudo_set_tempo executed: set to {tempo_set}")

# Commit-reveal values - setting weights to self
uids = np.array([0], dtype=np.int64)
revealed_weights = np.array([0.1], dtype=np.float32)
weight_uids, weight_vals = convert_weights_and_uids_for_emit(
uids=uids, weights=revealed_weights
)

# Fetch current block and calculate next tempo for the subnet
current_block = subtensor.get_current_block()
upcoming_tempo = next_tempo(current_block, tempo, netuid)

# Lower than this might mean weights will get revealed before we can check them
if upcoming_tempo - current_block < 3:
await wait_interval(
tempo,
subtensor,
netuid=netuid,
reporting_interval=1,
)

# Commit weights
success, message = subtensor.set_weights(
alice_wallet,
netuid,
uids=weight_uids,
weights=weight_vals,
wait_for_inclusion=True,
wait_for_finalization=True,
)

# Assert committing was a success
assert success is True
assert bool(re.match(r"reveal_round:\d+", message))
logging.console.info(
f"Successfully set weights: uids {weight_uids}, weights {weight_vals}"
)

# Parse expected reveal_round
expected_reveal_round = int(message.split(":")[1])

# Fetch current commits pending on the chain
commits_on_chain = subtensor.get_weight_commits(netuid=netuid)
address, commit, reveal_round = commits_on_chain[0]

# Assert correct values are committed on the chain
assert expected_reveal_round == reveal_round
assert address == alice_wallet.hotkey.ss58_address

# Ensure no weights are available as of now
assert subtensor.weights(netuid=netuid) == []

# Wait for the next tempo so weights can be revealed
await wait_interval(
subtensor.get_subnet_hyperparameters(netuid=netuid).tempo,
subtensor,
netuid=netuid,
reporting_interval=1,
)

# Fetch the latest drand pulse
latest_drand_round = subtensor.last_drand_round()

# Fetch weights on the chain as they should be revealed now
revealed_weights = subtensor.weights(netuid=netuid)[0][1]

# Assert correct weights were revealed
assert weight_uids[0] == revealed_weights[0][0]
assert weight_vals[0] == revealed_weights[0][1]

# Now that the commit has been revealed, there shouldn't be any pending commits
assert subtensor.get_weight_commits(netuid=netuid) == []

# Ensure the drand_round is always in the positive w.r.t expected when revealed
assert (
latest_drand_round - expected_reveal_round >= 0
), f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})"

logging.console.info("✅ Passed commit_reveal v3")
Loading