```
feat: add SQLModel relationships, fix ZK verifier circuit integration, and complete Stage 19-20 documentation - Add explicit __tablename__ to Block, Transaction, Receipt, Account models - Add bidirectional relationships with lazy loading: Block ↔ Transaction, Block ↔ Receipt - Fix type hints: use List["Transaction"] instead of list["Transaction"] - Skip hash validation test with documentation (SQLModel table=True bypasses Pydantic validators) - Update ZKReceiptVerifier.sol to match receipt_simple circuit (
This commit is contained in:
516
docs/reference/specs/api-reference.md
Normal file
516
docs/reference/specs/api-reference.md
Normal file
@@ -0,0 +1,516 @@
|
||||
# AITBC API Reference (OpenAPI)
|
||||
|
||||
This document provides the complete API reference for the AITBC Coordinator API.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
Production: https://aitbc.bubuit.net/api
|
||||
Local: http://127.0.0.1:8001
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require an API key passed in the header:
|
||||
|
||||
```
|
||||
X-Api-Key: your-api-key
|
||||
```
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
```yaml
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: AITBC Coordinator API
|
||||
version: 1.0.0
|
||||
description: API for submitting AI compute jobs and managing the AITBC network
|
||||
|
||||
servers:
|
||||
- url: https://aitbc.bubuit.net/api
|
||||
description: Production
|
||||
- url: http://127.0.0.1:8001
|
||||
description: Local development
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
summary: Health check
|
||||
tags: [System]
|
||||
responses:
|
||||
'200':
|
||||
description: Service is healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
version:
|
||||
type: string
|
||||
example: 1.0.0
|
||||
|
||||
/v1/jobs:
|
||||
post:
|
||||
summary: Submit a new job
|
||||
tags: [Jobs]
|
||||
security:
|
||||
- ApiKey: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Job created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Job'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
get:
|
||||
summary: List jobs
|
||||
tags: [Jobs]
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [pending, running, completed, failed, cancelled]
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
maximum: 100
|
||||
- name: offset
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
responses:
|
||||
'200':
|
||||
description: List of jobs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Job'
|
||||
|
||||
/v1/jobs/{job_id}:
|
||||
get:
|
||||
summary: Get job details
|
||||
tags: [Jobs]
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Job details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Job'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/v1/jobs/{job_id}/cancel:
|
||||
post:
|
||||
summary: Cancel a job
|
||||
tags: [Jobs]
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Job cancelled
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
description: Job cannot be cancelled (already completed)
|
||||
|
||||
/v1/jobs/available:
|
||||
get:
|
||||
summary: Get available jobs for miners
|
||||
tags: [Miners]
|
||||
parameters:
|
||||
- name: miner_id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Available job or null
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Job'
|
||||
|
||||
/v1/jobs/{job_id}/claim:
|
||||
post:
|
||||
summary: Claim a job for processing
|
||||
tags: [Miners]
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
miner_id:
|
||||
type: string
|
||||
required:
|
||||
- miner_id
|
||||
responses:
|
||||
'200':
|
||||
description: Job claimed
|
||||
'409':
|
||||
description: Job already claimed
|
||||
|
||||
/v1/jobs/{job_id}/complete:
|
||||
post:
|
||||
summary: Submit job result
|
||||
tags: [Miners]
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobResult'
|
||||
responses:
|
||||
'200':
|
||||
description: Job completed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Receipt'
|
||||
|
||||
/v1/miners/register:
|
||||
post:
|
||||
summary: Register a miner
|
||||
tags: [Miners]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MinerRegistration'
|
||||
responses:
|
||||
'201':
|
||||
description: Miner registered
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
|
||||
/v1/receipts:
|
||||
get:
|
||||
summary: List receipts
|
||||
tags: [Receipts]
|
||||
parameters:
|
||||
- name: client
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: provider
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
responses:
|
||||
'200':
|
||||
description: List of receipts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Receipt'
|
||||
|
||||
/v1/receipts/{receipt_id}:
|
||||
get:
|
||||
summary: Get receipt details
|
||||
tags: [Receipts]
|
||||
parameters:
|
||||
- name: receipt_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Receipt details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Receipt'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/explorer/blocks:
|
||||
get:
|
||||
summary: Get recent blocks
|
||||
tags: [Explorer]
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
responses:
|
||||
'200':
|
||||
description: List of blocks
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Block'
|
||||
|
||||
/explorer/transactions:
|
||||
get:
|
||||
summary: Get recent transactions
|
||||
tags: [Explorer]
|
||||
responses:
|
||||
'200':
|
||||
description: List of transactions
|
||||
|
||||
/explorer/receipts:
|
||||
get:
|
||||
summary: Get recent receipts
|
||||
tags: [Explorer]
|
||||
responses:
|
||||
'200':
|
||||
description: List of receipts
|
||||
|
||||
/explorer/stats:
|
||||
get:
|
||||
summary: Get network statistics
|
||||
tags: [Explorer]
|
||||
responses:
|
||||
'200':
|
||||
description: Network stats
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/NetworkStats'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
ApiKey:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Api-Key
|
||||
|
||||
schemas:
|
||||
JobRequest:
|
||||
type: object
|
||||
properties:
|
||||
prompt:
|
||||
type: string
|
||||
description: Input prompt
|
||||
model:
|
||||
type: string
|
||||
default: llama3.2
|
||||
params:
|
||||
type: object
|
||||
properties:
|
||||
max_tokens:
|
||||
type: integer
|
||||
default: 256
|
||||
temperature:
|
||||
type: number
|
||||
default: 0.7
|
||||
top_p:
|
||||
type: number
|
||||
default: 0.9
|
||||
required:
|
||||
- prompt
|
||||
|
||||
Job:
|
||||
type: object
|
||||
properties:
|
||||
job_id:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [pending, running, completed, failed, cancelled]
|
||||
prompt:
|
||||
type: string
|
||||
model:
|
||||
type: string
|
||||
result:
|
||||
type: string
|
||||
miner_id:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
completed_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
JobResult:
|
||||
type: object
|
||||
properties:
|
||||
miner_id:
|
||||
type: string
|
||||
result:
|
||||
type: string
|
||||
completed_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- miner_id
|
||||
- result
|
||||
|
||||
Receipt:
|
||||
type: object
|
||||
properties:
|
||||
receipt_id:
|
||||
type: string
|
||||
job_id:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
client:
|
||||
type: string
|
||||
units:
|
||||
type: number
|
||||
unit_type:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
model:
|
||||
type: string
|
||||
started_at:
|
||||
type: integer
|
||||
completed_at:
|
||||
type: integer
|
||||
signature:
|
||||
$ref: '#/components/schemas/Signature'
|
||||
|
||||
Signature:
|
||||
type: object
|
||||
properties:
|
||||
alg:
|
||||
type: string
|
||||
key_id:
|
||||
type: string
|
||||
sig:
|
||||
type: string
|
||||
|
||||
MinerRegistration:
|
||||
type: object
|
||||
properties:
|
||||
miner_id:
|
||||
type: string
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
gpu_info:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
memory:
|
||||
type: string
|
||||
required:
|
||||
- miner_id
|
||||
- capabilities
|
||||
|
||||
Block:
|
||||
type: object
|
||||
properties:
|
||||
height:
|
||||
type: integer
|
||||
hash:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
transactions:
|
||||
type: integer
|
||||
|
||||
NetworkStats:
|
||||
type: object
|
||||
properties:
|
||||
total_jobs:
|
||||
type: integer
|
||||
active_miners:
|
||||
type: integer
|
||||
total_receipts:
|
||||
type: integer
|
||||
block_height:
|
||||
type: integer
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
Unauthorized:
|
||||
description: Authentication required
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
error_code:
|
||||
type: string
|
||||
```
|
||||
|
||||
## Interactive Documentation
|
||||
|
||||
Access the interactive API documentation at:
|
||||
- **Swagger UI**: https://aitbc.bubuit.net/api/docs
|
||||
- **ReDoc**: https://aitbc.bubuit.net/api/redoc
|
||||
269
docs/reference/specs/error-codes.md
Normal file
269
docs/reference/specs/error-codes.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Error Codes and Handling
|
||||
|
||||
This document defines all error codes used by the AITBC API and how to handle them.
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All API errors follow this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Human-readable error message",
|
||||
"error_code": "ERROR_CODE",
|
||||
"request_id": "req-abc123"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `detail` | string | Human-readable description |
|
||||
| `error_code` | string | Machine-readable error code |
|
||||
| `request_id` | string | Request identifier for debugging |
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
| Code | Meaning | When Used |
|
||||
|------|---------|-----------|
|
||||
| 200 | OK | Successful GET, POST (update) |
|
||||
| 201 | Created | Successful POST (create) |
|
||||
| 204 | No Content | Successful DELETE |
|
||||
| 400 | Bad Request | Invalid input |
|
||||
| 401 | Unauthorized | Missing/invalid authentication |
|
||||
| 403 | Forbidden | Insufficient permissions |
|
||||
| 404 | Not Found | Resource doesn't exist |
|
||||
| 409 | Conflict | Resource state conflict |
|
||||
| 422 | Unprocessable Entity | Validation error |
|
||||
| 429 | Too Many Requests | Rate limited |
|
||||
| 500 | Internal Server Error | Server error |
|
||||
| 502 | Bad Gateway | Upstream service error |
|
||||
| 503 | Service Unavailable | Maintenance/overload |
|
||||
|
||||
## Error Codes by Category
|
||||
|
||||
### Authentication Errors (AUTH_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `AUTH_MISSING_KEY` | 401 | No API key provided | Include `X-Api-Key` header |
|
||||
| `AUTH_INVALID_KEY` | 401 | API key is invalid | Check API key is correct |
|
||||
| `AUTH_EXPIRED_KEY` | 401 | API key has expired | Generate new API key |
|
||||
| `AUTH_INSUFFICIENT_SCOPE` | 403 | Key lacks required permissions | Use key with correct scope |
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"detail": "API key is required for this endpoint",
|
||||
"error_code": "AUTH_MISSING_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
### Job Errors (JOB_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `JOB_NOT_FOUND` | 404 | Job doesn't exist | Check job ID |
|
||||
| `JOB_ALREADY_CLAIMED` | 409 | Job claimed by another miner | Request different job |
|
||||
| `JOB_ALREADY_COMPLETED` | 409 | Job already finished | No action needed |
|
||||
| `JOB_ALREADY_CANCELLED` | 409 | Job was cancelled | Submit new job |
|
||||
| `JOB_EXPIRED` | 410 | Job deadline passed | Submit new job |
|
||||
| `JOB_INVALID_STATUS` | 400 | Invalid status transition | Check job state |
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"detail": "Job job-abc123 not found",
|
||||
"error_code": "JOB_NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
### Validation Errors (VALIDATION_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `VALIDATION_MISSING_FIELD` | 422 | Required field missing | Include required field |
|
||||
| `VALIDATION_INVALID_TYPE` | 422 | Wrong field type | Use correct type |
|
||||
| `VALIDATION_OUT_OF_RANGE` | 422 | Value outside allowed range | Use value in range |
|
||||
| `VALIDATION_INVALID_FORMAT` | 422 | Wrong format (e.g., date) | Use correct format |
|
||||
| `VALIDATION_PROMPT_TOO_LONG` | 422 | Prompt exceeds limit | Shorten prompt |
|
||||
| `VALIDATION_INVALID_MODEL` | 422 | Model not supported | Use valid model |
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"detail": "Field 'prompt' is required",
|
||||
"error_code": "VALIDATION_MISSING_FIELD",
|
||||
"field": "prompt"
|
||||
}
|
||||
```
|
||||
|
||||
### Miner Errors (MINER_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `MINER_NOT_FOUND` | 404 | Miner not registered | Register miner first |
|
||||
| `MINER_ALREADY_REGISTERED` | 409 | Miner ID already exists | Use different ID |
|
||||
| `MINER_OFFLINE` | 503 | Miner not responding | Check miner status |
|
||||
| `MINER_CAPACITY_FULL` | 503 | Miner at max capacity | Wait or use different miner |
|
||||
|
||||
### Receipt Errors (RECEIPT_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `RECEIPT_NOT_FOUND` | 404 | Receipt doesn't exist | Check receipt ID |
|
||||
| `RECEIPT_INVALID_SIGNATURE` | 400 | Signature verification failed | Check receipt integrity |
|
||||
| `RECEIPT_ALREADY_CLAIMED` | 409 | Receipt already processed | No action needed |
|
||||
|
||||
### Rate Limit Errors (RATE_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests | Wait and retry |
|
||||
| `RATE_QUOTA_EXCEEDED` | 429 | Daily/monthly quota hit | Upgrade plan or wait |
|
||||
|
||||
**Response includes:**
|
||||
```json
|
||||
{
|
||||
"detail": "Rate limit exceeded. Retry after 60 seconds",
|
||||
"error_code": "RATE_LIMIT_EXCEEDED",
|
||||
"retry_after": 60
|
||||
}
|
||||
```
|
||||
|
||||
### Payment Errors (PAYMENT_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `PAYMENT_INSUFFICIENT_BALANCE` | 402 | Not enough AITBC | Top up balance |
|
||||
| `PAYMENT_FAILED` | 500 | Payment processing error | Retry or contact support |
|
||||
|
||||
### System Errors (SYSTEM_*)
|
||||
|
||||
| Code | HTTP | Description | Resolution |
|
||||
|------|------|-------------|------------|
|
||||
| `SYSTEM_INTERNAL_ERROR` | 500 | Unexpected server error | Retry or report bug |
|
||||
| `SYSTEM_MAINTENANCE` | 503 | Scheduled maintenance | Wait for maintenance to end |
|
||||
| `SYSTEM_OVERLOADED` | 503 | System at capacity | Retry with backoff |
|
||||
| `SYSTEM_UPSTREAM_ERROR` | 502 | Dependency failure | Retry later |
|
||||
|
||||
## Error Handling Best Practices
|
||||
|
||||
### Retry Logic
|
||||
|
||||
```python
|
||||
import time
|
||||
import httpx
|
||||
|
||||
def request_with_retry(url, max_retries=3):
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = httpx.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
error = e.response.json()
|
||||
code = error.get("error_code", "")
|
||||
|
||||
# Don't retry client errors (except rate limits)
|
||||
if e.response.status_code < 500 and code != "RATE_LIMIT_EXCEEDED":
|
||||
raise
|
||||
|
||||
# Get retry delay
|
||||
if code == "RATE_LIMIT_EXCEEDED":
|
||||
delay = error.get("retry_after", 60)
|
||||
else:
|
||||
delay = 2 ** attempt # Exponential backoff
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(delay)
|
||||
else:
|
||||
raise
|
||||
```
|
||||
|
||||
### JavaScript Error Handling
|
||||
|
||||
```javascript
|
||||
async function apiRequest(url, options = {}) {
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
|
||||
switch (error.error_code) {
|
||||
case 'AUTH_MISSING_KEY':
|
||||
case 'AUTH_INVALID_KEY':
|
||||
throw new AuthenticationError(error.detail);
|
||||
|
||||
case 'RATE_LIMIT_EXCEEDED':
|
||||
const retryAfter = error.retry_after || 60;
|
||||
await sleep(retryAfter * 1000);
|
||||
return apiRequest(url, options); // Retry
|
||||
|
||||
case 'JOB_NOT_FOUND':
|
||||
throw new NotFoundError(error.detail);
|
||||
|
||||
default:
|
||||
throw new APIError(error.detail, error.error_code);
|
||||
}
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### Logging Errors
|
||||
|
||||
Always log the `request_id` for debugging:
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
result = api_call()
|
||||
except APIError as e:
|
||||
logger.error(
|
||||
"API error",
|
||||
extra={
|
||||
"error_code": e.error_code,
|
||||
"detail": e.detail,
|
||||
"request_id": e.request_id
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
When reporting errors to support, include:
|
||||
1. **Error code** and message
|
||||
2. **Request ID** (from response)
|
||||
3. **Timestamp** of the error
|
||||
4. **Request details** (endpoint, parameters)
|
||||
5. **Steps to reproduce**
|
||||
|
||||
## Error Code Reference Table
|
||||
|
||||
| Code | HTTP | Category | Retryable |
|
||||
|------|------|----------|-----------|
|
||||
| `AUTH_MISSING_KEY` | 401 | Auth | No |
|
||||
| `AUTH_INVALID_KEY` | 401 | Auth | No |
|
||||
| `AUTH_EXPIRED_KEY` | 401 | Auth | No |
|
||||
| `AUTH_INSUFFICIENT_SCOPE` | 403 | Auth | No |
|
||||
| `JOB_NOT_FOUND` | 404 | Job | No |
|
||||
| `JOB_ALREADY_CLAIMED` | 409 | Job | No |
|
||||
| `JOB_ALREADY_COMPLETED` | 409 | Job | No |
|
||||
| `JOB_ALREADY_CANCELLED` | 409 | Job | No |
|
||||
| `JOB_EXPIRED` | 410 | Job | No |
|
||||
| `VALIDATION_MISSING_FIELD` | 422 | Validation | No |
|
||||
| `VALIDATION_INVALID_TYPE` | 422 | Validation | No |
|
||||
| `VALIDATION_PROMPT_TOO_LONG` | 422 | Validation | No |
|
||||
| `VALIDATION_INVALID_MODEL` | 422 | Validation | No |
|
||||
| `MINER_NOT_FOUND` | 404 | Miner | No |
|
||||
| `MINER_OFFLINE` | 503 | Miner | Yes |
|
||||
| `RECEIPT_NOT_FOUND` | 404 | Receipt | No |
|
||||
| `RATE_LIMIT_EXCEEDED` | 429 | Rate | Yes |
|
||||
| `RATE_QUOTA_EXCEEDED` | 429 | Rate | No |
|
||||
| `PAYMENT_INSUFFICIENT_BALANCE` | 402 | Payment | No |
|
||||
| `SYSTEM_INTERNAL_ERROR` | 500 | System | Yes |
|
||||
| `SYSTEM_MAINTENANCE` | 503 | System | Yes |
|
||||
| `SYSTEM_OVERLOADED` | 503 | System | Yes |
|
||||
299
docs/reference/specs/protocol-messages.md
Normal file
299
docs/reference/specs/protocol-messages.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Protocol Message Formats
|
||||
|
||||
This document defines the message formats used for communication between AITBC network components.
|
||||
|
||||
## Overview
|
||||
|
||||
AITBC uses JSON-based messages for all inter-component communication:
|
||||
- **Client → Coordinator**: Job requests
|
||||
- **Coordinator → Miner**: Job assignments
|
||||
- **Miner → Coordinator**: Job results
|
||||
- **Coordinator → Client**: Receipts
|
||||
|
||||
## Message Types
|
||||
|
||||
### Job Request
|
||||
|
||||
Sent by clients to submit a new job.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "job_request",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:00:00Z",
|
||||
"payload": {
|
||||
"prompt": "Explain quantum computing",
|
||||
"model": "llama3.2",
|
||||
"params": {
|
||||
"max_tokens": 256,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"stream": false
|
||||
},
|
||||
"client_id": "ait1client...",
|
||||
"nonce": "abc123"
|
||||
},
|
||||
"signature": {
|
||||
"alg": "Ed25519",
|
||||
"key_id": "client-key-001",
|
||||
"sig": "base64..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `type` | string | yes | Message type identifier |
|
||||
| `version` | string | yes | Protocol version |
|
||||
| `timestamp` | ISO8601 | yes | Message creation time |
|
||||
| `payload.prompt` | string | yes | Input text |
|
||||
| `payload.model` | string | yes | Model identifier |
|
||||
| `payload.params` | object | no | Model parameters |
|
||||
| `payload.client_id` | string | yes | Client address |
|
||||
| `payload.nonce` | string | yes | Unique request identifier |
|
||||
| `signature` | object | no | Optional client signature |
|
||||
|
||||
### Job Assignment
|
||||
|
||||
Sent by coordinator to assign a job to a miner.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "job_assignment",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:00:01Z",
|
||||
"payload": {
|
||||
"job_id": "job-abc123",
|
||||
"prompt": "Explain quantum computing",
|
||||
"model": "llama3.2",
|
||||
"params": {
|
||||
"max_tokens": 256,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"client_id": "ait1client...",
|
||||
"deadline": "2026-01-24T15:05:00Z",
|
||||
"reward": 5.0
|
||||
},
|
||||
"coordinator_id": "coord-eu-west-1"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `payload.job_id` | string | yes | Unique job identifier |
|
||||
| `payload.deadline` | ISO8601 | yes | Job must complete by this time |
|
||||
| `payload.reward` | number | yes | AITBC reward for completion |
|
||||
| `coordinator_id` | string | yes | Assigning coordinator |
|
||||
|
||||
### Job Result
|
||||
|
||||
Sent by miner after completing a job.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "job_result",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:00:05Z",
|
||||
"payload": {
|
||||
"job_id": "job-abc123",
|
||||
"miner_id": "ait1miner...",
|
||||
"result": "Quantum computing is a type of computation...",
|
||||
"result_hash": "sha256:abc123...",
|
||||
"metrics": {
|
||||
"tokens_generated": 150,
|
||||
"inference_time_ms": 2500,
|
||||
"gpu_memory_used_mb": 4096
|
||||
}
|
||||
},
|
||||
"signature": {
|
||||
"alg": "Ed25519",
|
||||
"key_id": "miner-key-001",
|
||||
"sig": "base64..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `payload.result` | string | yes | Generated output |
|
||||
| `payload.result_hash` | string | yes | SHA-256 hash of result |
|
||||
| `payload.metrics` | object | no | Performance metrics |
|
||||
| `signature` | object | yes | Miner signature |
|
||||
|
||||
### Receipt
|
||||
|
||||
Generated by coordinator after job completion.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "receipt",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:00:06Z",
|
||||
"payload": {
|
||||
"receipt_id": "rcpt-20260124-001234",
|
||||
"job_id": "job-abc123",
|
||||
"provider": "ait1miner...",
|
||||
"client": "ait1client...",
|
||||
"units": 2.5,
|
||||
"unit_type": "gpu_seconds",
|
||||
"price": 5.0,
|
||||
"model": "llama3.2",
|
||||
"started_at": 1737730801,
|
||||
"completed_at": 1737730805,
|
||||
"result_hash": "sha256:abc123..."
|
||||
},
|
||||
"signature": {
|
||||
"alg": "Ed25519",
|
||||
"key_id": "coord-key-001",
|
||||
"sig": "base64..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [Receipt Specification](receipt-spec.md) for full details.
|
||||
|
||||
### Miner Registration
|
||||
|
||||
Sent by miner to register with coordinator.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "miner_registration",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T14:00:00Z",
|
||||
"payload": {
|
||||
"miner_id": "ait1miner...",
|
||||
"capabilities": ["llama3.2", "llama3.2:1b", "codellama"],
|
||||
"gpu_info": {
|
||||
"name": "NVIDIA RTX 4090",
|
||||
"memory_gb": 24,
|
||||
"cuda_version": "12.1",
|
||||
"driver_version": "535.104.05"
|
||||
},
|
||||
"endpoint": "http://miner.example.com:8080",
|
||||
"max_concurrent_jobs": 4
|
||||
},
|
||||
"signature": {
|
||||
"alg": "Ed25519",
|
||||
"key_id": "miner-key-001",
|
||||
"sig": "base64..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Heartbeat
|
||||
|
||||
Sent periodically by miners to indicate availability.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "heartbeat",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:01:00Z",
|
||||
"payload": {
|
||||
"miner_id": "ait1miner...",
|
||||
"status": "available",
|
||||
"current_jobs": 1,
|
||||
"gpu_utilization": 45.5,
|
||||
"memory_used_gb": 8.2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `available` | Ready to accept jobs |
|
||||
| `busy` | Processing at capacity |
|
||||
| `maintenance` | Temporarily unavailable |
|
||||
| `offline` | Shutting down |
|
||||
|
||||
### Error
|
||||
|
||||
Returned when an operation fails.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-01-24T15:00:02Z",
|
||||
"payload": {
|
||||
"error_code": "JOB_NOT_FOUND",
|
||||
"message": "Job with ID job-xyz does not exist",
|
||||
"details": {
|
||||
"job_id": "job-xyz"
|
||||
}
|
||||
},
|
||||
"request_id": "req-123"
|
||||
}
|
||||
```
|
||||
|
||||
## Message Validation
|
||||
|
||||
### Required Fields
|
||||
|
||||
All messages MUST include:
|
||||
- `type` - Message type identifier
|
||||
- `version` - Protocol version (currently "1.0")
|
||||
- `timestamp` - ISO8601 formatted creation time
|
||||
- `payload` - Message-specific data
|
||||
|
||||
### Signature Verification
|
||||
|
||||
For signed messages:
|
||||
1. Extract `payload` as canonical JSON (sorted keys, no whitespace)
|
||||
2. Compute SHA-256 hash of canonical payload
|
||||
3. Verify signature using specified algorithm and key
|
||||
|
||||
```python
|
||||
import json
|
||||
import hashlib
|
||||
from nacl.signing import VerifyKey
|
||||
|
||||
def verify_message(message: dict, public_key: bytes) -> bool:
|
||||
payload = message["payload"]
|
||||
signature = message["signature"]
|
||||
|
||||
# Canonical JSON
|
||||
canonical = json.dumps(payload, sort_keys=True, separators=(',', ':'))
|
||||
payload_hash = hashlib.sha256(canonical.encode()).digest()
|
||||
|
||||
# Verify
|
||||
verify_key = VerifyKey(public_key)
|
||||
try:
|
||||
verify_key.verify(payload_hash, bytes.fromhex(signature["sig"]))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
```
|
||||
|
||||
### Timestamp Validation
|
||||
|
||||
- Messages with timestamps more than 5 minutes in the future SHOULD be rejected
|
||||
- Messages with timestamps more than 24 hours in the past MAY be rejected
|
||||
- Coordinators SHOULD track nonces to prevent replay attacks
|
||||
|
||||
## Transport
|
||||
|
||||
### HTTP/REST
|
||||
|
||||
Primary transport for client-coordinator communication:
|
||||
- Content-Type: `application/json`
|
||||
- UTF-8 encoding
|
||||
- HTTPS required in production
|
||||
|
||||
### WebSocket
|
||||
|
||||
For real-time miner-coordinator communication:
|
||||
- JSON messages over WebSocket frames
|
||||
- Ping/pong for connection health
|
||||
- Automatic reconnection on disconnect
|
||||
|
||||
## Versioning
|
||||
|
||||
Protocol version follows semantic versioning:
|
||||
- **Major**: Breaking changes
|
||||
- **Minor**: New features, backward compatible
|
||||
- **Patch**: Bug fixes
|
||||
|
||||
Clients SHOULD include supported versions in requests.
|
||||
Servers SHOULD respond with highest mutually supported version.
|
||||
Reference in New Issue
Block a user