```
chore: enhance .gitignore and remove obsolete documentation files - Reorganize .gitignore with categorized sections for better maintainability - Add comprehensive ignore patterns for Python, Node.js, databases, logs, and build artifacts - Add project-specific ignore rules for coordinator, explorer, and deployment files - Remove outdated documentation: BITCOIN-WALLET-SETUP.md, LOCAL_ASSETS_SUMMARY.md, README-CONTAINER-DEPLOYMENT.md, README-DOMAIN-DEPLOYMENT.md ```
This commit is contained in:
@@ -50,7 +50,7 @@ class ExplorerService:
|
||||
height=height,
|
||||
hash=job.id,
|
||||
timestamp=job.requested_at,
|
||||
tx_count=1,
|
||||
txCount=1,
|
||||
proposer=proposer,
|
||||
)
|
||||
)
|
||||
@@ -71,13 +71,22 @@ class ExplorerService:
|
||||
for index, job in enumerate(jobs):
|
||||
height = _DEFAULT_HEIGHT_BASE + offset + index
|
||||
status_label = _STATUS_LABELS.get(job.state, job.state.value.title())
|
||||
value = job.payload.get("value") if isinstance(job.payload, dict) else None
|
||||
if value is None:
|
||||
value_str = "0"
|
||||
elif isinstance(value, (int, float)):
|
||||
value_str = f"{value}"
|
||||
else:
|
||||
value_str = str(value)
|
||||
|
||||
# Try to get payment amount from receipt
|
||||
value_str = "0"
|
||||
if job.receipt and isinstance(job.receipt, dict):
|
||||
price = job.receipt.get("price")
|
||||
if price is not None:
|
||||
value_str = f"{price}"
|
||||
|
||||
# Fallback to payload value if no receipt
|
||||
if value_str == "0":
|
||||
value = job.payload.get("value") if isinstance(job.payload, dict) else None
|
||||
if value is not None:
|
||||
if isinstance(value, (int, float)):
|
||||
value_str = f"{value}"
|
||||
else:
|
||||
value_str = str(value)
|
||||
|
||||
items.append(
|
||||
TransactionSummary(
|
||||
@@ -100,14 +109,16 @@ class ExplorerService:
|
||||
address_map: dict[str, dict[str, object]] = defaultdict(
|
||||
lambda: {
|
||||
"address": "",
|
||||
"balance": "0",
|
||||
"balance": 0.0,
|
||||
"tx_count": 0,
|
||||
"last_active": datetime.min,
|
||||
"recent_transactions": deque(maxlen=5),
|
||||
"earned": 0.0,
|
||||
"spent": 0.0,
|
||||
}
|
||||
)
|
||||
|
||||
def touch(address: Optional[str], tx_id: str, when: datetime, value_hint: Optional[str] = None) -> None:
|
||||
def touch(address: Optional[str], tx_id: str, when: datetime, earned: float = 0.0, spent: float = 0.0) -> None:
|
||||
if not address:
|
||||
return
|
||||
entry = address_map[address]
|
||||
@@ -115,18 +126,27 @@ class ExplorerService:
|
||||
entry["tx_count"] = int(entry["tx_count"]) + 1
|
||||
if when > entry["last_active"]:
|
||||
entry["last_active"] = when
|
||||
if value_hint:
|
||||
entry["balance"] = value_hint
|
||||
# Track earnings and spending
|
||||
entry["earned"] = float(entry["earned"]) + earned
|
||||
entry["spent"] = float(entry["spent"]) + spent
|
||||
entry["balance"] = float(entry["earned"]) - float(entry["spent"])
|
||||
recent: deque[str] = entry["recent_transactions"] # type: ignore[assignment]
|
||||
recent.appendleft(tx_id)
|
||||
|
||||
for job in jobs:
|
||||
value = job.payload.get("value") if isinstance(job.payload, dict) else None
|
||||
value_hint: Optional[str] = None
|
||||
if value is not None:
|
||||
value_hint = str(value)
|
||||
touch(job.client_id, job.id, job.requested_at, value_hint=value_hint)
|
||||
touch(job.assigned_miner_id, job.id, job.requested_at)
|
||||
# Get payment amount from receipt if available
|
||||
price = 0.0
|
||||
if job.receipt and isinstance(job.receipt, dict):
|
||||
receipt_price = job.receipt.get("price")
|
||||
if receipt_price is not None:
|
||||
try:
|
||||
price = float(receipt_price)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
# Miner earns, client spends
|
||||
touch(job.assigned_miner_id, job.id, job.requested_at, earned=price)
|
||||
touch(job.client_id, job.id, job.requested_at, spent=price)
|
||||
|
||||
sorted_addresses = sorted(
|
||||
address_map.values(),
|
||||
@@ -138,7 +158,7 @@ class ExplorerService:
|
||||
items = [
|
||||
AddressSummary(
|
||||
address=entry["address"],
|
||||
balance=str(entry["balance"]),
|
||||
balance=f"{float(entry['balance']):.6f}",
|
||||
txCount=int(entry["tx_count"]),
|
||||
lastActive=entry["last_active"],
|
||||
recentTransactions=list(entry["recent_transactions"]),
|
||||
@@ -164,19 +184,24 @@ class ExplorerService:
|
||||
items: list[ReceiptSummary] = []
|
||||
for row in rows:
|
||||
payload = row.payload or {}
|
||||
miner = payload.get("miner") or payload.get("miner_id") or "unknown"
|
||||
coordinator = payload.get("coordinator") or payload.get("coordinator_id") or "unknown"
|
||||
# Extract miner from provider field (receipt format) or fallback
|
||||
miner = payload.get("provider") or payload.get("miner") or payload.get("miner_id") or "unknown"
|
||||
# Extract client as coordinator (receipt format) or fallback
|
||||
coordinator = payload.get("client") or payload.get("coordinator") or payload.get("coordinator_id") or "unknown"
|
||||
status = payload.get("status") or payload.get("state") or "Unknown"
|
||||
# Get job_id from payload
|
||||
job_id_from_payload = payload.get("job_id") or row.job_id
|
||||
items.append(
|
||||
ReceiptSummary(
|
||||
receipt_id=row.receipt_id,
|
||||
receiptId=row.receipt_id,
|
||||
miner=miner,
|
||||
coordinator=coordinator,
|
||||
issued_at=row.created_at,
|
||||
issuedAt=row.created_at,
|
||||
status=status,
|
||||
payload=payload,
|
||||
jobId=job_id_from_payload,
|
||||
)
|
||||
)
|
||||
|
||||
resolved_job_id = job_id or "all"
|
||||
return ReceiptListResponse(job_id=resolved_job_id, items=items)
|
||||
return ReceiptListResponse(jobId=resolved_job_id, items=items)
|
||||
|
||||
@@ -101,7 +101,7 @@ class JobService:
|
||||
return None
|
||||
|
||||
def _ensure_not_expired(self, job: Job) -> Job:
|
||||
if job.state == JobState.queued and job.expires_at <= datetime.utcnow():
|
||||
if job.state in {JobState.queued, JobState.running} and job.expires_at <= datetime.utcnow():
|
||||
job.state = JobState.expired
|
||||
job.error = "job expired"
|
||||
self.session.add(job)
|
||||
|
||||
@@ -32,6 +32,7 @@ class MinerService:
|
||||
miner.concurrency = payload.concurrency
|
||||
miner.region = payload.region
|
||||
miner.session_token = session_token
|
||||
miner.inflight = 0
|
||||
miner.last_heartbeat = datetime.utcnow()
|
||||
miner.status = "ONLINE"
|
||||
self.session.commit()
|
||||
|
||||
@@ -35,24 +35,60 @@ class ReceiptService:
|
||||
) -> Dict[str, Any] | None:
|
||||
if self._signer is None:
|
||||
return None
|
||||
metrics = result_metrics or {}
|
||||
result_payload = job_result or {}
|
||||
unit_type = _first_present([
|
||||
metrics.get("unit_type"),
|
||||
result_payload.get("unit_type"),
|
||||
], default="gpu_seconds")
|
||||
|
||||
units = _coerce_float(_first_present([
|
||||
metrics.get("units"),
|
||||
result_payload.get("units"),
|
||||
]))
|
||||
if units is None:
|
||||
duration_ms = _coerce_float(metrics.get("duration_ms"))
|
||||
if duration_ms is not None:
|
||||
units = duration_ms / 1000.0
|
||||
else:
|
||||
duration_seconds = _coerce_float(_first_present([
|
||||
metrics.get("duration_seconds"),
|
||||
metrics.get("compute_time"),
|
||||
result_payload.get("execution_time"),
|
||||
result_payload.get("duration"),
|
||||
]))
|
||||
units = duration_seconds
|
||||
if units is None:
|
||||
units = 0.0
|
||||
|
||||
unit_price = _coerce_float(_first_present([
|
||||
metrics.get("unit_price"),
|
||||
result_payload.get("unit_price"),
|
||||
]))
|
||||
if unit_price is None:
|
||||
unit_price = 0.02
|
||||
|
||||
price = _coerce_float(_first_present([
|
||||
metrics.get("price"),
|
||||
result_payload.get("price"),
|
||||
metrics.get("aitbc_earned"),
|
||||
result_payload.get("aitbc_earned"),
|
||||
metrics.get("cost"),
|
||||
result_payload.get("cost"),
|
||||
]))
|
||||
if price is None:
|
||||
price = round(units * unit_price, 6)
|
||||
payload = {
|
||||
"version": "1.0",
|
||||
"receipt_id": token_hex(16),
|
||||
"job_id": job.id,
|
||||
"provider": miner_id,
|
||||
"client": job.client_id,
|
||||
"units": _first_present([
|
||||
(result_metrics or {}).get("units"),
|
||||
(job_result or {}).get("units"),
|
||||
], default=0.0),
|
||||
"unit_type": _first_present([
|
||||
(result_metrics or {}).get("unit_type"),
|
||||
(job_result or {}).get("unit_type"),
|
||||
], default="gpu_seconds"),
|
||||
"price": _first_present([
|
||||
(result_metrics or {}).get("price"),
|
||||
(job_result or {}).get("price"),
|
||||
]),
|
||||
"status": job.state.value,
|
||||
"units": units,
|
||||
"unit_type": unit_type,
|
||||
"unit_price": unit_price,
|
||||
"price": price,
|
||||
"started_at": int(job.requested_at.timestamp()) if job.requested_at else int(datetime.utcnow().timestamp()),
|
||||
"completed_at": int(datetime.utcnow().timestamp()),
|
||||
"metadata": {
|
||||
@@ -105,3 +141,13 @@ def _first_present(values: list[Optional[Any]], default: Optional[Any] = None) -
|
||||
if value is not None:
|
||||
return value
|
||||
return default
|
||||
|
||||
|
||||
def _coerce_float(value: Any) -> Optional[float]:
|
||||
"""Coerce a value to float, returning None if not possible"""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user