Based on the repository's commit message style and the changes in the diff, here's an appropriate commit message:

```
feat: add websocket tests, PoA metrics, marketplace endpoints, and enhanced observability

- Add comprehensive websocket tests for blocks and transactions streams including multi-subscriber and high-volume scenarios
- Extend PoA consensus with per-proposer block metrics and rotation tracking
- Add latest block interval gauge and RPC error spike alerting
- Enhance mock coordinator
This commit is contained in:
oib
2025-12-22 07:55:09 +01:00
parent b8b640666d
commit fa5a6fddf3
70 changed files with 3472 additions and 246 deletions

View File

@@ -1 +1,9 @@
"""Router modules for the coordinator API."""
from .client import router as client
from .miner import router as miner
from .admin import router as admin
from .marketplace import router as marketplace
from .explorer import router as explorer
__all__ = ["client", "miner", "admin", "marketplace", "explorer"]

View File

@@ -0,0 +1,63 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from ..models import (
BlockListResponse,
TransactionListResponse,
AddressListResponse,
ReceiptListResponse,
)
from ..services import ExplorerService
from ..storage import SessionDep
router = APIRouter(prefix="/explorer", tags=["explorer"])
def _service(session: SessionDep) -> ExplorerService:
return ExplorerService(session)
@router.get("/blocks", response_model=BlockListResponse, summary="List recent blocks")
async def list_blocks(
*,
session: SessionDep,
limit: int = Query(default=20, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> BlockListResponse:
return _service(session).list_blocks(limit=limit, offset=offset)
@router.get(
"/transactions",
response_model=TransactionListResponse,
summary="List recent transactions",
)
async def list_transactions(
*,
session: SessionDep,
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> TransactionListResponse:
return _service(session).list_transactions(limit=limit, offset=offset)
@router.get("/addresses", response_model=AddressListResponse, summary="List address summaries")
async def list_addresses(
*,
session: SessionDep,
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> AddressListResponse:
return _service(session).list_addresses(limit=limit, offset=offset)
@router.get("/receipts", response_model=ReceiptListResponse, summary="List job receipts")
async def list_receipts(
*,
session: SessionDep,
job_id: str | None = Query(default=None, description="Filter by job identifier"),
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> ReceiptListResponse:
return _service(session).list_receipts(job_id=job_id, limit=limit, offset=offset)

View File

@@ -0,0 +1,57 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import status as http_status
from ..models import MarketplaceBidRequest, MarketplaceOfferView, MarketplaceStatsView
from ..services import MarketplaceService
from ..storage import SessionDep
router = APIRouter(tags=["marketplace"])
def _get_service(session: SessionDep) -> MarketplaceService:
return MarketplaceService(session)
@router.get(
"/marketplace/offers",
response_model=list[MarketplaceOfferView],
summary="List marketplace offers",
)
async def list_marketplace_offers(
*,
session: SessionDep,
status_filter: str | None = Query(default=None, alias="status", description="Filter by offer status"),
limit: int = Query(default=100, ge=1, le=500),
offset: int = Query(default=0, ge=0),
) -> list[MarketplaceOfferView]:
service = _get_service(session)
try:
return service.list_offers(status=status_filter, limit=limit, offset=offset)
except ValueError:
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail="invalid status filter") from None
@router.get(
"/marketplace/stats",
response_model=MarketplaceStatsView,
summary="Get marketplace summary statistics",
)
async def get_marketplace_stats(*, session: SessionDep) -> MarketplaceStatsView:
service = _get_service(session)
return service.get_stats()
@router.post(
"/marketplace/bids",
status_code=http_status.HTTP_202_ACCEPTED,
summary="Submit a marketplace bid",
)
async def submit_marketplace_bid(
payload: MarketplaceBidRequest,
session: SessionDep,
) -> dict[str, str]:
service = _get_service(session)
bid = service.create_bid(payload)
return {"id": bid.id}