From 8cc4076aab2990135bdee0faf93b14a925ead322 Mon Sep 17 00:00:00 2001 From: guibibeau Date: Thu, 9 May 2024 13:32:40 +0100 Subject: [PATCH] feat: implement miner certificate --- subnet/miner-cloudflare/api/__init__.py | 0 subnet/miner-cloudflare/api/api.py | 33 ++++++++++ .../certification/__init__.py | 0 .../certification/certification_manager.py | 65 +++++++++++++++++++ subnet/miner-cloudflare/certification/hash.py | 14 ++++ subnet/miner-cloudflare/config.py | 7 -- subnet/miner-cloudflare/dockerfile | 6 +- subnet/miner-cloudflare/main.py | 58 +---------------- subnet/miner-cloudflare/miner/__init__.py | 0 subnet/miner-cloudflare/miner/miner.py | 33 ++++++++++ .../{ => miner}/stream_miner.py | 6 +- subnet/miner-cloudflare/protocol.py | 1 - subnet/validator/Dockerfile | 12 ++++ 13 files changed, 166 insertions(+), 69 deletions(-) create mode 100644 subnet/miner-cloudflare/api/__init__.py create mode 100644 subnet/miner-cloudflare/api/api.py create mode 100644 subnet/miner-cloudflare/certification/__init__.py create mode 100644 subnet/miner-cloudflare/certification/certification_manager.py create mode 100644 subnet/miner-cloudflare/certification/hash.py create mode 100644 subnet/miner-cloudflare/miner/__init__.py create mode 100644 subnet/miner-cloudflare/miner/miner.py rename subnet/miner-cloudflare/{ => miner}/stream_miner.py (98%) create mode 100644 subnet/validator/Dockerfile diff --git a/subnet/miner-cloudflare/api/__init__.py b/subnet/miner-cloudflare/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subnet/miner-cloudflare/api/api.py b/subnet/miner-cloudflare/api/api.py new file mode 100644 index 0000000..71d622b --- /dev/null +++ b/subnet/miner-cloudflare/api/api.py @@ -0,0 +1,33 @@ +import asyncio +from fastapi import FastAPI +from pydantic import BaseModel +from miner.miner import miner +from certification.certification_manager import run_certification_manager + + +class ChatRequest(BaseModel): + messages: list + model: str + +app = FastAPI() + +@app.get("/") +def index(): + return "ok" + + +@app.post("/chat") +async def chat(request: ChatRequest): + print(request) + messages = request.messages + model = request.model + + response = await miner.prompt(messages=messages, model=model) + messages.append({"role": "system", "content": response}) + + return messages + + +@app.on_event("startup") +async def startup_event(): + asyncio.create_task(run_certification_manager()) diff --git a/subnet/miner-cloudflare/certification/__init__.py b/subnet/miner-cloudflare/certification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subnet/miner-cloudflare/certification/certification_manager.py b/subnet/miner-cloudflare/certification/certification_manager.py new file mode 100644 index 0000000..4366e75 --- /dev/null +++ b/subnet/miner-cloudflare/certification/certification_manager.py @@ -0,0 +1,65 @@ +import aiohttp +import os +import asyncio +from .hash import hash_multiple_files +from dotenv import load_dotenv +import urllib.parse + + +class CertificationManager: + current_hash: str + image_signature: str + service_mesh_url: str + _certificate: str = None + + def __init__(self): + """ Initialize the CertificationManager class. """ + load_dotenv() + self.current_hash = hash_multiple_files(['main.py', 'protocol.py', './api/api.py']) + self.image_signature = os.getenv("DOCKER_IMAGE_SIGNATURE", '') + self.service_mesh_url = os.getenv('SERVICE_MESH_URL') + + async def run(self): + """ Run the CertificationManager and start the certification process """ + await self._get_certificate() + + async def _get_certificate(self): + """ Get the renewed certificate """ + try: + async with aiohttp.ClientSession() as session: + + # we must get the certificate for the current docker image and proove the right code is present. + params = { + "hash": self.current_hash, + "imageSignature": self.image_signature + } + # encode parameters + search = urllib.parse.urlencode(params) + + async with session.get(f"{self.service_mesh_url}/api/certification?{search}", ) as response: + if response.status == 200: + self._certificate = await response.text() + else: + print(f"Error getting certificate") + except aiohttp.ClientError as e: + # Handle any errors that occur during the request + print(f"Error discovering miners: {e}") + except Exception as e: + # Handle any other unexpected errors + print(f"Unexpected error: {e}") + + @property + def certificate(self): + return self._certificate + + + +certification_manager = CertificationManager() + + +async def run_certification_manager(): + while True: + await certification_manager.run() + # get recertified often to avoid getting the wrong rotation of certificate + await asyncio.sleep(120) + diff --git a/subnet/miner-cloudflare/certification/hash.py b/subnet/miner-cloudflare/certification/hash.py new file mode 100644 index 0000000..e4d3645 --- /dev/null +++ b/subnet/miner-cloudflare/certification/hash.py @@ -0,0 +1,14 @@ +import hashlib + +def hash_multiple_files(file_paths): + """Generate MD5 hash for the concatenated content of multiple files.""" + md5 = hashlib.md5() + # Process each file in the list + for file_path in file_paths: + # Open each file in binary read mode + with open(file_path, "rb") as file: + # Read and update hash string value in blocks of 4K + for chunk in iter(lambda: file.read(4096), b""): + md5.update(chunk) + # Return the hexadecimal MD5 hash of the concatenated content + return md5.hexdigest() diff --git a/subnet/miner-cloudflare/config.py b/subnet/miner-cloudflare/config.py index 425bf91..163b424 100644 --- a/subnet/miner-cloudflare/config.py +++ b/subnet/miner-cloudflare/config.py @@ -91,13 +91,6 @@ def get_config() -> "bt.Config": default=False, ) - parser.add_argument( - "--api_only", - action="store_true", - help="Bypass connection to metagraph and subtensor and only starts the akeru API layer", - default=True, - ) - # Adds subtensor specific arguments i.e. --subtensor.chain_endpoint ... --subtensor.network ... bt.subtensor.add_args(parser) diff --git a/subnet/miner-cloudflare/dockerfile b/subnet/miner-cloudflare/dockerfile index d754115..32157c9 100644 --- a/subnet/miner-cloudflare/dockerfile +++ b/subnet/miner-cloudflare/dockerfile @@ -3,10 +3,8 @@ FROM python:3.10-slim WORKDIR /code COPY . /code -# COPY ./requirements.txt /code/requirements.txt -# COPY ./setup.py /code/setup.py RUN python -m pip install -e . - -CMD ["python", "main.py"] \ No newline at end of file +ENTRYPOINT ["python"] +CMD ["main.py"] diff --git a/subnet/miner-cloudflare/main.py b/subnet/miner-cloudflare/main.py index 76159e1..7ab6671 100644 --- a/subnet/miner-cloudflare/main.py +++ b/subnet/miner-cloudflare/main.py @@ -1,65 +1,11 @@ -import argparse import os -import aiohttp -import bittensor as bt from dotenv import load_dotenv -from protocol import StreamPrompting -from fastapi import FastAPI -from pydantic import BaseModel +from api.api import app -from stream_miner import StreamMiner -load_dotenv() - - -class Miner(StreamMiner): - def config(self) -> "bt.Config": - parser = argparse.ArgumentParser(description="Streaming Miner Configs") - self.add_args(parser) - return bt.config(parser) - - def add_args(cls, parser: argparse.ArgumentParser): - pass - - async def prompt(self, messages, model) -> StreamPrompting: - async with aiohttp.ClientSession() as session: - response = await session.post( - f"https://api.cloudflare.com/client/v4/accounts/{self.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/meta/{model}", - headers={"Authorization": f"Bearer {self.CLOUDFLARE_AUTH_TOKEN}"}, - json={ - "messages": messages - } - ) - json_resp = await response.json() - - return json_resp['result']['response'] - - -app = FastAPI() -miner = Miner() - - -class ChatRequest(BaseModel): - messages: list - model: str - - -@app.get("/") -def index(): - return "ok" - - -@app.post("/chat") -async def chat(request: ChatRequest): - messages = request.messages - model = request.model - - response = await miner.prompt(messages=messages, model=model) - messages.append({"role": "system", "content": response}) - - return messages if __name__ == "__main__": + load_dotenv() import uvicorn uvicorn.run(app, host="0.0.0.0", port=os.getenv('PORT', 8080)) diff --git a/subnet/miner-cloudflare/miner/__init__.py b/subnet/miner-cloudflare/miner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subnet/miner-cloudflare/miner/miner.py b/subnet/miner-cloudflare/miner/miner.py new file mode 100644 index 0000000..47ea728 --- /dev/null +++ b/subnet/miner-cloudflare/miner/miner.py @@ -0,0 +1,33 @@ +import argparse +import aiohttp +import bittensor as bt +from dotenv import load_dotenv +from protocol import StreamPrompting + +from miner.stream_miner import StreamMiner + +load_dotenv() + +class Miner(StreamMiner): + def config(self) -> "bt.Config": + parser = argparse.ArgumentParser(description="Streaming Miner Configs") + self.add_args(parser) + return bt.config(parser) + + def add_args(cls, parser: argparse.ArgumentParser): + pass + + async def prompt(self, messages, model) -> StreamPrompting: + async with aiohttp.ClientSession() as session: + response = await session.post( + f"https://api.cloudflare.com/client/v4/accounts/{self.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/meta/{model}", + headers={"Authorization": f"Bearer {self.CLOUDFLARE_AUTH_TOKEN}"}, + json={ + "messages": messages + } + ) + json_resp = await response.json() + + return json_resp['result']['response'] + +miner = Miner() diff --git a/subnet/miner-cloudflare/stream_miner.py b/subnet/miner-cloudflare/miner/stream_miner.py similarity index 98% rename from subnet/miner-cloudflare/stream_miner.py rename to subnet/miner-cloudflare/miner/stream_miner.py index b3ed594..6d7d1a4 100644 --- a/subnet/miner-cloudflare/stream_miner.py +++ b/subnet/miner-cloudflare/miner/stream_miner.py @@ -16,7 +16,7 @@ from protocol import StreamPrompting -from config import check_config, get_config, get_ip_address +from config import check_config, get_config from dotenv import load_dotenv from requests import post @@ -56,6 +56,7 @@ def __init__(self, config=None, axon=None, wallet=None, subtensor=None): # Wallet holds cryptographic information, ensuring secure transactions and communication. self.wallet = wallet or bt.wallet(config=self.config) + print(self.wallet) bt.logging.info(f"Wallet {self.wallet}") # subtensor manages the blockchain connection, facilitating interaction with the Bittensor blockchain. @@ -67,6 +68,7 @@ def __init__(self, config=None, axon=None, wallet=None, subtensor=None): # metagraph provides the network's current state, holding state about other participants in a subnet. self.metagraph = self.subtensor.metagraph(self.config.netuid) + print(self.metagraph) bt.logging.info(f"Metagraph: {self.metagraph}") if self.wallet.hotkey.ss58_address not in self.metagraph.hotkeys: @@ -96,6 +98,8 @@ def __init__(self, config=None, axon=None, wallet=None, subtensor=None): **self.miner_services } + json.dumps(service_map_dict) + # send to the service map post(f'{url}/api/miner', data=json.dumps(service_map_dict), headers=headers) diff --git a/subnet/miner-cloudflare/protocol.py b/subnet/miner-cloudflare/protocol.py index 174450a..cc20ef4 100644 --- a/subnet/miner-cloudflare/protocol.py +++ b/subnet/miner-cloudflare/protocol.py @@ -3,7 +3,6 @@ import json from typing import List -from starlette.responses import StreamingResponse class StreamPrompting(bt.StreamingSynapse): diff --git a/subnet/validator/Dockerfile b/subnet/validator/Dockerfile new file mode 100644 index 0000000..b40a04f --- /dev/null +++ b/subnet/validator/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.10-slim + +WORKDIR /code + +COPY . /code +# COPY ./requirements.txt /code/requirements.txt +# COPY ./setup.py /code/setup.py + +RUN python -m pip install -e . + + +CMD ["python", "main.py"]