Clawdbot commited on
Commit
a890f8f
·
1 Parent(s): 7bec764

Upgrade to Phase 2 (Auction Logic) to save API costs

Browse files
hub/__pycache__/main.cpython-310.pyc CHANGED
Binary files a/hub/__pycache__/main.cpython-310.pyc and b/hub/__pycache__/main.cpython-310.pyc differ
 
hub/__pycache__/models.cpython-310.pyc CHANGED
Binary files a/hub/__pycache__/models.cpython-310.pyc and b/hub/__pycache__/models.cpython-310.pyc differ
 
hub/main.py CHANGED
@@ -2,7 +2,7 @@ from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
2
  from typing import Dict, List
3
  import uuid
4
 
5
- from models import NodeRegistration, TaskCreate, TaskResult, NodeBalance
6
 
7
  app = FastAPI(title="Chronos Protocol L1 Hub", description="The Time Exchange Clearinghouse", version="0.1.1")
8
 
@@ -39,15 +39,18 @@ async def submit_task(task: TaskCreate):
39
  "consumer_id": task.consumer_id,
40
  "payload": task.payload,
41
  "bounty": task.bounty,
42
- "status": "pending",
43
- "target_node": task.target_node
 
44
  }
45
  active_tasks[task_id] = task_data
46
 
47
- # Target specific node if requested (Direct Message)
48
  if task.target_node:
49
  if task.target_node in connected_nodes:
50
  try:
 
 
51
  await connected_nodes[task.target_node].send_json({"event": "new_task", "data": task_data})
52
  return {"status": "success", "task_id": task_id, "routed_to": task.target_node}
53
  except:
@@ -55,16 +58,43 @@ async def submit_task(task: TaskCreate):
55
  else:
56
  return {"status": "error", "detail": "Target node not currently connected to Hub"}
57
 
58
- # Otherwise, Broadcast to all connected nodes EXCEPT the consumer
 
 
 
 
 
 
59
  for node_id, ws in list(connected_nodes.items()):
60
  if node_id != task.consumer_id:
61
  try:
62
- await ws.send_json({"event": "new_task", "data": task_data})
63
  except:
64
  pass
65
 
66
  return {"status": "success", "task_id": task_id}
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  @app.post("/tasks/complete")
69
  async def complete_task(result: TaskResult):
70
  task = active_tasks.get(result.task_id)
 
2
  from typing import Dict, List
3
  import uuid
4
 
5
+ from models import NodeRegistration, TaskCreate, TaskResult, NodeBalance, TaskBid
6
 
7
  app = FastAPI(title="Chronos Protocol L1 Hub", description="The Time Exchange Clearinghouse", version="0.1.1")
8
 
 
39
  "consumer_id": task.consumer_id,
40
  "payload": task.payload,
41
  "bounty": task.bounty,
42
+ "status": "bidding",
43
+ "target_node": task.target_node,
44
+ "model_requirement": task.model_requirement
45
  }
46
  active_tasks[task_id] = task_data
47
 
48
+ # Target specific node if requested (Direct Message skips bidding)
49
  if task.target_node:
50
  if task.target_node in connected_nodes:
51
  try:
52
+ task_data["status"] = "assigned"
53
+ task_data["provider_id"] = task.target_node
54
  await connected_nodes[task.target_node].send_json({"event": "new_task", "data": task_data})
55
  return {"status": "success", "task_id": task_id, "routed_to": task.target_node}
56
  except:
 
58
  else:
59
  return {"status": "error", "detail": "Target node not currently connected to Hub"}
60
 
61
+ # Phase 2: Broadcast RFC (Request For Compute) to all connected nodes EXCEPT the consumer
62
+ rfc_data = {
63
+ "id": task_id,
64
+ "consumer_id": task.consumer_id,
65
+ "bounty": task.bounty,
66
+ "model_requirement": task.model_requirement
67
+ }
68
  for node_id, ws in list(connected_nodes.items()):
69
  if node_id != task.consumer_id:
70
  try:
71
+ await ws.send_json({"event": "rfc", "data": rfc_data})
72
  except:
73
  pass
74
 
75
  return {"status": "success", "task_id": task_id}
76
 
77
+ @app.post("/tasks/bid")
78
+ async def place_bid(bid: TaskBid):
79
+ task = active_tasks.get(bid.task_id)
80
+ if not task:
81
+ raise HTTPException(status_code=404, detail="Task not found or already completed")
82
+
83
+ if task["status"] != "bidding":
84
+ return {"status": "rejected", "detail": "Task already assigned to another node"}
85
+
86
+ # Phase 2 Fast Auction: Accept the first valid bid
87
+ task["status"] = "assigned"
88
+ task["provider_id"] = bid.provider_id
89
+
90
+ # Return the full payload to the winner
91
+ return {
92
+ "status": "accepted",
93
+ "payload": task["payload"],
94
+ "consumer_id": task["consumer_id"],
95
+ "model_requirement": task.get("model_requirement")
96
+ }
97
+
98
  @app.post("/tasks/complete")
99
  async def complete_task(result: TaskResult):
100
  task = active_tasks.get(result.task_id)
hub/models.py CHANGED
@@ -11,6 +11,11 @@ class TaskCreate(BaseModel):
11
  payload: str
12
  bounty: float
13
  target_node: Optional[str] = None # Direct messaging / specific bot targeting
 
 
 
 
 
14
 
15
  class TaskResult(BaseModel):
16
  task_id: str
 
11
  payload: str
12
  bounty: float
13
  target_node: Optional[str] = None # Direct messaging / specific bot targeting
14
+ model_requirement: Optional[str] = None
15
+
16
+ class TaskBid(BaseModel):
17
+ task_id: str
18
+ provider_id: str
19
 
20
  class TaskResult(BaseModel):
21
  task_id: str
node/mep_miner.py CHANGED
@@ -51,6 +51,8 @@ class MEPMiner:
51
 
52
  if data["event"] == "new_task":
53
  await self.process_task(data["data"])
 
 
54
 
55
  except asyncio.TimeoutError:
56
  continue # Keep connection alive
@@ -61,6 +63,44 @@ class MEPMiner:
61
  except Exception as e:
62
  print(f"[MEP Miner {self.node_id}] WebSocket error: {e}")
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  async def process_task(self, task_data: dict):
65
  """Process a task and earn SECONDS."""
66
  task_id = task_data["id"]
 
51
 
52
  if data["event"] == "new_task":
53
  await self.process_task(data["data"])
54
+ elif data["event"] == "rfc":
55
+ await self.handle_rfc(data["data"])
56
 
57
  except asyncio.TimeoutError:
58
  continue # Keep connection alive
 
63
  except Exception as e:
64
  print(f"[MEP Miner {self.node_id}] WebSocket error: {e}")
65
 
66
+ async def handle_rfc(self, rfc_data: dict):
67
+ """Phase 2: Evaluate Request For Compute and submit Bid."""
68
+ task_id = rfc_data["id"]
69
+ bounty = rfc_data["bounty"]
70
+ model = rfc_data.get("model_requirement")
71
+
72
+ # Simple evaluation logic
73
+ if bounty < 0.1 and bounty != 0.0: # Allow 0 for testing DM
74
+ print(f"[MEP Miner {self.node_id}] Ignored RFC {task_id[:8]} (Bounty too low: {bounty})")
75
+ return
76
+
77
+ print(f"[MEP Miner {self.node_id}] Received RFC {task_id[:8]} for {bounty:.6f} SECONDS. Placing bid...")
78
+
79
+ # Place bid
80
+ try:
81
+ resp = requests.post(f"{HUB_URL}/tasks/bid", json={
82
+ "task_id": task_id,
83
+ "provider_id": self.node_id
84
+ })
85
+
86
+ if resp.status_code == 200:
87
+ data = resp.json()
88
+ if data["status"] == "accepted":
89
+ print(f"[MEP Miner {self.node_id}] 🏁 BID WON for task {task_id[:8]}! Processing payload...")
90
+
91
+ # Reconstruct task_data to pass to process_task
92
+ task_data = {
93
+ "id": task_id,
94
+ "payload": data["payload"],
95
+ "bounty": bounty,
96
+ "consumer_id": data["consumer_id"]
97
+ }
98
+ await self.process_task(task_data)
99
+ else:
100
+ print(f"[MEP Miner {self.node_id}] Bid rejected (too slow): {data.get('detail', '')}")
101
+ except Exception as e:
102
+ print(f"[MEP Miner {self.node_id}] Error placing bid: {e}")
103
+
104
  async def process_task(self, task_data: dict):
105
  """Process a task and earn SECONDS."""
106
  task_id = task_data["id"]
node/test_auction.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import websockets
3
+ import json
4
+ import requests
5
+ import uuid
6
+
7
+ HUB_URL = "http://localhost:8000"
8
+
9
+ async def test():
10
+ miner = f'mep-miner-{uuid.uuid4().hex[:6]}'
11
+ requests.post(f'{HUB_URL}/register', json={'pubkey': miner})
12
+ async with websockets.connect(f'ws://localhost:8000/ws/{miner}') as ws:
13
+ consumer = 'test-consumer'
14
+ requests.post(f'{HUB_URL}/register', json={'pubkey': consumer})
15
+ requests.post(f'{HUB_URL}/tasks/submit', json={'consumer_id': consumer, 'payload': 'Test payload', 'bounty': 1.0})
16
+
17
+ msg = await asyncio.wait_for(ws.recv(), timeout=2.0)
18
+ data = json.loads(msg)
19
+ print('Received:', data)
20
+
21
+ if data['event'] == 'rfc':
22
+ task_id = data['data']['id']
23
+ resp = requests.post(f'{HUB_URL}/tasks/bid', json={'task_id': task_id, 'provider_id': miner})
24
+ print('Bid response:', resp.json())
25
+
26
+ complete_resp = requests.post(f'{HUB_URL}/tasks/complete', json={
27
+ 'task_id': task_id,
28
+ 'provider_id': miner,
29
+ 'result_payload': 'Done!'
30
+ })
31
+ print('Complete response:', complete_resp.json())
32
+
33
+ if __name__ == '__main__':
34
+ asyncio.run(test())