security: remove all hardcoded API keys, require from environment
This commit is contained in:
@@ -2,9 +2,9 @@ APP_ENV=dev
|
|||||||
APP_HOST=127.0.0.1
|
APP_HOST=127.0.0.1
|
||||||
APP_PORT=8011
|
APP_PORT=8011
|
||||||
DATABASE_URL=sqlite:///./coordinator.db
|
DATABASE_URL=sqlite:///./coordinator.db
|
||||||
CLIENT_API_KEYS=REDACTED_CLIENT_KEY,client_dev_key_2
|
CLIENT_API_KEYS=${CLIENT_API_KEY},client_dev_key_2
|
||||||
MINER_API_KEYS=REDACTED_MINER_KEY,miner_dev_key_2
|
MINER_API_KEYS=${MINER_API_KEY},miner_dev_key_2
|
||||||
ADMIN_API_KEYS=REDACTED_ADMIN_KEY
|
ADMIN_API_KEYS=${ADMIN_API_KEY}
|
||||||
HMAC_SECRET=change_me
|
HMAC_SECRET=change_me
|
||||||
ALLOW_ORIGINS=*
|
ALLOW_ORIGINS=*
|
||||||
JOB_TTL_SECONDS=900
|
JOB_TTL_SECONDS=900
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
database_url: str = "sqlite:///./coordinator.db"
|
database_url: str = "sqlite:///./coordinator.db"
|
||||||
|
|
||||||
client_api_keys: List[str] = ["REDACTED_CLIENT_KEY"]
|
client_api_keys: List[str] = []
|
||||||
miner_api_keys: List[str] = ["REDACTED_MINER_KEY"]
|
miner_api_keys: List[str] = []
|
||||||
admin_api_keys: List[str] = ["REDACTED_ADMIN_KEY"]
|
admin_api_keys: List[str] = []
|
||||||
|
|
||||||
hmac_secret: Optional[str] = None
|
hmac_secret: Optional[str] = None
|
||||||
allow_origins: List[str] = ["*"]
|
allow_origins: List[str] = ["*"]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def test_receipt_endpoint_returns_signed_receipt(test_client: TestClient):
|
|||||||
resp = test_client.post(
|
resp = test_client.post(
|
||||||
"/v1/miners/register",
|
"/v1/miners/register",
|
||||||
json={"capabilities": {"price": 1}, "concurrency": 1},
|
json={"capabilities": {"price": 1}, "concurrency": 1},
|
||||||
headers={"X-Api-Key": "REDACTED_MINER_KEY"},
|
headers={"X-Api-Key": "${MINER_API_KEY}"},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ def test_receipt_endpoint_returns_signed_receipt(test_client: TestClient):
|
|||||||
resp = test_client.post(
|
resp = test_client.post(
|
||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json=job_payload,
|
json=job_payload,
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"},
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
job_id = resp.json()["job_id"]
|
job_id = resp.json()["job_id"]
|
||||||
@@ -46,7 +46,7 @@ def test_receipt_endpoint_returns_signed_receipt(test_client: TestClient):
|
|||||||
poll_resp = test_client.post(
|
poll_resp = test_client.post(
|
||||||
"/v1/miners/poll",
|
"/v1/miners/poll",
|
||||||
json={"max_wait_seconds": 1},
|
json={"max_wait_seconds": 1},
|
||||||
headers={"X-Api-Key": "REDACTED_MINER_KEY"},
|
headers={"X-Api-Key": "${MINER_API_KEY}"},
|
||||||
)
|
)
|
||||||
assert poll_resp.status_code in (200, 204)
|
assert poll_resp.status_code in (200, 204)
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ def test_receipt_endpoint_returns_signed_receipt(test_client: TestClient):
|
|||||||
result_resp = test_client.post(
|
result_resp = test_client.post(
|
||||||
f"/v1/miners/{job_id}/result",
|
f"/v1/miners/{job_id}/result",
|
||||||
json=result_payload,
|
json=result_payload,
|
||||||
headers={"X-Api-Key": "REDACTED_MINER_KEY"},
|
headers={"X-Api-Key": "${MINER_API_KEY}"},
|
||||||
)
|
)
|
||||||
assert result_resp.status_code == 200
|
assert result_resp.status_code == 200
|
||||||
signed_receipt = result_resp.json()["receipt"]
|
signed_receipt = result_resp.json()["receipt"]
|
||||||
@@ -67,7 +67,7 @@ def test_receipt_endpoint_returns_signed_receipt(test_client: TestClient):
|
|||||||
# fetch receipt via client endpoint
|
# fetch receipt via client endpoint
|
||||||
receipt_resp = test_client.get(
|
receipt_resp = test_client.get(
|
||||||
f"/v1/jobs/{job_id}/receipt",
|
f"/v1/jobs/{job_id}/receipt",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"},
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"},
|
||||||
)
|
)
|
||||||
assert receipt_resp.status_code == 200
|
assert receipt_resp.status_code == 200
|
||||||
payload = receipt_resp.json()
|
payload = receipt_resp.json()
|
||||||
|
|||||||
@@ -812,7 +812,7 @@
|
|||||||
// Display demo offers
|
// Display demo offers
|
||||||
displayGPUOffers([{
|
displayGPUOffers([{
|
||||||
id: '1',
|
id: '1',
|
||||||
provider: 'REDACTED_MINER_KEY',
|
provider: '${MINER_API_KEY}',
|
||||||
capacity: 1,
|
capacity: 1,
|
||||||
price: 50,
|
price: 50,
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Settings(BaseSettings):
|
|||||||
debug: bool = Field(default=False)
|
debug: bool = Field(default=False)
|
||||||
|
|
||||||
coordinator_base_url: str = Field(default="http://localhost:8011", alias="COORDINATOR_BASE_URL")
|
coordinator_base_url: str = Field(default="http://localhost:8011", alias="COORDINATOR_BASE_URL")
|
||||||
coordinator_api_key: str = Field(default="REDACTED_CLIENT_KEY", alias="COORDINATOR_API_KEY")
|
coordinator_api_key: str = Field(default="${CLIENT_API_KEY}", alias="COORDINATOR_API_KEY")
|
||||||
|
|
||||||
rest_prefix: str = Field(default="/v1", alias="REST_PREFIX")
|
rest_prefix: str = Field(default="/v1", alias="REST_PREFIX")
|
||||||
ledger_db_path: Path = Field(default=Path("./data/wallet_ledger.db"), alias="LEDGER_DB_PATH")
|
ledger_db_path: Path = Field(default=Path("./data/wallet_ledger.db"), alias="LEDGER_DB_PATH")
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ python3 client.py --url http://localhost:8000 --api-key your_key submit inferenc
|
|||||||
```
|
```
|
||||||
|
|
||||||
Default credentials:
|
Default credentials:
|
||||||
- Client API Key: `REDACTED_CLIENT_KEY`
|
- Client API Key: `${CLIENT_API_KEY}`
|
||||||
- Miner API Key: `REDACTED_MINER_KEY`
|
- Miner API Key: `${MINER_API_KEY}`
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from typing import Optional
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
||||||
DEFAULT_API_KEY = "REDACTED_CLIENT_KEY"
|
DEFAULT_API_KEY = "${CLIENT_API_KEY}"
|
||||||
|
|
||||||
class AITBCClient:
|
class AITBCClient:
|
||||||
def __init__(self, coordinator_url: str, api_key: str):
|
def __init__(self, coordinator_url: str, api_key: str):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import Optional
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
DEFAULT_COORDINATOR = "http://localhost:8001"
|
DEFAULT_COORDINATOR = "http://localhost:8001"
|
||||||
DEFAULT_API_KEY = "REDACTED_MINER_KEY"
|
DEFAULT_API_KEY = "${MINER_API_KEY}"
|
||||||
DEFAULT_MINER_ID = "cli-miner"
|
DEFAULT_MINER_ID = "cli-miner"
|
||||||
|
|
||||||
class AITBCMiner:
|
class AITBCMiner:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import sys
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
DEFAULT_COORDINATOR = "http://localhost:8001"
|
DEFAULT_COORDINATOR = "http://localhost:8001"
|
||||||
DEFAULT_API_KEY = "REDACTED_MINER_KEY"
|
DEFAULT_API_KEY = "${MINER_API_KEY}"
|
||||||
DEFAULT_MINER_ID = "localhost-gpu-miner"
|
DEFAULT_MINER_ID = "localhost-gpu-miner"
|
||||||
|
|
||||||
def test_miner_registration(coordinator_url):
|
def test_miner_registration(coordinator_url):
|
||||||
@@ -72,7 +72,7 @@ def test_job_processing(coordinator_url):
|
|||||||
f"{coordinator_url}/v1/jobs",
|
f"{coordinator_url}/v1/jobs",
|
||||||
headers={
|
headers={
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Api-Key": "REDACTED_CLIENT_KEY"
|
"X-Api-Key": "${CLIENT_API_KEY}"
|
||||||
},
|
},
|
||||||
json={
|
json={
|
||||||
"payload": {
|
"payload": {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import httpx
|
|||||||
# Configuration
|
# Configuration
|
||||||
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
||||||
DEFAULT_BLOCKCHAIN = "http://127.0.0.1:19000"
|
DEFAULT_BLOCKCHAIN = "http://127.0.0.1:19000"
|
||||||
DEFAULT_API_KEY = "REDACTED_CLIENT_KEY"
|
DEFAULT_API_KEY = "${CLIENT_API_KEY}"
|
||||||
DEFAULT_PROMPT = "What is the capital of France?"
|
DEFAULT_PROMPT = "What is the capital of France?"
|
||||||
DEFAULT_MODEL = "llama3.2:latest"
|
DEFAULT_MODEL = "llama3.2:latest"
|
||||||
DEFAULT_TIMEOUT = 180
|
DEFAULT_TIMEOUT = 180
|
||||||
@@ -150,7 +150,7 @@ def main() -> int:
|
|||||||
# Check miner registration
|
# Check miner registration
|
||||||
print("\n📋 Checking miner registration...")
|
print("\n📋 Checking miner registration...")
|
||||||
with httpx.Client() as client:
|
with httpx.Client() as client:
|
||||||
miner_info = get_miner_info(client, args.coordinator_url, "REDACTED_ADMIN_KEY")
|
miner_info = get_miner_info(client, args.coordinator_url, "${ADMIN_API_KEY}")
|
||||||
if miner_info:
|
if miner_info:
|
||||||
print(f"✅ Found registered miner: {miner_info.get('miner_id')}")
|
print(f"✅ Found registered miner: {miner_info.get('miner_id')}")
|
||||||
print(f" Status: {miner_info.get('status')}")
|
print(f" Status: {miner_info.get('status')}")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from typing import Optional
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
||||||
DEFAULT_API_KEY = "REDACTED_CLIENT_KEY"
|
DEFAULT_API_KEY = "${CLIENT_API_KEY}"
|
||||||
DEFAULT_PROMPT = "hello"
|
DEFAULT_PROMPT = "hello"
|
||||||
DEFAULT_TIMEOUT = 180
|
DEFAULT_TIMEOUT = 180
|
||||||
POLL_INTERVAL = 3
|
POLL_INTERVAL = 3
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ All AITBC API endpoints require authentication using API keys.
|
|||||||
|
|
||||||
### Testing/Development
|
### Testing/Development
|
||||||
For integration tests and development, these test keys are available:
|
For integration tests and development, these test keys are available:
|
||||||
- `REDACTED_CLIENT_KEY` - For client API access
|
- `${CLIENT_API_KEY}` - For client API access
|
||||||
- `REDACTED_MINER_KEY` - For miner registration
|
- `${MINER_API_KEY}` - For miner registration
|
||||||
- `test-tenant` - Default tenant ID for testing
|
- `test-tenant` - Default tenant ID for testing
|
||||||
|
|
||||||
## Using API Keys
|
## Using API Keys
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ This document tracks components that have been successfully deployed and are ope
|
|||||||
- Result submission now returns 200 OK instead of 500 Internal Server Error
|
- Result submission now returns 200 OK instead of 500 Internal Server Error
|
||||||
|
|
||||||
- ✅ **Miner Configuration Fix**
|
- ✅ **Miner Configuration Fix**
|
||||||
- Updated miner ID from `host-gpu-miner` to `REDACTED_MINER_KEY` for proper job assignment
|
- Updated miner ID from `host-gpu-miner` to `${MINER_API_KEY}` for proper job assignment
|
||||||
- Added explicit flush logging handler for better systemd journal visibility
|
- Added explicit flush logging handler for better systemd journal visibility
|
||||||
- Enhanced systemd unit with unbuffered logging environment variables
|
- Enhanced systemd unit with unbuffered logging environment variables
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ This document illustrates the complete flow of a job submission through the CLI
|
|||||||
1. Bash script (`aitbc-cli.sh`) parses arguments
|
1. Bash script (`aitbc-cli.sh`) parses arguments
|
||||||
2. Sets environment variables:
|
2. Sets environment variables:
|
||||||
- `AITBC_URL=http://127.0.0.1:18000`
|
- `AITBC_URL=http://127.0.0.1:18000`
|
||||||
- `CLIENT_KEY=REDACTED_CLIENT_KEY`
|
- `CLIENT_KEY=${CLIENT_API_KEY}`
|
||||||
3. Calls Python client: `python3 cli/client.py --url $AITBC_URL --api-key $CLIENT_KEY submit inference --prompt "..."`
|
3. Calls Python client: `python3 cli/client.py --url $AITBC_URL --api-key $CLIENT_KEY submit inference --prompt "..."`
|
||||||
|
|
||||||
### 2. Python Client Processing
|
### 2. Python Client Processing
|
||||||
@@ -40,7 +40,7 @@ This document illustrates the complete flow of a job submission through the CLI
|
|||||||
"type": "inference",
|
"type": "inference",
|
||||||
"prompt": "What is machine learning?",
|
"prompt": "What is machine learning?",
|
||||||
"model": "llama3.2:latest",
|
"model": "llama3.2:latest",
|
||||||
"client_key": "REDACTED_CLIENT_KEY",
|
"client_key": "${CLIENT_API_KEY}",
|
||||||
"timestamp": "2025-01-29T14:50:00Z"
|
"timestamp": "2025-01-29T14:50:00Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -52,7 +52,7 @@ This document illustrates the complete flow of a job submission through the CLI
|
|||||||
POST /v1/jobs
|
POST /v1/jobs
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Api-Key: REDACTED_CLIENT_KEY
|
X-Api-Key: ${CLIENT_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "inference",
|
"type": "inference",
|
||||||
@@ -84,7 +84,7 @@ X-Api-Key: REDACTED_CLIENT_KEY
|
|||||||
{
|
{
|
||||||
"type": "submit_job",
|
"type": "submit_job",
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
"client": "REDACTED_CLIENT_KEY",
|
"client": "${CLIENT_API_KEY}",
|
||||||
"payload_hash": "abc123...",
|
"payload_hash": "abc123...",
|
||||||
"reward": "100aitbc"
|
"reward": "100aitbc"
|
||||||
}
|
}
|
||||||
@@ -110,14 +110,14 @@ X-Api-Key: REDACTED_CLIENT_KEY
|
|||||||
2. Miner selection algorithm runs:
|
2. Miner selection algorithm runs:
|
||||||
- Check available miners
|
- Check available miners
|
||||||
- Select based on stake, reputation, capacity
|
- Select based on stake, reputation, capacity
|
||||||
3. Selected miner: `REDACTED_MINER_KEY`
|
3. Selected miner: `${MINER_API_KEY}`
|
||||||
|
|
||||||
**Coordinator → Miner Daemon (Port 18001):**
|
**Coordinator → Miner Daemon (Port 18001):**
|
||||||
```http
|
```http
|
||||||
POST /v1/jobs/assign
|
POST /v1/jobs/assign
|
||||||
Host: 127.0.0.1:18001
|
Host: 127.0.0.1:18001
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Api-Key: REDACTED_ADMIN_KEY
|
X-Api-Key: ${ADMIN_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
@@ -183,7 +183,7 @@ Content-Type: application/json
|
|||||||
POST /v1/jobs/job_123456/complete
|
POST /v1/jobs/job_123456/complete
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Miner-Key: REDACTED_MINER_KEY
|
X-Miner-Key: ${MINER_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
@@ -210,8 +210,8 @@ X-Miner-Key: REDACTED_MINER_KEY
|
|||||||
{
|
{
|
||||||
"receipt_id": "receipt_789",
|
"receipt_id": "receipt_789",
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
"client": "REDACTED_CLIENT_KEY",
|
"client": "${CLIENT_API_KEY}",
|
||||||
"miner": "REDACTED_MINER_KEY",
|
"miner": "${MINER_API_KEY}",
|
||||||
"amount_paid": "0.25aitbc",
|
"amount_paid": "0.25aitbc",
|
||||||
"result_hash": "hash_of_result",
|
"result_hash": "hash_of_result",
|
||||||
"block_height": 12345,
|
"block_height": 12345,
|
||||||
@@ -244,7 +244,7 @@ X-Miner-Key: REDACTED_MINER_KEY
|
|||||||
```http
|
```http
|
||||||
GET /v1/jobs/job_123456
|
GET /v1/jobs/job_123456
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
X-Api-Key: REDACTED_CLIENT_KEY
|
X-Api-Key: ${CLIENT_API_KEY}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ incus exec aitbc -- systemctl reload nginx
|
|||||||
```bash
|
```bash
|
||||||
# Check each service
|
# Check each service
|
||||||
curl -k https://aitbc.bubuit.net/api/health
|
curl -k https://aitbc.bubuit.net/api/health
|
||||||
curl -k https://aitbc.bubuit.net/admin/stats -H "X-Api-Key: REDACTED_ADMIN_KEY"
|
curl -k https://aitbc.bubuit.net/admin/stats -H "X-Api-Key: ${ADMIN_API_KEY}"
|
||||||
curl -k https://aitbc.bubuit.net/rpc/head
|
curl -k https://aitbc.bubuit.net/rpc/head
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ These instructions cover the newly scaffolded services. Install dependencies usi
|
|||||||
python - <<'PY'
|
python - <<'PY'
|
||||||
from aitbc_sdk import CoordinatorReceiptClient, verify_receipt
|
from aitbc_sdk import CoordinatorReceiptClient, verify_receipt
|
||||||
|
|
||||||
client = CoordinatorReceiptClient("http://localhost:8011", "REDACTED_CLIENT_KEY")
|
client = CoordinatorReceiptClient("http://localhost:8011", "${CLIENT_API_KEY}")
|
||||||
receipt = client.fetch_latest("<job_id>")
|
receipt = client.fetch_latest("<job_id>")
|
||||||
verification = verify_receipt(receipt)
|
verification = verify_receipt(receipt)
|
||||||
print("miner signature valid:", verification.miner_signature.valid)
|
print("miner signature valid:", verification.miner_signature.valid)
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ DATABASE_URL=sqlite:///./coordinator.db
|
|||||||
# or: DATABASE_URL=postgresql://user:pass@localhost:5432/aitbc
|
# or: DATABASE_URL=postgresql://user:pass@localhost:5432/aitbc
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
CLIENT_API_KEYS=REDACTED_CLIENT_KEY,client_dev_key_2
|
CLIENT_API_KEYS=${CLIENT_API_KEY},client_dev_key_2
|
||||||
MINER_API_KEYS=REDACTED_MINER_KEY,miner_dev_key_2
|
MINER_API_KEYS=${MINER_API_KEY},miner_dev_key_2
|
||||||
ADMIN_API_KEYS=REDACTED_ADMIN_KEY
|
ADMIN_API_KEYS=${ADMIN_API_KEY}
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
HMAC_SECRET=change_me
|
HMAC_SECRET=change_me
|
||||||
@@ -349,7 +349,7 @@ def match_next_job(miner):
|
|||||||
**Client creates a job**
|
**Client creates a job**
|
||||||
```bash
|
```bash
|
||||||
curl -sX POST http://127.0.0.1:8011/v1/jobs \
|
curl -sX POST http://127.0.0.1:8011/v1/jobs \
|
||||||
-H 'X-Api-Key: REDACTED_CLIENT_KEY' \
|
-H 'X-Api-Key: ${CLIENT_API_KEY}' \
|
||||||
-H 'Idempotency-Key: 7d4a...' \
|
-H 'Idempotency-Key: 7d4a...' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
@@ -361,12 +361,12 @@ curl -sX POST http://127.0.0.1:8011/v1/jobs \
|
|||||||
**Miner registers + polls**
|
**Miner registers + polls**
|
||||||
```bash
|
```bash
|
||||||
curl -sX POST http://127.0.0.1:8011/v1/miners/register \
|
curl -sX POST http://127.0.0.1:8011/v1/miners/register \
|
||||||
-H 'X-Api-Key: REDACTED_MINER_KEY' \
|
-H 'X-Api-Key: ${MINER_API_KEY}' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"capabilities":{"gpu":"RTX4060Ti","cuda":"12.3","vram_gb":16},"concurrency":2,"region":"eu-central"}'
|
-d '{"capabilities":{"gpu":"RTX4060Ti","cuda":"12.3","vram_gb":16},"concurrency":2,"region":"eu-central"}'
|
||||||
|
|
||||||
curl -i -sX POST http://127.0.0.1:8011/v1/miners/poll \
|
curl -i -sX POST http://127.0.0.1:8011/v1/miners/poll \
|
||||||
-H 'X-Api-Key: REDACTED_MINER_KEY' \
|
-H 'X-Api-Key: ${MINER_API_KEY}' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"max_wait_seconds":10}'
|
-d '{"max_wait_seconds":10}'
|
||||||
```
|
```
|
||||||
@@ -374,7 +374,7 @@ curl -i -sX POST http://127.0.0.1:8011/v1/miners/poll \
|
|||||||
**Miner submits result**
|
**Miner submits result**
|
||||||
```bash
|
```bash
|
||||||
curl -sX POST http://127.0.0.1:8011/v1/miners/<JOB_ID>/result \
|
curl -sX POST http://127.0.0.1:8011/v1/miners/<JOB_ID>/result \
|
||||||
-H 'X-Api-Key: REDACTED_MINER_KEY' \
|
-H 'X-Api-Key: ${MINER_API_KEY}' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"result":{"sum":5},"metrics":{"latency_ms":42}}'
|
-d '{"result":{"sum":5},"metrics":{"latency_ms":42}}'
|
||||||
```
|
```
|
||||||
@@ -382,7 +382,7 @@ curl -sX POST http://127.0.0.1:8011/v1/miners/<JOB_ID>/result \
|
|||||||
**Client fetches result**
|
**Client fetches result**
|
||||||
```bash
|
```bash
|
||||||
curl -s http://127.0.0.1:8011/v1/jobs/<JOB_ID>/result \
|
curl -s http://127.0.0.1:8011/v1/jobs/<JOB_ID>/result \
|
||||||
-H 'X-Api-Key: REDACTED_CLIENT_KEY'
|
-H 'X-Api-Key: ${CLIENT_API_KEY}'
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- ✅ Deployed real GPU miner on host with NVIDIA RTX 4060 Ti (16GB)
|
- ✅ Deployed real GPU miner on host with NVIDIA RTX 4060 Ti (16GB)
|
||||||
- ✅ Integrated Ollama for LLM inference across 13+ models
|
- ✅ Integrated Ollama for LLM inference across 13+ models
|
||||||
- ✅ Configured systemd service (`aitbc-host-gpu-miner.service`)
|
- ✅ Configured systemd service (`aitbc-host-gpu-miner.service`)
|
||||||
- ✅ Fixed miner ID configuration (REDACTED_MINER_KEY)
|
- ✅ Fixed miner ID configuration (${MINER_API_KEY})
|
||||||
- ✅ Enhanced logging with flush handlers for systemd journal visibility
|
- ✅ Enhanced logging with flush handlers for systemd journal visibility
|
||||||
- ✅ Verified end-to-end workflow: job polling → Ollama inference → result submission → receipt generation
|
- ✅ Verified end-to-end workflow: job polling → Ollama inference → result submission → receipt generation
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
### Integration Points
|
### Integration Points
|
||||||
- Coordinator API: http://127.0.0.1:18000 (via Incus proxy)
|
- Coordinator API: http://127.0.0.1:18000 (via Incus proxy)
|
||||||
- Miner ID: REDACTED_MINER_KEY
|
- Miner ID: ${MINER_API_KEY}
|
||||||
- Heartbeat interval: 15 seconds
|
- Heartbeat interval: 15 seconds
|
||||||
- Job polling: 3-second intervals
|
- Job polling: 3-second intervals
|
||||||
- Result submission: JSON with metrics and execution details
|
- Result submission: JSON with metrics and execution details
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ CREATE TABLE job_payments (
|
|||||||
### 1. Client Creates Job
|
### 1. Client Creates Job
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:18000/v1/jobs \
|
curl -X POST http://localhost:18000/v1/jobs \
|
||||||
-H "X-Api-Key: REDACTED_CLIENT_KEY" \
|
-H "X-Api-Key: ${CLIENT_API_KEY}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"payload": {
|
"payload": {
|
||||||
@@ -124,7 +124,7 @@ curl -X POST http://localhost:18000/v1/jobs \
|
|||||||
### 3. Job Completion & Payment Release
|
### 3. Job Completion & Payment Release
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:18000/v1/payments/pay456/release \
|
curl -X POST http://localhost:18000/v1/payments/pay456/release \
|
||||||
-H "X-Api-Key: REDACTED_CLIENT_KEY" \
|
-H "X-Api-Key: ${CLIENT_API_KEY}" \
|
||||||
-d '{"job_id": "abc123", "reason": "Job completed"}'
|
-d '{"job_id": "abc123", "reason": "Job completed"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
### 4. Missing API Keys
|
### 4. Missing API Keys
|
||||||
- **Problem**: Some requests were missing the required `X-Api-Key` header
|
- **Problem**: Some requests were missing the required `X-Api-Key` header
|
||||||
- **Solution**: Added `X-Api-Key: REDACTED_CLIENT_KEY` to all requests
|
- **Solution**: Added `X-Api-Key: ${CLIENT_API_KEY}` to all requests
|
||||||
|
|
||||||
### 5. Non-existent Endpoints
|
### 5. Non-existent Endpoints
|
||||||
- **Problem**: Tests were calling endpoints that don't exist (e.g., `/v1/jobs/{id}/complete`)
|
- **Problem**: Tests were calling endpoints that don't exist (e.g., `/v1/jobs/{id}/complete`)
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ python -m pytest -m integration
|
|||||||
|
|
||||||
### Authentication Issues?
|
### Authentication Issues?
|
||||||
- Use correct API keys:
|
- Use correct API keys:
|
||||||
- Client: `REDACTED_CLIENT_KEY`
|
- Client: `${CLIENT_API_KEY}`
|
||||||
- Miner: `REDACTED_MINER_KEY`
|
- Miner: `${MINER_API_KEY}`
|
||||||
- Admin: `REDACTED_ADMIN_KEY`
|
- Admin: `${ADMIN_API_KEY}`
|
||||||
|
|
||||||
## 📝 Next Steps
|
## 📝 Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ This roadmap aggregates high-priority tasks derived from the bootstrap specifica
|
|||||||
- ✅ Validate receipt payload structure and signature generation
|
- ✅ Validate receipt payload structure and signature generation
|
||||||
|
|
||||||
- **Miner Configuration & Optimization**
|
- **Miner Configuration & Optimization**
|
||||||
- ✅ Fix miner ID mismatch (host-gpu-miner → REDACTED_MINER_KEY)
|
- ✅ Fix miner ID mismatch (host-gpu-miner → ${MINER_API_KEY})
|
||||||
- ✅ Enhance logging with explicit flush handlers for systemd journal
|
- ✅ Enhance logging with explicit flush handlers for systemd journal
|
||||||
- ✅ Configure unbuffered Python logging environment variables
|
- ✅ Configure unbuffered Python logging environment variables
|
||||||
- ✅ Create systemd service unit with proper environment configuration
|
- ✅ Create systemd service unit with proper environment configuration
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
# Configuration - using the SSH tunnel to remote server
|
# Configuration - using the SSH tunnel to remote server
|
||||||
COORDINATOR_URL = "http://localhost:8001"
|
COORDINATOR_URL = "http://localhost:8001"
|
||||||
CLIENT_API_KEY = "REDACTED_CLIENT_KEY"
|
CLIENT_API_KEY = "${CLIENT_API_KEY}"
|
||||||
|
|
||||||
def create_job():
|
def create_job():
|
||||||
"""Create a job on the remote coordinator"""
|
"""Create a job on the remote coordinator"""
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'cli'))
|
|||||||
class AITBCClient:
|
class AITBCClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.coordinator_url = "http://localhost:8001"
|
self.coordinator_url = "http://localhost:8001"
|
||||||
self.api_key = "REDACTED_CLIENT_KEY"
|
self.api_key = "${CLIENT_API_KEY}"
|
||||||
|
|
||||||
def submit_job(self, prompt, model="llama3.2:latest", wait_for_result=True):
|
def submit_job(self, prompt, model="llama3.2:latest", wait_for_result=True):
|
||||||
"""Submit a job and optionally wait for result"""
|
"""Submit a job and optionally wait for result"""
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def get_job_result(job_id: str) -> Optional[dict]:
|
|||||||
with httpx.Client() as client:
|
with httpx.Client() as client:
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{DEFAULT_COORDINATOR}/v1/jobs/{job_id}/result",
|
f"{DEFAULT_COORDINATOR}/v1/jobs/{job_id}/result",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"},
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"},
|
||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Miners earn 150% of the cost (50% markup).
|
|||||||
```python
|
```python
|
||||||
from client_plugin import OllamaClient
|
from client_plugin import OllamaClient
|
||||||
|
|
||||||
client = OllamaClient("http://localhost:8001", "REDACTED_CLIENT_KEY")
|
client = OllamaClient("http://localhost:8001", "${CLIENT_API_KEY}")
|
||||||
|
|
||||||
job_id = client.submit_generation(
|
job_id = client.submit_generation(
|
||||||
model="llama3.2:latest",
|
model="llama3.2:latest",
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ def main():
|
|||||||
|
|
||||||
parser = argparse.ArgumentParser(description="AITBC Ollama Client")
|
parser = argparse.ArgumentParser(description="AITBC Ollama Client")
|
||||||
parser.add_argument("--url", default="http://localhost:8001", help="Coordinator URL")
|
parser.add_argument("--url", default="http://localhost:8001", help="Coordinator URL")
|
||||||
parser.add_argument("--api-key", default="REDACTED_CLIENT_KEY", help="API key")
|
parser.add_argument("--api-key", default="${CLIENT_API_KEY}", help="API key")
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def main():
|
|||||||
print(f"✅ Ollama running with {len(models)} models")
|
print(f"✅ Ollama running with {len(models)} models")
|
||||||
|
|
||||||
# Create client
|
# Create client
|
||||||
client = OllamaClient("http://localhost:8001", "REDACTED_CLIENT_KEY")
|
client = OllamaClient("http://localhost:8001", "${CLIENT_API_KEY}")
|
||||||
|
|
||||||
# Submit a few different jobs
|
# Submit a few different jobs
|
||||||
jobs = []
|
jobs = []
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ if __name__ == "__main__":
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
coordinator_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8001"
|
coordinator_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8001"
|
||||||
api_key = sys.argv[2] if len(sys.argv) > 2 else "REDACTED_MINER_KEY"
|
api_key = sys.argv[2] if len(sys.argv) > 2 else "${MINER_API_KEY}"
|
||||||
miner_id = sys.argv[3] if len(sys.argv) > 3 else "ollama-miner"
|
miner_id = sys.argv[3] if len(sys.argv) > 3 else "ollama-miner"
|
||||||
|
|
||||||
# Create and run miner
|
# Create and run miner
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def test_client_miner_flow():
|
|||||||
print("\n🔄 Testing Client-Miner Flow...")
|
print("\n🔄 Testing Client-Miner Flow...")
|
||||||
|
|
||||||
# Create client
|
# Create client
|
||||||
client = OllamaClient("http://localhost:8001", "REDACTED_CLIENT_KEY")
|
client = OllamaClient("http://localhost:8001", "${CLIENT_API_KEY}")
|
||||||
|
|
||||||
# Submit a job
|
# Submit a job
|
||||||
print("1. Submitting inference job...")
|
print("1. Submitting inference job...")
|
||||||
@@ -89,7 +89,7 @@ def test_client_miner_flow():
|
|||||||
miner_cmd = [
|
miner_cmd = [
|
||||||
"python3", "miner_plugin.py",
|
"python3", "miner_plugin.py",
|
||||||
"http://localhost:8001",
|
"http://localhost:8001",
|
||||||
"REDACTED_MINER_KEY",
|
"${MINER_API_KEY}",
|
||||||
"ollama-miner-test"
|
"ollama-miner-test"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||||||
CLI_PY="$ROOT_DIR/cli/client.py"
|
CLI_PY="$ROOT_DIR/cli/client.py"
|
||||||
|
|
||||||
AITBC_URL="${AITBC_URL:-http://127.0.0.1:18000}"
|
AITBC_URL="${AITBC_URL:-http://127.0.0.1:18000}"
|
||||||
CLIENT_KEY="${CLIENT_KEY:-REDACTED_CLIENT_KEY}"
|
CLIENT_KEY="${CLIENT_KEY:?Set CLIENT_KEY env var}"
|
||||||
ADMIN_KEY="${ADMIN_KEY:-REDACTED_ADMIN_KEY}"
|
ADMIN_KEY="${ADMIN_KEY:?Set ADMIN_KEY env var}"
|
||||||
MINER_KEY="${MINER_KEY:-REDACTED_MINER_KEY}"
|
MINER_KEY="${MINER_KEY:?Set MINER_KEY env var}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -28,9 +28,9 @@ Usage:
|
|||||||
|
|
||||||
Environment overrides:
|
Environment overrides:
|
||||||
AITBC_URL (default: http://127.0.0.1:18000)
|
AITBC_URL (default: http://127.0.0.1:18000)
|
||||||
CLIENT_KEY (default: REDACTED_CLIENT_KEY)
|
CLIENT_KEY (required)
|
||||||
ADMIN_KEY (default: REDACTED_ADMIN_KEY)
|
ADMIN_KEY (required)
|
||||||
MINER_KEY (default: REDACTED_MINER_KEY)
|
MINER_KEY (required)
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import json
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
COORDINATOR_URL = "http://localhost:8001"
|
COORDINATOR_URL = "http://localhost:8001"
|
||||||
MINER_API_KEY = "REDACTED_MINER_KEY"
|
MINER_API_KEY = "${MINER_API_KEY}"
|
||||||
MINER_ID = "localhost-gpu-miner"
|
MINER_ID = "localhost-gpu-miner"
|
||||||
|
|
||||||
def assign_proposer_to_latest_block():
|
def assign_proposer_to_latest_block():
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ ssh $SERVER "systemctl status aitbc-blockchain --no-pager -l | head -10"
|
|||||||
print_status "Testing endpoints..."
|
print_status "Testing endpoints..."
|
||||||
ssh $SERVER "curl -s http://127.0.0.1:8000/v1/health | head -c 100"
|
ssh $SERVER "curl -s http://127.0.0.1:8000/v1/health | head -c 100"
|
||||||
echo ""
|
echo ""
|
||||||
ssh $SERVER "curl -s http://127.0.0.1:8000/v1/admin/stats -H 'X-Api-Key: REDACTED_ADMIN_KEY' | head -c 100"
|
ssh $SERVER "curl -s http://127.0.0.1:8000/v1/admin/stats -H 'X-Api-Key: ${ADMIN_API_KEY}' | head -c 100"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from datetime import datetime
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
COORDINATOR_URL = "http://127.0.0.1:18000"
|
COORDINATOR_URL = "http://127.0.0.1:18000"
|
||||||
MINER_ID = "REDACTED_MINER_KEY"
|
MINER_ID = "${MINER_API_KEY}"
|
||||||
AUTH_TOKEN = "REDACTED_MINER_KEY"
|
AUTH_TOKEN = "${MINER_API_KEY}"
|
||||||
HEARTBEAT_INTERVAL = 15
|
HEARTBEAT_INTERVAL = 15
|
||||||
MAX_RETRIES = 10
|
MAX_RETRIES = 10
|
||||||
RETRY_DELAY = 30
|
RETRY_DELAY = 30
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ def test_endpoints():
|
|||||||
for name, url in endpoints:
|
for name, url in endpoints:
|
||||||
try:
|
try:
|
||||||
if "admin" in url:
|
if "admin" in url:
|
||||||
response = requests.get(url, headers={"X-Api-Key": "REDACTED_ADMIN_KEY"}, timeout=2)
|
response = requests.get(url, headers={"X-Api-Key": "${ADMIN_API_KEY}"}, timeout=2)
|
||||||
else:
|
else:
|
||||||
response = requests.get(url, timeout=2)
|
response = requests.get(url, timeout=2)
|
||||||
print(f" {name}: ✅ {response.status_code}")
|
print(f" {name}: ✅ {response.status_code}")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
COORDINATOR_URL = "http://localhost:8001"
|
COORDINATOR_URL = "http://localhost:8001"
|
||||||
MINER_API_KEY = "REDACTED_MINER_KEY"
|
MINER_API_KEY = "${MINER_API_KEY}"
|
||||||
MINER_ID = "localhost-gpu-miner"
|
MINER_ID = "localhost-gpu-miner"
|
||||||
|
|
||||||
def poll_and_accept_job():
|
def poll_and_accept_job():
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ echo "API Health:"
|
|||||||
curl -s http://127.0.0.1:8000/v1/health | head -c 100
|
curl -s http://127.0.0.1:8000/v1/health | head -c 100
|
||||||
|
|
||||||
echo -e "\n\nAdmin Stats:"
|
echo -e "\n\nAdmin Stats:"
|
||||||
curl -s http://127.0.0.1:8000/v1/admin/stats -H "X-Api-Key: REDACTED_ADMIN_KEY" | head -c 100
|
curl -s http://127.0.0.1:8000/v1/admin/stats -H "X-Api-Key: ${ADMIN_API_KEY}" | head -c 100
|
||||||
|
|
||||||
echo -e "\n\nMarketplace Offers:"
|
echo -e "\n\nMarketplace Offers:"
|
||||||
curl -s http://127.0.0.1:8000/v1/marketplace/offers | head -c 100
|
curl -s http://127.0.0.1:8000/v1/marketplace/offers | head -c 100
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n "Admin API: "
|
echo -n "Admin API: "
|
||||||
if curl -s http://127.0.0.1:8000/v1/admin/stats -H "X-Api-Key: REDACTED_ADMIN_KEY" > /dev/null; then
|
if curl -s http://127.0.0.1:8000/v1/admin/stats -H "X-Api-Key: ${ADMIN_API_KEY}" > /dev/null; then
|
||||||
echo "✅ OK"
|
echo "✅ OK"
|
||||||
else
|
else
|
||||||
echo "❌ Failed"
|
echo "❌ Failed"
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ def main():
|
|||||||
print(" • Professional, production-ready interface")
|
print(" • Professional, production-ready interface")
|
||||||
|
|
||||||
print("\n💡 Note: Most transactions show:")
|
print("\n💡 Note: Most transactions show:")
|
||||||
print(" • From: REDACTED_CLIENT_KEY")
|
print(" • From: ${CLIENT_API_KEY}")
|
||||||
print(" • To: null (not assigned to miner yet)")
|
print(" • To: null (not assigned to miner yet)")
|
||||||
print(" • Value: 0 (cost shown when completed)")
|
print(" • Value: 0 (cost shown when completed)")
|
||||||
print(" • Status: Queued/Running/Expired")
|
print(" • Status: Queued/Running/Expired")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import json
|
|||||||
# Configuration
|
# Configuration
|
||||||
COORDINATOR_URL = "http://127.0.0.1:8000/v1"
|
COORDINATOR_URL = "http://127.0.0.1:8000/v1"
|
||||||
CLIENT_KEY = "test_client_key_123"
|
CLIENT_KEY = "test_client_key_123"
|
||||||
MINER_KEY = "REDACTED_MINER_KEY"
|
MINER_KEY = "${MINER_API_KEY}"
|
||||||
|
|
||||||
async def register_client():
|
async def register_client():
|
||||||
"""Register a test client"""
|
"""Register a test client"""
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# Configuration
|
# Configuration
|
||||||
COORDINATOR_URL = "https://aitbc.bubuit.net/api"
|
COORDINATOR_URL = "https://aitbc.bubuit.net/api"
|
||||||
CLIENT_KEY = "test_client_key_123"
|
CLIENT_KEY = "test_client_key_123"
|
||||||
MINER_KEY = "REDACTED_MINER_KEY"
|
MINER_KEY = "${MINER_API_KEY}"
|
||||||
|
|
||||||
class PaymentIntegrationTest:
|
class PaymentIntegrationTest:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Configuration - Using localhost as we're testing from the server
|
# Configuration - Using localhost as we're testing from the server
|
||||||
COORDINATOR_URL = "http://127.0.0.1:8000/v1"
|
COORDINATOR_URL = "http://127.0.0.1:8000/v1"
|
||||||
CLIENT_KEY = "REDACTED_CLIENT_KEY"
|
CLIENT_KEY = "${CLIENT_API_KEY}"
|
||||||
MINER_KEY = "REDACTED_MINER_KEY"
|
MINER_KEY = "${MINER_API_KEY}"
|
||||||
|
|
||||||
class PaymentIntegrationTest:
|
class PaymentIntegrationTest:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json=job_data,
|
json=job_data,
|
||||||
headers={
|
headers={
|
||||||
"X-Api-Key": "REDACTED_CLIENT_KEY", # Valid API key from config
|
"X-Api-Key": "${CLIENT_API_KEY}", # Valid API key from config
|
||||||
"X-Tenant-ID": "test-tenant"
|
"X-Tenant-ID": "test-tenant"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -46,7 +46,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
# 2. Get job status
|
# 2. Get job status
|
||||||
response = coordinator_client.get(
|
response = coordinator_client.get(
|
||||||
f"/v1/jobs/{job_id}",
|
f"/v1/jobs/{job_id}",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["job_id"] == job_id # Fixed: use job_id
|
assert response.json()["job_id"] == job_id # Fixed: use job_id
|
||||||
@@ -54,7 +54,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
# 3. Test that we can get receipts (even if empty)
|
# 3. Test that we can get receipts (even if empty)
|
||||||
response = coordinator_client.get(
|
response = coordinator_client.get(
|
||||||
f"/v1/jobs/{job_id}/receipts",
|
f"/v1/jobs/{job_id}/receipts",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
receipts = response.json()
|
receipts = response.json()
|
||||||
@@ -74,7 +74,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
response = coordinator_client.post(
|
response = coordinator_client.post(
|
||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json={"payload": {"job_type": "test", "parameters": {}}, "ttl_seconds": 900},
|
json={"payload": {"job_type": "test", "parameters": {}}, "ttl_seconds": 900},
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY", "X-Tenant-ID": "tenant-a"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}", "X-Tenant-ID": "tenant-a"}
|
||||||
)
|
)
|
||||||
tenant_a_jobs.append(response.json()["job_id"]) # Fixed: use job_id
|
tenant_a_jobs.append(response.json()["job_id"]) # Fixed: use job_id
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
response = coordinator_client.post(
|
response = coordinator_client.post(
|
||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json={"payload": {"job_type": "test", "parameters": {}}, "ttl_seconds": 900},
|
json={"payload": {"job_type": "test", "parameters": {}}, "ttl_seconds": 900},
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY", "X-Tenant-ID": "tenant-b"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}", "X-Tenant-ID": "tenant-b"}
|
||||||
)
|
)
|
||||||
tenant_b_jobs.append(response.json()["job_id"]) # Fixed: use job_id
|
tenant_b_jobs.append(response.json()["job_id"]) # Fixed: use job_id
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class TestJobToBlockchainWorkflow:
|
|||||||
# Try to access other tenant's job (currently returns 200, not 404)
|
# Try to access other tenant's job (currently returns 200, not 404)
|
||||||
response = coordinator_client.get(
|
response = coordinator_client.get(
|
||||||
f"/v1/jobs/{tenant_b_jobs[0]}",
|
f"/v1/jobs/{tenant_b_jobs[0]}",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY", "X-Tenant-ID": "tenant-a"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}", "X-Tenant-ID": "tenant-a"}
|
||||||
)
|
)
|
||||||
# The API doesn't enforce tenant isolation yet
|
# The API doesn't enforce tenant isolation yet
|
||||||
assert response.status_code in [200, 404] # Accept either for now
|
assert response.status_code in [200, 404] # Accept either for now
|
||||||
@@ -122,7 +122,7 @@ class TestWalletToCoordinatorIntegration:
|
|||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json=job_data,
|
json=job_data,
|
||||||
headers={
|
headers={
|
||||||
"X-Api-Key": "REDACTED_CLIENT_KEY",
|
"X-Api-Key": "${CLIENT_API_KEY}",
|
||||||
"X-Tenant-ID": "test-tenant"
|
"X-Tenant-ID": "test-tenant"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -137,7 +137,7 @@ class TestWalletToCoordinatorIntegration:
|
|||||||
# Get payment details
|
# Get payment details
|
||||||
response = coordinator_client.get(
|
response = coordinator_client.get(
|
||||||
f"/v1/jobs/{job_id}/payment",
|
f"/v1/jobs/{job_id}/payment",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
payment = response.json()
|
payment = response.json()
|
||||||
@@ -155,7 +155,7 @@ class TestWalletToCoordinatorIntegration:
|
|||||||
"job_id": job_id,
|
"job_id": job_id,
|
||||||
"reason": "Job completed successfully"
|
"reason": "Job completed successfully"
|
||||||
},
|
},
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
# Note: This might fail if wallet daemon is not running
|
# Note: This might fail if wallet daemon is not running
|
||||||
# That's OK for this test
|
# That's OK for this test
|
||||||
@@ -253,7 +253,7 @@ class TestMarketplaceIntegration:
|
|||||||
response = coordinator_client.post(
|
response = coordinator_client.post(
|
||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json=job_data,
|
json=job_data,
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
job = response.json()
|
job = response.json()
|
||||||
@@ -285,7 +285,7 @@ class TestSecurityIntegration:
|
|||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json=job_data,
|
json=job_data,
|
||||||
headers={
|
headers={
|
||||||
"X-Api-Key": "REDACTED_CLIENT_KEY",
|
"X-Api-Key": "${CLIENT_API_KEY}",
|
||||||
"X-Tenant-ID": "secure-tenant"
|
"X-Tenant-ID": "secure-tenant"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -300,7 +300,7 @@ class TestSecurityIntegration:
|
|||||||
# Test that we can retrieve the job securely
|
# Test that we can retrieve the job securely
|
||||||
response = coordinator_client.get(
|
response = coordinator_client.get(
|
||||||
f"/v1/jobs/{job_id}",
|
f"/v1/jobs/{job_id}",
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
retrieved_job = response.json()
|
retrieved_job = response.json()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def test_job_endpoint_structure():
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
"/v1/jobs",
|
"/v1/jobs",
|
||||||
json={},
|
json={},
|
||||||
headers={"X-Api-Key": "REDACTED_CLIENT_KEY"}
|
headers={"X-Api-Key": "${CLIENT_API_KEY}"}
|
||||||
)
|
)
|
||||||
# Should get validation error, not auth or not found
|
# Should get validation error, not auth or not found
|
||||||
assert response.status_code in [400, 422], f"Expected validation error, got {response.status_code}"
|
assert response.status_code in [400, 422], f"Expected validation error, got {response.status_code}"
|
||||||
@@ -84,7 +84,7 @@ def test_miner_endpoint_structure():
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
"/v1/miners/register",
|
"/v1/miners/register",
|
||||||
json={},
|
json={},
|
||||||
headers={"X-Api-Key": "REDACTED_MINER_KEY"}
|
headers={"X-Api-Key": "${MINER_API_KEY}"}
|
||||||
)
|
)
|
||||||
# Should get validation error, not auth or not found
|
# Should get validation error, not auth or not found
|
||||||
assert response.status_code in [400, 422], f"Expected validation error, got {response.status_code}"
|
assert response.status_code in [400, 422], f"Expected validation error, got {response.status_code}"
|
||||||
|
|||||||
@@ -174,7 +174,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API_BASE = '/api';
|
const API_BASE = '/api';
|
||||||
const ADMIN_API_KEY = 'REDACTED_ADMIN_KEY';
|
const ADMIN_API_KEY = '${ADMIN_API_KEY}';
|
||||||
const headers = { 'X-Api-Key': ADMIN_API_KEY };
|
const headers = { 'X-Api-Key': ADMIN_API_KEY };
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
|
|||||||
@@ -357,7 +357,7 @@
|
|||||||
<li>Sets environment variables:
|
<li>Sets environment variables:
|
||||||
<ul>
|
<ul>
|
||||||
<li><span class="inline-code">AITBC_URL=http://127.0.0.1:18000</span></li>
|
<li><span class="inline-code">AITBC_URL=http://127.0.0.1:18000</span></li>
|
||||||
<li><span class="inline-code">CLIENT_KEY=REDACTED_CLIENT_KEY</span></li>
|
<li><span class="inline-code">CLIENT_KEY=${CLIENT_API_KEY}</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>Calls Python client: <span class="inline-code">python3 cli/client.py --url $AITBC_URL --api-key $CLIENT_KEY submit inference --prompt "..."</span></li>
|
<li>Calls Python client: <span class="inline-code">python3 cli/client.py --url $AITBC_URL --api-key $CLIENT_KEY submit inference --prompt "..."</span></li>
|
||||||
@@ -374,7 +374,7 @@
|
|||||||
"type": "inference",
|
"type": "inference",
|
||||||
"prompt": "What is machine learning?",
|
"prompt": "What is machine learning?",
|
||||||
"model": "llama3.2:latest",
|
"model": "llama3.2:latest",
|
||||||
"client_key": "REDACTED_CLIENT_KEY",
|
"client_key": "${CLIENT_API_KEY}",
|
||||||
"timestamp": "2025-01-29T14:50:00Z"
|
"timestamp": "2025-01-29T14:50:00Z"
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
</li>
|
</li>
|
||||||
@@ -386,7 +386,7 @@
|
|||||||
<pre><code>POST /v1/jobs
|
<pre><code>POST /v1/jobs
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Api-Key: REDACTED_CLIENT_KEY
|
X-Api-Key: ${CLIENT_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "inference",
|
"type": "inference",
|
||||||
@@ -418,7 +418,7 @@ X-Api-Key: REDACTED_CLIENT_KEY
|
|||||||
<pre><code>{
|
<pre><code>{
|
||||||
"type": "submit_job",
|
"type": "submit_job",
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
"client": "REDACTED_CLIENT_KEY",
|
"client": "${CLIENT_API_KEY}",
|
||||||
"payload_hash": "abc123...",
|
"payload_hash": "abc123...",
|
||||||
"reward": "100aitbc"
|
"reward": "100aitbc"
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
@@ -445,7 +445,7 @@ X-Api-Key: REDACTED_CLIENT_KEY
|
|||||||
<li>Select based on stake, reputation, capacity</li>
|
<li>Select based on stake, reputation, capacity</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>Selected miner: <span class="inline-code">REDACTED_MINER_KEY</span></li>
|
<li>Selected miner: <span class="inline-code">${MINER_API_KEY}</span></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="http-request">
|
<div class="http-request">
|
||||||
@@ -453,7 +453,7 @@ X-Api-Key: REDACTED_CLIENT_KEY
|
|||||||
<pre><code>POST /v1/jobs/assign
|
<pre><code>POST /v1/jobs/assign
|
||||||
Host: 127.0.0.1:18001
|
Host: 127.0.0.1:18001
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Api-Key: REDACTED_ADMIN_KEY
|
X-Api-Key: ${ADMIN_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
@@ -518,7 +518,7 @@ Content-Type: application/json
|
|||||||
<pre><code>POST /v1/jobs/job_123456/complete
|
<pre><code>POST /v1/jobs/job_123456/complete
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Miner-Key: REDACTED_MINER_KEY
|
X-Miner-Key: ${MINER_API_KEY}
|
||||||
|
|
||||||
{
|
{
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
@@ -544,8 +544,8 @@ X-Miner-Key: REDACTED_MINER_KEY
|
|||||||
<pre><code>{
|
<pre><code>{
|
||||||
"receipt_id": "receipt_789",
|
"receipt_id": "receipt_789",
|
||||||
"job_id": "job_123456",
|
"job_id": "job_123456",
|
||||||
"client": "REDACTED_CLIENT_KEY",
|
"client": "${CLIENT_API_KEY}",
|
||||||
"miner": "REDACTED_MINER_KEY",
|
"miner": "${MINER_API_KEY}",
|
||||||
"amount_paid": "0.25aitbc",
|
"amount_paid": "0.25aitbc",
|
||||||
"result_hash": "hash_of_result",
|
"result_hash": "hash_of_result",
|
||||||
"block_height": 12345,
|
"block_height": 12345,
|
||||||
@@ -573,7 +573,7 @@ X-Miner-Key: REDACTED_MINER_KEY
|
|||||||
<h4>HTTP Request:</h4>
|
<h4>HTTP Request:</h4>
|
||||||
<pre><code>GET /v1/jobs/job_123456
|
<pre><code>GET /v1/jobs/job_123456
|
||||||
Host: 127.0.0.1:18000
|
Host: 127.0.0.1:18000
|
||||||
X-Api-Key: REDACTED_CLIENT_KEY</code></pre>
|
X-Api-Key: ${CLIENT_API_KEY}</code></pre>
|
||||||
|
|
||||||
<h4>Response:</h4>
|
<h4>Response:</h4>
|
||||||
<pre><code>{
|
<pre><code>{
|
||||||
|
|||||||
Reference in New Issue
Block a user