Skip to content

Commit

Permalink
the bot integration
Browse files Browse the repository at this point in the history
  • Loading branch information
konrad0960 committed Sep 24, 2024
1 parent 4a4dfa0 commit 2058179
Show file tree
Hide file tree
Showing 22 changed files with 323 additions and 134 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cd app/src
pdm run manage.py wait_for_database --timeout 10
pdm run manage.py migrate
pdm run manage.py runserver
pdm run manage.py run_bot
```

# Setup production environment (git deployment)
Expand Down
4 changes: 2 additions & 2 deletions app/src/auto_validator/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

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


Expand Down
24 changes: 20 additions & 4 deletions app/src/auto_validator/core/api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
from rest_framework import mixins, parsers, routers, viewsets

from rest_framework.permissions import AllowAny
from auto_validator.core.models import UploadedFile
from auto_validator.core.serializers import UploadedFileSerializer
from .utils.bot import trigger_bot_send_message
from .authentication import HotkeyAuthentication


class FilesViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = UploadedFileSerializer
parser_classes = [parsers.MultiPartParser]
queryset = UploadedFile.objects.all()

def get_queryset(self):
return UploadedFile.objects.filter(user=self.request.user).order_by("id")
authentication_classes = [HotkeyAuthentication]
permission_classes = [AllowAny]

def perform_create(self, serializer):
serializer.save(user=self.request.user)
uploaded_file = serializer.save()
note = self.request.headers.get('Note')
channel_name = self.request.headers.get('SubnetID')
realm = self.request.headers.get('Realm')
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
)


class APIRootView(routers.DefaultRouter.APIRootView):
Expand Down
50 changes: 50 additions & 0 deletions app/src/auto_validator/core/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from rest_framework import authentication, exceptions
from .models import Hotkey
from bittensor import Keypair
import json

class HotkeyAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
hotkey_address = request.headers.get('Hotkey')
nonce = request.headers.get('Nonce')
signature = request.headers.get('Signature')

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

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

client_headers = {
'Nonce': nonce,
'Hotkey': hotkey_address,
'Note': request.headers.get('Note'),
'SubnetID': request.headers.get('SubnetID'),
'Realm': request.headers.get('Realm')
}
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:
uploaded_file = request.FILES['file']
file_content = uploaded_file.read()
decoded_file_content = file_content.decode(errors='ignore')
data_to_sign += decoded_file_content

data_to_sign = data_to_sign.encode()
try:
is_valid = Keypair(ss58_address=hotkey_address).verify(
data=data_to_sign,
signature=bytes.fromhex(signature)
)
except Exception as e:
raise exceptions.AuthenticationFailed(f'Signature verification failed: {e}')

if not is_valid:
raise exceptions.AuthenticationFailed('Invalid signature.')

return (None, None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.15 on 2024-09-24 20:57

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0005_alter_validatorinstance_server_and_more'),
]

operations = [
migrations.RemoveField(
model_name='uploadedfile',
name='user',
),
]
10 changes: 8 additions & 2 deletions app/src/auto_validator/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ def validate_hotkey_length(value):


class UploadedFile(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.CASCADE)
file_name = models.CharField(max_length=4095)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
Expand All @@ -21,7 +20,14 @@ class UploadedFile(models.Model):
file_size = models.PositiveBigIntegerField(db_comment="File size in bytes")

def __str__(self):
return f"{self.file_name!r} uploaded by {self.user}"
return f"{self.file_name!r}"

def get_full_url(self, request):
"""
Return the full URL to the file, including the domain.
"""
relative_url = default_storage.url(self.storage_file_name)
return request.build_absolute_uri(relative_url)

@property
def url(self):
Expand Down
12 changes: 8 additions & 4 deletions app/src/auto_validator/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ def uploaded_file_size_validator(value):

class UploadedFileSerializer(serializers.ModelSerializer):
file = serializers.FileField(write_only=True, validators=[uploaded_file_size_validator])
url = serializers.URLField(read_only=True)
url = serializers.SerializerMethodField()

class Meta:
model = UploadedFile
fields = ("id", "file_name", "file_size", "description", "created_at", "url", "file")
read_only_fields = ("id", "file_name", "file_size", "created_at", "url")

def get_url(self, obj):
request = self.context.get('request')
if request:
return obj.get_full_url(request)
return obj.url

def create(self, validated_data):
file = validated_data.pop("file")
user = validated_data.pop("user")
# Generate a semi-random name for the file to prevent guessing the file name
semi_random_name = f"{user.id}-{secrets.token_urlsafe(16)}-{file.name}"
semi_random_name = f"{secrets.token_urlsafe(16)}-{file.name}"
filename_in_storage = default_storage.save(semi_random_name, file, max_length=4095)
return UploadedFile.objects.create(
user=user,
file_name=file.name,
file_size=file.size,
storage_file_name=filename_in_storage,
Expand Down
12 changes: 12 additions & 0 deletions app/src/auto_validator/core/utils/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import redis
import json

def trigger_bot_send_message(channel_name: str, message: str, realm: str):
redis_client = redis.Redis(host='localhost', port=8379, db=0)
command = {
'action': 'send_message',
'channel_name': channel_name,
'message': message,
'realm': realm
}
redis_client.publish('bot_commands', json.dumps(command))
Loading

0 comments on commit 2058179

Please sign in to comment.