Files
aitbc/apps/wallet/src/app/chain/chain_aware_wallet_service.py
oib 15427c96c0 chore: update file permissions to executable across repository
- Change file mode from 644 to 755 for all project files
- Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet"
- Rename Miner.extra_meta_data to extra_metadata for consistency
2026-03-06 22:17:54 +01:00

415 lines
17 KiB
Python
Executable File

"""
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}")