refactor: consolidate blockchain explorer into single app and update backup ignore patterns

- Remove standalone explorer-web app (README, HTML, package files)
- Add /web endpoint to blockchain-explorer for web interface access
- Update .gitignore to exclude application backup archives (*.tar.gz, *.zip)
- Add backup documentation files to .gitignore (BACKUP_INDEX.md, README.md)
- Consolidate explorer functionality into main blockchain-explorer application
This commit is contained in:
oib
2026-03-06 18:14:49 +01:00
parent dc1561d457
commit bb5363bebc
295 changed files with 35501 additions and 3734 deletions

View File

@@ -0,0 +1,22 @@
"""
Multi-Chain Support Module for Wallet Daemon
This module provides multi-chain capabilities for the wallet daemon,
including chain management, chain-specific storage, and chain-aware
wallet operations.
"""
from .manager import ChainManager, ChainConfig, ChainStatus, chain_manager
from .multichain_ledger import MultiChainLedgerAdapter, ChainLedgerRecord, ChainWalletMetadata
from .chain_aware_wallet_service import ChainAwareWalletService
__all__ = [
"ChainManager",
"ChainConfig",
"ChainStatus",
"chain_manager",
"MultiChainLedgerAdapter",
"ChainLedgerRecord",
"ChainWalletMetadata",
"ChainAwareWalletService"
]

View File

@@ -0,0 +1,414 @@
"""
Chain-Aware Wallet Service for Wallet Daemon
Multi-chain wallet operations with proper chain context,
isolation, and management across different blockchain networks.
"""
from typing import Dict, List, Optional, Any
from pathlib import Path
import logging
from datetime import datetime
from .manager import ChainManager, ChainConfig, ChainStatus
from .multichain_ledger import MultiChainLedgerAdapter, ChainWalletMetadata
from ..keystore.persistent_service import PersistentKeystoreService
from ..security import wipe_buffer
logger = logging.getLogger(__name__)
class ChainAwareWalletService:
"""Chain-aware wallet service with multi-chain support"""
def __init__(self, chain_manager: ChainManager, multichain_ledger: MultiChainLedgerAdapter):
self.chain_manager = chain_manager
self.multichain_ledger = multichain_ledger
# Chain-specific keystores
self.chain_keystores: Dict[str, PersistentKeystoreService] = {}
self._initialize_chain_keystores()
def _initialize_chain_keystores(self):
"""Initialize keystore for each chain"""
for chain in self.chain_manager.list_chains():
self._init_chain_keystore(chain.chain_id)
def _init_chain_keystore(self, chain_id: str):
"""Initialize keystore for a specific chain"""
try:
chain = self.chain_manager.get_chain(chain_id)
if not chain:
return
keystore_path = chain.keystore_path or f"./data/keystore_{chain_id}"
keystore = PersistentKeystoreService(keystore_path)
self.chain_keystores[chain_id] = keystore
logger.info(f"Initialized keystore for chain: {chain_id}")
except Exception as e:
logger.error(f"Failed to initialize keystore for chain {chain_id}: {e}")
def _get_keystore(self, chain_id: str) -> Optional[PersistentKeystoreService]:
"""Get keystore for a specific chain"""
if chain_id not in self.chain_keystores:
self._init_chain_keystore(chain_id)
return self.chain_keystores.get(chain_id)
def create_wallet(self, chain_id: str, wallet_id: str, password: str,
secret_key: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Optional[ChainWalletMetadata]:
"""Create a wallet in a specific chain"""
try:
# Validate chain
if not self.chain_manager.validate_chain_id(chain_id):
logger.error(f"Invalid or inactive chain: {chain_id}")
return None
# Get keystore for chain
keystore = self._get_keystore(chain_id)
if not keystore:
logger.error(f"Failed to get keystore for chain: {chain_id}")
return None
# Create wallet in keystore
keystore_record = keystore.create_wallet(wallet_id, password, secret_key, metadata or {})
# Create wallet in ledger
success = self.multichain_ledger.create_wallet(
chain_id, wallet_id, keystore_record.public_key,
metadata=keystore_record.metadata
)
if not success:
# Rollback keystore creation
try:
keystore.delete_wallet(wallet_id, password)
except:
pass
return None
# Get wallet metadata
wallet_metadata = self.multichain_ledger.get_wallet(chain_id, wallet_id)
# Record creation event
self.multichain_ledger.record_event(chain_id, wallet_id, "created", {
"public_key": keystore_record.public_key,
"chain_id": chain_id,
"metadata": metadata or {}
})
logger.info(f"Created wallet {wallet_id} in chain {chain_id}")
return wallet_metadata
except Exception as e:
logger.error(f"Failed to create wallet {wallet_id} in chain {chain_id}: {e}")
return None
def get_wallet(self, chain_id: str, wallet_id: str) -> Optional[ChainWalletMetadata]:
"""Get wallet metadata from a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return None
return self.multichain_ledger.get_wallet(chain_id, wallet_id)
except Exception as e:
logger.error(f"Failed to get wallet {wallet_id} from chain {chain_id}: {e}")
return None
def list_wallets(self, chain_id: Optional[str] = None) -> List[ChainWalletMetadata]:
"""List wallets from a specific chain or all chains"""
try:
if chain_id:
if not self.chain_manager.validate_chain_id(chain_id):
return []
return self.multichain_ledger.list_wallets(chain_id)
else:
# List from all active chains
all_wallets = []
for chain in self.chain_manager.get_active_chains():
chain_wallets = self.multichain_ledger.list_wallets(chain.chain_id)
all_wallets.extend(chain_wallets)
return all_wallets
except Exception as e:
logger.error(f"Failed to list wallets: {e}")
return []
def delete_wallet(self, chain_id: str, wallet_id: str, password: str) -> bool:
"""Delete a wallet from a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return False
# Get keystore
keystore = self._get_keystore(chain_id)
if not keystore:
return False
# Delete from keystore
keystore_success = keystore.delete_wallet(wallet_id, password)
if not keystore_success:
return False
# Record deletion event
self.multichain_ledger.record_event(chain_id, wallet_id, "deleted", {
"chain_id": chain_id
})
# Note: We keep the wallet metadata in ledger for audit purposes
logger.info(f"Deleted wallet {wallet_id} from chain {chain_id}")
return True
except Exception as e:
logger.error(f"Failed to delete wallet {wallet_id} from chain {chain_id}: {e}")
return False
def sign_message(self, chain_id: str, wallet_id: str, password: str, message: bytes,
ip_address: Optional[str] = None) -> Optional[str]:
"""Sign a message with wallet private key in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return None
# Get keystore
keystore = self._get_keystore(chain_id)
if not keystore:
return None
# Sign message
signature = keystore.sign_message(wallet_id, password, message, ip_address)
if signature:
# Record signing event
self.multichain_ledger.record_event(chain_id, wallet_id, "signed", {
"message_length": len(message),
"ip_address": ip_address,
"chain_id": chain_id
})
logger.info(f"Signed message for wallet {wallet_id} in chain {chain_id}")
return signature
except Exception as e:
logger.error(f"Failed to sign message for wallet {wallet_id} in chain {chain_id}: {e}")
return None
def unlock_wallet(self, chain_id: str, wallet_id: str, password: str) -> bool:
"""Unlock a wallet in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return False
# Get keystore
keystore = self._get_keystore(chain_id)
if not keystore:
return False
# Unlock wallet
success = keystore.unlock_wallet(wallet_id, password)
if success:
# Record unlock event
self.multichain_ledger.record_event(chain_id, wallet_id, "unlocked", {
"chain_id": chain_id
})
logger.info(f"Unlocked wallet {wallet_id} in chain {chain_id}")
return success
except Exception as e:
logger.error(f"Failed to unlock wallet {wallet_id} in chain {chain_id}: {e}")
return False
def lock_wallet(self, chain_id: str, wallet_id: str) -> bool:
"""Lock a wallet in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return False
# Get keystore
keystore = self._get_keystore(chain_id)
if not keystore:
return False
# Lock wallet
success = keystore.lock_wallet(wallet_id)
if success:
# Record lock event
self.multichain_ledger.record_event(chain_id, wallet_id, "locked", {
"chain_id": chain_id
})
logger.info(f"Locked wallet {wallet_id} in chain {chain_id}")
return success
except Exception as e:
logger.error(f"Failed to lock wallet {wallet_id} in chain {chain_id}: {e}")
return False
def get_wallet_events(self, chain_id: str, wallet_id: str,
event_type: Optional[str] = None, limit: int = 100) -> List[Dict[str, Any]]:
"""Get events for a wallet in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return []
events = self.multichain_ledger.get_wallet_events(chain_id, wallet_id, event_type, limit)
return [
{
"chain_id": event.chain_id,
"wallet_id": event.wallet_id,
"event_type": event.event_type,
"timestamp": event.timestamp.isoformat(),
"data": event.data,
"success": event.success
}
for event in events
]
except Exception as e:
logger.error(f"Failed to get events for wallet {wallet_id} in chain {chain_id}: {e}")
return []
def get_chain_wallet_stats(self, chain_id: str) -> Dict[str, Any]:
"""Get wallet statistics for a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return {}
# Get ledger stats
ledger_stats = self.multichain_ledger.get_chain_stats(chain_id)
# Get keystore stats
keystore = self._get_keystore(chain_id)
keystore_stats = {}
if keystore:
keystore_stats = {
"total_wallets": len(keystore.list_wallets()),
"unlocked_wallets": len([w for w in keystore.list_wallets() if w.get("unlocked", False)])
}
return {
"chain_id": chain_id,
"ledger_stats": ledger_stats,
"keystore_stats": keystore_stats
}
except Exception as e:
logger.error(f"Failed to get stats for chain {chain_id}: {e}")
return {}
def get_all_chain_wallet_stats(self) -> Dict[str, Any]:
"""Get wallet statistics for all chains"""
stats = {
"total_chains": 0,
"total_wallets": 0,
"chain_stats": {}
}
for chain in self.chain_manager.get_active_chains():
chain_stats = self.get_chain_wallet_stats(chain.chain_id)
if chain_stats:
stats["chain_stats"][chain.chain_id] = chain_stats
stats["total_wallets"] += chain_stats.get("ledger_stats", {}).get("wallet_count", 0)
stats["total_chains"] += 1
return stats
def migrate_wallet_between_chains(self, source_chain_id: str, target_chain_id: str,
wallet_id: str, password: str, new_password: Optional[str] = None) -> bool:
"""Migrate a wallet from one chain to another"""
try:
# Validate both chains
if not self.chain_manager.validate_chain_id(source_chain_id):
logger.error(f"Invalid source chain: {source_chain_id}")
return False
if not self.chain_manager.validate_chain_id(target_chain_id):
logger.error(f"Invalid target chain: {target_chain_id}")
return False
# Get source wallet
source_wallet = self.get_wallet(source_chain_id, wallet_id)
if not source_wallet:
logger.error(f"Wallet {wallet_id} not found in source chain {source_chain_id}")
return False
# Check if wallet already exists in target chain
target_wallet = self.get_wallet(target_chain_id, wallet_id)
if target_wallet:
logger.error(f"Wallet {wallet_id} already exists in target chain {target_chain_id}")
return False
# Get source keystore
source_keystore = self._get_keystore(source_chain_id)
target_keystore = self._get_keystore(target_chain_id)
if not source_keystore or not target_keystore:
logger.error("Failed to get keystores for migration")
return False
# Export wallet from source chain
try:
# This would require adding export/import methods to keystore
# For now, we'll create a new wallet with the same keys
source_keystore_record = source_keystore.get_wallet(wallet_id)
if not source_keystore_record:
logger.error("Failed to get source wallet record")
return False
# Create wallet in target chain with same keys
target_wallet = self.create_wallet(
target_chain_id, wallet_id, new_password or password,
source_keystore_record.get("secret_key"), source_wallet.metadata
)
if target_wallet:
# Record migration events
self.multichain_ledger.record_event(source_chain_id, wallet_id, "migrated_from", {
"target_chain": target_chain_id,
"migration_timestamp": datetime.now().isoformat()
})
self.multichain_ledger.record_event(target_chain_id, wallet_id, "migrated_to", {
"source_chain": source_chain_id,
"migration_timestamp": datetime.now().isoformat()
})
logger.info(f"Migrated wallet {wallet_id} from {source_chain_id} to {target_chain_id}")
return True
else:
logger.error("Failed to create wallet in target chain")
return False
except Exception as e:
logger.error(f"Failed to migrate wallet {wallet_id}: {e}")
return False
except Exception as e:
logger.error(f"Wallet migration failed: {e}")
return False
def cleanup(self):
"""Cleanup resources"""
try:
# Close all keystore connections
for chain_id, keystore in self.chain_keystores.items():
try:
keystore.close()
logger.info(f"Closed keystore for chain: {chain_id}")
except Exception as e:
logger.error(f"Failed to close keystore for chain {chain_id}: {e}")
self.chain_keystores.clear()
# Close ledger connections
self.multichain_ledger.close_all_connections()
except Exception as e:
logger.error(f"Failed to cleanup wallet service: {e}")

View File

@@ -0,0 +1,273 @@
"""
Multi-Chain Manager for Wallet Daemon
Central management for multiple blockchain networks, providing
chain context, routing, and isolation for wallet operations.
"""
from typing import Dict, List, Optional, Any
from pathlib import Path
from dataclasses import dataclass, field
from enum import Enum
import json
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
class ChainStatus(Enum):
"""Chain operational status"""
ACTIVE = "active"
INACTIVE = "inactive"
MAINTENANCE = "maintenance"
ERROR = "error"
@dataclass
class ChainConfig:
"""Configuration for a specific blockchain network"""
chain_id: str
name: str
coordinator_url: str
coordinator_api_key: str
status: ChainStatus = ChainStatus.ACTIVE
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
metadata: Dict[str, Any] = field(default_factory=dict)
# Chain-specific settings
default_gas_limit: int = 10000000
default_gas_price: int = 20000000000
transaction_timeout: int = 300
max_retries: int = 3
# Storage configuration
ledger_db_path: Optional[str] = None
keystore_path: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for serialization"""
return {
"chain_id": self.chain_id,
"name": self.name,
"coordinator_url": self.coordinator_url,
"coordinator_api_key": self.coordinator_api_key,
"status": self.status.value,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
"metadata": self.metadata,
"default_gas_limit": self.default_gas_limit,
"default_gas_price": self.default_gas_price,
"transaction_timeout": self.transaction_timeout,
"max_retries": self.max_retries,
"ledger_db_path": self.ledger_db_path,
"keystore_path": self.keystore_path
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ChainConfig":
"""Create from dictionary"""
# Ensure data is a dict and make a copy
if not isinstance(data, dict):
raise ValueError(f"Expected dict, got {type(data)}")
data = data.copy()
data["status"] = ChainStatus(data["status"])
data["created_at"] = datetime.fromisoformat(data["created_at"])
data["updated_at"] = datetime.fromisoformat(data["updated_at"])
return cls(**data)
class ChainManager:
"""Central manager for multi-chain operations"""
def __init__(self, config_path: Optional[Path] = None):
self.config_path = config_path or Path("./data/chains.json")
self.config_path.parent.mkdir(parents=True, exist_ok=True)
self.chains: Dict[str, ChainConfig] = {}
self.default_chain_id: Optional[str] = None
self._load_chains()
def _load_chains(self):
"""Load chain configurations from file"""
try:
if self.config_path.exists():
with open(self.config_path, 'r') as f:
data = json.load(f)
for chain_data in data.get("chains", []):
chain = ChainConfig.from_dict(chain_data)
self.chains[chain.chain_id] = chain
self.default_chain_id = data.get("default_chain_id")
logger.info(f"Loaded {len(self.chains)} chain configurations")
else:
# Create default chain configuration
self._create_default_chain()
except Exception as e:
logger.error(f"Failed to load chain configurations: {e}")
self._create_default_chain()
def _create_default_chain(self):
"""Create default chain configuration"""
default_chain = ChainConfig(
chain_id="ait-devnet",
name="AITBC Development Network",
coordinator_url="http://localhost:8011",
coordinator_api_key="dev-coordinator-key",
ledger_db_path="./data/wallet_ledger_devnet.db",
keystore_path="./data/keystore_devnet"
)
self.chains[default_chain.chain_id] = default_chain
self.default_chain_id = default_chain.chain_id
self._save_chains()
logger.info(f"Created default chain: {default_chain.chain_id}")
def _save_chains(self):
"""Save chain configurations to file"""
try:
data = {
"chains": [chain.to_dict() for chain in self.chains.values()],
"default_chain_id": self.default_chain_id,
"updated_at": datetime.now().isoformat()
}
with open(self.config_path, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"Saved {len(self.chains)} chain configurations")
except Exception as e:
logger.error(f"Failed to save chain configurations: {e}")
def add_chain(self, chain_config: ChainConfig) -> bool:
"""Add a new chain configuration"""
try:
if chain_config.chain_id in self.chains:
logger.warning(f"Chain {chain_config.chain_id} already exists")
return False
self.chains[chain_config.chain_id] = chain_config
# Set as default if no default exists
if self.default_chain_id is None:
self.default_chain_id = chain_config.chain_id
self._save_chains()
logger.info(f"Added chain: {chain_config.chain_id}")
return True
except Exception as e:
logger.error(f"Failed to add chain {chain_config.chain_id}: {e}")
return False
def remove_chain(self, chain_id: str) -> bool:
"""Remove a chain configuration"""
try:
if chain_id not in self.chains:
logger.warning(f"Chain {chain_id} not found")
return False
if chain_id == self.default_chain_id:
logger.error(f"Cannot remove default chain {chain_id}")
return False
del self.chains[chain_id]
self._save_chains()
logger.info(f"Removed chain: {chain_id}")
return True
except Exception as e:
logger.error(f"Failed to remove chain {chain_id}: {e}")
return False
def get_chain(self, chain_id: str) -> Optional[ChainConfig]:
"""Get chain configuration by ID"""
return self.chains.get(chain_id)
def get_default_chain(self) -> Optional[ChainConfig]:
"""Get default chain configuration"""
if self.default_chain_id:
return self.chains.get(self.default_chain_id)
return None
def set_default_chain(self, chain_id: str) -> bool:
"""Set default chain"""
try:
if chain_id not in self.chains:
logger.error(f"Chain {chain_id} not found")
return False
self.default_chain_id = chain_id
self._save_chains()
logger.info(f"Set default chain: {chain_id}")
return True
except Exception as e:
logger.error(f"Failed to set default chain {chain_id}: {e}")
return False
def list_chains(self) -> List[ChainConfig]:
"""List all chain configurations"""
return list(self.chains.values())
def get_active_chains(self) -> List[ChainConfig]:
"""Get only active chains"""
return [chain for chain in self.chains.values() if chain.status == ChainStatus.ACTIVE]
def update_chain_status(self, chain_id: str, status: ChainStatus) -> bool:
"""Update chain status"""
try:
if chain_id not in self.chains:
logger.error(f"Chain {chain_id} not found")
return False
self.chains[chain_id].status = status
self.chains[chain_id].updated_at = datetime.now()
self._save_chains()
logger.info(f"Updated chain {chain_id} status to {status.value}")
return True
except Exception as e:
logger.error(f"Failed to update chain status {chain_id}: {e}")
return False
def validate_chain_id(self, chain_id: str) -> bool:
"""Validate that a chain ID exists and is active"""
chain = self.chains.get(chain_id)
return chain is not None and chain.status == ChainStatus.ACTIVE
def get_chain_config_for_wallet(self, chain_id: str, wallet_id: str) -> Optional[ChainConfig]:
"""Get chain configuration for a specific wallet operation"""
if not self.validate_chain_id(chain_id):
logger.error(f"Invalid or inactive chain: {chain_id}")
return None
chain = self.chains[chain_id]
# Add wallet-specific context to metadata
chain.metadata["last_wallet_access"] = wallet_id
chain.metadata["last_access_time"] = datetime.now().isoformat()
return chain
def get_chain_stats(self) -> Dict[str, Any]:
"""Get statistics about chains"""
active_chains = self.get_active_chains()
return {
"total_chains": len(self.chains),
"active_chains": len(active_chains),
"inactive_chains": len(self.chains) - len(active_chains),
"default_chain": self.default_chain_id,
"chain_list": [
{
"chain_id": chain.chain_id,
"name": chain.name,
"status": chain.status.value,
"coordinator_url": chain.coordinator_url
}
for chain in self.chains.values()
]
}
# Global chain manager instance
chain_manager = ChainManager()

View File

@@ -0,0 +1,427 @@
"""
Multi-Chain Ledger Adapter for Wallet Daemon
Chain-specific storage and ledger management for wallet operations
across multiple blockchain networks.
"""
from typing import Dict, List, Optional, Any
from pathlib import Path
import sqlite3
import threading
import json
from datetime import datetime
from dataclasses import dataclass, asdict
import logging
from .manager import ChainManager, ChainConfig
logger = logging.getLogger(__name__)
@dataclass
class ChainLedgerRecord:
"""Chain-specific ledger record"""
chain_id: str
wallet_id: str
event_type: str
timestamp: datetime
data: Dict[str, Any]
success: bool = True
@dataclass
class ChainWalletMetadata:
"""Chain-specific wallet metadata"""
chain_id: str
wallet_id: str
public_key: str
address: Optional[str]
metadata: Dict[str, str]
created_at: datetime
updated_at: datetime
class MultiChainLedgerAdapter:
"""Multi-chain ledger adapter with chain-specific storage"""
def __init__(self, chain_manager: ChainManager, base_data_path: Optional[Path] = None):
self.chain_manager = chain_manager
self.base_data_path = base_data_path or Path("./data")
self.base_data_path.mkdir(parents=True, exist_ok=True)
# Separate database connections per chain
self.chain_connections: Dict[str, sqlite3.Connection] = {}
self.chain_locks: Dict[str, threading.Lock] = {}
# Initialize databases for all chains
self._initialize_chain_databases()
def _initialize_chain_databases(self):
"""Initialize database for each chain"""
for chain in self.chain_manager.list_chains():
self._init_chain_database(chain.chain_id)
def _get_chain_db_path(self, chain_id: str) -> Path:
"""Get database path for a specific chain"""
chain = self.chain_manager.get_chain(chain_id)
if chain and chain.ledger_db_path:
return Path(chain.ledger_db_path)
# Default path based on chain ID
return self.base_data_path / f"wallet_ledger_{chain_id}.db"
def _init_chain_database(self, chain_id: str):
"""Initialize database for a specific chain"""
try:
db_path = self._get_chain_db_path(chain_id)
db_path.parent.mkdir(parents=True, exist_ok=True)
# Create connection and lock for this chain
conn = sqlite3.connect(db_path)
self.chain_connections[chain_id] = conn
self.chain_locks[chain_id] = threading.Lock()
# Initialize schema
with self.chain_locks[chain_id]:
self._create_chain_schema(conn, chain_id)
logger.info(f"Initialized database for chain: {chain_id}")
except Exception as e:
logger.error(f"Failed to initialize database for chain {chain_id}: {e}")
def _create_chain_schema(self, conn: sqlite3.Connection, chain_id: str):
"""Create database schema for a specific chain"""
cursor = conn.cursor()
# Wallet metadata table
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS wallet_metadata_{chain_id} (
wallet_id TEXT PRIMARY KEY,
public_key TEXT NOT NULL,
address TEXT,
metadata TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
""")
# Ledger events table
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS ledger_events_{chain_id} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wallet_id TEXT NOT NULL,
event_type TEXT NOT NULL,
timestamp TEXT NOT NULL,
data TEXT,
success BOOLEAN DEFAULT TRUE,
FOREIGN KEY (wallet_id) REFERENCES wallet_metadata_{chain_id} (wallet_id)
)
""")
# Chain-specific indexes
cursor.execute(f"""
CREATE INDEX IF NOT EXISTS idx_wallet_events_{chain_id}
ON ledger_events_{chain_id} (wallet_id, timestamp)
""")
cursor.execute(f"""
CREATE INDEX IF NOT EXISTS idx_wallet_created_{chain_id}
ON wallet_metadata_{chain_id} (created_at)
""")
conn.commit()
def _get_connection(self, chain_id: str) -> Optional[sqlite3.Connection]:
"""Get database connection for a specific chain"""
if chain_id not in self.chain_connections:
self._init_chain_database(chain_id)
return self.chain_connections.get(chain_id)
def _get_lock(self, chain_id: str) -> threading.Lock:
"""Get lock for a specific chain"""
if chain_id not in self.chain_locks:
self.chain_locks[chain_id] = threading.Lock()
return self.chain_locks[chain_id]
def create_wallet(self, chain_id: str, wallet_id: str, public_key: str,
address: Optional[str] = None, metadata: Optional[Dict[str, str]] = None) -> bool:
"""Create wallet in chain-specific database"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
logger.error(f"Invalid chain: {chain_id}")
return False
conn = self._get_connection(chain_id)
if not conn:
return False
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
# Check if wallet already exists
cursor.execute(f"""
SELECT wallet_id FROM wallet_metadata_{chain_id} WHERE wallet_id = ?
""", (wallet_id,))
if cursor.fetchone():
logger.warning(f"Wallet {wallet_id} already exists in chain {chain_id}")
return False
# Insert wallet metadata
now = datetime.now().isoformat()
metadata_json = json.dumps(metadata or {})
cursor.execute(f"""
INSERT INTO wallet_metadata_{chain_id}
(wallet_id, public_key, address, metadata, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
""", (wallet_id, public_key, address, metadata_json, now, now))
# Record creation event
self.record_event(chain_id, wallet_id, "created", {
"public_key": public_key,
"address": address,
"metadata": metadata or {}
})
conn.commit()
logger.info(f"Created wallet {wallet_id} in chain {chain_id}")
return True
except Exception as e:
logger.error(f"Failed to create wallet {wallet_id} in chain {chain_id}: {e}")
return False
def get_wallet(self, chain_id: str, wallet_id: str) -> Optional[ChainWalletMetadata]:
"""Get wallet metadata from chain-specific database"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return None
conn = self._get_connection(chain_id)
if not conn:
return None
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
cursor.execute(f"""
SELECT wallet_id, public_key, address, metadata, created_at, updated_at
FROM wallet_metadata_{chain_id} WHERE wallet_id = ?
""", (wallet_id,))
row = cursor.fetchone()
if not row:
return None
metadata = json.loads(row[3]) if row[3] else {}
return ChainWalletMetadata(
chain_id=chain_id,
wallet_id=row[0],
public_key=row[1],
address=row[2],
metadata=metadata,
created_at=datetime.fromisoformat(row[4]),
updated_at=datetime.fromisoformat(row[5])
)
except Exception as e:
logger.error(f"Failed to get wallet {wallet_id} from chain {chain_id}: {e}")
return None
def list_wallets(self, chain_id: str) -> List[ChainWalletMetadata]:
"""List all wallets in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return []
conn = self._get_connection(chain_id)
if not conn:
return []
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
cursor.execute(f"""
SELECT wallet_id, public_key, address, metadata, created_at, updated_at
FROM wallet_metadata_{chain_id} ORDER BY created_at DESC
""")
wallets = []
for row in cursor.fetchall():
metadata = json.loads(row[3]) if row[3] else {}
wallets.append(ChainWalletMetadata(
chain_id=chain_id,
wallet_id=row[0],
public_key=row[1],
address=row[2],
metadata=metadata,
created_at=datetime.fromisoformat(row[4]),
updated_at=datetime.fromisoformat(row[5])
))
return wallets
except Exception as e:
logger.error(f"Failed to list wallets in chain {chain_id}: {e}")
return []
def record_event(self, chain_id: str, wallet_id: str, event_type: str,
data: Dict[str, Any], success: bool = True) -> bool:
"""Record an event for a wallet in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return False
conn = self._get_connection(chain_id)
if not conn:
return False
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
# Insert event
cursor.execute(f"""
INSERT INTO ledger_events_{chain_id}
(wallet_id, event_type, timestamp, data, success)
VALUES (?, ?, ?, ?, ?)
""", (wallet_id, event_type, datetime.now().isoformat(),
json.dumps(data), success))
conn.commit()
return True
except Exception as e:
logger.error(f"Failed to record event for wallet {wallet_id} in chain {chain_id}: {e}")
return False
def get_wallet_events(self, chain_id: str, wallet_id: str,
event_type: Optional[str] = None, limit: int = 100) -> List[ChainLedgerRecord]:
"""Get events for a wallet in a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return []
conn = self._get_connection(chain_id)
if not conn:
return []
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
if event_type:
cursor.execute(f"""
SELECT wallet_id, event_type, timestamp, data, success
FROM ledger_events_{chain_id}
WHERE wallet_id = ? AND event_type = ?
ORDER BY timestamp DESC LIMIT ?
""", (wallet_id, event_type, limit))
else:
cursor.execute(f"""
SELECT wallet_id, event_type, timestamp, data, success
FROM ledger_events_{chain_id}
WHERE wallet_id = ?
ORDER BY timestamp DESC LIMIT ?
""", (wallet_id, limit))
events = []
for row in cursor.fetchall():
data = json.loads(row[3]) if row[3] else {}
events.append(ChainLedgerRecord(
chain_id=chain_id,
wallet_id=row[0],
event_type=row[1],
timestamp=datetime.fromisoformat(row[2]),
data=data,
success=row[4]
))
return events
except Exception as e:
logger.error(f"Failed to get events for wallet {wallet_id} in chain {chain_id}: {e}")
return []
def get_chain_stats(self, chain_id: str) -> Dict[str, Any]:
"""Get statistics for a specific chain"""
try:
if not self.chain_manager.validate_chain_id(chain_id):
return {}
conn = self._get_connection(chain_id)
if not conn:
return {}
lock = self._get_lock(chain_id)
with lock:
cursor = conn.cursor()
# Wallet count
cursor.execute(f"SELECT COUNT(*) FROM wallet_metadata_{chain_id}")
wallet_count = cursor.fetchone()[0]
# Event count by type
cursor.execute(f"""
SELECT event_type, COUNT(*) FROM ledger_events_{chain_id}
GROUP BY event_type
""")
event_counts = dict(cursor.fetchall())
# Recent activity
cursor.execute(f"""
SELECT COUNT(*) FROM ledger_events_{chain_id}
WHERE timestamp > datetime('now', '-1 hour')
""")
recent_activity = cursor.fetchone()[0]
return {
"chain_id": chain_id,
"wallet_count": wallet_count,
"event_counts": event_counts,
"recent_activity": recent_activity,
"database_path": str(self._get_chain_db_path(chain_id))
}
except Exception as e:
logger.error(f"Failed to get stats for chain {chain_id}: {e}")
return {}
def get_all_chain_stats(self) -> Dict[str, Any]:
"""Get statistics for all chains"""
stats = {
"total_chains": 0,
"total_wallets": 0,
"chain_stats": {}
}
for chain in self.chain_manager.get_active_chains():
chain_stats = self.get_chain_stats(chain.chain_id)
if chain_stats:
stats["chain_stats"][chain.chain_id] = chain_stats
stats["total_wallets"] += chain_stats.get("wallet_count", 0)
stats["total_chains"] += 1
return stats
def close_all_connections(self):
"""Close all database connections"""
for chain_id, conn in self.chain_connections.items():
try:
conn.close()
logger.info(f"Closed connection for chain: {chain_id}")
except Exception as e:
logger.error(f"Failed to close connection for chain {chain_id}: {e}")
self.chain_connections.clear()
self.chain_locks.clear()