Files
aitbc/apps/blockchain-node/src/aitbc_chain/economics/staking.py
aitbc c876b0aa20 feat: implement AITBC mesh network deployment infrastructure
 Phase 0: Pre-implementation checklist completed
- Environment configurations (dev/staging/production)
- Directory structure setup (logs, backups, monitoring)
- Virtual environment with dependencies

 Master deployment script created
- Single command deployment with validation
- Progress tracking and rollback capability
- Health checks and deployment reporting

 Validation script created
- Module import validation
- Basic functionality testing
- Configuration and script verification

 Implementation fixes
- Fixed dataclass import in consensus keys
- Fixed async function syntax in tests
- Updated deployment script for virtual environment

🚀 Ready for deployment: ./scripts/deploy-mesh-network.sh dev
2026-04-02 12:08:15 +02:00

399 lines
16 KiB
Python

"""
Staking Mechanism Implementation
Handles validator staking, delegation, and stake management
"""
import asyncio
import time
import json
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
from decimal import Decimal
class StakingStatus(Enum):
ACTIVE = "active"
UNSTAKING = "unstaking"
WITHDRAWN = "withdrawn"
SLASHED = "slashed"
@dataclass
class StakePosition:
validator_address: str
delegator_address: str
amount: Decimal
staked_at: float
lock_period: int # days
status: StakingStatus
rewards: Decimal
slash_count: int
@dataclass
class ValidatorStakeInfo:
validator_address: str
total_stake: Decimal
self_stake: Decimal
delegated_stake: Decimal
delegators_count: int
commission_rate: float # percentage
performance_score: float
is_active: bool
class StakingManager:
"""Manages validator staking and delegation"""
def __init__(self, min_stake_amount: float = 1000.0):
self.min_stake_amount = Decimal(str(min_stake_amount))
self.stake_positions: Dict[str, StakePosition] = {} # key: validator:delegator
self.validator_info: Dict[str, ValidatorStakeInfo] = {}
self.unstaking_requests: Dict[str, float] = {} # key: validator:delegator, value: request_time
self.slashing_events: List[Dict] = []
# Staking parameters
self.unstaking_period = 21 # days
self.max_delegators_per_validator = 100
self.commission_range = (0.01, 0.10) # 1% to 10%
def stake(self, validator_address: str, delegator_address: str, amount: float,
lock_period: int = 30) -> Tuple[bool, str]:
"""Stake tokens for validator"""
try:
amount_decimal = Decimal(str(amount))
# Validate amount
if amount_decimal < self.min_stake_amount:
return False, f"Amount must be at least {self.min_stake_amount}"
# Check if validator exists and is active
validator_info = self.validator_info.get(validator_address)
if not validator_info or not validator_info.is_active:
return False, "Validator not found or not active"
# Check delegator limit
if delegator_address != validator_address:
delegator_count = len([
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.delegator_address == delegator_address and
pos.status == StakingStatus.ACTIVE
])
if delegator_count >= 1: # One stake per delegator per validator
return False, "Already staked to this validator"
# Check total delegators limit
total_delegators = len([
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.delegator_address != validator_address and
pos.status == StakingStatus.ACTIVE
])
if total_delegators >= self.max_delegators_per_validator:
return False, "Validator has reached maximum delegator limit"
# Create stake position
position_key = f"{validator_address}:{delegator_address}"
stake_position = StakePosition(
validator_address=validator_address,
delegator_address=delegator_address,
amount=amount_decimal,
staked_at=time.time(),
lock_period=lock_period,
status=StakingStatus.ACTIVE,
rewards=Decimal('0'),
slash_count=0
)
self.stake_positions[position_key] = stake_position
# Update validator info
self._update_validator_stake_info(validator_address)
return True, "Stake successful"
except Exception as e:
return False, f"Staking failed: {str(e)}"
def unstake(self, validator_address: str, delegator_address: str) -> Tuple[bool, str]:
"""Request unstaking (start unlock period)"""
position_key = f"{validator_address}:{delegator_address}"
position = self.stake_positions.get(position_key)
if not position:
return False, "Stake position not found"
if position.status != StakingStatus.ACTIVE:
return False, f"Cannot unstake from {position.status.value} position"
# Check lock period
if time.time() - position.staked_at < (position.lock_period * 24 * 3600):
return False, "Stake is still in lock period"
# Start unstaking
position.status = StakingStatus.UNSTAKING
self.unstaking_requests[position_key] = time.time()
# Update validator info
self._update_validator_stake_info(validator_address)
return True, "Unstaking request submitted"
def withdraw(self, validator_address: str, delegator_address: str) -> Tuple[bool, str, float]:
"""Withdraw unstaked tokens"""
position_key = f"{validator_address}:{delegator_address}"
position = self.stake_positions.get(position_key)
if not position:
return False, "Stake position not found", 0.0
if position.status != StakingStatus.UNSTAKING:
return False, f"Position not in unstaking status: {position.status.value}", 0.0
# Check unstaking period
request_time = self.unstaking_requests.get(position_key, 0)
if time.time() - request_time < (self.unstaking_period * 24 * 3600):
remaining_time = (self.unstaking_period * 24 * 3600) - (time.time() - request_time)
return False, f"Unstaking period not completed. {remaining_time/3600:.1f} hours remaining", 0.0
# Calculate withdrawal amount (including rewards)
withdrawal_amount = float(position.amount + position.rewards)
# Update position status
position.status = StakingStatus.WITHDRAWN
# Clean up
self.unstaking_requests.pop(position_key, None)
# Update validator info
self._update_validator_stake_info(validator_address)
return True, "Withdrawal successful", withdrawal_amount
def register_validator(self, validator_address: str, self_stake: float,
commission_rate: float = 0.05) -> Tuple[bool, str]:
"""Register a new validator"""
try:
self_stake_decimal = Decimal(str(self_stake))
# Validate self stake
if self_stake_decimal < self.min_stake_amount:
return False, f"Self stake must be at least {self.min_stake_amount}"
# Validate commission rate
if not (self.commission_range[0] <= commission_rate <= self.commission_range[1]):
return False, f"Commission rate must be between {self.commission_range[0]} and {self.commission_range[1]}"
# Check if already registered
if validator_address in self.validator_info:
return False, "Validator already registered"
# Create validator info
self.validator_info[validator_address] = ValidatorStakeInfo(
validator_address=validator_address,
total_stake=self_stake_decimal,
self_stake=self_stake_decimal,
delegated_stake=Decimal('0'),
delegators_count=0,
commission_rate=commission_rate,
performance_score=1.0,
is_active=True
)
# Create self-stake position
position_key = f"{validator_address}:{validator_address}"
stake_position = StakePosition(
validator_address=validator_address,
delegator_address=validator_address,
amount=self_stake_decimal,
staked_at=time.time(),
lock_period=90, # 90 days for validator self-stake
status=StakingStatus.ACTIVE,
rewards=Decimal('0'),
slash_count=0
)
self.stake_positions[position_key] = stake_position
return True, "Validator registered successfully"
except Exception as e:
return False, f"Validator registration failed: {str(e)}"
def unregister_validator(self, validator_address: str) -> Tuple[bool, str]:
"""Unregister validator (if no delegators)"""
validator_info = self.validator_info.get(validator_address)
if not validator_info:
return False, "Validator not found"
# Check for delegators
delegator_positions = [
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.delegator_address != validator_address and
pos.status == StakingStatus.ACTIVE
]
if delegator_positions:
return False, "Cannot unregister validator with active delegators"
# Unstake self stake
success, message = self.unstake(validator_address, validator_address)
if not success:
return False, f"Cannot unstake self stake: {message}"
# Mark as inactive
validator_info.is_active = False
return True, "Validator unregistered successfully"
def slash_validator(self, validator_address: str, slash_percentage: float,
reason: str) -> Tuple[bool, str]:
"""Slash validator for misbehavior"""
try:
validator_info = self.validator_info.get(validator_address)
if not validator_info:
return False, "Validator not found"
# Get all stake positions for this validator
validator_positions = [
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.status in [StakingStatus.ACTIVE, StakingStatus.UNSTAKING]
]
if not validator_positions:
return False, "No active stakes found for validator"
# Apply slash to all positions
total_slashed = Decimal('0')
for position in validator_positions:
slash_amount = position.amount * Decimal(str(slash_percentage))
position.amount -= slash_amount
position.rewards = Decimal('0') # Reset rewards
position.slash_count += 1
total_slashed += slash_amount
# Mark as slashed if amount is too low
if position.amount < self.min_stake_amount:
position.status = StakingStatus.SLASHED
# Record slashing event
self.slashing_events.append({
'validator_address': validator_address,
'slash_percentage': slash_percentage,
'reason': reason,
'timestamp': time.time(),
'total_slashed': float(total_slashed),
'affected_positions': len(validator_positions)
})
# Update validator info
validator_info.performance_score = max(0.0, validator_info.performance_score - 0.1)
self._update_validator_stake_info(validator_address)
return True, f"Slashed {len(validator_positions)} stake positions"
except Exception as e:
return False, f"Slashing failed: {str(e)}"
def _update_validator_stake_info(self, validator_address: str):
"""Update validator stake information"""
validator_positions = [
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.status == StakingStatus.ACTIVE
]
if not validator_positions:
if validator_address in self.validator_info:
self.validator_info[validator_address].total_stake = Decimal('0')
self.validator_info[validator_address].delegated_stake = Decimal('0')
self.validator_info[validator_address].delegators_count = 0
return
validator_info = self.validator_info.get(validator_address)
if not validator_info:
return
# Calculate stakes
self_stake = Decimal('0')
delegated_stake = Decimal('0')
delegators = set()
for position in validator_positions:
if position.delegator_address == validator_address:
self_stake += position.amount
else:
delegated_stake += position.amount
delegators.add(position.delegator_address)
validator_info.self_stake = self_stake
validator_info.delegated_stake = delegated_stake
validator_info.total_stake = self_stake + delegated_stake
validator_info.delegators_count = len(delegators)
def get_stake_position(self, validator_address: str, delegator_address: str) -> Optional[StakePosition]:
"""Get stake position"""
position_key = f"{validator_address}:{delegator_address}"
return self.stake_positions.get(position_key)
def get_validator_stake_info(self, validator_address: str) -> Optional[ValidatorStakeInfo]:
"""Get validator stake information"""
return self.validator_info.get(validator_address)
def get_all_validators(self) -> List[ValidatorStakeInfo]:
"""Get all registered validators"""
return list(self.validator_info.values())
def get_active_validators(self) -> List[ValidatorStakeInfo]:
"""Get active validators"""
return [v for v in self.validator_info.values() if v.is_active]
def get_delegators(self, validator_address: str) -> List[StakePosition]:
"""Get delegators for validator"""
return [
pos for pos in self.stake_positions.values()
if pos.validator_address == validator_address and
pos.delegator_address != validator_address and
pos.status == StakingStatus.ACTIVE
]
def get_total_staked(self) -> Decimal:
"""Get total amount staked across all validators"""
return sum(
pos.amount for pos in self.stake_positions.values()
if pos.status == StakingStatus.ACTIVE
)
def get_staking_statistics(self) -> Dict:
"""Get staking system statistics"""
active_positions = [
pos for pos in self.stake_positions.values()
if pos.status == StakingStatus.ACTIVE
]
return {
'total_validators': len(self.get_active_validators()),
'total_staked': float(self.get_total_staked()),
'total_delegators': len(set(pos.delegator_address for pos in active_positions
if pos.delegator_address != pos.validator_address)),
'average_stake_per_validator': float(sum(v.total_stake for v in self.get_active_validators()) / len(self.get_active_validators())) if self.get_active_validators() else 0,
'total_slashing_events': len(self.slashing_events),
'unstaking_requests': len(self.unstaking_requests)
}
# Global staking manager
staking_manager: Optional[StakingManager] = None
def get_staking_manager() -> Optional[StakingManager]:
"""Get global staking manager"""
return staking_manager
def create_staking_manager(min_stake_amount: float = 1000.0) -> StakingManager:
"""Create and set global staking manager"""
global staking_manager
staking_manager = StakingManager(min_stake_amount)
return staking_manager