pyblock/pybitblock/block_explorer.py
GaltRanch 893aabc85d Security audit round 2: eliminate shell=True, mask secrets, fix race conditions
- Replace all shell=True subprocess calls with Python-native processing
  (nodeconnection.py, SPV/nodeconnection.py, SPV/ppi.py)
- Mask sensitive inputs (private keys, passwords, tokens) with getpass
- Add threading.Lock to block_explorer.py shared state
- Use json.loads() instead of fragile string splitting in apisnd.py
- Add path validation before file open in apisnd.py
- Replace random.randint with secrets.randbelow for mining nonces
- Fix destructive exception handlers in clone.py and feed.py
- Replace bare except clauses with specific exceptions + logging
- Remove unused imports (psutil, xmltodict, block_visualizer, base64, say)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:15:59 -03:00

123 lines
4.7 KiB
Python

import asyncio
from rich.live import Live
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich.text import Text
from rich.align import Align
from rich.console import Group
import subprocess
import json
import time
from threading import Event, Lock, Thread
from execute_load_config import load_config
# Load configuration
path, settings, settingsClock = load_config()
_block_tables_lock = Lock()
def fetch_blockchain_info(path):
raw_info = subprocess.run([path["bitcoincli"], "getblockchaininfo"], capture_output=True, text=True)
blockchain_info = json.loads(raw_info.stdout)
return blockchain_info
def fetch_block_info(path, blockhash):
raw_block_info = subprocess.run([path["bitcoincli"], "getblock", blockhash], capture_output=True, text=True)
block_info = json.loads(raw_block_info.stdout)
return block_info
def create_block_info_table(block_height, block_data):
table = Table(title=f"Block #{block_height}")
table.add_column("Metric", style="green")
table.add_column("Value", style="yellow")
table.add_row("Transactions", str(block_data['nTx']))
table.add_row("Size", f"{block_data['size']} bytes")
table.add_row("Weight", f"{block_data['weight']} weight units")
table.add_row("Version", str(block_data['version']))
table.add_row("Merkle Root", block_data['merkleroot'])
table.add_row("Time", time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(block_data['time'])))
table.add_row("Median Time", time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(block_data['mediantime'])))
table.add_row("Nonce", str(block_data['nonce']))
table.add_row("Bits", str(block_data['bits']))
table.add_row("Difficulty", f"{block_data['difficulty']:.2f}")
table.add_row("Chainwork", block_data['chainwork'])
table.add_row("Previous Block", block_data['previousblockhash'])
if 'nextblockhash' in block_data:
table.add_row("Next Block", block_data['nextblockhash'])
return table
def fetch_and_store_block_data(path, start_height, count, block_tables):
latest_height = start_height
for i in range(count): # Limitar a los bloques solicitados
block_height = latest_height - i
block_hash = subprocess.run([path["bitcoincli"], "getblockhash", str(block_height)], capture_output=True, text=True).stdout.strip()
block_data = fetch_block_info(path, block_hash)
table = create_block_info_table(block_height, block_data)
with _block_tables_lock:
block_tables.append(table)
def background_block_fetch(path, block_tables, stop_event):
latest_height = fetch_blockchain_info(path)['blocks']
while not stop_event.is_set():
current_height = fetch_blockchain_info(path)['blocks']
if current_height > latest_height:
latest_height = current_height
with _block_tables_lock:
block_tables.clear()
fetch_and_store_block_data(path, current_height, 3, block_tables)
time.sleep(10)
async def display_blocks_info():
layout = Layout()
layout.split_column(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
Layout(name="footer", size=1),
)
layout["main"].split_row(
Layout(name="recent_blocks", ratio=1),
)
layout["footer"].update(Text("Cypherpunk style loading..."))
layout["recent_blocks"].update(Panel(Text("Cypherpunk Style loading..."), title="Recent Blocks"))
layout["header"].update(Text("Block Monitor", style="bold cyan"))
block_tables = []
blockchain_info = fetch_blockchain_info(path)
latest_block_height = blockchain_info['blocks']
fetch_and_store_block_data(path, latest_block_height, 3, block_tables)
stop_event = Event()
fetch_thread = Thread(target=background_block_fetch, args=(path, block_tables, stop_event))
fetch_thread.start()
async def input_handler():
while True:
key = await asyncio.get_event_loop().run_in_executor(None, input)
if key == 'q':
stop_event.set()
fetch_thread.join()
break
with Live(layout, refresh_per_second=1, screen=True):
input_task = asyncio.create_task(input_handler())
while not stop_event.is_set():
with _block_tables_lock:
tables_snapshot = list(block_tables)
recent_blocks_group = Group(*tables_snapshot)
centered_recent_blocks = Align.center(recent_blocks_group)
layout["recent_blocks"].update(Panel(centered_recent_blocks, title="Recent Blocks"))
layout["footer"].update(Text("Running the node."))
await asyncio.sleep(1)
def call_blocks():
asyncio.run(display_blocks_info())
if __name__ == "__main__":
call_blocks()