diff --git a/CHANGELOG.md b/CHANGELOG.md
index a14773ff..1120c551 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## 2.2.5 / 2024-05-31
+
+## What's Changed
+* resyn metagraph for miner by @eclipsevortex in https://github.com/eclipsevortex/SubVortex/pull/62
+* fix subtensor by @eclipsevortex in https://github.com/eclipsevortex/SubVortex/pull/65
+* fix country api by @eclipsevortex in https://github.com/eclipsevortex/SubVortex/pull/64
+* adjust weight for the scores by @eclipsevortex in https://github.com/eclipsevortex/SubVortex/pull/66
+
+
+**Full Changelog**: https://github.com/eclipsevortex/SubVortex/compare/v2.2.4...v2.2.5
+
## 2.2.4 / 2024-05-20
## What's Changed
diff --git a/README.md b/README.md
index e668cc08..4e4f0800 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ Team responsabilities
- **EclipseVortex** - Development and technology
- **Ch3RNØbØG** - Operations and business development
-- **tww9** - Strategy and public relations
+- **CryptoMinedMind** - Strategy and public relations
- **HcL-CO** - QA Lead and support
Team timezone
@@ -182,6 +182,7 @@ Bittensor technology is still new and promising, and participants are eager to s
- **Bittensor**: for providing a subnet template that enabled us to quickly set up our subnet.
- **Subtensor**: for their local subtensor, scripts, and invaluable assistance.
+- **andrewoflaherty**: for implementing the country api using MaxMind and IPInfo ([github](https://github.com/OFlahertyAndrew))
- **Subnet Storage (SN21)**: for their excellent subnet design, which adheres to best practices and simplifies the lives of developers.
- **Users**: YES!!! Without you, we are nothing, and our vision to advance this new technology would never have materialized.
- **Others**: undoubtedly, there are many other contributors deserving of recognition, and we look forward to acknowledging them in the future.
diff --git a/VERSION b/VERSION
index 04761555..ecf00d90 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.2.4
\ No newline at end of file
+2.2.5
\ No newline at end of file
diff --git a/docs/features/incentive_mechanism.md b/docs/features/incentive_mechanism.md
index 6b5e377b..adb39911 100644
--- a/docs/features/incentive_mechanism.md
+++ b/docs/features/incentive_mechanism.md
@@ -74,10 +74,10 @@ The final score is the score for the current challenge. It will be computed as t
The weight for each score will be as follows:
-- Availability: Weight will be **3** if the miner's subtensor is synchronized, and **1** otherwise.
-- Latency: Weight will be **1**.
-- Reliability: Weight will be **1**.
-- Distribution: Weight will be **1**.
+- Availability: Weight will be **8** if the miner's subtensor is synchronized, and **3** otherwise.
+- Latency: Weight will be **7**.
+- Reliability: Weight will be **3**.
+- Distribution: Weight will be **2**.
So, the final formula will be calculated using the following expression
diff --git a/neurons/miner.py b/neurons/miner.py
index 86f12b16..62fc4638 100644
--- a/neurons/miner.py
+++ b/neurons/miner.py
@@ -176,6 +176,7 @@ def __init__(self):
self.thread: threading.Thread = None
self.lock = asyncio.Lock()
self.request_timestamps: typing.Dict = {}
+ self.previous_last_updates = []
self.step = 0
@@ -263,6 +264,23 @@ def __exit__(self, exc_type, exc_value, traceback):
"""
self.stop_run_thread()
+ def should_sync_metagraph(self):
+ """
+ True if the metagraph has been resynced, False otherwise.
+ """
+ last_updates = self.subtensor.substrate.query(
+ module="SubtensorModule",
+ storage_function="LastUpdate",
+ params=[self.config.netuid],
+ )
+ if self.previous_last_updates == last_updates:
+ return False
+
+ # Save the new updates
+ self.previous_last_updates = last_updates
+
+ return True
+
def run_miner():
"""
diff --git a/neurons/validator.py b/neurons/validator.py
index 6365f480..e191c848 100644
--- a/neurons/validator.py
+++ b/neurons/validator.py
@@ -28,6 +28,7 @@
from subnet import __version__ as THIS_VERSION
from subnet.monitor.monitor import Monitor
+from subnet.country.country import CountryService
from subnet.shared.checks import check_registration
from subnet.shared.utils import get_redis_password, should_upgrade
@@ -36,7 +37,6 @@
from subnet.shared.mock import MockMetagraph, MockDendrite, MockSubtensor
from subnet.validator.config import config, check_config, add_args
-from subnet.validator.localisation import get_country, get_localisation
from subnet.validator.forward import forward
from subnet.validator.models import Miner
from subnet.validator.version import VersionControl
@@ -168,19 +168,6 @@ def __init__(self, config=None):
self.dendrite = bt.dendrite(wallet=self.wallet)
bt.logging.debug(str(self.dendrite))
- # Get the validator country
- self.country = get_country(self.dendrite.external_ip)
- country_localisation = get_localisation(self.country)
- country_name = (
- country_localisation["country"] if country_localisation else "None"
- )
- bt.logging.debug(f"Validator based in {country_name}")
-
- # Init wandb.
- if not self.config.wandb.off:
- bt.logging.debug("loading wandb")
- init_wandb(self)
-
# Init the event loop.
self.loop = asyncio.get_event_loop()
@@ -195,6 +182,7 @@ def __init__(self, config=None):
self.rebalance_queue = []
self.miners: List[Miner] = []
self.last_upgrade_check = 0
+ self.last_upgrade_country = None
async def run(self):
bt.logging.info("run()")
@@ -203,6 +191,24 @@ async def run(self):
dump_path = self.config.database.redis_dump_path
self.version_control = VersionControl(self.database, dump_path)
+ # Monitor miners
+ self.monitor = Monitor(self.config.netuid)
+ self.monitor.start()
+
+ # Country service
+ self.country_service = CountryService(self.config.netuid)
+ self.country_service.start()
+ self.country_service.wait()
+
+ # Get the validator country
+ self.country_code = self.country_service.get_country(self.dendrite.external_ip)
+ bt.logging.debug(f"Validator based in {self.country_code}")
+
+ # Init wandb.
+ if not self.config.wandb.off:
+ bt.logging.debug("loading wandb")
+ init_wandb(self)
+
# Init miners
self.miners = await get_all_miners(self)
bt.logging.debug(f"Miners loaded {len(self.miners)}")
@@ -210,10 +216,6 @@ async def run(self):
# Load the state
load_state(self)
- # Monitor miners
- self.monitor = Monitor(self.config.netuid)
- self.monitor.start()
-
try:
while 1:
# Start the upgrade process every 10 minutes
@@ -229,7 +231,12 @@ async def run(self):
start_epoch = time.time()
- await resync_metagraph_and_miners(self)
+ # Check if the coutry changed
+ last_upgrade_country = self.country_service.get_last_modified()
+ has_country_changed = self.last_upgrade_country != last_upgrade_country
+ self.last_upgrade_country = last_upgrade_country
+
+ await resync_metagraph_and_miners(self, has_country_changed)
prev_set_weights_block = self.metagraph.last_update[self.uid].item()
# --- Wait until next step epoch.
@@ -308,6 +315,9 @@ async def run_forward():
if self.monitor:
self.monitor.stop()
+ if self.country_service:
+ self.country_service.stop()
+
if hasattr(self, "subtensor"):
bt.logging.debug("Closing subtensor connection")
self.subtensor.close()
diff --git a/scripts/release/release-2.2.5/RELEASE-2.2.5.md b/scripts/release/release-2.2.5/RELEASE-2.2.5.md
new file mode 100644
index 00000000..f95f582f
--- /dev/null
+++ b/scripts/release/release-2.2.5/RELEASE-2.2.5.md
@@ -0,0 +1,152 @@
+This guide provides step-by-step instructions for the release 2.2.5.
+
+Previous Release: 2.2.4
+
+
+
+---
+
+- [Validator](#validators)
+ - [Rollout Process](#validator-rollout-process)
+ - [Rollback Process](#validator-rollback-process)
+- [Miner](#miner)
+ - [Rollout Process](#miner-rollout-process)
+ - [Rollback Process](#miner-rollback-process)
+- [Additional Resources](#additional-resources)
+- [Troubleshooting](#troubleshooting)
+
+---
+
+
+
+# Validator
+
+## Rollout Process
+
+1. **Upgrade Subnet**: Fetch the remote tags
+
+ ```bash
+ git fetch --tags --force
+ ```
+
+ Then, checkout the new release tag
+
+ ```bash
+ git checkout tags/v2.2.5
+ ```
+
+ Finally, install the dependencies
+
+ ```bash
+ pip install --upgrade SubVortex
+ pip install -e .
+ ```
+
+2. **Restart validator**: Restart your validator to take the new version
+
+ ```bash
+ pm2 restart validator-7
+ ```
+
+3. **Check logs**: Check the validator logs to see if you see some `New Block`
+ ```bash
+ pm2 logs validator-7
+ ```
+
+
+
+## Rollback Process
+
+If any issues arise during or after the rollout, follow these steps to perform a rollback:
+
+1. **Downgrade Subnet**: Checkout the previous release tag
+
+ ```bash
+ git checkout tags/v2.2.4
+ ```
+
+ Then, install the dependencies
+
+ ```bash
+ pip install --upgrade SubVortex
+ pip install -e .
+ ```
+
+2. **Restart validator**: Restart your validator to take the old version
+
+ ```bash
+ pm2 restart validator-7
+ ```
+
+3. **Check logs**: Check the validator logs to see if you see some `New Block`
+ ```bash
+ pm2 logs validator-7
+ ```
+
+
+
+# Miner
+
+## Rollout Process
+
+1. **Upgrade Subnet**: Fetch the remote tags
+
+ ```bash
+ git fetch --tags --force
+ ```
+
+ Then, checkout the new release tag
+
+ ```bash
+ git checkout tags/v2.2.5
+ ```
+
+ Finally, install the dependencies
+
+ ```bash
+ pip install --upgrade SubVortex
+ pip install -e .
+ ```
+
+2. **Restart miner**: Restart your miner to take the new version
+
+ ```bash
+ pm2 restart miner-7
+ ```
+
+3. **Check logs**: Check the miner logs to see if you see some `New Block`
+ ```bash
+ pm2 logs miner-7
+ ```
+
+## Rollback Process
+
+1. **Downgrade Subnet**: Checkout the previous release tag
+
+ ```bash
+ git checkout tags/v2.2.4
+ ```
+
+ Then, install the dependencies
+
+ ```bash
+ pip install --upgrade SubVortex
+ pip install -e .
+ ```
+
+2. **Restart miner**: Restart your miner to take the old version
+
+ ```bash
+ pm2 restart miner-7
+ ```
+
+3. **Check logs**: Check the miner logs to see if you see some `New Block`
+ ```bash
+ pm2 logs miner-7
+ ```
+
+
+
+# Additional Resources
+
+For any further assistance or inquiries, please contact [**SubVortex Team**](https://discord.com/channels/799672011265015819/1215311984799653918)
\ No newline at end of file
diff --git a/scripts/subtensor/scripts/subtensor_start.sh b/scripts/subtensor/scripts/subtensor_start.sh
index 09517531..9384e721 100755
--- a/scripts/subtensor/scripts/subtensor_start.sh
+++ b/scripts/subtensor/scripts/subtensor_start.sh
@@ -9,7 +9,7 @@ function run_command()
F_NETWORK=$1
F_NODE_TYPE=$2
F_BIN_PATH=$3
-
+
# Different command options by network and node type
MAINNET_CHAIN='--chain ./raw_spec.json'
TESTNET_CHAIN='--chain ./raw_testspec.json'
@@ -18,50 +18,47 @@ function run_command()
TESTNET_BOOTNODE='--bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr'
NODE_TYPE_ARCHIVE='--pruning=archive'
NODE_TYPE_LITE='--sync warp'
-
-
+
+
# Options by the type of node we offer
MAINNET_ARCHIVE_OPTIONS="$MAINNET_CHAIN $MAINNET_BOOTNODE $NODE_TYPE_ARCHIVE"
MAINNET_LITE_OPTIONS="$MAINNET_CHAIN $MAINNET_BOOTNODE $NODE_TYPE_LITE"
TESTNET_ARCHIVE_OPTIONS="$TESTNET_CHAIN $TESTNET_BOOTNODE $NODE_TYPE_ARCHIVE"
TESTNET_LITE_OPTIONS="$TESTNET_CHAIN $TESTNET_BOOTNODE $NODE_TYPE_LITE"
-
+
# Checking options to use
if [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then
SPECIFIC_OPTIONS=$MAINNET_ARCHIVE_OPTIONS
- elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then
- SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS
- elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then
- SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS
- elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then
- SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS
+ elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then
+ SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS
+ elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then
+ SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS
+ elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then
+ SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS
fi
-
+
if [ ! -f $F_BIN_PATH ]; then
echo "Binary '$F_BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location."
echo "Please ensure you have compiled the binary first."
exit 1
fi
-
+
# Command to run subtensor
$F_BIN_PATH \
- --base-path /tmp/blockchain \
- --execution wasm \
- --wasm-execution compiled \
- --max-runtime-instances 32 \
- --rpc-max-response-size 2048 \
- --rpc-external \
- --rpc-cors all \
- --no-mdns \
- --ws-max-connections 16000 \
- --in-peers 8000 \
- --out-peers 8000 \
- --prometheus-external \
- --rpc-port 9933 \
- --port 30333 \
- --ws-port 9944 \
- --ws-external \
- $SPECIFIC_OPTIONS
+ --base-path /tmp/blockchain \
+ --execution wasm \
+ --wasm-execution compiled \
+ --port 30333 \
+ --max-runtime-instances 64 \
+ --rpc-max-response-size 2048 \
+ --rpc-cors all \
+ --rpc-port 9944 \
+ --no-mdns \
+ --in-peers 8000 \
+ --out-peers 8000 \
+ --prometheus-external \
+ --rpc-external \
+ $SPECIFIC_OPTIONS
}
@@ -74,44 +71,44 @@ BIN_PATH="./target/release/node-subtensor"
# Getting arguments from user
while [[ $# -gt 0 ]]; do
- case $1 in
- -h|--help)
- help
- exit 0
- ;;
- -e|--execution)
- EXEC_TYPE="$2"
- shift # past argument
- shift # past value
- ;;
- -b|--build)
- BUILD="--build"
- shift # past argument
- ;;
- -n|--network)
- NETWORK="$2"
- shift
- shift
- ;;
- -t|--node-type)
- NODE_TYPE="$2"
- shift
- shift
- ;;
- -p|--bin-path)
- BIN_PATH="$2"
- shift
- shift
- ;;
- -*|--*)
- echo "Unknown option $1"
- exit 1
- ;;
- *)
- POSITIONAL_ARGS+=("$1")
- shift
- ;;
- esac
+ case $1 in
+ -h|--help)
+ help
+ exit 0
+ ;;
+ -e|--execution)
+ EXEC_TYPE="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -b|--build)
+ BUILD="--build"
+ shift # past argument
+ ;;
+ -n|--network)
+ NETWORK="$2"
+ shift
+ shift
+ ;;
+ -t|--node-type)
+ NODE_TYPE="$2"
+ shift
+ shift
+ ;;
+ -p|--bin-path)
+ BIN_PATH="$2"
+ shift
+ shift
+ ;;
+ -*|--*)
+ echo "Unknown option $1"
+ exit 1
+ ;;
+ *)
+ POSITIONAL_ARGS+=("$1")
+ shift
+ ;;
+ esac
done
# Verifying arguments values
@@ -133,10 +130,10 @@ fi
# Running subtensor
case $EXEC_TYPE in
docker)
- cd $HOME/subtensor
- docker compose down --remove-orphans
- echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE"
- docker compose up $BUILD --detach $NETWORK-$NODE_TYPE
+ cd $HOME/subtensor
+ docker compose down --remove-orphans
+ echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE"
+ docker compose up $BUILD --detach $NETWORK-$NODE_TYPE
;;
binary)
run_command $NETWORK $NODE_TYPE $BIN_PATH
diff --git a/subnet/__init__.py b/subnet/__init__.py
index 3c5e85ac..9e9b7903 100644
--- a/subnet/__init__.py
+++ b/subnet/__init__.py
@@ -50,7 +50,7 @@ def __lt__(self, other):
)
-__version__ = "2.2.4"
+__version__ = "2.2.5"
version = SubnetVersion.from_string(__version__)
__spec_version__ = version.to_spec_version()
diff --git a/subnet/constants.py b/subnet/constants.py
index 4d761391..9d0116f0 100644
--- a/subnet/constants.py
+++ b/subnet/constants.py
@@ -5,10 +5,10 @@
RELIABILLITY_FAILURE_REWARD = 0.0
# Score weights
-AVAILABILITY_WEIGHT = 3.0
-LATENCY_WEIGHT = 1.0
-RELIABILLITY_WEIGHT = 1.0
-DISTRIBUTION_WEIGHT = 1.0
+AVAILABILITY_WEIGHT = 8
+LATENCY_WEIGHT = 7
+RELIABILLITY_WEIGHT = 3
+DISTRIBUTION_WEIGHT = 2
# Reset config
RELIABILLITY_RESET = 1080 # Reset challenge statistics every 3 epochs
diff --git a/subnet/country/country.py b/subnet/country/country.py
new file mode 100644
index 00000000..431f2706
--- /dev/null
+++ b/subnet/country/country.py
@@ -0,0 +1,185 @@
+import time
+import copy
+import requests
+import threading
+import ipaddress
+import bittensor as bt
+from datetime import datetime
+from typing import List
+
+from subnet.country.country_constants import (
+ COUNTRY_URL,
+ LOGGING_NAME,
+ COUNTRY_SLEEP,
+)
+from subnet.validator.localisation import (
+ get_country_by_country_is,
+ get_country_by_ip_api,
+ get_country_by_ipinfo_io,
+ get_country_by_my_api,
+)
+
+
+class CountryService(threading.Thread):
+ def __init__(self, netuid: int):
+ super().__init__()
+ self.stop_flag = threading.Event()
+ self._lock = threading.Lock()
+ self._data = {}
+ self.netuid = netuid
+ self.last_modified = None
+ self.show_not_found = True
+ self.first_try = True
+
+ # Allow us to not display multiple time the same errors
+ self.error_message = None
+
+ def _is_custom_api_enabled(self):
+ with self._lock:
+ return self._data.get("enable_custom_api") or True
+
+ def get_last_modified(self):
+ with self._lock:
+ return self.last_modified
+
+ def get_locations(self) -> List[str]:
+ with self._lock:
+ localisations = self._data.get("localisations") or {}
+ return copy.deepcopy(localisations)
+
+ def get_ipv4(self, ip):
+ try:
+ # First, try to interpret the input as an IPv4 address
+ ipv4 = ipaddress.IPv4Address(ip)
+ return str(ipv4)
+ except ipaddress.AddressValueError:
+ pass
+
+ try:
+ # Next, try to interpret the input as an IPv6 address
+ ipv6 = ipaddress.IPv6Address(ip)
+ if ipv6.ipv4_mapped:
+ return str(ipv6.ipv4_mapped)
+ except ipaddress.AddressValueError:
+ pass
+
+ return ip
+
+ def get_country(self, ip: str):
+ """
+ Get the country code of the ip
+ """
+ ip_ipv4 = self.get_ipv4(ip)
+
+ country = None
+ with self._lock:
+ overrides = self._data.get("overrides") or {}
+ country = overrides.get(ip_ipv4)
+
+ if country:
+ return country
+
+ country, reason1 = (
+ get_country_by_my_api(ip_ipv4)
+ if self._is_custom_api_enabled()
+ else (None, None)
+ )
+ if country:
+ return country
+
+ country, reason2 = get_country_by_country_is(ip_ipv4)
+ if country:
+ return country
+
+ country, reason3 = get_country_by_ip_api(ip_ipv4)
+ if country:
+ return country
+
+ country, reason4 = get_country_by_ipinfo_io(ip_ipv4)
+ if country:
+ return country
+
+ bt.logging.warning(
+ f"Could not get the country of the ip {ip_ipv4}: Api 1: {reason1} / Api 2: {reason2} / Api 3: {reason3} / Api 4: {reason4}"
+ )
+ return None
+
+ def start(self):
+ super().start()
+ bt.logging.debug(f"Country started")
+
+ def stop(self):
+ self.stop_flag.set()
+ super().join()
+ bt.logging.debug(f"Country stopped")
+
+ def wait(self):
+ """
+ Wait until we have execute the run method at least one
+ """
+ attempt = 0
+ while self.first_try or attempt > 5:
+ time.sleep(1)
+ attempt += 1
+
+ def run(self):
+ while not self.stop_flag.is_set():
+ response = None
+ try:
+ # Sleep before requesting again
+ if not self.first_try:
+ time.sleep(COUNTRY_SLEEP)
+
+ url = COUNTRY_URL.get(self.netuid)
+ if not url:
+ bt.logging.warning(
+ f"Could not find the country file for the subnet {self.netuid}"
+ )
+
+ response = requests.get(url)
+ if response.status_code != 200:
+ if response.status_code == 404 and not self.show_not_found:
+ continue
+
+ self.show_not_found = response.status_code != 404
+
+ error_message = f"[{LOGGING_NAME}] Could not get the country file {response.status_code}: {response.reason}"
+ if error_message != self.error_message:
+ bt.logging.warning(error_message)
+ self.error_message = error_message
+
+ continue
+
+ # Load the data
+ data = response.json() or {}
+
+ # Check is date can be retrieved
+ remote_last_modified = data.get("last-modified")
+ if remote_last_modified is None:
+ continue
+
+ # Check if data changed
+ last_modified = datetime.strptime(
+ remote_last_modified, "%Y-%m-%d %H:%M:%S.%f"
+ )
+ if self.last_modified and last_modified <= self.last_modified:
+ continue
+
+ if self._data == data:
+ continue
+
+ with self._lock:
+ self._data = data
+ self.last_modified = last_modified
+
+ bt.logging.success(
+ f"[{LOGGING_NAME}] Country file proceed successfully",
+ )
+ except Exception as err:
+ content = response.content if response else ""
+ error_message = f"[{LOGGING_NAME}] An error during country file processing: {err} {type(err)} {content}"
+ if error_message != self.error_message:
+ bt.logging.error(error_message)
+ self.error_message = error_message
+ finally:
+ self.first_try = False
diff --git a/subnet/country/country_constants.py b/subnet/country/country_constants.py
new file mode 100644
index 00000000..9a00bebe
--- /dev/null
+++ b/subnet/country/country_constants.py
@@ -0,0 +1,9 @@
+# Logging
+LOGGING_NAME = "Country"
+
+# Monitor
+COUNTRY_SLEEP = 5 * 60 # Every 5 minutes
+COUNTRY_URL = {
+ 7: "http://drive.google.com/uc?id=14RkFaEuwfc8lnJghNc3oKLT32kqcdjTd&export=download",
+ 92: "http://drive.google.com/uc?id=1r1uBSkLnehMsIx9csVcrtr-Iod8ij8ZE&export=download",
+}
diff --git a/subnet/miner/run.py b/subnet/miner/run.py
index ac3f1d5a..ca34336e 100644
--- a/subnet/miner/run.py
+++ b/subnet/miner/run.py
@@ -57,6 +57,16 @@ def run(self):
def handler(obj, update_nr, subscription_id):
current_block = obj["header"]["number"]
bt.logging.debug(f"New block #{current_block}")
+ bt.logging.debug(
+ f"Blocks since epoch: {(current_block + netuid + 1) % (tempo + 1)}"
+ )
+
+ # --- Check to resync the metagraph.
+ should_sync = self.should_sync_metagraph()
+ bt.logging.debug(f"should_sync_metagraph() {should_sync}")
+ if should_sync:
+ self.metagraph.sync(subtensor=self.subtensor)
+ bt.logging.info("Metagraph resynced")
# --- Check for registration every 100 blocks (20 minutes).
if current_block % 100 == 0:
@@ -71,10 +81,6 @@ def handler(obj, update_nr, subscription_id):
self.last_upgrade_check = time.time()
- bt.logging.debug(
- f"Blocks since epoch: {(current_block + netuid + 1) % (tempo + 1)}"
- )
-
if self.should_exit:
return True
diff --git a/subnet/validator/challenge.py b/subnet/validator/challenge.py
index e3f4c74b..062c7a84 100644
--- a/subnet/validator/challenge.py
+++ b/subnet/validator/challenge.py
@@ -6,7 +6,6 @@
from subnet.constants import DEFAULT_PROCESS_TIME
from subnet.shared.subtensor import get_current_block
from subnet.validator.models import Miner
-from subnet.validator.miner import get_miner_ip_occurences
from subnet.validator.synapse import send_scope
from subnet.validator.security import is_miner_suspicious
from subnet.validator.utils import (
@@ -106,6 +105,9 @@ async def challenge_data(self):
suspicious_uids = self.monitor.get_suspicious_uids()
bt.logging.debug(f"[{CHALLENGE_NAME}] Suspicious uids {suspicious_uids}")
+ # Get the locations
+ locations = self.country_service.get_locations()
+
# Execute the challenges
tasks = []
reasons = []
@@ -126,6 +128,7 @@ async def challenge_data(self):
miner: Miner = next((miner for miner in self.miners if miner.uid == uid), None)
bt.logging.info(f"[{CHALLENGE_NAME}][{miner.uid}] Computing score...")
+ bt.logging.debug(f"[{CHALLENGE_NAME}][{miner.uid}] Country {miner.country}")
# Check if the miner is suspicious
miner.suspicious, miner.penalty_factor = is_miner_suspicious(
@@ -151,7 +154,9 @@ async def challenge_data(self):
)
# Compute score for latency
- miner.latency_score = compute_latency_score(self.country, miner, self.miners)
+ miner.latency_score = compute_latency_score(
+ self.country_code, miner, self.miners, locations
+ )
bt.logging.debug(
f"[{CHALLENGE_NAME}][{miner.uid}] Latency score {miner.latency_score}"
)
diff --git a/subnet/validator/localisation.py b/subnet/validator/localisation.py
index b2fc81b8..954314ba 100644
--- a/subnet/validator/localisation.py
+++ b/subnet/validator/localisation.py
@@ -1,9 +1,8 @@
-import json
-import os
import requests
import bittensor as bt
from math import radians, sin, cos, sqrt, atan2
+MY_API_BASE_URL = "http://api.ip-from.com"
COUNTRY_IS_BASE_URL = "https://api.country.is"
IP_API_BASE_URL = "http://ip-api.com/json"
IPINFO_IO_BASE_URL = "https://ipinfo.io"
@@ -11,19 +10,24 @@
countries = {}
-def get_localisation(country_code: str):
+def get_country_by_my_api(ip: str):
"""
- Get the longitude and latitude of the country
+ Get the country code of the ip (use Maxwind and IpInfo)
+ Reference: http://api.ip-from.com
"""
- global countries
- if len(countries) == 0:
- current_dir = os.path.dirname(os.path.realpath(__file__))
- file_path = os.path.join(current_dir, "..", "localisation.json")
+ url = f"{MY_API_BASE_URL}/{ip}"
- with open(file_path, "r") as f:
- countries = json.load(f)
+ response = requests.get(url)
+
+ if response.status_code != 200:
+ return None, response.reason
+
+ data = response.json()
- return countries.get(country_code)
+ maxmind_country = data.get("maxmind_country")
+ ipinfo_country = data.get("ipinfo_country")
+
+ return (maxmind_country, None) if maxmind_country else (ipinfo_country, None)
def get_country_by_country_is(ip: str):
@@ -77,28 +81,6 @@ def get_country_by_ipinfo_io(ip: str):
return data.get("country"), None
-def get_country(ip: str):
- """
- Get the country code of the ip
- """
- country, reason1 = get_country_by_country_is(ip)
- if country:
- return country
-
- country, reason2 = get_country_by_ip_api(ip)
- if country:
- return country
-
- country, reason3 = get_country_by_ipinfo_io(ip)
- if country:
- return country
-
- bt.logging.warning(
- f"Could not get the country of the ip {ip}: Api 1: {reason1} / Api 2: {reason2} / Api 3: {reason3}"
- )
- return None
-
-
def compute_localisation_distance(lat1, lon1, lat2, lon2):
"""
Compute the distance between two localisations using Haversine formula
diff --git a/subnet/validator/miner.py b/subnet/validator/miner.py
index bf1dd18f..f85fd33f 100644
--- a/subnet/validator/miner.py
+++ b/subnet/validator/miner.py
@@ -9,7 +9,6 @@
compute_final_score,
)
from subnet.validator.utils import get_available_uids, check_uid_availability
-from subnet.validator.localisation import get_country
from subnet.validator.database import (
get_hotkey_statistics,
remove_hotkey_stastitics,
@@ -52,16 +51,16 @@ async def get_all_miners(self) -> List[Miner]:
uid=uid,
ip=axon.ip,
hotkey=axon.hotkey,
- country=get_country(axon.ip),
+ country=self.country_service.get_country(axon.ip),
ip_occurences=ip_occurences,
)
- await update_hotkey_statistics(axon.hotkey, miner.snapshot, self.database)
else:
# In hash set everything is stored as a string to the verified need to be manage differently
+ country = self.country_service.get_country(axon.ip)
+ if country == None:
+ country = get_field_value(statistics.get(b"country"))
+
version = get_field_value(statistics.get(b"version"), "0.0.0")
- country = get_field_value(statistics.get(b"country")) or get_country(
- axon.ip
- )
verified = get_field_value(statistics.get(b"verified"), "0")
score = get_field_value(statistics.get(b"score"), 0)
availability_score = get_field_value(
@@ -98,6 +97,9 @@ async def get_all_miners(self) -> List[Miner]:
process_time=process_time,
)
+ # Update the database just to be sure we have the right country
+ await update_hotkey_statistics(axon.hotkey, miner.snapshot, self.database)
+
miners.append(miner)
return miners
@@ -107,7 +109,9 @@ async def add_new_miner(self, uid: int, ip: str, hotkey: str):
"""
Add a new miner
"""
- miner = Miner(uid=uid, ip=ip, hotkey=hotkey, country=get_country(ip))
+ miner = Miner(
+ uid=uid, ip=ip, hotkey=hotkey, country=self.country_service.get_country(ip)
+ )
self.miners.append(miner)
return miner
@@ -123,19 +127,19 @@ async def replace_old_miner(self, ip: str, hotkey: str, miner: Miner):
await remove_hotkey_stastitics(miner.hotkey, self.database)
# Reset the new miner
- miner.reset(ip, hotkey, get_country(ip))
+ miner.reset(ip, hotkey, self.country_service.get_country(ip))
return old_hotkey
-def move_miner(ip: str, miner: Miner):
+def move_miner(self, ip: str, miner: Miner):
"""
Move an existing miner from a host to another one
"""
previous_ip = miner.ip
# Reset the miner as it changed ip so everything has to be re-evaluated
- miner.reset(ip, miner.hotkey, get_country(ip))
+ miner.reset(ip, miner.hotkey, self.country_service.get_country(ip))
return previous_ip
@@ -197,11 +201,20 @@ async def resync_miners(self):
# Check the miner has been moved to another VPS
if miner.ip != ip:
- previous_ip = move_miner(ip, miner)
+ previous_ip = move_miner(self, ip, miner)
bt.logging.success(
f"[{miner.uid}] Miner moved from {previous_ip} to {miner.ip}"
)
+ # Check if the country has been overrided
+ country_overrided = self.country_service.get_country(miner.ip)
+ if country_overrided and miner.country != country_overrided:
+ previous_country = miner.country
+ miner.country = country_overrided
+ bt.logging.success(
+ f"[{miner.uid}][{previous_country}] Miner's country overrided by {miner.country}"
+ )
+
# Focus on impacts resulting of these changes
bt.logging.debug("resync_miners() refreshing ip occurences")
ips = [miner.ip for miner in self.miners]
@@ -210,12 +223,15 @@ async def resync_miners(self):
miner.ip_occurences = get_miner_ip_occurences(miner.ip, ips)
bt.logging.debug("resync_miners() refreshing scores")
+ locations = self.country_service.get_locations()
for miner in self.miners:
# Refresh the availability score
miner.availability_score = compute_availability_score(miner)
# Refresh latency score
- miner.latency_score = compute_latency_score(self.country, miner, self.miners)
+ miner.latency_score = compute_latency_score(
+ self.country_code, miner, self.miners, locations
+ )
# Refresh the distribution score
miner.distribution_score = compute_distribution_score(miner, self.miners)
diff --git a/subnet/validator/score.py b/subnet/validator/score.py
index de7a33dc..93b14232 100644
--- a/subnet/validator/score.py
+++ b/subnet/validator/score.py
@@ -4,10 +4,7 @@
from subnet.validator.models import Miner
from subnet.validator.bonding import wilson_score_interval
-from subnet.validator.localisation import (
- compute_localisation_distance,
- get_localisation,
-)
+from subnet.validator.localisation import compute_localisation_distance
from subnet.validator.constants import CHALLENGE_NAME
from subnet.constants import (
AVAILABILITY_FAILURE_REWARD,
@@ -94,7 +91,9 @@ def can_compute_latency_score(miner: Miner):
return miner.verified and not miner.has_ip_conflicts
-def compute_latency_score(validator_country: str, miner: Miner, miners: List[Miner]):
+def compute_latency_score(
+ validator_country: str, miner: Miner, miners: List[Miner], locations
+):
"""
Compute the latency score of the uid based on the process time of all uids
"""
@@ -104,7 +103,7 @@ def compute_latency_score(validator_country: str, miner: Miner, miners: List[Min
bt.logging.trace(f"[{miner.uid}][Score][Latency] Process time {miner.process_time}")
# Step 1: Get the localisation of the validator
- validator_localisation = get_localisation(validator_country)
+ validator_localisation = locations.get(validator_country)
# Step 2: Compute the miners process times by adding a tolerance
miner_index = -1
@@ -115,8 +114,8 @@ def compute_latency_score(validator_country: str, miner: Miner, miners: List[Min
continue
distance = 0
- location = get_localisation(item.country)
- if location is not None:
+ location = locations.get(item.country)
+ if location is not None and validator_localisation is not None:
distance = compute_localisation_distance(
validator_localisation["latitude"],
validator_localisation["longitude"],
@@ -124,9 +123,15 @@ def compute_latency_score(validator_country: str, miner: Miner, miners: List[Min
location["longitude"],
)
else:
- bt.logging.warning(
- f"[{miner.uid}][Score][Latency] The country '{item.country}' could not be found. No tolerance applied."
- )
+ if validator_localisation is None:
+ bt.logging.warning(
+ f"[{miner.uid}][Score][Latency] The validator's country '{validator_country}' could not be found. No tolerance applied."
+ )
+
+ if location is None:
+ bt.logging.warning(
+ f"[{miner.uid}][Score][Latency] The country '{item.country}' could not be found. No tolerance applied."
+ )
scaled_distance = distance / MAX_DISTANCE
tolerance = 1 - scaled_distance
@@ -215,7 +220,7 @@ def compute_final_score(miner: Miner):
"""
# Use a smaller weight if the subtensor is available but desync (miner block < validator block - 1)
availability_weight = (
- 1 if miner.verified and not miner.sync else AVAILABILITY_WEIGHT
+ 3 if miner.verified and not miner.sync else AVAILABILITY_WEIGHT
)
numerator = (
diff --git a/subnet/validator/state.py b/subnet/validator/state.py
index eac83fdf..b7445630 100644
--- a/subnet/validator/state.py
+++ b/subnet/validator/state.py
@@ -41,12 +41,12 @@ def should_checkpoint(current_block, prev_step_block, checkpoint_block_length):
return current_block - prev_step_block >= checkpoint_block_length
-async def resync_metagraph_and_miners(self):
+async def resync_metagraph_and_miners(self, force_refresh = False):
"""Checkpoints the training process."""
bt.logging.info("checkpoint()")
resynched = resync_metagraph(self)
- if resynched:
+ if resynched or force_refresh:
# Resync miners list
await resync_miners(self)
@@ -328,7 +328,7 @@ def init_wandb(self):
THIS_VERSION,
str(THIS_SPEC_VERSION),
f"netuid_{self.metagraph.netuid}",
- self.country,
+ self.country_code,
]
if self.config.mock:
diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py
index f89d38c8..a742bb8b 100644
--- a/tests/unit_tests/conftest.py
+++ b/tests/unit_tests/conftest.py
@@ -13,6 +13,7 @@ def validator():
config.wandb.off = True
config.neuron.dont_save_events = True
validator = Validator(config)
+ validator.country_code = "GB"
bt.logging.off()
mock = AsyncMock(aioredis.Redis)
diff --git a/tests/unit_tests/subnet/validator/test_final_score.py b/tests/unit_tests/subnet/validator/test_final_score.py
index 2719e4b8..d4fe1e26 100644
--- a/tests/unit_tests/subnet/validator/test_final_score.py
+++ b/tests/unit_tests/subnet/validator/test_final_score.py
@@ -30,7 +30,7 @@ def test_a_not_sync_miner_should_return_a_score_different_of_zero():
miner.reliability_score = 0.30
miner.distribution_score = 0.40
- expected_score = (0.10 + 0.20 + 0.30 + 0.40) / 4
+ expected_score = (0.10 * 3 + 0.20 * 7 + 0.30 * 3 + 0.40 * 2) / 15
# Act
result = compute_final_score(miner)
@@ -67,7 +67,7 @@ def test_a_suspicious_miner_with_penalty_factor_should_return_a_score_different_
miner.distribution_score = 0.40
miner.penalty_factor = 0.4
- expected_score = ((0.10 + 0.20 + 0.30 + 0.40) / 4) * 0.4
+ expected_score = ((0.10 * 3 + 0.20 * 7 + 0.30 * 3 + 0.40 * 2) / 15) * 0.4
# Act
result = compute_final_score(miner)
@@ -86,7 +86,7 @@ def test_a_verified_and_sync_miner_should_return_a_score_different_of_zero():
miner.reliability_score = 0.30
miner.distribution_score = 0.40
- expected_score = (3 * 0.10 + 0.20 + 0.30 + 0.40) / 6
+ expected_score = (0.10 * 8 + 0.20 * 7 + 0.30 * 3 + 0.40 * 2) / 20
# Act
result = compute_final_score(miner)
diff --git a/tests/unit_tests/subnet/validator/test_latency_score.py b/tests/unit_tests/subnet/validator/test_latency_score.py
index aee3032d..1dbe4ae6 100644
--- a/tests/unit_tests/subnet/validator/test_latency_score.py
+++ b/tests/unit_tests/subnet/validator/test_latency_score.py
@@ -2,13 +2,17 @@
import tests.unit_tests.mocks.mock_miners as mocks
+locations = {
+ "DE": {"country": "Germany", "latitude": 51.165691, "longitude": 10.451526},
+}
+
def test_a_not_verified_miner_should_return_a_score_of_zero():
# Arrange
miner = mocks.miner_not_verified_1
# Act
- result = compute_latency_score(miner.country, miner, [miner])
+ result = compute_latency_score(miner.country, miner, [miner], locations)
# Assert
assert 0.0 == result
@@ -19,7 +23,7 @@ def test_an_ip_conflicts_miner_should_return_a_score_of_zero():
miner = mocks.miner_with_ip_conflicts_1
# Act
- result = compute_latency_score(miner.country, miner, [miner])
+ result = compute_latency_score(miner.country, miner, [miner], locations)
# Assert
assert 0.0 == result
@@ -30,7 +34,7 @@ def test_a_not_verified_and_ip_conflicts_miner_should_return_a_score_of_zero():
miner = mocks.miner_not_verified_and_ip_conflicts_1
# Act
- result = compute_latency_score(miner.country, miner, [miner])
+ result = compute_latency_score(miner.country, miner, [miner], locations)
# Assert
assert 0.0 == result
@@ -43,7 +47,7 @@ def test_a_verified_miner_when_alone_should_return_a_score_of_one():
miners = [miner]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 1.0 == result
@@ -60,7 +64,7 @@ def test_a_verified_miner_when_other_miners_are_not_verified_should_return_a_sco
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 1.0 == result
@@ -73,7 +77,7 @@ def test_a_verified_miner_when_other_miners_have_ip_conflicts_should_return_a_sc
miners = [miner, mocks.miner_with_ip_conflicts_1, mocks.miner_with_ip_conflicts_2]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 1.0 == result
@@ -86,7 +90,7 @@ def test_a_verified_miner_when_other_miners_are_not_verified_and_have_ip_conflic
miners = [miner, mocks.miner_with_ip_conflicts_1, mocks.miner_with_ip_conflicts_2]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 1.0 == result
@@ -102,7 +106,7 @@ def test_a_verified_miner_when_evaluating_the_best_one_should_return_a_score_of_
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 1.0 == result
@@ -118,7 +122,7 @@ def test_a_verified_miner_when_evaluating_the_worst_one_should_return_a_score_of
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 0.0 == result
@@ -134,7 +138,7 @@ def test_a_verified_miner_when_evaluating_a_middle_one_should_return_a_score_bet
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 0.5 == result
@@ -150,7 +154,7 @@ def test_a_verified_miner_when_evaluating_a_30_percent_close_to_the_best_should_
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert 0.7 == result
@@ -166,7 +170,7 @@ def test_a_verified_miner_when_evaluating_a_30_percent_close_to_the_worst_should
]
# Act
- result = compute_latency_score(miner.country, miner, miners)
+ result = compute_latency_score(miner.country, miner, miners, locations)
# Assert
assert abs(0.3 - result) < 0.000000000000001
diff --git a/tests/unit_tests/subnet/validator/test_resync_miners.py b/tests/unit_tests/subnet/validator/test_resync_miners.py
index d84ffb8d..683a870f 100644
--- a/tests/unit_tests/subnet/validator/test_resync_miners.py
+++ b/tests/unit_tests/subnet/validator/test_resync_miners.py
@@ -1,7 +1,7 @@
import copy
import pytest
import unittest
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
from tests.unit_tests.mocks import mock_redis, mock_country
from tests.unit_tests.utils.metagraph import (
@@ -34,6 +34,15 @@
{"ip": "9.91.241.48", "country": "ES"},
]
+locations = {
+ "US": {"country": "United States", "latitude": 37.09024, "longitude": -95.712891},
+ "SG": {"country": "Singapore", "latitude": 1.352083, "longitude": 103.819836},
+ "BR": {"country": "Brazil", "latitude": -14.235004, "longitude": -51.92528},
+ "ES": {"country": "Spain", "latitude": 40.463667, "longitude": -3.74922},
+ "KR": {"country": "South Korea", "latitude": 35.907757, "longitude": 127.766922},
+ "IR": {"country": "Iran", "latitude": 32.427908, "longitude": 53.688046},
+}
+
class TestResyncMiners(unittest.IsolatedAsyncioTestCase):
@pytest.fixture(autouse=True)
@@ -47,10 +56,8 @@ def tearDown(self):
self.validator.subtensor.chain_state = self.chain_state
sync_metagraph(self.validator, default_axons_details)
- @patch("subnet.validator.miner.get_country")
async def test_given_a_metagraph_when_no_change_should_return_the_same_list_of_miners(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details)
@@ -59,7 +66,11 @@ async def test_given_a_metagraph_when_no_change_should_return_the_same_list_of_m
for idx, axon in enumerate(axons):
axon.ip = axons_details[idx]["ip"]
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
+
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
@@ -74,17 +85,18 @@ async def test_given_a_metagraph_when_no_change_should_return_the_same_list_of_m
# Assert
assert miners == self.validator.miners
- @patch("subnet.validator.miner.get_country")
async def test_given_a_partially_full_metagraph_when_a_new_neuron_is_added_should_be_added_to_the_list(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details) + [
{"ip": "19.91.241.48", "country": "US"}
]
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
@@ -125,19 +137,22 @@ async def test_given_a_partially_full_metagraph_when_a_new_neuron_is_added_shoul
assert False == miner.sync
assert False == miner.verified
- @patch("subnet.validator.miner.get_country")
async def test_given_a_full_metagraph_when_a_uid_has_a_new_hotkey_with_same_ip_should_replace_the_old_miner_by_the_new_one_in_the_list(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details)
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
+ self.validator.country_service.get_locations.return_value = locations
+
sync_metagraph(self.validator, axons_details)
miners = await get_all_miners(self.validator)
self.validator.miners = copy.deepcopy(miners)
@@ -172,19 +187,22 @@ async def test_given_a_full_metagraph_when_a_uid_has_a_new_hotkey_with_same_ip_s
assert False == miner.sync
assert False == miner.verified
- @patch("subnet.validator.miner.get_country")
async def test_given_a_full_metagraph_when_a_uid_has_a_same_hotkey_with_different_ip_should_replace_the_old_miner_by_the_new_one_in_the_list(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details)
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
+ self.validator.country_service.get_locations.return_value = locations
+
sync_metagraph(self.validator, axons_details)
miners = await get_all_miners(self.validator)
self.validator.miners = copy.deepcopy(miners)
@@ -220,19 +238,22 @@ async def test_given_a_full_metagraph_when_a_uid_has_a_same_hotkey_with_differen
assert False == miner.sync
assert False == miner.verified
- @patch("subnet.validator.miner.get_country")
async def test_given_a_full_metagraph_when_a_uid_has_a_new_hotkey_with_different_ip_should_replace_the_old_miner_by_the_new_one_in_the_list(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details)
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
+ self.validator.country_service.get_locations.return_value = locations
+
sync_metagraph(self.validator, axons_details)
miners = await get_all_miners(self.validator)
self.validator.miners = copy.deepcopy(miners)
@@ -267,19 +288,22 @@ async def test_given_a_full_metagraph_when_a_uid_has_a_new_hotkey_with_different
assert False == miner.sync
assert False == miner.verified
- @patch("subnet.validator.miner.get_country")
async def test_given_a_metagraph_when_a_uid_is_not_running_should_be_removed_from_the_list(
self,
- mock_get_country,
):
# Arrange
axons_details = copy.deepcopy(default_axons_details)
- mock_country.mock_get_country(mock_get_country, axons_details)
+ self.validator.country_service = MagicMock()
+ mock_country.mock_get_country(
+ self.validator.country_service.get_country, axons_details
+ )
self.validator.database = mock_redis.mock_get_statistics(
self.validator.metagraph.hotkeys
)
+ self.validator.country_service.get_locations.return_value = locations
+
sync_metagraph(self.validator, axons_details)
miners = await get_all_miners(self.validator)
self.validator.miners = copy.deepcopy(miners)