Skip to content

Commit

Permalink
Merge pull request #50 from Sturdy-Subnet/release/1.4.0
Browse files Browse the repository at this point in the history
Release/1.4.0
  • Loading branch information
Shr1ftyy authored Sep 10, 2024
2 parents c82730d + a93cb9b commit 91f1a33
Show file tree
Hide file tree
Showing 26 changed files with 733 additions and 847 deletions.
2 changes: 1 addition & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dotenv.config()

const accounts = {
mnemonic: process.env.MNEMONIC || "test test test test test test test test test test test junk",
accountsBalance: "1000000000000000000000000",
accountsBalance: "100000000000000000000000000000",
}

/** @type import('hardhat/config').HardhatUserConfig */
Expand Down
2 changes: 1 addition & 1 deletion min_compute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Even then - storage isn't utilized very often - so disk performance won't really be a bottleneck vs, CPU, RAM,
# and network bandwidth.

version: '1.3.2' # update this version key as needed, ideally should match your release version
version: '1.4.0' # update this version key as needed, ideally should match your release version

compute_spec:

Expand Down
25 changes: 13 additions & 12 deletions neurons/miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import time
import typing

import bittensor as bt

# Bittensor Miner Template:
import sturdy
from sturdy.algo import naive_algorithm

# import base miner class which takes care of most of the boilerplate
from sturdy.base.miner import BaseMinerNeuron
from sturdy.algo import naive_algorithm


class Miner(BaseMinerNeuron):
Expand All @@ -42,8 +43,8 @@ class Miner(BaseMinerNeuron):
requests based on stake, and forwarding requests to the forward function. If you need to define custom
"""

def __init__(self, config=None):
super(Miner, self).__init__(config=config)
def __init__(self, config=None) -> None:
super().__init__(config=config)

async def forward(self, synapse: sturdy.protocol.AllocateAssets) -> sturdy.protocol.AllocateAssets:
"""
Expand Down Expand Up @@ -73,7 +74,7 @@ async def forward(self, synapse: sturdy.protocol.AllocateAssets) -> sturdy.proto
bt.logging.info(f"sending allocations: {synapse.allocations}")
return synapse

async def blacklist(self, synapse: sturdy.protocol.AllocateAssets) -> typing.Tuple[bool, str]:
async def blacklist(self, synapse: sturdy.protocol.AllocateAssets) -> typing.Tuple[bool, str]: # noqa: UP006
"""
Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should
define the logic for blacklisting requests based on your needs and desired security parameters.
Expand Down Expand Up @@ -106,17 +107,17 @@ async def blacklist(self, synapse: sturdy.protocol.AllocateAssets) -> typing.Tup

bt.logging.info("Checking miner blacklist")

if synapse.dendrite.hotkey not in self.metagraph.hotkeys:
if synapse.dendrite.hotkey not in self.metagraph.hotkeys: # type: ignore[]
return True, "Hotkey is not registered"

requesting_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
requesting_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) # type: ignore[]
stake = self.metagraph.S[requesting_uid].item()

bt.logging.info(f"Requesting UID: {requesting_uid} | Stake at UID: {stake}")

if stake <= self.config.validator.min_stake:
bt.logging.info(
f"Hotkey: {synapse.dendrite.hotkey}: stake below minimum threshold of {self.config.validator.min_stake}"
f"Hotkey: {synapse.dendrite.hotkey}: stake below minimum threshold of {self.config.validator.min_stake}" # type: ignore[]
)
return True, "Stake below minimum threshold"

Expand Down Expand Up @@ -147,15 +148,15 @@ async def priority(self, synapse: sturdy.protocol.AllocateAssets) -> float:
Example priority logic:
- A higher stake results in a higher priority value.
"""
caller_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) # Get the caller index.
prirority = float(self.metagraph.S[caller_uid]) # Return the stake as the priority.
bt.logging.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: ", prirority)
return prirority
caller_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) # Get the caller index. # type: ignore[]
priority = float(self.metagraph.S[caller_uid]) # Return the stake as the priority.
bt.logging.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: ", priority) # type: ignore[]
return priority


# This is the main function, which runs the miner.
if __name__ == "__main__":
with Miner() as miner:
while True:
bt.logging.info("Miner running...", time.time())
bt.logging.info(f"Miner running... {time.time()}")
time.sleep(5)
104 changes: 49 additions & 55 deletions neurons/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,28 @@
# DEALINGS IN THE SOFTWARE.


import time
import asyncio
from typing import List, Optional
import time
import uuid
from typing import Any

# Bittensor
import bittensor as bt
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, Response
from starlette.status import (
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_429_TOO_MANY_REQUESTS,
)
import uvicorn
import web3
import web3.constants
from web3.constants import ADDRESS_ZERO

# api key db
from sturdy.validator import sql
# import base validator class which takes care of most of the boilerplate
from sturdy.base.validator import BaseValidatorNeuron

# Bittensor Validator Template:
from sturdy.pools import PoolFactory
from sturdy.validator import forward, query_and_score_miners
from sturdy.utils.misc import get_synapse_from_body
from sturdy.protocol import (
REQUEST_TYPES,
AllocateAssets,
Expand All @@ -50,10 +47,11 @@
GetAllocationResponse,
RequestInfoResponse,
)
from sturdy.validator.simulator import Simulator
from sturdy.utils.misc import get_synapse_from_body

# import base validator class which takes care of most of the boilerplate
from sturdy.base.validator import BaseValidatorNeuron
# api key db
from sturdy.validator import forward, query_and_score_miners, sql
from sturdy.validator.simulator import Simulator


class Validator(BaseValidatorNeuron):
Expand All @@ -70,14 +68,14 @@ class Validator(BaseValidatorNeuron):
end of each epoch.
"""

def __init__(self, config=None):
super(Validator, self).__init__(config=config)
def __init__(self, config=None) -> None:
super().__init__(config=config)
bt.logging.info("load_state()")
self.load_state()
self.uid_to_response = {}
self.simulator = Simulator()

async def forward(self):
async def forward(self) -> Any:
"""
Validator forward pass. Consists of:
- Generating the query.
Expand All @@ -94,18 +92,18 @@ async def forward(self):
app = FastAPI(debug=False)


def _get_api_key(request: Request):
def _get_api_key(request: Request) -> Any:
auth_header = request.headers.get("Authorization")
if not auth_header:
return None
if auth_header.startswith("Bearer "):
return auth_header.split(" ")[1]
else:
return auth_header

return auth_header


@app.middleware("http")
async def api_key_validator(request, call_next):
async def api_key_validator(request, call_next) -> Response:
if request.url.path in ["/docs", "/openapi.json", "/favicon.ico", "/redoc"]:
return await call_next(request)

Expand All @@ -121,8 +119,7 @@ async def api_key_validator(request, call_next):

if api_key_info is None:
return JSONResponse(status_code=HTTP_401_UNAUTHORIZED, content={"detail": "Invalid API key"})
# endpoint = request.url.path.split("/")[-1]
# credits_required = ENDPOINT_TO_CREDITS_USED.get(endpoint, 1)

credits_required = 1 # TODO: make this non-constant in the future???? (i.e. dependent on number of pools)????

# Now check credits
Expand Down Expand Up @@ -153,23 +150,21 @@ async def api_key_validator(request, call_next):


# Initialize core_validator outside of the event loop
core_validator = None # type: ignore[attr-defined]
core_validator = None # type: ignore[]


@app.get("/vali")
async def vali():
ret = {"step": core_validator.step, "config": core_validator.config}
return ret
async def vali() -> dict:
return {"step": core_validator.step, "config": core_validator.config} # type: ignore[]


@app.get("/status")
async def status():
ret = {"status": "OK"}
return ret
async def status() -> dict:
return {"status": "OK"}


@app.post("/allocate", response_model=AllocateAssetsResponse)
async def allocate(body: AllocateAssetsRequest):
async def allocate(body: AllocateAssetsRequest) -> AllocateAssetsResponse | None:
"""
Handles allocation requests by creating pools, querying and scoring miners, and returning the allocations.
Expand Down Expand Up @@ -214,9 +209,9 @@ async def allocate(body: AllocateAssetsRequest):
}
}
"""
synapse = get_synapse_from_body(body=body, synapse_model=AllocateAssets)
synapse: Any = get_synapse_from_body(body=body, synapse_model=AllocateAssets)
bt.logging.debug(f"Synapse:\n{synapse}")
pools = synapse.assets_and_pools["pools"]
pools: Any = synapse.assets_and_pools["pools"]

new_pools = {}
for uid, pool in pools.items():
Expand All @@ -236,9 +231,9 @@ async def allocate(body: AllocateAssetsRequest):
case _: # TODO: We assume this is an "organic request"
new_pool = PoolFactory.create_pool(
pool_type=pool.pool_type,
web3_provider=core_validator.w3,
web3_provider=core_validator.w3, # type: ignore[]
user_address=(
pool.user_address if pool.user_address != web3.constants.ADDRESS_ZERO else synapse.user_address
pool.user_address if pool.user_address != ADDRESS_ZERO else synapse.user_address
), # TODO: is there a cleaner way to do this?
contract_address=pool.contract_address,
)
Expand All @@ -261,26 +256,26 @@ async def allocate(body: AllocateAssetsRequest):
return ret


@app.get("/get_allocation/", response_model=List[GetAllocationResponse])
@app.get("/get_allocation/", response_model=list[GetAllocationResponse])
async def get_allocations(
request_uid: Optional[str] = None,
miner_uid: Optional[str] = None,
from_ts: Optional[int] = None,
to_ts: Optional[int] = None,
):
request_uid: str | None = None,
miner_uid: str | None = None,
from_ts: int | None = None,
to_ts: int | None = None,
) -> list[dict]:
with sql.get_db_connection() as conn:
allocations = sql.get_filtered_allocations(conn, request_uid, miner_uid, from_ts, to_ts)
if not allocations:
raise HTTPException(status_code=404, detail="No allocations found")
return allocations


@app.get("/request_info/", response_model=List[RequestInfoResponse])
@app.get("/request_info/", response_model=list[RequestInfoResponse])
async def request_info(
request_uid: Optional[str] = None,
from_ts: Optional[int] = None,
to_ts: Optional[int] = None,
):
request_uid: str | None = None,
from_ts: int | None = None,
to_ts: int | None = None,
) -> list[dict]:
with sql.get_db_connection() as conn:
info = sql.get_request_info(conn, request_uid, from_ts, to_ts)
if not info:
Expand All @@ -289,27 +284,27 @@ async def request_info(


# Function to run the main loop
async def run_main_loop():
async def run_main_loop() -> None:
try:
core_validator.run_in_background_thread()
core_validator.run_in_background_thread() # type: ignore[]
except KeyboardInterrupt:
print("Keyboard interrupt received, exiting...")
bt.logging.info("Keyboard interrupt received, exiting...")


# Function to run the Uvicorn server
async def run_uvicorn_server():
config = uvicorn.Config(app, host="0.0.0.0", port=core_validator.config.api_port, loop="asyncio")
async def run_uvicorn_server() -> None:
config = uvicorn.Config(app, host="0.0.0.0", port=core_validator.config.api_port, loop="asyncio") # noqa: S104 # type: ignore[]
server = uvicorn.Server(config)
await server.serve()


async def main():
global core_validator
async def main() -> None:
global core_validator # noqa: PLW0603
core_validator = Validator()
if not (core_validator.config.synthetic or core_validator.config.organic):
bt.logging.error(
"You did not select a validator type to run! Ensure you select to run either a synthetic or organic validator. \
Shutting down..."
Shutting down...",
)
return

Expand All @@ -318,14 +313,13 @@ async def main():
if core_validator.config.organic:
await asyncio.gather(run_uvicorn_server(), run_main_loop())
else:
# await run_main_loop()
with core_validator:
while True:
bt.logging.debug("Running synthetic vali...")
time.sleep(10)
time.sleep(10) # noqa: ASYNC251


def start():
def start() -> None:
asyncio.run(main())


Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ indent-width = 4
target-version = "py311"

[tool.ruff.lint]
ignore = ["F405", "F403", "E402"]
select = ["E", "W"]
ignore = ["F405", "F403", "E402", "D", "ANN001", "FBT001", "FBT002", "TD002", "TD003", "PLR", "C901", "BLE001", "ANN401", "N801", "EM101", "EM102", "TRY003", "S608", "FIX002", "N805", "N815", "N806", "PT009", "COM812", "S101", "SLF001", "T201"]
select = ["ALL"]

[tool.ruff.format]
# Like Black, use double quotes for strings.
Expand All @@ -21,4 +21,4 @@ indent-style = "space"
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
line-ending = "auto"
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def read_requirements(path):
install_requires=requirements,
entry_points={
"console_scripts": [
"sturdy=sturdycli:cli",
"sturdy=sturdy.sturdycli:cli",
],
},
classifiers=[
Expand Down
2 changes: 1 addition & 1 deletion sturdy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# DEALINGS IN THE SOFTWARE.

# Define the version of the template module.
__version__ = "1.3.3"
__version__ = "1.4.0"
version_split = __version__.split(".")
__spec_version__ = (1000 * int(version_split[0])) + (10 * int(version_split[1])) + (1 * int(version_split[2]))

Expand Down
Loading

0 comments on commit 91f1a33

Please sign in to comment.