Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/subnet sync #37

Merged
merged 25 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .codespellrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[codespell]
ignore-words-list = ded, bU, te
skip = node_modules, *.min.js, *.bundle.js, package-lock.json, pdm.lock
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ media/
.terraform/
.nox/
__pycache__
app/src/manager/celerybeat-schedule
/app/src/auto_validator/core/celerybeat-schedule
/app/src/auto_validator/core/frontend/node_modules
29 changes: 27 additions & 2 deletions app/src/auto_validator/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.contrib import admin
from django.db.models import Case, IntegerField, Value, When
from django.shortcuts import redirect
from django.urls import path, reverse
from rest_framework.authtoken.admin import TokenAdmin

from auto_validator.core.models import (
Expand All @@ -11,6 +13,7 @@
UploadedFile,
ValidatorInstance,
)
from auto_validator.core.utils.utils import fetch_and_compare_subnets

admin.site.site_header = "auto_validator Administration"
admin.site.site_title = "auto_validator"
Expand All @@ -21,8 +24,8 @@

@admin.register(UploadedFile)
class UploadedFileAdmin(admin.ModelAdmin):
list_display = ("file_name", "file_size", "created_at")
list_filter = ("created_at", "file_size")
list_display = ("file_name", "file_size", "hotkey", "description", "created_at")
list_filter = ("hotkey", "created_at", "file_size")
search_fields = ("file_name",)


Expand All @@ -31,10 +34,32 @@ class SubnetAdmin(admin.ModelAdmin):
list_display = (
"name",
"description",
"mainnet_id",
"testnet_id",
"owner_nick",
"registered_networks",
)
search_fields = ("name", "slots__netuid")

def create_server(self, request, queryset):
subnet = queryset.first()
return redirect("admin:select_provider", subnet_id=subnet.id)

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path("sync-subnets/", self.admin_site.admin_view(self.sync_subnet), name="sync_subnets"),
]
return custom_urls + urls

def sync_subnet(self, request):
return fetch_and_compare_subnets(request)

def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context["sync_subnets_url"] = reverse("admin:sync_subnets")
return super().changelist_view(request, extra_context=extra_context)


@admin.register(SubnetSlot)
class SubnetSlotAdmin(admin.ModelAdmin):
Expand Down
31 changes: 27 additions & 4 deletions app/src/auto_validator/core/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from rest_framework import mixins, parsers, routers, viewsets
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import AllowAny

from auto_validator.core.models import UploadedFile
from auto_validator.core.models import Hotkey, Server, UploadedFile, ValidatorInstance
from auto_validator.core.serializers import UploadedFileSerializer
from auto_validator.core.utils.utils import get_user_ip

from .authentication import HotkeyAuthentication
from .utils.bot import trigger_bot_send_message
Expand All @@ -11,16 +13,37 @@
class FilesViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = UploadedFileSerializer
parser_classes = [parsers.MultiPartParser]
queryset = UploadedFile.objects.all()

authentication_classes = [HotkeyAuthentication]
permission_classes = [AllowAny]

def get_queryset(self):
hotkey_str = self.request.headers.get("Hotkey")
try:
hotkey = Hotkey.objects.get(hotkey=hotkey_str)
except Hotkey.DoesNotExist:
return []
return UploadedFile.objects.filter(hotkey=hotkey).order_by("id")

def perform_create(self, serializer):
uploaded_file = serializer.save()
note = self.request.headers.get("Note")
hotkey_str = self.request.headers.get("Hotkey")
channel_name = self.request.headers.get("SubnetID")
realm = self.request.headers.get("Realm")
ip_address = get_user_ip(self.request)
try:
hotkey = Hotkey.objects.get(hotkey=hotkey_str)
server = Server.objects.get(ip_address=ip_address)
subnetslot = ValidatorInstance.objects.get(hotkey=hotkey, server=server).subnet_slot
except ValidatorInstance.DoesNotExist:
raise AuthenticationFailed("Invalid Hotkey")
uploaded_file = serializer.save(
meta_info={
"note": note,
"hotkey": hotkey_str,
"subnet_name": subnetslot.subnet.name,
"netuid": subnetslot.netuid,
}
)
file_url = uploaded_file.get_full_url(self.request)
trigger_bot_send_message(
channel_name=channel_name, message=(f"{note}\n" f"New validator logs:\n" f"{file_url}"), realm=realm
Expand Down
17 changes: 11 additions & 6 deletions app/src/auto_validator/core/authentication.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import time

from bittensor import Keypair
from django.conf import settings
Expand All @@ -9,17 +10,23 @@

class HotkeyAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
if settings.TESTING:
# Bypass authentication during tests
return (None, None)

hotkey_address = request.headers.get("Hotkey")
nonce = request.headers.get("Nonce")
signature = request.headers.get("Signature")
method = request.method.upper()
url = request.build_absolute_uri()

if method == "GET":
return (None, None)

if not hotkey_address or not nonce or not signature:
raise exceptions.AuthenticationFailed("Missing authentication headers.")

nonce_float = float(nonce)
current_time = time.time()
if abs(current_time - nonce_float) > int(settings.SIGNATURE_EXPIRE_DURATION):
raise exceptions.AuthenticationFailed("Invalid nonce")

if not Hotkey.objects.filter(hotkey=hotkey_address).exists():
raise exceptions.AuthenticationFailed("Unauthorized hotkey.")

Expand All @@ -33,8 +40,6 @@ def authenticate(self, request):
client_headers = {k: v for k, v in client_headers.items() if v is not None}
headers_str = json.dumps(client_headers, sort_keys=True)

method = request.method.upper()
url = request.build_absolute_uri()
data_to_sign = f"{method}{url}{headers_str}"

if "file" in request.FILES:
Expand Down
18 changes: 18 additions & 0 deletions app/src/auto_validator/core/frontend/js/sync_subnets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Diff2Html from 'diff2html';
import 'diff2html/bundles/css/diff2html.min.css';

document.addEventListener('DOMContentLoaded', () => {
const diff_str = document.getElementById('diff_str').textContent;
const githubData = document.getElementById('github_data').textContent;
if (diff_str === '') {
document.getElementById('diff').innerHTML = '<h2>No changes found</h2>';
return;
}
const decodedDiffStr = decodeURIComponent(JSON.parse('"' + diff_str.replace(/\"/g, '\\"') + '"'));
const diffHtml = Diff2Html.html(
decodedDiffStr,
{ drawFileList: false, matching: 'lines', outputFormat: 'side-by-side' }
);
document.getElementById('diff').innerHTML = diffHtml;
document.getElementById('new_data').value = JSON.stringify(githubData);
});
Loading
Loading