Add temporary read-only debug route for crypto wallet scanning issue

TEMPORARY: /crypto/debug/wallet-scan endpoint to diagnose why late payment
scanner shows 'No transfers found' while wallet actually has transactions.

READ-ONLY route that:
- Shows crypto processor count and scan positions
- Tests wallet RPC with same parameters as scanner
- Compares transfer counts to identify if issue is RPC or scanner logic
- Masks sensitive data (shop IDs, amounts, addresses)

TODO: Comment out this route when debugging is complete.
This commit is contained in:
Russell Ballestrini 2025-09-28 15:53:35 -04:00
parent b776d1cd7e
commit 6afd6764d9
2 changed files with 98 additions and 0 deletions

View file

@ -155,3 +155,4 @@ def includeme(config):
config.add_route("crypto_quote", "/crypto/quote/{payment_id}")
config.add_route("crypto_cancel", "/crypto/cancel/{payment_id}")
config.add_route("crypto_quotes_history", "/u/crypto-quotes")
config.add_route("crypto_debug_wallet_scan", "/crypto/debug/wallet-scan")

View file

@ -1154,3 +1154,100 @@ def get_payment_status_info(status):
"no-refund-complete": {"label": "✓ No Refund Possible", "color": "#dc3545"},
}
return status_mapping.get(status, {"label": status.title(), "color": "#6c757d"})
# TODO: COMMENT OUT WHEN DEBUGGING COMPLETE - TEMPORARY PRODUCTION DEBUG ROUTE
@view_config(
route_name="crypto_debug_wallet_scan",
request_method="GET",
renderer="string",
)
def crypto_debug_wallet_scan(request):
"""READ-ONLY debug route to diagnose wallet scanning issue - NO SECRETS EXPOSED"""
try:
from ..lib.crypto_watcher import get_crypto_client
from ..models.crypto_processor import CryptoProcessor
settings = request.registry.settings
db = request.dbsession
debug_output = []
debug_output.append("=== CRYPTO WALLET SCAN DEBUG (READ-ONLY) ===\n")
# Check crypto processors
processors = (
db.query(CryptoProcessor).filter(CryptoProcessor.enabled == True).all()
)
debug_output.append(f"Active processors: {len(processors)}")
for processor in processors:
# Show processor info but mask shop IDs for security
shop_id_masked = (
processor.shop_id[:8] + "***" if len(processor.shop_id) > 8 else "***"
)
debug_output.append(
f" - {processor.coin_type} processor (shop {shop_id_masked})"
)
debug_output.append(f" Scan semaphore: {processor.last_scan_semaphore}")
debug_output.append("")
# Test XMR wallet RPC (READ-ONLY)
if any(p.coin_type == "XMR" for p in processors):
debug_output.append("=== TESTING XMR WALLET RPC (READ-ONLY) ===")
try:
client = get_crypto_client(settings, "XMR")
debug_output.append("✓ XMR client created successfully")
# Test the exact same call as the scanner
query_params = {
"in": True,
"out": False,
"pending": True,
"failed": False,
"pool": True,
}
debug_output.append(f"Query params: {query_params}")
result = client._call("get_transfers", query_params)
debug_output.append(
f"RPC result keys: {list(result.keys()) if result else 'None'}"
)
total_transfers = 0
for transfer_type in ["in", "pending", "pool"]:
if transfer_type in result:
transfers = result[transfer_type]
debug_output.append(
f" {transfer_type}: {len(transfers)} transfers"
)
total_transfers += len(transfers)
# Show minimal transfer details (no addresses/amounts for security)
for i, tx in enumerate(transfers[:3]):
subaddr = tx.get("subaddr_index", {})
debug_output.append(
f" Transfer {i+1}: height={tx.get('height')}, "
f"subaddr={subaddr.get('major', 0)}.{subaddr.get('minor')}, "
f"confirmations={tx.get('confirmations', 0)}"
)
debug_output.append(f"Total transfers found: {total_transfers}")
except Exception as e:
debug_output.append(f"❌ XMR wallet RPC error: {e}")
debug_output.append("\n=== DIAGNOSIS ===")
debug_output.append(
"If transfers found > 0 but scanner logs show 'No transfers', there's a bug in scanner logic"
)
debug_output.append(
"If transfers found = 0, wallet has no transaction history or RPC issue"
)
debug_output.append("\n=== DEBUG COMPLETE ===")
return Response("\n".join(debug_output), content_type="text/plain")
except Exception as e:
return Response(f"Debug error: {e}", content_type="text/plain", status=500)