Skip to content
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
1 change: 1 addition & 0 deletions app/src/infinite_hashes/auctions/mechanism_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any

MECHANISM_SPLIT_DENOMINATOR = 65535
AUCTION_MECHANISM_SHARE_FRACTION = 1.0


async def fetch_mechanism_share_fraction(
Expand Down
8 changes: 2 additions & 6 deletions app/src/infinite_hashes/consensus/bidding.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import structlog
from pydantic import Field

from infinite_hashes.auctions.mechanism_split import fetch_mechanism_share_fraction
from infinite_hashes.auctions.mechanism_split import AUCTION_MECHANISM_SHARE_FRACTION

from .commitment import CompactCommitment
from .price import (
Expand Down Expand Up @@ -312,11 +312,7 @@ async def select_auction_winners_async(
share_fp18 = miners_share_fp18
share_fraction: float | None = None
if share_fp18 is None:
share_fraction = await fetch_mechanism_share_fraction(
bt,
netuid,
mechanism_id,
)
share_fraction = AUCTION_MECHANISM_SHARE_FRACTION
share_fp18 = int(share_fraction * BASE_MINER_SHARE * FP)
miner_share_per_block = float(share_fp18) / FP

Expand Down
23 changes: 23 additions & 0 deletions app/src/infinite_hashes/consensus/tests/test_bidding_budget.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
MECHANISM_SPLIT_DENOMINATOR,
fetch_mechanism_share_fraction,
)
from infinite_hashes.consensus.bidding import FP, _compute_budget_ph
from infinite_hashes.testutils.integration.budget_helper import alpha_tao_to_fp18, compute_alpha_tao_for_budget


class _FakeState:
Expand Down Expand Up @@ -56,3 +58,24 @@ class _NoStateBT:

with pytest.raises(RuntimeError):
await fetch_mechanism_share_fraction(_NoStateBT(), netuid=1, mechanism_id=1)


def test_alpha_tao_helper_keeps_full_budget_reachable_for_100_percent_auction_share():
target_budget_ph = 10.0
alpha_tao = compute_alpha_tao_for_budget(
target_budget_ph,
tao_usdc=45.0,
hashp_usdc=50.0,
mechanism_share=1.0,
)

computed_budget_ph = _compute_budget_ph(
alpha_tao_to_fp18(alpha_tao),
int(45.0 * FP),
int(50.0 * FP),
0,
0,
int(0.41 * FP),
)

assert computed_budget_ph >= target_budget_ph
18 changes: 2 additions & 16 deletions app/src/infinite_hashes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,8 @@ def wrapped(*args, **kwargs):
CELERY_MESSAGE_COMPRESSION = "gzip" # result compression
CELERY_SEND_EVENTS = True # needed for worker monitoring
CELERY_BEAT_SCHEDULE = { # type: ignore
# Legacy mechanism 0 (weight-based)
"calculate_weights": {
"task": "infinite_hashes.validator.tasks.calculate_weights",
"schedule": datetime.timedelta(minutes=1),
"options": {
"expires": datetime.timedelta(minutes=1).total_seconds(),
},
},
"set_weights": {
"task": "infinite_hashes.validator.tasks.set_weights",
"schedule": datetime.timedelta(minutes=1),
"options": {
"expires": datetime.timedelta(minutes=1).total_seconds(),
},
},
# Auction mechanism 1
# Legacy mechanism 0 scheduling disabled. Auction weights are mirrored to
# both mechanisms from the auction pipeline.
"process_auctions": {
"task": "infinite_hashes.validator.tasks.process_auctions",
"schedule": datetime.timedelta(minutes=5),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
Allows easy control of available PH budget by adjusting ALPHA_TAO price.
"""

import math

MECHANISM_SPLIT_DENOMINATOR = 65535
DEFAULT_MECHANISM_SPLIT = (52428, 13107) # 80% -> mechanism 0, 20% -> mechanism 1
DEFAULT_MECHANISM_SPLIT = (0, 65535) # 0% -> mechanism 0, 100% -> mechanism 1
DEFAULT_MECHANISM_0_SHARE = DEFAULT_MECHANISM_SPLIT[0] / MECHANISM_SPLIT_DENOMINATOR
DEFAULT_MECHANISM_1_SHARE = DEFAULT_MECHANISM_SPLIT[1] / MECHANISM_SPLIT_DENOMINATOR

Expand Down Expand Up @@ -72,7 +74,9 @@ def alpha_tao_to_fp18(alpha_tao: float) -> int:
>>> alpha_tao_to_fp18(0.25)
250000000000000000
"""
return int(alpha_tao * 10**18)
# Round upward so test scenarios targeting an exact PH budget do not end up
# infinitesimally below the desired budget after FP18 truncation.
return math.ceil(alpha_tao * 10**18)


def compute_budget_summary(
Expand Down
153 changes: 74 additions & 79 deletions app/src/infinite_hashes/validator/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from django.conf import settings
from django.db import transaction

from infinite_hashes.auctions.mechanism_split import fetch_mechanism_share_fraction
from infinite_hashes.auctions.mechanism_split import AUCTION_MECHANISM_SHARE_FRACTION
from infinite_hashes.celery import app
from infinite_hashes.utils import run_async
from infinite_hashes.validator import auction_processing as auct
Expand All @@ -30,6 +30,8 @@
WEIGHT_SETTING_ATTEMPTS = 100
WEIGHT_SETTING_FAILURE_BACKOFF = 5
WEIGHT_SETTING_FINALIZATION_TIMEOUT = 120
AUCTION_WEIGHT_MECHANISM_IDS = (0, 1)
BASE_MINER_SHARE = 0.41

Hashrates: TypeAlias = dict[str, list[int]]

Expand Down Expand Up @@ -674,25 +676,14 @@ def _check_all_windows_processed():
)

blocks_per_day = 7200
base_miner_share = 0.41 # Base miner allocation per block (pre-mechanism split)
mechanism_share_fraction = await fetch_mechanism_share_fraction(
bittensor,
settings.BITTENSOR_NETUID,
mechanism_id=1,
)
if mechanism_share_fraction is None or mechanism_share_fraction <= 0:
raise RuntimeError(
f"Mechanism emission share must be positive; received {mechanism_share_fraction!r} for mechanism 1"
)

miner_share_per_block = base_miner_share * mechanism_share_fraction
mechanism_share_fraction = AUCTION_MECHANISM_SHARE_FRACTION
miner_share_per_block = BASE_MINER_SHARE * mechanism_share_fraction
daily_alpha = blocks_per_day * miner_share_per_block

logger.debug(
"Mechanism emission share",
mechanism_id=1,
"Auction emission share fixed",
mechanism_share_fraction=mechanism_share_fraction,
base_miner_share=base_miner_share,
base_miner_share=BASE_MINER_SHARE,
miner_share_per_block=miner_share_per_block,
daily_alpha=daily_alpha,
)
Expand Down Expand Up @@ -892,7 +883,7 @@ def calculate_auction_weights(*, event_loop: Any = None):


async def set_auction_weights_async() -> bool:
"""Set weights for mechanism 1 using set_mechanism_weights."""
"""Set auction weights for both mechanism 0 and mechanism 1."""
batches = [
batch
async for batch in WeightsBatch.objects.filter(
Expand Down Expand Up @@ -976,80 +967,84 @@ async def set_auction_weights_async() -> bool:
batch.scored = True

if not weights_by_uid:
logger.warning("No weights to set for mechanism 1")
logger.warning("No auction weights to set")
return False

# Commit weights using commit/reveal scheme (same pattern as mechanism 0)
reveal_round = None
use_direct_set = False
try:
async for attempt in tenacity.AsyncRetrying(
before_sleep=tenacity.before_sleep_log(logger, logging.ERROR),
stop=tenacity.stop_after_attempt(1),
wait=tenacity.wait_fixed(WEIGHT_SETTING_FAILURE_BACKOFF),
):
with attempt:
reveal_round = await commit_mechanism_weights(
bittensor=bittensor,
netuid=settings.BITTENSOR_NETUID,
mechanism_id=1,
weights=weights_by_uid,
version_key=0,
)
except Exception as exc:
message = str(exc).lower()
if isinstance(exc, turbobt.subtensor.exceptions.CommitRevealDisabled) or (
"commitrevealdisabled" in message
or "commitrevealv3disabled" in message
or "commit reveal" in message
or "commit/reveal" in message
):
logger.warning(
"Commit-reveal disabled for mechanism 1, falling back to set_mechanism_weights",
error=str(exc),
)
use_direct_set = True
else:
raise
submission_results = []
weights_u16 = normalize_weights_to_u16(weights_by_uid)

if use_direct_set:
weights_u16 = normalize_weights_to_u16(weights_by_uid)
for mechanism_id in AUCTION_WEIGHT_MECHANISM_IDS:
reveal_round = None
use_direct_set = False
try:
uids, weight_values = zip(*weights_u16.items())
except ValueError:
uids, weight_values = [], []
async for attempt in tenacity.AsyncRetrying(
before_sleep=tenacity.before_sleep_log(logger, logging.ERROR),
stop=tenacity.stop_after_attempt(1),
wait=tenacity.wait_fixed(WEIGHT_SETTING_FAILURE_BACKOFF),
):
with attempt:
reveal_round = await commit_mechanism_weights(
bittensor=bittensor,
netuid=settings.BITTENSOR_NETUID,
mechanism_id=mechanism_id,
weights=weights_by_uid,
version_key=0,
)
except Exception as exc:
message = str(exc).lower()
if isinstance(exc, turbobt.subtensor.exceptions.CommitRevealDisabled) or (
"commitrevealdisabled" in message
or "commitrevealv3disabled" in message
or "commit reveal" in message
or "commit/reveal" in message
):
logger.warning(
"Commit-reveal disabled for mechanism, falling back to set_mechanism_weights",
mechanism_id=mechanism_id,
error=str(exc),
)
use_direct_set = True
else:
raise

extrinsic = await bittensor.subtensor.subtensor_module.set_mechanism_weights(
settings.BITTENSOR_NETUID,
list(uids),
mechanism_id=1,
weights=list(weight_values),
version_key=0,
wallet=bittensor.wallet,
)
await asyncio.wait_for(
extrinsic.wait_for_finalization(),
timeout=WEIGHT_SETTING_FINALIZATION_TIMEOUT,
if use_direct_set:
try:
uids, weight_values = zip(*weights_u16.items())
except ValueError:
uids, weight_values = [], []

extrinsic = await bittensor.subtensor.subtensor_module.set_mechanism_weights(
settings.BITTENSOR_NETUID,
list(uids),
mechanism_id=mechanism_id,
weights=list(weight_values),
version_key=0,
wallet=bittensor.wallet,
)
await asyncio.wait_for(
extrinsic.wait_for_finalization(),
timeout=WEIGHT_SETTING_FINALIZATION_TIMEOUT,
)

submission_results.append(
{
"mechanism_id": mechanism_id,
"mode": "direct" if use_direct_set else "commit_reveal",
"reveal_round": reveal_round,
}
)

await WeightsBatch.objects.abulk_update(
batches,
fields=("scored",),
)

if use_direct_set:
logger.info(
"Auction weights set for mechanism 1 (direct): %s UIDs, batches: [%s]",
len(weights_by_uid),
", ".join(str(batch.id) for batch in batches),
)
else:
logger.info(
"Auction weights committed for mechanism 1 (commit/reveal): %s UIDs, reveal_round: %s, batches: [%s]",
len(weights_by_uid),
reveal_round,
", ".join(str(batch.id) for batch in batches),
)
logger.info(
"Auction weights submitted",
mechanisms=submission_results,
uid_count=len(weights_by_uid),
batches=[batch.id for batch in batches],
)

return True

Expand Down
33 changes: 33 additions & 0 deletions app/src/infinite_hashes/validator/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from infinite_hashes.validator.tasks import (
calculate_weights,
scrape_metrics,
set_auction_weights,
set_weights,
)

Expand Down Expand Up @@ -143,6 +144,38 @@ def test_set_weights_expired_batches(bittensor):
assert batch.should_be_scored is False


@pytest.mark.django_db
def test_set_auction_weights_sets_mech0_and_mech1(bittensor, monkeypatch):
bittensor.head.get = unittest.mock.AsyncMock(
return_value=unittest.mock.MagicMock(
number=1010,
),
)
bittensor.subnet.return_value.epoch.return_value = range(719, 1080)

batch = WeightsBatch.objects.create(
block=1007,
epoch_start=719,
mechanism_id=1,
should_be_scored=True,
weights={
"hotkey_1": 1,
"hotkey_3": 3,
},
)

commit_mock = unittest.mock.AsyncMock(side_effect=[111, 111])
monkeypatch.setattr("infinite_hashes.validator.tasks.commit_mechanism_weights", commit_mock)

assert set_auction_weights()

mechanism_ids = [call.kwargs["mechanism_id"] for call in commit_mock.await_args_list]
assert mechanism_ids == [0, 1]

batch.refresh_from_db()
assert batch.scored is True


@pytest.mark.django_db
@pytest.mark.api_integration
@override_settings(BITTENSOR_NETWORK="wss://entrypoint-finney.opentensor.ai:443")
Expand Down
Loading