chore: enhance .gitignore and remove obsolete documentation files - Reorganize .gitignore with categorized sections for better maintainability - Add comprehensive ignore patterns for Python, Node.js, databases, logs, and build artifacts - Add project-specific ignore rules for coordinator, explorer, and deployment files - Remove outdated documentation: BITCOIN-WALLET-SETUP.md, LOCAL_ASSETS_SUMMARY.md, README-CONTAINER-DEPLOYMENT.md, README-DOMAIN-DEPLOYMENT.md ```
314 lines
11 KiB
Python
Executable File
314 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
AITBC Blockchain Sync Monitor
|
|
Real-time monitoring of blockchain synchronization status
|
|
"""
|
|
|
|
import time
|
|
import json
|
|
import sys
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Optional, Tuple
|
|
import threading
|
|
import signal
|
|
|
|
class SyncMonitor:
|
|
def __init__(self, node_url: str = "http://localhost:8545"):
|
|
"""Initialize the sync monitor"""
|
|
self.node_url = node_url
|
|
self.running = False
|
|
self.start_time = None
|
|
self.last_block = 0
|
|
self.sync_history = []
|
|
self.max_history = 100
|
|
|
|
# ANSI colors for terminal output
|
|
self.colors = {
|
|
'red': '\033[91m',
|
|
'green': '\033[92m',
|
|
'yellow': '\033[93m',
|
|
'blue': '\033[94m',
|
|
'magenta': '\033[95m',
|
|
'cyan': '\033[96m',
|
|
'white': '\033[97m',
|
|
'end': '\033[0m'
|
|
}
|
|
|
|
def rpc_call(self, method: str, params: List = None) -> Optional[Dict]:
|
|
"""Make JSON-RPC call to node"""
|
|
try:
|
|
response = requests.post(
|
|
self.node_url,
|
|
json={
|
|
"jsonrpc": "2.0",
|
|
"method": method,
|
|
"params": params or [],
|
|
"id": 1
|
|
},
|
|
timeout=5
|
|
)
|
|
return response.json().get('result')
|
|
except Exception as e:
|
|
return None
|
|
|
|
def get_sync_status(self) -> Dict:
|
|
"""Get current sync status"""
|
|
sync_result = self.rpc_call("eth_syncing")
|
|
|
|
if sync_result is False:
|
|
# Fully synced
|
|
latest_block = self.rpc_call("eth_blockNumber")
|
|
return {
|
|
'syncing': False,
|
|
'current_block': int(latest_block, 16) if latest_block else 0,
|
|
'highest_block': int(latest_block, 16) if latest_block else 0,
|
|
'sync_percent': 100.0
|
|
}
|
|
else:
|
|
# Still syncing
|
|
current = int(sync_result.get('currentBlock', '0x0'), 16)
|
|
highest = int(sync_result.get('highestBlock', '0x0'), 16)
|
|
percent = (current / highest * 100) if highest > 0 else 0
|
|
|
|
return {
|
|
'syncing': True,
|
|
'current_block': current,
|
|
'highest_block': highest,
|
|
'sync_percent': percent,
|
|
'starting_block': int(sync_result.get('startingBlock', '0x0'), 16),
|
|
'pulled_states': sync_result.get('pulledStates', '0x0'),
|
|
'known_states': sync_result.get('knownStates', '0x0')
|
|
}
|
|
|
|
def get_peer_count(self) -> int:
|
|
"""Get number of connected peers"""
|
|
result = self.rpc_call("net_peerCount")
|
|
return int(result, 16) if result else 0
|
|
|
|
def get_block_time(self, block_number: int) -> Optional[datetime]:
|
|
"""Get block timestamp"""
|
|
block = self.rpc_call("eth_getBlockByNumber", [hex(block_number), False])
|
|
if block and 'timestamp' in block:
|
|
return datetime.fromtimestamp(int(block['timestamp'], 16))
|
|
return None
|
|
|
|
def calculate_sync_speed(self) -> Optional[float]:
|
|
"""Calculate current sync speed (blocks/second)"""
|
|
if len(self.sync_history) < 2:
|
|
return None
|
|
|
|
# Get last two data points
|
|
recent = self.sync_history[-2:]
|
|
blocks_diff = recent[1]['current_block'] - recent[0]['current_block']
|
|
time_diff = (recent[1]['timestamp'] - recent[0]['timestamp']).total_seconds()
|
|
|
|
if time_diff > 0:
|
|
return blocks_diff / time_diff
|
|
return None
|
|
|
|
def estimate_time_remaining(self, current: int, target: int, speed: float) -> str:
|
|
"""Estimate time remaining to sync"""
|
|
if speed <= 0:
|
|
return "Unknown"
|
|
|
|
blocks_remaining = target - current
|
|
seconds_remaining = blocks_remaining / speed
|
|
|
|
if seconds_remaining < 60:
|
|
return f"{int(seconds_remaining)} seconds"
|
|
elif seconds_remaining < 3600:
|
|
return f"{int(seconds_remaining / 60)} minutes"
|
|
elif seconds_remaining < 86400:
|
|
return f"{int(seconds_remaining / 3600)} hours"
|
|
else:
|
|
return f"{int(seconds_remaining / 86400)} days"
|
|
|
|
def print_status_bar(self, status: Dict):
|
|
"""Print a visual sync status bar"""
|
|
width = 50
|
|
filled = int(width * status['sync_percent'] / 100)
|
|
bar = '█' * filled + '░' * (width - filled)
|
|
|
|
color = self.colors['green'] if status['sync_percent'] > 90 else \
|
|
self.colors['yellow'] if status['sync_percent'] > 50 else \
|
|
self.colors['red']
|
|
|
|
print(f"\r{color}[{bar}]{self.colors['end']} {status['sync_percent']:.2f}%", end='', flush=True)
|
|
|
|
def print_detailed_status(self, status: Dict, speed: float, peers: int):
|
|
"""Print detailed sync information"""
|
|
print(f"\n{'='*60}")
|
|
print(f"{self.colors['cyan']}AITBC Blockchain Sync Monitor{self.colors['end']}")
|
|
print(f"{'='*60}")
|
|
|
|
# Sync status
|
|
if status['syncing']:
|
|
print(f"\n{self.colors['yellow']}Syncing...{self.colors['end']}")
|
|
else:
|
|
print(f"\n{self.colors['green']}Fully Synchronized!{self.colors['end']}")
|
|
|
|
# Block information
|
|
print(f"\n{self.colors['blue']}Block Information:{self.colors['end']}")
|
|
print(f" Current: {status['current_block']:,}")
|
|
print(f" Highest: {status['highest_block']:,}")
|
|
print(f" Progress: {status['sync_percent']:.2f}%")
|
|
|
|
if status['syncing'] and speed:
|
|
eta = self.estimate_time_remaining(
|
|
status['current_block'],
|
|
status['highest_block'],
|
|
speed
|
|
)
|
|
print(f" ETA: {eta}")
|
|
|
|
# Sync speed
|
|
if speed:
|
|
print(f"\n{self.colors['blue']}Sync Speed:{self.colors['end']}")
|
|
print(f" {speed:.2f} blocks/second")
|
|
|
|
# Calculate blocks per minute/hour
|
|
print(f" {speed * 60:.0f} blocks/minute")
|
|
print(f" {speed * 3600:.0f} blocks/hour")
|
|
|
|
# Network information
|
|
print(f"\n{self.colors['blue']}Network:{self.colors['end']}")
|
|
print(f" Peers connected: {peers}")
|
|
|
|
# State sync (if available)
|
|
if status.get('pulled_states') and status.get('known_states'):
|
|
pulled = int(status['pulled_states'], 16)
|
|
known = int(status['known_states'], 16)
|
|
if known > 0:
|
|
state_percent = (pulled / known) * 100
|
|
print(f" State sync: {state_percent:.2f}%")
|
|
|
|
# Time information
|
|
if self.start_time:
|
|
elapsed = datetime.now() - self.start_time
|
|
print(f"\n{self.colors['blue']}Time:{self.colors['end']}")
|
|
print(f" Started: {self.start_time.strftime('%H:%M:%S')}")
|
|
print(f" Elapsed: {str(elapsed).split('.')[0]}")
|
|
|
|
def monitor_loop(self, interval: int = 5, detailed: bool = False):
|
|
"""Main monitoring loop"""
|
|
self.running = True
|
|
self.start_time = datetime.now()
|
|
|
|
print(f"Starting sync monitor (interval: {interval}s)")
|
|
print("Press Ctrl+C to stop\n")
|
|
|
|
try:
|
|
while self.running:
|
|
# Get current status
|
|
status = self.get_sync_status()
|
|
peers = self.get_peer_count()
|
|
|
|
# Add to history
|
|
status['timestamp'] = datetime.now()
|
|
self.sync_history.append(status)
|
|
if len(self.sync_history) > self.max_history:
|
|
self.sync_history.pop(0)
|
|
|
|
# Calculate sync speed
|
|
speed = self.calculate_sync_speed()
|
|
|
|
# Display
|
|
if detailed:
|
|
self.print_detailed_status(status, speed, peers)
|
|
else:
|
|
self.print_status_bar(status)
|
|
|
|
# Check if fully synced
|
|
if not status['syncing']:
|
|
if not detailed:
|
|
print() # New line after status bar
|
|
print(f"\n{self.colors['green']}✓ Sync completed!{self.colors['end']}")
|
|
break
|
|
|
|
# Wait for next interval
|
|
time.sleep(interval)
|
|
|
|
except KeyboardInterrupt:
|
|
self.running = False
|
|
print(f"\n\n{self.colors['yellow']}Sync monitor stopped by user{self.colors['end']}")
|
|
|
|
# Print final summary
|
|
self.print_summary()
|
|
|
|
def print_summary(self):
|
|
"""Print sync summary"""
|
|
if not self.sync_history:
|
|
return
|
|
|
|
print(f"\n{self.colors['cyan']}Sync Summary{self.colors['end']}")
|
|
print("-" * 40)
|
|
|
|
if self.start_time:
|
|
total_time = datetime.now() - self.start_time
|
|
print(f"Total time: {str(total_time).split('.')[0]}")
|
|
|
|
if len(self.sync_history) >= 2:
|
|
blocks_synced = self.sync_history[-1]['current_block'] - self.sync_history[0]['current_block']
|
|
print(f"Blocks synced: {blocks_synced:,}")
|
|
|
|
if total_time.total_seconds() > 0:
|
|
avg_speed = blocks_synced / total_time.total_seconds()
|
|
print(f"Average speed: {avg_speed:.2f} blocks/second")
|
|
|
|
def save_report(self, filename: str):
|
|
"""Save sync report to file"""
|
|
report = {
|
|
'start_time': self.start_time.isoformat() if self.start_time else None,
|
|
'end_time': datetime.now().isoformat(),
|
|
'sync_history': [
|
|
{
|
|
'timestamp': entry['timestamp'].isoformat(),
|
|
'current_block': entry['current_block'],
|
|
'highest_block': entry['highest_block'],
|
|
'sync_percent': entry['sync_percent']
|
|
}
|
|
for entry in self.sync_history
|
|
]
|
|
}
|
|
|
|
with open(filename, 'w') as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print(f"Report saved to: {filename}")
|
|
|
|
def signal_handler(signum, frame):
|
|
"""Handle Ctrl+C"""
|
|
print("\n\nStopping sync monitor...")
|
|
sys.exit(0)
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='AITBC Blockchain Sync Monitor')
|
|
parser.add_argument('--node', default='http://localhost:8545', help='Node URL')
|
|
parser.add_argument('--interval', type=int, default=5, help='Update interval (seconds)')
|
|
parser.add_argument('--detailed', action='store_true', help='Show detailed output')
|
|
parser.add_argument('--report', help='Save report to file')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Set up signal handler
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
# Create and run monitor
|
|
monitor = SyncMonitor(args.node)
|
|
|
|
try:
|
|
monitor.monitor_loop(interval=args.interval, detailed=args.detailed)
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
sys.exit(1)
|
|
|
|
# Save report if requested
|
|
if args.report:
|
|
monitor.save_report(args.report)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|