Skip to content

Commit eacb84b

Browse files
committed
fix: LND Monitoring bugfix (Invoice value & Channel alias)
1 parent e610895 commit eacb84b

File tree

1 file changed

+80
-67
lines changed

1 file changed

+80
-67
lines changed

nodesentinel.py

Lines changed: 80 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ async def run_remote_command(command, timeout=DEFAULT_SSH_TIMEOUT):
209209
logger.error(f"SSH Error (code {e.returncode}): {err_msg}")
210210
return None, f"⚠️ SSH Error (code {e.returncode}): {err_msg}"
211211
except subprocess.TimeoutExpired:
212-
logger.error("Remote SSH Timeout.")
213212
return None, "⚠️ Remote SSH Timeout (node unresponsive)"
214213
except Exception as e:
215214
logger.error(f"Generic SSH error: {e}")
@@ -529,8 +528,13 @@ async def monitor_lnd_task(app):
529528
newch = cur_ch - prev_channels
530529
closed = prev_channels - cur_ch
531530
for cp in newch:
531+
# CORREZIONE: Recupera l'alias del partner del canale
532+
remote_pubkey = next((c.remote_pubkey for c in channels if c.channel_point == cp), None)
533+
alias = get_alias_for_pubkey(client, remote_pubkey) if remote_pubkey else None
534+
display_name = alias or remote_pubkey[:10] + '...'
535+
532536
logger.info(f"Channel Opened: {cp}")
533-
await send_alert(app, f"🔔 Channel Opened: **{cp}**")
537+
await send_alert(app, f"🔔 Channel Opened with **{display_name}**")
534538
for cp in closed:
535539
logger.info(f"Channel Closed: {cp}")
536540
await send_alert(app, f"🔕 Channel Closed: **{cp}**")
@@ -542,15 +546,25 @@ async def monitor_lnd_task(app):
542546
try:
543547
invoices = client.list_invoices().invoices
544548
cur_settled = set()
549+
# CORREZIONE: Ottenere il valore in sats e formattare l'alert
545550
for inv in invoices:
546551
settled = getattr(inv, "settled", False)
547552
if settled:
548-
r = getattr(inv, "r_hash_str", None) or str(getattr(inv, "add_index", ""))
549-
cur_settled.add(r)
550-
new_settled = cur_settled - prev_settled_invoices
551-
for r in new_settled:
552-
logger.info(f"Invoice Settled: {r}")
553-
await send_alert(app, f"💰 Invoice Settled: **{r}**")
553+
# Usiamo r_hash_str come chiave per lo stato
554+
r_hash = getattr(inv, "r_hash_str", None) or str(getattr(inv, "add_index", ""))
555+
value_sats = getattr(inv, "value", 0)
556+
cur_settled.add((r_hash, value_sats))
557+
558+
# Calcola le nuove fatture saldate
559+
new_settled = [ (rh, val) for rh, val in cur_settled if rh not in set(rh_prev for rh_prev, val_prev in prev_settled_invoices) ]
560+
removed_settled = [ (rh, val) for rh, val in prev_settled_invoices if rh not in set(rh_curr for rh_curr, val_curr in cur_settled) ]
561+
562+
for rh, value_sats in new_settled:
563+
logger.info(f"Invoice Settled: {rh} for {value_sats} sats")
564+
# INVIO ALERT CON VALORE CORRETTO E TRADOTTO
565+
await send_alert(app, f"💰 Invoice Settled: **{value_sats:,} sats**")
566+
567+
# Aggiorna lo stato dei pagamenti
554568
prev_settled_invoices = cur_settled
555569
except Exception as e:
556570
await send_alert(app, f"⚠️ LND invoices error: {e}")
@@ -691,25 +705,25 @@ async def restartlnd_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
691705
await update.message.reply_text(final_msg, parse_mode='Markdown')
692706

693707
async def mempool_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
694-
logger.info(f"Command /mempool received from {update.effective_user.id}")
708+
logger.info(f"Comando /mempool ricevuto da {update.effective_user.id}")
695709
fees, error = await crypto_utils.get_fee_data()
696710

697711
if error:
698712
await update.message.reply_text(error)
699713
return
700714

701715
report = (
702-
f"⛽ Mempool Fees (sat/vB):\n"
703-
f"- Fast (Next block): **{fees.get('fastestFee', 'n/a')}**\n"
704-
f"- Medium (30 min): **{fees.get('halfHourFee', 'n/a')}**\n"
705-
f"- Low (1 hr): **{fees.get('hourFee', 'n/a')}**"
716+
f"⛽ Tariffe Mempool Attuali (sat/vB):\n"
717+
f"- Veloce (Next block): **{fees.get('fastestFee', 'n/d')}**\n"
718+
f"- Media (30 min): **{fees.get('halfHourFee', 'n/d')}**\n"
719+
f"- Bassa (1 hr): **{fees.get('hourFee', 'n/d')}**"
706720
)
707721

708722
await update.message.reply_text(report, parse_mode='Markdown')
709723

710724

711725
async def btcinfo_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
712-
logger.info(f"Command /btcinfo received from {update.effective_user.id}")
726+
logger.info(f"Comando /btcinfo ricevuto da {update.effective_user.id}")
713727

714728
info, error = await crypto_utils.get_onchain_info()
715729

@@ -724,31 +738,31 @@ async def btcinfo_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
724738
adj_date = time.strftime('%d-%m-%Y %H:%M UTC', time.localtime(ts))
725739

726740
report = (
727-
f"📊 **Bitcoin On-Chain Analysis**\n"
741+
f"📊 **Analisi On-Chain Bitcoin**\n"
728742
f"----------------------------------------\n"
729-
f"⚒️ Current Difficulty: **{formatted_difficulty:.2f} T**\n"
730-
f"⚙️ Adjustment Progress: **{info['adjustment_progress']:.2f}%**\n"
731-
f"🧱 Remaining Blocks: **{info['blocks_remaining']}**\n"
732-
f"⏳ Estimated Next Adjustment: *{adj_date}*\n"
743+
f"⚒️ Difficoltà Attuale: **{formatted_difficulty:.2f} T**\n"
744+
f"⚙️ Progresso Aggiustamento: **{info['adjustment_progress']:.2f}%**\n"
745+
f"🧱 Blocchi Rimanenti: **{info['blocks_remaining']}**\n"
746+
f"⏳ Stima Prossimo Aggiustamento: *{adj_date}*\n"
733747
)
734748

735749
if info['adjustment_progress'] > 80:
736-
report += "\n🔔 *Note: Difficulty adjustment is imminent (over 80%).*"
750+
report += "\n🔔 *Nota: L'aggiustamento della difficoltà è imminente (oltre l'80%).*"
737751

738752
await update.message.reply_text(report, parse_mode='Markdown')
739753

740754
async def price_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
741-
logger.info(f"Command /price received from {update.effective_user.id}")
755+
logger.info(f"Comando /price ricevuto da {update.effective_user.id}")
742756

743757
currency = context.args[0] if context.args else "EUR"
744758
prices, error = await crypto_utils.get_price_data(currency=currency)
745759

746760
if error or not prices:
747-
await update.message.reply_text(f"⚠️ Error retrieving prices: {error or 'Data missing'}")
761+
await update.message.reply_text(f"⚠️ Errore nel recupero dei prezzi: {error or 'Dati mancanti'}")
748762
return
749763

750764
report = (
751-
f"💸 Current Bitcoin Prices:\n"
765+
f"💸 Prezzi Attuali di Bitcoin:\n"
752766
f"- BTC/EUR: **€{prices.get('eur', 0):,.2f}**\n"
753767
f"- BTC/USD: **${prices.get('usd', 0):,.2f}**"
754768
)
@@ -757,7 +771,7 @@ async def price_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
757771

758772

759773
async def peers_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
760-
logger.info(f"Command /peers received from {update.effective_user.id}")
774+
logger.info(f"Comando /peers ricevuto da {update.effective_user.id}")
761775

762776
def escape_html(text):
763777
if text is None:
@@ -770,7 +784,7 @@ def escape_html(text):
770784
client = get_lnd_client()
771785
peers = client.list_peers().peers
772786
if not peers:
773-
await update.message.reply_text("No connected peers")
787+
await update.message.reply_text("Nessun peer connesso")
774788
return
775789

776790
lines = []
@@ -779,47 +793,47 @@ def escape_html(text):
779793
addr = getattr(p, "address", "")
780794
alias = get_alias_for_pubkey(client, pk)
781795

782-
# Sanitize dynamic elements and use HTML <b> for bold
796+
# Sanifica tutti gli elementi dinamici e usa l'HTML per il grassetto (<b>)
783797
display_alias = alias or pk[:10] + '...'
784798
safe_alias = escape_html(display_alias)
785799
safe_pk = escape_html(pk)
786800
safe_addr = escape_html(addr)
787801

788802
lines.append(f"- <b>{safe_alias}</b> ({safe_pk}) {safe_addr}")
789803

790-
msg = "🔗 Active Peers:\n" + "\n".join(lines)
804+
msg = "🔗 Peers attivi:\n" + "\n".join(lines)
791805

792806
# Send in HTML mode
793807
await update.message.reply_text(msg, parse_mode='HTML')
794808

795809
except Exception as e:
796-
logger.error(f"Error executing /peers command: {e}")
810+
logger.error(f"Errore comando /peers: {e}")
797811
await update.message.reply_text(f"⚠️ LND error: {e}")
798812

799813
async def channels_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
800-
logger.info(f"Command /channels received from {update.effective_user.id}")
814+
logger.info(f"Comando /channels ricevuto da {update.effective_user.id}")
801815
try:
802816
client = get_lnd_client()
803817
channels = client.list_channels().channels
804818
if not channels:
805-
await update.message.reply_text("No open channels")
819+
await update.message.reply_text("Nessun canale aperto")
806820
return
807-
msg = "🔔 Open Channels:\n" + "\n".join(f"- **{getattr(c,'remote_pubkey','?')[:10] + '...'}** ({getattr(c,'capacity','?')} sats)" for c in channels)
821+
msg = "🔔 Canali Aperti:\n" + "\n".join(f"- **{getattr(c,'remote_pubkey','?')[:10] + '...'}** ({getattr(c,'capacity','?')} sats)" for c in channels)
808822
await update.message.reply_text(msg, parse_mode='Markdown')
809823
except Exception as e:
810-
logger.error(f"Error executing /channels command: {e}")
824+
logger.error(f"Errore comando /channels: {e}")
811825
await update.message.reply_text(f"⚠️ LND error: {e}")
812826

813827
async def invoices_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
814-
logger.info(f"Command /invoices received from {update.effective_user.id}")
828+
logger.info(f"Comando /invoices ricevuto da {update.effective_user.id}")
815829
try:
816830
client = get_lnd_client()
817831
invoices = client.list_invoices().invoices
818832
if not invoices:
819-
await update.message.reply_text("No invoices")
833+
await update.message.reply_text("Nessuna invoice")
820834
return
821835
last = invoices[-10:]
822-
msg = "💰 Last Invoices (max 10):\n"
836+
msg = "💰 Ultime Invoice (max 10):\n"
823837
for i in last:
824838
memo = getattr(i, "memo", "")
825839
value = getattr(i, "value", 0)
@@ -828,36 +842,36 @@ async def invoices_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
828842
msg += f"- *{memo}* : **{value} sats** - Status: {status_emoji}\n"
829843
await update.message.reply_text(msg, parse_mode='Markdown')
830844
except Exception as e:
831-
logger.error(f"Error executing /invoices command: {e}")
845+
logger.error(f"Errore comando /invoices: {e}")
832846
await update.message.reply_text(f"⚠️ LND error: {e}")
833847

834848
async def hardware_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
835-
logger.info(f"Command /hardware received from {update.effective_user.id}")
836-
# Remote Hardware Status
849+
logger.info(f"Comando /hardware ricevuto da {update.effective_user.id}")
850+
# Stato Hardware Remoto del NODO (CPU/RAM/Load)
837851
remote_data, remote_system_report = await get_remote_system_status()
838852

839-
# Remote Disk Status
853+
# Stato Dischi Remoto del NODO
840854
remote_disk_report, _ = await get_remote_disk_status(REMOTE_MOUNTS_TO_CHECK)
841855

842856
final_message = (
843-
f"--- 💾 Hardware NODE (Remote) ---\n"
857+
f"--- 💾 Hardware NODO Bitcoin (Remoto) ---\n"
844858
f"{remote_system_report}\n"
845-
f"Disks:\n"
859+
f"Dischi:\n"
846860
f"{remote_disk_report}"
847861
)
848862
await update.message.reply_text(final_message, parse_mode='Markdown')
849863

850864

851865
async def netscan_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
852-
logger.info(f"Command /netscan received from {update.effective_user.id}")
866+
logger.info(f"Comando /netscan ricevuto da {update.effective_user.id}")
853867

854868
if not context.args or len(context.args) < 1:
855-
await update.message.reply_text("⚠️ Usage: /netscan <subnet_base> (E.g.: /netscan 10.21.10)", parse_mode='Markdown')
869+
await update.message.reply_text("⚠️ Uso: /netscan <subnet_base> (Es: /netscan 10.21.10)", parse_mode='Markdown')
856870
return
857871

858872
subnet_base = context.args[0].strip()
859873

860-
await update.message.reply_text(f"⏳ Starting lightweight ping scan on subnet **{subnet_base}.x** (May take up to 2 minutes)...", parse_mode='Markdown')
874+
await update.message.reply_text(f"⏳ Avvio scansione ping leggera sulla subnet **{subnet_base}.x** (Potrebbe richiedere fino a 2 minuti)...", parse_mode='Markdown')
861875

862876
# SSH Scan Command (ping sweep from the remote node)
863877
SCAN_CMD = (
@@ -867,38 +881,38 @@ async def netscan_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
867881
output, error = await run_remote_command(SCAN_CMD, timeout=120)
868882

869883
if error:
870-
report = f"❌ **SCAN FAILED!** Error during SSH execution: {error}"
884+
report = f"❌ **SCAN FALLITO!** Errore durante l'esecuzione SSH: {error}"
871885
elif not output:
872-
report = f"🔍 **SCAN COMPLETE:** No active hosts detected on subnet **{subnet_base}.x**."
886+
report = f"🔍 **SCAN COMPLETATO:** Nessun host attivo rilevato sulla subnet **{subnet_base}.x**."
873887
else:
874888
active_hosts = output.replace('Host up: ', '- ').replace(':', '').splitlines()
875-
report = f"✅ **SCAN COMPLETE:** Detected **{len(active_hosts)}** active hosts on **{subnet_base}.x**:\n" + "\n".join(active_hosts)
889+
report = f"✅ **SCAN COMPLETATO:** Rilevati **{len(active_hosts)}** host attivi su **{subnet_base}.x**:\n" + "\n".join(active_hosts)
876890

877891
await update.message.reply_text(report, parse_mode='Markdown')
878892

879893

880894
async def main():
881895
app = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
882-
logger.info("Telegram Bot started.")
896+
logger.info("Bot Telegram avviato.")
883897

884898
await app.bot.set_my_commands([
885-
BotCommand("start", "Start the bot"),
886-
BotCommand("status", "Show node status"),
887-
BotCommand("peers", "List active peers"),
888-
BotCommand("channels", "List open channels"),
889-
BotCommand("invoices", "Last invoices"),
890-
BotCommand("hardware", "Node hardware status"),
899+
BotCommand("start", "Avvia il bot"),
900+
BotCommand("status", "Mostra lo stato del nodo"),
901+
BotCommand("peers", "Lista dei peers"),
902+
BotCommand("channels", "Lista dei canali"),
903+
BotCommand("invoices", "Ultime invoice"),
904+
BotCommand("hardware", "Stato hardware del NODO"),
891905

892-
# MONITORING AND MARKET
893-
BotCommand("mempool", "BTC transaction fees (sat/vB)"),
894-
BotCommand("price", "BTC price in fiat (e.g., /price USD)"),
895-
BotCommand("btcinfo", "Bitcoin Difficulty and Adjustment Analysis"),
906+
# MONITORAGGIO E MERCATO
907+
BotCommand("mempool", "Tariffe transazione BTC (sat/vB)"),
908+
BotCommand("price", "Prezzo BTC in EUR/USD (es. /price USD)"),
909+
BotCommand("btcinfo", "Analisi Difficoltà e Aggiustamento Bitcoin"),
896910

897-
# DIAGNOSTICS AND REMOTE ACTIONS
898-
BotCommand("diagnose", "Status of LND and Bitcoin Core services"),
899-
BotCommand("restartlnd", "Restart LND service (Admin Only)"),
900-
BotCommand("restartbtc", "Restart Bitcoin Core (Admin Only)"),
901-
BotCommand("netscan", "Perform local network scan")
911+
# DIAGNOSI E AZIONI REMOTE
912+
BotCommand("diagnose", "Stato dei servizi LND e Bitcoin Core"),
913+
BotCommand("restartlnd", "Riavvia il servizio LND (Solo Admin)"),
914+
BotCommand("restartbtc", "Riavvia Bitcoin Core (Solo Admin)"),
915+
BotCommand("netscan", "Scansione di rete locale")
902916
])
903917

904918
app.add_handler(CommandHandler("start", start_cmd))
@@ -909,7 +923,7 @@ async def main():
909923
app.add_handler(CommandHandler("hardware", hardware_cmd))
910924
app.add_handler(CommandHandler("netscan", netscan_cmd))
911925

912-
# REGISTER NEW HANDLERS
926+
# REGISTRAZIONE NUOVI HANDLER
913927
app.add_handler(CommandHandler("mempool", mempool_cmd))
914928
app.add_handler(CommandHandler("price", price_cmd))
915929
app.add_handler(CommandHandler("btcinfo", btcinfo_cmd))
@@ -922,8 +936,8 @@ async def main():
922936
loop.create_task(monitor_lnd_task(app))
923937
loop.create_task(crypto_utils.surveillance_task(app))
924938

925-
print("=== NodeSentinel Live & Fully Operational ===")
926-
logger.info("Starting polling...")
939+
print("=== NodeSentinel Live & Full Running ===")
940+
logger.info("Avvio del polling...")
927941
await app.run_polling()
928942

929943
# ---------- Run ----------
@@ -939,4 +953,3 @@ async def main():
939953
loop.run_until_complete(main())
940954
finally:
941955
pass
942-

0 commit comments

Comments
 (0)