Skip to content
Open
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ test:
validator) SERVICES="$(SERVICES_validator)";; \
*) echo "Unknown component: $$comp"; exit 1;; \
esac; \
if [ -d "subvortex/$$comp/core" ]; then \
echo "🔍 Testing subvortex/$$comp/core..."; \
PYTHONPATH=. pytest subvortex/$$comp/core || test $$? -eq 5 || exit $$?; \
else \
echo "⚠️ Warning: subvortex/$$comp/core not found, skipping..."; \
fi; \
for svc in $$SERVICES; do \
svc_path=subvortex/$$comp/$$svc; \
if [ -d "$$svc_path" ]; then \
Expand Down
16 changes: 16 additions & 0 deletions subvortex/core/core_bittensor/wallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import bittensor_wallet as btw
import bittensor_wallet.mock as btwm

def get_mock_wallet(coldkey: "btw.Keypair" = None, hotkey: "btw.Keypair" = None):
wallet = btwm.MockWallet(name="mock_wallet", hotkey="mock", path="/tmp/mock_wallet")

if not coldkey:
coldkey = btw.Keypair.create_from_mnemonic(btw.Keypair.generate_mnemonic())
if not hotkey:
hotkey = btw.Keypair.create_from_mnemonic(btw.Keypair.generate_mnemonic())

wallet.set_coldkey(coldkey, encrypt=False, overwrite=True)
wallet.set_coldkeypub(coldkey, encrypt=False, overwrite=True)
wallet.set_hotkey(hotkey, encrypt=False, overwrite=True)

return wallet
3 changes: 1 addition & 2 deletions subvortex/core/shared/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ class MockSubtensor(btum.MockSubtensor):
def __init__(self, netuid, n=16, wallet=None, network="mock"):
super().__init__(network=network)

if not self.subnet_exists(netuid):
self.create_subnet(netuid)
self.create_subnet(netuid)

# Register ourself (the validator) as a neuron at uid=0
if wallet is not None:
Expand Down
69 changes: 69 additions & 0 deletions subvortex/validator/challenger/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# syntax=docker/dockerfile:1.4

########################################
# Stage 0 — Build wheels using wheel-builder
########################################
FROM wheelbuilder AS wheels

WORKDIR /build

COPY ./subvortex/validator/challenger/requirements.txt ./requirements.txt

RUN pip wheel -r requirements.txt -w /wheels \
&& rm -rf ~/.cache/pip

########################################
# Stage 1 — Final runtime image
########################################
FROM python:3.11-slim

# ---------- Metadata & Labels ----------
ARG MAINTAINER="subvortex.bt@gmail.com"
ARG VERSION="0.0.0"
ARG ROLE_VERSION="0.0.0"
ARG COMPONENT_VERSION="0.0.0"
LABEL maintainer=$MAINTAINER
LABEL version=$VERSION
LABEL validator.version=$ROLE_VERSION
LABEL validator.challenger.version=$COMPONENT_VERSION

# ---------- Environment ----------
ENV PYTHONUNBUFFERED=1
ARG SUBVORTEX_OBSERVER_TYPE=file
ENV SV_OBSERVER_TYPE=${SUBVORTEX_OBSERVER_TYPE}

# ✅ Install required runtime libraries
RUN apt-get update && apt-get install -y \
libnfnetlink0 \
libnetfilter-queue1 \
iptables \
libcap2-bin \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 🧱 Copy only built wheels from Stage 0
COPY --from=wheels /wheels /tmp/wheels

# ✅ Only copy wheels and install, discard after
COPY --from=wheels /wheels /tmp/wheels
COPY ./subvortex/validator/challenger/requirements.txt ./requirements.txt
RUN pip install --no-cache-dir --find-links=/tmp/wheels -r requirements.txt \
&& rm -rf /tmp/wheels ~/.cache/pip

# 📁 Copy source code
COPY ./pyproject-validator.toml ./pyproject.toml
COPY ./subvortex/pyproject.toml ./subvortex/pyproject.toml
COPY ./subvortex/core ./subvortex/core
COPY ./subvortex/validator/version.py ./subvortex/validator/version.py
COPY ./subvortex/validator/core ./subvortex/validator/core
COPY ./subvortex/validator/challenger/src ./subvortex/validator/challenger/src
COPY ./subvortex/validator/challenger/entrypoint.sh ./subvortex/validator/challenger/entrypoint.sh
COPY ./subvortex/validator/challenger/pyproject.toml ./subvortex/validator/challenger/pyproject.toml

# 🧩 Install project (editable)
RUN pip install "./subvortex/validator/challenger[observer-${SV_OBSERVER_TYPE}]" \
&& pip install -e . \
&& rm -rf ~/.cache/pip

ENTRYPOINT ["/bin/bash", "./subvortex/validator/challenger/entrypoint.sh"]
22 changes: 22 additions & 0 deletions subvortex/validator/challenger/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: bump-patch bump-minor bump-major bump-alpha bump-rc clean

VERSION_SCRIPT = ../../../scripts/cicd/cicd_bump_version.py
DIST_DIR = ../../dist

bump-patch:
@python3 $(VERSION_SCRIPT) patch

bump-minor:
@python3 $(VERSION_SCRIPT) minor

bump-ajor:
@python3 $(VERSION_SCRIPT) major

bump-alpha:
@python3 $(VERSION_SCRIPT) alpha

bump-rc:
@python3 $(VERSION_SCRIPT) rc

clean:
rm -rf $(DIST_DIR) *.egg-info **/*.egg-info build/
1 change: 1 addition & 0 deletions subvortex/validator/challenger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Coming soon
37 changes: 37 additions & 0 deletions subvortex/validator/challenger/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash -eu

# Load environment variables
export $(grep -v '^#' .env | xargs)

# Build CLI args from SUBVORTEX_ environment variables
ARGS=()
PREFIX="SUBVORTEX_"

while IFS= read -r line; do
key="${line%%=*}"
value="${line#*=}"

# Skip if key doesn't start with PREFIX or value is empty (even if it's just "")
if [[ $key != ${PREFIX}* || -z "${value//\"/}" ]]; then
continue
fi

key_suffix="${key#$PREFIX}"
cli_key="--$(echo "$key_suffix" | tr '[:upper:]' '[:lower:]' | tr '_' '.')"
value_lower="$(echo "$value" | tr '[:upper:]' '[:lower:]')"

if [[ "$value_lower" == "true" ]]; then
ARGS+=("$cli_key")
elif [[ "$value_lower" == "false" ]]; then
continue
else
ARGS+=("$cli_key" "$value")
fi
done < <(env)

if [ $# -eq 0 ]; then
python ./subvortex/validator/challenger/src/main.py \
"${ARGS[@]}"
else
exec "$@"
fi
10 changes: 10 additions & 0 deletions subvortex/validator/challenger/env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SUBVORTEX_NETUID=7
SUBVORTEX_AXON_IP=127.0.0.1
SUBVORTEX_AXON_PORT=8091
SUBVORTEX_AXON_EXTERNAL_PORT=8091
SUBVORTEX_SUBTENSOR_NETWORK=finney
SUBVORTEX_DATABASE_HOST=localhost
SUBVORTEX_DATABASE_PASSWORD=
SUBVORTEX_DATABASE_PORT=6379
SUBVORTEX_DATABASE_INDEX=0
SUBVORTEX_LOGGING_DEBUG=True
27 changes: 27 additions & 0 deletions subvortex/validator/challenger/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"id": "validator-challenger",
"name": "subvortex-validator-challenger",
"description": "SubVortex Validator challenger",
"version": "3.1.0",
"validator.version": "3.1.0",
"validator.challenger.version": "3.1.0",
"type": "python",
"challenger": "validator",
"component": "challenger",
"common": {
"user": "root",
"group": "root"
},
"service": {
"environment": {
"PYTHONUNBUFFERED": 1
},
"restart": "on-failure",
"restart_sec": "10s",
"log_prefix": "subvortex-validator-challenger"
},
"depends_on": [
"validator-redis",
"validator-metagraph"
]
}
46 changes: 46 additions & 0 deletions subvortex/validator/challenger/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[build-system]
requires = ["setuptools>=70"]
build-backend = "setuptools.build_meta"

[project]
name = "subvortex-validator-challenger"
version = "3.1.0"
description = "SubVortex Validator Challenger"
authors = [{ name = "Eclipse Vortex", email = "subvortex.bt@gmail.com" }]
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Mathematics",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
]

[project.license]
text = "MIT"

[tool.setuptools]
include-package-data = true

[tool.setuptools.packages.find]
where = ["."]

[tool.setuptools.package-data]
"*" = [
"deployment/**/*",
"requirements.txt",
"README.md",
"pyproject.toml",
"env.template",
"docker-compose.yml",
"metadata.json",
"manifest.json"
]
4 changes: 4 additions & 0 deletions subvortex/validator/challenger/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-r requirements.txt

pytest==8.3.5
pytest_asyncio==0.26.0
2 changes: 2 additions & 0 deletions subvortex/validator/challenger/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bittensor==9.6.0
bittensor-wallet==3.0.10
110 changes: 110 additions & 0 deletions subvortex/validator/challenger/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import asyncio
import argparse
import traceback
from dotenv import load_dotenv

import bittensor.utils.btlogging as btul
import bittensor.core.config as btcc
import bittensor.core.async_subtensor as btcas
import bittensor.core.metagraph as btcm

import subvortex.core.core_bittensor.config.config_utils as scccu
import subvortex.core.challenger.challenger as sccc
import subvortex.core.challenger.database as sccd

import subvortex.validator.metagraph.src.settings as svme


# Load the environment variables for the whole process
load_dotenv(override=True)


async def wait_for_database_connection(
settings: svme.Settings, database: sccd.NeuronDatabase
) -> None:
btul.logging.warning(
"⏳ Waiting for Redis to become available...",
prefix=settings.logging_name,
)

# Ensure the connection
await database.ensure_connection()

while True:
if await database.is_connection_alive():
btul.logging.info("✅ Connected to Redis.", prefix=settings.logging_name)
return

await asyncio.sleep(1)


async def main():
parser = argparse.ArgumentParser()
btul.logging.add_args(parser)
btcas.AsyncSubtensor.add_args(parser)

# Create the configuration
config = btcc.Config(parser)

# Create settings
settings = svme.Settings.create()
scccu.update_config(settings, config, parser)

# Initialise logging
btul.logging(config=config, debug=True)
btul.logging.set_trace(config.logging.trace)
btul.logging._stream_formatter.set_trace(config.logging.trace)
btul.logging.debug(str(config))

# Display the settings
btul.logging.info(f"metagraph settings: {settings}")

database = None
challenger = None
subtensor = None
try:
# Create the storage
database = sccd.ChallengerDatabase(settings=settings)
await wait_for_database_connection(settings=settings, database=database)

# Initialize the subtensor
subtensor = btcas.AsyncSubtensor(config=config)
await subtensor.initialize()
btul.logging.info(str(subtensor))

# Initialize the metagraph
# TODO: Tell OTF if i provide the subtensor the network wil be finney even if the subtensor is in test!
metagraph = btcm.AsyncMetagraph(
netuid=settings.netuid, network=subtensor.network, sync=False
)
btul.logging.info(str(metagraph))

# Create and run the metagraph observer
challenger = sccc.Challenger(
settings=settings,
subtensor=subtensor,
metagraph=metagraph,
database=database,
)
await challenger.start()

except Exception as err:
btul.logging.error("Error in training loop", str(err))
btul.logging.debug(traceback.print_exception(type(err), err, err.__traceback__))

except KeyboardInterrupt:
btul.logging.info("Keyboard interrupt detected, exiting.")

finally:
if database:
await database.mark_as_unready()

if challenger:
await challenger.stop()

if subtensor:
await subtensor.close()


if __name__ == "__main__":
asyncio.run(main())
Loading