| import logging | |
| from django.shortcuts import get_object_or_404 | |
| from rest_framework import status | |
| from rest_framework.decorators import api_view, permission_classes | |
| from rest_framework.pagination import PageNumberPagination | |
| from rest_framework.permissions import IsAuthenticated | |
| from rest_framework.response import Response | |
| from .models import ProofOfCompute, ValidatorNode, Verification, VerificationTask | |
| from .serializers import ( | |
| ProofOfComputeSerializer, | |
| ValidatorNodeSerializer, | |
| VerificationSerializer, | |
| VerificationTaskSerializer, | |
| ) | |
| logger = logging.getLogger(__name__) | |
| class StandardPagination(PageNumberPagination): | |
| page_size = 20 | |
| page_size_query_param = "page_size" | |
| max_page_size = 100 | |
| # ═══════════════════════════════════════════════════════════════ | |
| # VALIDATOR NODE MANAGEMENT | |
| # ═══════════════════════════════════════════════════════════════ | |
| def register_validator(request): | |
| """Register the authenticated user as a validator (stake required).""" | |
| if hasattr(request.user, "validator_node"): | |
| return Response( | |
| {"error": "Already a validator"}, status=status.HTTP_400_BAD_REQUEST | |
| ) | |
| stake_amount = request.data.get("stake_amount_usd") | |
| if not stake_amount: | |
| return Response( | |
| {"error": "stake_amount_usd required"}, status=status.HTTP_400_BAD_REQUEST | |
| ) | |
| try: | |
| stake_amount = float(stake_amount) | |
| if stake_amount < 10.0: | |
| return Response( | |
| {"error": "Minimum stake is $10"}, status=status.HTTP_400_BAD_REQUEST | |
| ) | |
| except ValueError: | |
| return Response( | |
| {"error": "Invalid stake amount"}, status=status.HTTP_400_BAD_REQUEST | |
| ) | |
| # In production: verify the user actually has the funds in their escrow account | |
| node = ValidatorNode.objects.create( | |
| user=request.user, staked_amount_usd=stake_amount, status="active" | |
| ) | |
| return Response(ValidatorNodeSerializer(node).data, status=status.HTTP_201_CREATED) | |
| def my_validator_status(request): | |
| """Get the authenticated user's validator status.""" | |
| if not hasattr(request.user, "validator_node"): | |
| return Response({"error": "Not a validator"}, status=status.HTTP_404_NOT_FOUND) | |
| return Response(ValidatorNodeSerializer(request.user.validator_node).data) | |
| def update_validator(request): | |
| """Update the validator's stake or status.""" | |
| node = get_object_or_404(ValidatorNode, user=request.user) | |
| if "staked_amount_usd" in request.data: | |
| new_stake = request.data["staked_amount_usd"] | |
| node.staked_amount_usd = new_stake | |
| node.save(update_fields=["staked_amount_usd"]) | |
| if "status" in request.data: | |
| new_status = request.data["status"] | |
| if new_status in ("active", "inactive"): | |
| node.status = new_status | |
| node.save(update_fields=["status"]) | |
| return Response(ValidatorNodeSerializer(node).data) | |
| def validator_list(request): | |
| """List all active validators with their stats.""" | |
| qs = ValidatorNode.objects.filter(status="active").order_by("-accuracy_score") | |
| search = request.query_params.get("q") | |
| if search: | |
| qs = qs.filter(user__username__icontains=search) | |
| paginator = StandardPagination() | |
| page = paginator.paginate_queryset(qs, request) | |
| return paginator.get_paginated_response( | |
| ValidatorNodeSerializer(page, many=True).data | |
| ) | |
| def validator_detail(request, node_id): | |
| """View a specific validator's details.""" | |
| node = get_object_or_404(ValidatorNode, id=node_id) | |
| return Response(ValidatorNodeSerializer(node).data) | |
| # ═══════════════════════════════════════════════════════════════ | |
| # VERIFICATION TASKS | |
| # ═══════════════════════════════════════════════════════════════ | |
| def verification_task_list(request): | |
| """List all verification tasks.""" | |
| qs = VerificationTask.objects.all().order_by("-created_at") | |
| status_filter = request.query_params.get("status") | |
| if status_filter: | |
| qs = qs.filter(status=status_filter) | |
| paginator = StandardPagination() | |
| page = paginator.paginate_queryset(qs, request) | |
| return paginator.get_paginated_response( | |
| VerificationTaskSerializer(page, many=True).data | |
| ) | |
| def verification_task_detail(request, task_id): | |
| """Get details of a verification task including all validator results.""" | |
| task = get_object_or_404(VerificationTask, id=task_id) | |
| data = VerificationTaskSerializer(task).data | |
| data["verifications"] = VerificationSerializer( | |
| task.verifications.all(), many=True | |
| ).data | |
| return Response(data) | |
| def my_verification_tasks(request): | |
| """List verification tasks assigned to the authenticated validator.""" | |
| if not hasattr(request.user, "validator_node"): | |
| return Response({"error": "Not a validator"}, status=status.HTTP_404_NOT_FOUND) | |
| node = request.user.validator_node | |
| tasks = ( | |
| VerificationTask.objects.filter(verifications__validator=node) | |
| .distinct() | |
| .order_by("-created_at") | |
| ) | |
| paginator = StandardPagination() | |
| page = paginator.paginate_queryset(tasks, request) | |
| return paginator.get_paginated_response( | |
| VerificationTaskSerializer(page, many=True).data | |
| ) | |
| # ═══════════════════════════════════════════════════════════════ | |
| # SUBMIT VERIFICATION | |
| # ═══════════════════════════════════════════════════════════════ | |
| def submit_verification(request, task_id): | |
| """ | |
| A validator submits their verification result for a task. | |
| Body: { "result": "valid" or "invalid", "confidence": 0.0-1.0, "evidence": "..." } | |
| """ | |
| if not hasattr(request.user, "validator_node"): | |
| return Response({"error": "Not a validator"}, status=status.HTTP_403_FORBIDDEN) | |
| node = request.user.validator_node | |
| if not node.is_active: | |
| return Response( | |
| {"error": "Validator is not active"}, status=status.HTTP_403_FORBIDDEN | |
| ) | |
| task = get_object_or_404(VerificationTask, id=task_id) | |
| if task.status not in ("pending", "in_progress"): | |
| return Response( | |
| {"error": f"Task is {task.status}, cannot accept new verifications"}, | |
| status=status.HTTP_400_BAD_REQUEST, | |
| ) | |
| result = request.data.get("result") | |
| if result not in ("valid", "invalid"): | |
| return Response( | |
| {"error": "result must be 'valid' or 'invalid'"}, | |
| status=status.HTTP_400_BAD_REQUEST, | |
| ) | |
| confidence = float(request.data.get("confidence", 1.0)) | |
| evidence = request.data.get("evidence", "") | |
| # Create or update verification | |
| verification, created = Verification.objects.update_or_create( | |
| task=task, | |
| validator=node, | |
| defaults={ | |
| "result": result, | |
| "confidence": confidence, | |
| "evidence": evidence, | |
| }, | |
| ) | |
| # Update task status | |
| if task.status == "pending": | |
| task.status = "in_progress" | |
| task.save(update_fields=["status"]) | |
| # Update validator stats (will be finalized after consensus) | |
| node.total_verifications += 1 | |
| node.save(update_fields=["total_verifications"]) | |
| return Response( | |
| VerificationSerializer(verification).data, | |
| status=status.HTTP_201_CREATED if created else status.HTTP_200_OK, | |
| ) | |
| # ═══════════════════════════════════════════════════════════════ | |
| # PROOF OF COMPUTE | |
| # ═══════════════════════════════════════════════════════════════ | |
| def proof_of_compute_detail(request, job_id): | |
| """Get the proof of compute for a specific job.""" | |
| proof = get_object_or_404(ProofOfCompute, job_id=job_id) | |
| return Response(ProofOfComputeSerializer(proof).data) | |
| def generate_proof(request, job_id): | |
| """ | |
| Generate a proof of compute for a job (called by the provider). | |
| Body: { "outputs_hash": "...", "tee_attestation": "..." } | |
| """ | |
| from gpu_marketplace.models import ComputeJob | |
| job = get_object_or_404(ComputeJob, id=job_id) | |
| if job.provider != request.user: | |
| return Response( | |
| {"error": "Only the provider can generate proof"}, | |
| status=status.HTTP_403_FORBIDDEN, | |
| ) | |
| outputs_hash = request.data.get("outputs_hash", "") | |
| tee_attestation = request.data.get("tee_attestation", "") | |
| proof, created = ProofOfCompute.objects.update_or_create( | |
| job=job, | |
| defaults={ | |
| "hash_signature": outputs_hash, | |
| "tee_attestation": tee_attestation, | |
| }, | |
| ) | |
| return Response( | |
| ProofOfComputeSerializer(proof).data, | |
| status=status.HTTP_201_CREATED if created else status.HTTP_200_OK, | |
| ) | |
| # ═══════════════════════════════════════════════════════════════ | |
| # CONSENSUS RESOLUTION | |
| # ═══════════════════════════════════════════════════════════════ | |
| def finalize_consensus(request, task_id): | |
| """Manually trigger consensus check (or it auto-checks on each vote).""" | |
| task = get_object_or_404(VerificationTask, id=task_id) | |
| if task.status != "in_progress": | |
| return Response( | |
| {"error": "Task is not in progress"}, status=status.HTTP_400_BAD_REQUEST | |
| ) | |
| reached = task.check_consensus() | |
| if reached: | |
| # Update validator correctness scores | |
| for v in task.verifications.all(): | |
| if v.result == task.consensus_result: | |
| v.validator.correct_verifications += 1 | |
| else: | |
| v.validator.incorrect_verifications += 1 | |
| v.validator.update_accuracy() | |
| task.status = "completed" | |
| task.save(update_fields=["status"]) | |
| return Response(VerificationTaskSerializer(task).data) | |
Xet Storage Details
- Size:
- 11.5 kB
- Xet hash:
- 3a71703c08e93985ab8eede22c559e3c40c09867cba59e4234db8e9b68499974
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.