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

Adds e2e setup & tests #2221

Merged
merged 4 commits into from
Aug 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
Empty file added tests/e2e_tests/__init__.py
Empty file.
84 changes: 84 additions & 0 deletions tests/e2e_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import re
import shlex
import signal
import subprocess
import time

import pytest
from substrateinterface import SubstrateInterface

from bittensor import logging
from tests.e2e_tests.utils.test_utils import (
clone_or_update_templates,
install_templates,
template_path,
uninstall_templates,
)


# Fixture for setting up and tearing down a localnet.sh chain between tests
@pytest.fixture(scope="function")
def local_chain(request):
param = request.param if hasattr(request, "param") else None
# Get the environment variable for the script path
script_path = os.getenv("LOCALNET_SH_PATH")

if not script_path:
# Skip the test if the localhost.sh path is not set
logging.warning("LOCALNET_SH_PATH env variable is not set, e2e test skipped.")
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}"

# Compile commands to send to process
cmds = shlex.split(f"{script_path} {args}")

# Start new node process
process = subprocess.Popen(
cmds, stdout=subprocess.PIPE, text=True, preexec_fn=os.setsid
)

# Pattern match indicates node is compiled and ready
pattern = re.compile(r"Imported #1")

# install neuron templates
logging.info("downloading and installing neuron templates from github")
templates_dir = clone_or_update_templates()
install_templates(templates_dir)

timestamp = int(time.time())

def wait_for_node_start(process, pattern):
for line in process.stdout:
print(line.strip())
# 10 min as timeout
if int(time.time()) - timestamp > 10 * 60:
print("Subtensor not started in time")
break
if pattern.search(line):
print("Node started!")
break

wait_for_node_start(process, pattern)

# Run the test, passing in substrate interface
yield SubstrateInterface(url="ws://127.0.0.1:9945")

# Terminate the process group (includes all child processes)
os.killpg(os.getpgid(process.pid), signal.SIGTERM)

# Give some time for the process to terminate
time.sleep(1)

# If the process is not terminated, send SIGKILL
if process.poll() is None:
os.killpg(os.getpgid(process.pid), signal.SIGKILL)

# Ensure the process has terminated
process.wait()

# uninstall templates
logging.info("uninstalling neuron templates")
uninstall_templates(template_path)
128 changes: 128 additions & 0 deletions tests/e2e_tests/test_axon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import asyncio
import sys

import pytest

import bittensor
from bittensor import logging
from bittensor.utils import networking
from tests.e2e_tests.utils.chain_interactions import register_neuron, register_subnet
from tests.e2e_tests.utils.test_utils import (
setup_wallet,
template_path,
templates_repo,
)


@pytest.mark.asyncio
async def test_axon(local_chain):
"""
Test the Axon mechanism and successful registration on the network.

Steps:
1. Register a subnet and register Alice
2. Check if metagraph.axon is updated and check axon attributes
3. Run Alice as a miner on the subnet
4. Check the metagraph again after running the miner and verify all attributes
Raises:
AssertionError: If any of the checks or verifications fail
"""

logging.info("Testing test_axon")

netuid = 1
# Register root as Alice - the subnet owner
alice_keypair, wallet = setup_wallet("//Alice")

# Register a subnet, netuid 1
assert register_subnet(local_chain, wallet), "Subnet wasn't created"

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

# Register Alice to the network
assert register_neuron(
local_chain, wallet, netuid
), f"Neuron wasn't registered to subnet {netuid}"

metagraph = bittensor.metagraph(netuid=netuid, network="ws://localhost:9945")

# Validate current metagraph stats
old_axon = metagraph.axons[0]
assert len(metagraph.axons) == 1, f"Expected 1 axon, but got {len(metagraph.axons)}"
assert old_axon.hotkey == alice_keypair.ss58_address, "Hotkey mismatch for the axon"
assert (
old_axon.coldkey == alice_keypair.ss58_address
), "Coldkey mismatch for the axon"
assert old_axon.ip == "0.0.0.0", f"Expected IP 0.0.0.0, but got {old_axon.ip}"
assert old_axon.port == 0, f"Expected port 0, but got {old_axon.port}"
assert old_axon.ip_type == 0, f"Expected IP type 0, but got {old_axon.ip_type}"

# Prepare to run the miner
cmd = " ".join(
[
f"{sys.executable}",
f'"{template_path}{templates_repo}/neurons/miner.py"',
"--no_prompt",
"--netuid",
str(netuid),
"--subtensor.network",
"local",
"--subtensor.chain_endpoint",
"ws://localhost:9945",
"--wallet.path",
wallet.path,
"--wallet.name",
wallet.name,
"--wallet.hotkey",
"default",
]
)

# Run the miner in the background
await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)

logging.info("Neuron Alice is now mining")

# Waiting for 5 seconds for metagraph to be updated
await asyncio.sleep(5)

# Refresh the metagraph
metagraph = bittensor.metagraph(netuid=netuid, network="ws://localhost:9945")
updated_axon = metagraph.axons[0]
external_ip = networking.get_external_ip()

# Assert updated attributes
assert (
len(metagraph.axons) == 1
), f"Expected 1 axon, but got {len(metagraph.axons)} after mining"

assert (
len(metagraph.neurons) == 1
), f"Expected 1 neuron, but got {len(metagraph.neurons)}"

assert (
updated_axon.ip == external_ip
), f"Expected IP {external_ip}, but got {updated_axon.ip}"

assert (
updated_axon.ip_type == networking.ip_version(external_ip)
), f"Expected IP type {networking.ip_version(external_ip)}, but got {updated_axon.ip_type}"

assert updated_axon.port == 8091, f"Expected port 8091, but got {updated_axon.port}"

assert (
updated_axon.hotkey == alice_keypair.ss58_address
), "Hotkey mismatch after mining"

assert (
updated_axon.coldkey == alice_keypair.ss58_address
), "Coldkey mismatch after mining"

logging.info("✅ Passed test_axon")
136 changes: 136 additions & 0 deletions tests/e2e_tests/test_dendrite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import asyncio
import sys

import pytest

import bittensor
from bittensor import logging, Subtensor

from tests.e2e_tests.utils.test_utils import (
setup_wallet,
template_path,
templates_repo,
)
from tests.e2e_tests.utils.chain_interactions import (
register_neuron,
register_subnet,
add_stake,
wait_epoch,
)


@pytest.mark.asyncio
async def test_dendrite(local_chain):
"""
Test the Dendrite mechanism

Steps:
1. Register a subnet through Alice
2. Register Bob as a validator
3. Add stake to Bob and ensure neuron is not a validator yet
4. Run Bob as a validator and wait epoch
5. Ensure Bob's neuron has all correct attributes of a validator
Raises:
AssertionError: If any of the checks or verifications fail
"""

logging.info("Testing test_dendrite")
netuid = 1

# Register root as Alice - the subnet owner
alice_keypair, alice_wallet = setup_wallet("//Alice")

# Register a subnet, netuid 1
assert register_subnet(local_chain, alice_wallet), "Subnet wasn't created"

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

# Register Bob
bob_keypair, bob_wallet = setup_wallet("//Bob")

# Register Bob to the network
assert register_neuron(
local_chain, bob_wallet, netuid
), f"Neuron wasn't registered to subnet {netuid}"

metagraph = bittensor.metagraph(netuid=netuid, network="ws://localhost:9945")
subtensor = Subtensor(network="ws://localhost:9945")

# Assert one neuron is Bob
assert len(subtensor.neurons(netuid=netuid)) == 1
neuron = metagraph.neurons[0]
assert neuron.hotkey == bob_keypair.ss58_address
assert neuron.coldkey == bob_keypair.ss58_address

# Assert stake is 0
assert neuron.stake.tao == 0

# Stake to become to top neuron after the first epoch
assert add_stake(local_chain, bob_wallet, bittensor.Balance.from_tao(10_000))

# Refresh metagraph
metagraph = bittensor.metagraph(netuid=netuid, network="ws://localhost:9945")
old_neuron = metagraph.neurons[0]

# Assert stake is 10000
assert (
old_neuron.stake.tao == 10_000.0
), f"Expected 10_000.0 staked TAO, but got {neuron.stake.tao}"

# Assert neuron is not a validator yet
assert old_neuron.active is True
assert old_neuron.validator_permit is False
assert old_neuron.validator_trust == 0.0
assert old_neuron.pruning_score == 0

# Prepare to run the validator
cmd = " ".join(
[
f"{sys.executable}",
f'"{template_path}{templates_repo}/neurons/validator.py"',
"--no_prompt",
"--netuid",
str(netuid),
"--subtensor.network",
"local",
"--subtensor.chain_endpoint",
"ws://localhost:9945",
"--wallet.path",
bob_wallet.path,
"--wallet.name",
bob_wallet.name,
"--wallet.hotkey",
"default",
]
)

# Run the validator in the background
await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
logging.info("Neuron Alice is now validating")
await asyncio.sleep(
5
) # wait for 5 seconds for the metagraph and subtensor to refresh with latest data

await wait_epoch(subtensor, netuid=netuid)

# Refresh metagraph
metagraph = bittensor.metagraph(netuid=netuid, network="ws://localhost:9945")

# Refresh validator neuron
updated_neuron = metagraph.neurons[0]

assert len(metagraph.neurons) == 1
assert updated_neuron.active is True
assert updated_neuron.validator_permit is True
assert updated_neuron.hotkey == bob_keypair.ss58_address
assert updated_neuron.coldkey == bob_keypair.ss58_address
assert updated_neuron.pruning_score != 0

logging.info("✅ Passed test_dendrite")
Loading