feat: add blockchain RPC blocks-range endpoint and marketplace bid listing
Blockchain Node: - Replace /blocks (pagination) with /blocks-range (height range query) - Add start/end height parameters with 1000-block max range validation - Return blocks in ascending height order instead of descending - Update metrics names (rpc_get_blocks_range_*) - Remove total count from response (return start/end/count instead) Coordinator API: - Add effective_url property to DatabaseConfig (SQLite/PostgreSQL defaults
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
from collections import defaultdict, deque
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
@@ -38,25 +39,51 @@ class ExplorerService:
|
||||
self.session = session
|
||||
|
||||
def list_blocks(self, *, limit: int = 20, offset: int = 0) -> BlockListResponse:
|
||||
statement = select(Job).order_by(Job.requested_at.desc())
|
||||
jobs = self.session.exec(statement.offset(offset).limit(limit)).all()
|
||||
# Fetch real blockchain data from RPC API
|
||||
try:
|
||||
# Use the blockchain RPC API running on localhost:8082
|
||||
with httpx.Client(timeout=10.0) as client:
|
||||
response = client.get("http://localhost:8082/rpc/blocks", params={"limit": limit, "offset": offset})
|
||||
response.raise_for_status()
|
||||
rpc_data = response.json()
|
||||
|
||||
items: list[BlockSummary] = []
|
||||
for block in rpc_data.get("blocks", []):
|
||||
items.append(
|
||||
BlockSummary(
|
||||
height=block["height"],
|
||||
hash=block["hash"],
|
||||
timestamp=datetime.fromisoformat(block["timestamp"]),
|
||||
txCount=block["tx_count"],
|
||||
proposer=block["proposer"],
|
||||
)
|
||||
)
|
||||
|
||||
next_offset: Optional[int] = offset + len(items) if len(items) == limit else None
|
||||
return BlockListResponse(items=items, next_offset=next_offset)
|
||||
|
||||
except Exception as e:
|
||||
# Fallback to fake data if RPC is unavailable
|
||||
print(f"Warning: Failed to fetch blocks from RPC: {e}, falling back to fake data")
|
||||
statement = select(Job).order_by(Job.requested_at.desc())
|
||||
jobs = self.session.exec(statement.offset(offset).limit(limit)).all()
|
||||
|
||||
items: list[BlockSummary] = []
|
||||
for index, job in enumerate(jobs):
|
||||
height = _DEFAULT_HEIGHT_BASE + offset + index
|
||||
proposer = job.assigned_miner_id or "unassigned"
|
||||
items.append(
|
||||
BlockSummary(
|
||||
height=height,
|
||||
hash=job.id,
|
||||
timestamp=job.requested_at,
|
||||
txCount=1,
|
||||
proposer=proposer,
|
||||
items: list[BlockSummary] = []
|
||||
for index, job in enumerate(jobs):
|
||||
height = _DEFAULT_HEIGHT_BASE + offset + index
|
||||
proposer = job.assigned_miner_id or "unassigned"
|
||||
items.append(
|
||||
BlockSummary(
|
||||
height=height,
|
||||
hash=job.id,
|
||||
timestamp=job.requested_at,
|
||||
txCount=1,
|
||||
proposer=proposer,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
next_offset: Optional[int] = offset + len(items) if len(items) == limit else None
|
||||
return BlockListResponse(items=items, next_offset=next_offset)
|
||||
next_offset: Optional[int] = offset + len(items) if len(items) == limit else None
|
||||
return BlockListResponse(items=items, next_offset=next_offset)
|
||||
|
||||
def list_transactions(self, *, limit: int = 50, offset: int = 0) -> TransactionListResponse:
|
||||
statement = (
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..schemas import (
|
||||
MarketplaceBidRequest,
|
||||
MarketplaceOfferView,
|
||||
MarketplaceStatsView,
|
||||
MarketplaceBidView,
|
||||
)
|
||||
|
||||
|
||||
@@ -70,6 +71,47 @@ class MarketplaceService:
|
||||
self.session.refresh(bid)
|
||||
return bid
|
||||
|
||||
def list_bids(
|
||||
self,
|
||||
*,
|
||||
status: Optional[str] = None,
|
||||
provider: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> list[MarketplaceBidView]:
|
||||
stmt = select(MarketplaceBid).order_by(MarketplaceBid.submitted_at.desc())
|
||||
|
||||
if status is not None:
|
||||
normalised = status.strip().lower()
|
||||
if normalised not in ("pending", "accepted", "rejected"):
|
||||
raise ValueError(f"invalid status: {status}")
|
||||
stmt = stmt.where(MarketplaceBid.status == normalised)
|
||||
|
||||
if provider is not None:
|
||||
stmt = stmt.where(MarketplaceBid.provider == provider)
|
||||
|
||||
stmt = stmt.offset(offset).limit(limit)
|
||||
bids = self.session.exec(stmt).all()
|
||||
return [self._to_bid_view(bid) for bid in bids]
|
||||
|
||||
def get_bid(self, bid_id: str) -> Optional[MarketplaceBidView]:
|
||||
bid = self.session.get(MarketplaceBid, bid_id)
|
||||
if bid:
|
||||
return self._to_bid_view(bid)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _to_bid_view(bid: MarketplaceBid) -> MarketplaceBidView:
|
||||
return MarketplaceBidView(
|
||||
id=bid.id,
|
||||
provider=bid.provider,
|
||||
capacity=bid.capacity,
|
||||
price=bid.price,
|
||||
notes=bid.notes,
|
||||
status=bid.status,
|
||||
submitted_at=bid.submitted_at,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _to_offer_view(offer: MarketplaceOffer) -> MarketplaceOfferView:
|
||||
status_val = offer.status.value if hasattr(offer.status, "value") else offer.status
|
||||
|
||||
Reference in New Issue
Block a user