mirror of
https://github.com/Such-Software/xmrcheckout.git
synced 2026-10-17 05:51:34 +02:00
Cache Monero connectivity in system status
This commit is contained in:
@@ -261,6 +261,10 @@ def startup():
|
||||
"last_reconcile_started_at TIMESTAMP WITH TIME ZONE, "
|
||||
"last_reconcile_completed_at TIMESTAMP WITH TIME ZONE, "
|
||||
"last_reconcile_error VARCHAR, "
|
||||
"wallet_rpc VARCHAR, "
|
||||
"daemon VARCHAR, "
|
||||
"daemon_height INTEGER, "
|
||||
"checked_at TIMESTAMP WITH TIME ZONE, "
|
||||
"updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()"
|
||||
")"
|
||||
)
|
||||
@@ -283,6 +287,30 @@ def startup():
|
||||
"ADD COLUMN IF NOT EXISTS last_reconcile_error VARCHAR"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"ALTER TABLE system_status "
|
||||
"ADD COLUMN IF NOT EXISTS wallet_rpc VARCHAR"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"ALTER TABLE system_status "
|
||||
"ADD COLUMN IF NOT EXISTS daemon VARCHAR"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"ALTER TABLE system_status "
|
||||
"ADD COLUMN IF NOT EXISTS daemon_height INTEGER"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"ALTER TABLE system_status "
|
||||
"ADD COLUMN IF NOT EXISTS checked_at TIMESTAMP WITH TIME ZONE"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"ALTER TABLE system_status "
|
||||
@@ -295,6 +323,12 @@ def startup():
|
||||
"ON CONFLICT (name) DO NOTHING"
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"INSERT INTO system_status (name) VALUES ('monero_connectivity') "
|
||||
"ON CONFLICT (name) DO NOTHING"
|
||||
)
|
||||
)
|
||||
finally:
|
||||
if lock_acquired:
|
||||
connection.execute(
|
||||
|
||||
@@ -152,4 +152,8 @@ class SystemStatus(Base):
|
||||
last_reconcile_started_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_reconcile_completed_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_reconcile_error = Column(String, nullable=True)
|
||||
wallet_rpc = Column(String, nullable=True)
|
||||
daemon = Column(String, nullable=True)
|
||||
daemon_height = Column(Integer, nullable=True)
|
||||
checked_at = Column(DateTime(timezone=True), nullable=True)
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
+60
-1
@@ -17,6 +17,7 @@ from .monero_service import MoneroWalletService, TransferDetail
|
||||
from .webhooks import dispatch_webhooks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
MONERO_CONNECTIVITY_STATUS_NAME = "monero_connectivity"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -27,11 +28,12 @@ def main() -> None:
|
||||
level_name = "INFO"
|
||||
level = getattr(logging, level_name.upper(), logging.INFO)
|
||||
logging.basicConfig(level=level)
|
||||
service = MoneroWalletService()
|
||||
while True:
|
||||
status_db: Session | None = None
|
||||
try:
|
||||
status_db = SessionLocal()
|
||||
service = MoneroWalletService()
|
||||
_safe_update_monero_connectivity_status(status_db, service)
|
||||
_safe_update_reconciler_status(
|
||||
status_db,
|
||||
started_at=datetime.now(timezone.utc),
|
||||
@@ -48,6 +50,7 @@ def main() -> None:
|
||||
logger.exception("Invoice reconcile failed: %s", exc)
|
||||
if status_db is None:
|
||||
status_db = SessionLocal()
|
||||
_safe_update_monero_connectivity_error(status_db)
|
||||
_safe_update_reconciler_status(
|
||||
status_db,
|
||||
error_message=str(exc),
|
||||
@@ -294,6 +297,62 @@ def _update_reconciler_status(
|
||||
db.commit()
|
||||
|
||||
|
||||
def _update_monero_connectivity_status(
|
||||
db: Session,
|
||||
*,
|
||||
wallet_rpc: str,
|
||||
daemon: str,
|
||||
daemon_height: int | None,
|
||||
) -> None:
|
||||
status_row = (
|
||||
db.query(SystemStatus)
|
||||
.filter(SystemStatus.name == MONERO_CONNECTIVITY_STATUS_NAME)
|
||||
.first()
|
||||
)
|
||||
if status_row is None:
|
||||
status_row = SystemStatus(name=MONERO_CONNECTIVITY_STATUS_NAME)
|
||||
status_row.wallet_rpc = wallet_rpc
|
||||
status_row.daemon = daemon
|
||||
status_row.daemon_height = daemon_height
|
||||
status_row.checked_at = datetime.now(timezone.utc)
|
||||
db.add(status_row)
|
||||
db.commit()
|
||||
|
||||
|
||||
def _safe_update_monero_connectivity_status(
|
||||
db: Session,
|
||||
service: MoneroWalletService,
|
||||
) -> None:
|
||||
try:
|
||||
status_payload = service.get_status()
|
||||
_update_monero_connectivity_status(
|
||||
db,
|
||||
wallet_rpc=str(status_payload.get("wallet_rpc", "unreachable")),
|
||||
daemon=str(status_payload.get("daemon", "unknown")),
|
||||
daemon_height=(
|
||||
int(status_payload["daemon_height"])
|
||||
if isinstance(status_payload.get("daemon_height"), int)
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
logger.warning("Unable to persist Monero connectivity status", exc_info=True)
|
||||
|
||||
|
||||
def _safe_update_monero_connectivity_error(db: Session) -> None:
|
||||
try:
|
||||
_update_monero_connectivity_status(
|
||||
db,
|
||||
wallet_rpc="unreachable",
|
||||
daemon="unknown",
|
||||
daemon_height=None,
|
||||
)
|
||||
except SQLAlchemyError:
|
||||
db.rollback()
|
||||
logger.warning("Unable to persist Monero connectivity fallback", exc_info=True)
|
||||
|
||||
|
||||
def _safe_update_reconciler_status(
|
||||
db: Session,
|
||||
*,
|
||||
|
||||
+46
-26
@@ -60,8 +60,6 @@ from .security import (
|
||||
)
|
||||
from .webhooks import build_webhook_payload, dispatch_webhooks
|
||||
from .qr_codes import ensure_invoice_qr_png, invoice_qr_url, resolve_qr_settings
|
||||
from .monero_service import MoneroWalletService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
WEBHOOK_EVENTS = (
|
||||
@@ -80,12 +78,57 @@ def _require_donations_enabled() -> None:
|
||||
)
|
||||
|
||||
MAX_QR_LOGO_DATA_URL_LENGTH = 120_000
|
||||
MONERO_CONNECTIVITY_STATUS_NAME = "monero_connectivity"
|
||||
|
||||
|
||||
def _get_user_for_api_key(db: Session, api_key: str) -> User | None:
|
||||
return db.query(User).filter(User.api_key_hash == hash_api_key(api_key)).first()
|
||||
|
||||
|
||||
def _status_response_from_rows(
|
||||
monero_status: SystemStatus | None,
|
||||
reconciler_status: SystemStatus | None,
|
||||
) -> SystemStatusResponse:
|
||||
wallet_rpc = (
|
||||
str(monero_status.wallet_rpc)
|
||||
if monero_status and monero_status.wallet_rpc
|
||||
else "unreachable"
|
||||
)
|
||||
daemon = (
|
||||
str(monero_status.daemon)
|
||||
if monero_status and monero_status.daemon
|
||||
else "unknown"
|
||||
)
|
||||
daemon_height = monero_status.daemon_height if monero_status else None
|
||||
return SystemStatusResponse(
|
||||
wallet_rpc=wallet_rpc,
|
||||
daemon=daemon,
|
||||
daemon_height=daemon_height,
|
||||
invoice_reconcile_interval_seconds=INVOICE_RECONCILE_INTERVAL_SECONDS,
|
||||
last_reconcile_started_at=(
|
||||
reconciler_status.last_reconcile_started_at if reconciler_status else None
|
||||
),
|
||||
last_reconcile_completed_at=(
|
||||
reconciler_status.last_reconcile_completed_at if reconciler_status else None
|
||||
),
|
||||
last_reconcile_error=(
|
||||
reconciler_status.last_reconcile_error if reconciler_status else None
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _load_public_system_status(db: Session) -> SystemStatusResponse:
|
||||
reconciler_status = (
|
||||
db.query(SystemStatus).filter(SystemStatus.name == "reconciler").first()
|
||||
)
|
||||
monero_status = (
|
||||
db.query(SystemStatus)
|
||||
.filter(SystemStatus.name == MONERO_CONNECTIVITY_STATUS_NAME)
|
||||
.first()
|
||||
)
|
||||
return _status_response_from_rows(monero_status, reconciler_status)
|
||||
|
||||
|
||||
def _get_founder_user(db: Session) -> User:
|
||||
if not FOUNDER_PAYMENT_ADDRESS or not FOUNDER_VIEW_KEY:
|
||||
raise HTTPException(
|
||||
@@ -541,30 +584,7 @@ def get_donation_status(
|
||||
|
||||
@router.get("/api/core/public/system/status", response_model=SystemStatusResponse)
|
||||
def get_public_system_status(db: Session = Depends(get_db)):
|
||||
service = MoneroWalletService()
|
||||
status_payload = service.get_status()
|
||||
reconciler_status = (
|
||||
db.query(SystemStatus).filter(SystemStatus.name == "reconciler").first()
|
||||
)
|
||||
return SystemStatusResponse(
|
||||
wallet_rpc=str(status_payload.get("wallet_rpc", "unreachable")),
|
||||
daemon=str(status_payload.get("daemon", "unknown")),
|
||||
daemon_height=(
|
||||
int(status_payload["daemon_height"])
|
||||
if isinstance(status_payload.get("daemon_height"), int)
|
||||
else None
|
||||
),
|
||||
invoice_reconcile_interval_seconds=INVOICE_RECONCILE_INTERVAL_SECONDS,
|
||||
last_reconcile_started_at=(
|
||||
reconciler_status.last_reconcile_started_at if reconciler_status else None
|
||||
),
|
||||
last_reconcile_completed_at=(
|
||||
reconciler_status.last_reconcile_completed_at if reconciler_status else None
|
||||
),
|
||||
last_reconcile_error=(
|
||||
reconciler_status.last_reconcile_error if reconciler_status else None
|
||||
),
|
||||
)
|
||||
return _load_public_system_status(db)
|
||||
|
||||
|
||||
@router.post("/api/core/webhooks", response_model=WebhookResponse)
|
||||
|
||||
Reference in New Issue
Block a user