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
This commit is contained in:
aitbc
2026-04-02 12:08:15 +02:00
parent d68aa9a234
commit c876b0aa20
206 changed files with 47861 additions and 1 deletions

View File

@@ -0,0 +1,166 @@
"""
Tests for Multi-Validator PoA Consensus
"""
import pytest
import asyncio
from unittest.mock import Mock, patch
from aitbc_chain.consensus.multi_validator_poa import MultiValidatorPoA, ValidatorRole
class TestMultiValidatorPoA:
"""Test cases for multi-validator PoA consensus"""
def setup_method(self):
"""Setup test environment"""
self.consensus = MultiValidatorPoA("test-chain")
# Add test validators
self.validator_addresses = [
"0x1234567890123456789012345678901234567890",
"0x2345678901234567890123456789012345678901",
"0x3456789012345678901234567890123456789012",
"0x4567890123456789012345678901234567890123",
"0x5678901234567890123456789012345678901234"
]
for address in self.validator_addresses:
self.consensus.add_validator(address, 1000.0)
def test_add_validator(self):
"""Test adding a new validator"""
new_validator = "0x6789012345678901234567890123456789012345"
result = self.consensus.add_validator(new_validator, 1500.0)
assert result is True
assert new_validator in self.consensus.validators
assert self.consensus.validators[new_validator].stake == 1500.0
def test_add_duplicate_validator(self):
"""Test adding duplicate validator fails"""
result = self.consensus.add_validator(self.validator_addresses[0], 2000.0)
assert result is False
def test_remove_validator(self):
"""Test removing a validator"""
validator_to_remove = self.validator_addresses[0]
result = self.consensus.remove_validator(validator_to_remove)
assert result is True
assert not self.consensus.validators[validator_to_remove].is_active
assert self.consensus.validators[validator_to_remove].role == ValidatorRole.STANDBY
def test_remove_nonexistent_validator(self):
"""Test removing non-existent validator fails"""
result = self.consensus.remove_validator("0xnonexistent")
assert result is False
def test_select_proposer_round_robin(self):
"""Test round-robin proposer selection"""
# Set all validators as proposers
for address in self.validator_addresses:
self.consensus.validators[address].role = ValidatorRole.PROPOSER
# Test proposer selection for different heights
proposer_0 = self.consensus.select_proposer(0)
proposer_1 = self.consensus.select_proposer(1)
proposer_2 = self.consensus.select_proposer(2)
assert proposer_0 in self.validator_addresses
assert proposer_1 in self.validator_addresses
assert proposer_2 in self.validator_addresses
assert proposer_0 != proposer_1
assert proposer_1 != proposer_2
def test_select_proposer_no_validators(self):
"""Test proposer selection with no active validators"""
# Deactivate all validators
for address in self.validator_addresses:
self.consensus.validators[address].is_active = False
proposer = self.consensus.select_proposer(0)
assert proposer is None
def test_validate_block_valid_proposer(self):
"""Test block validation with valid proposer"""
from aitbc_chain.models import Block
# Set first validator as proposer
proposer = self.validator_addresses[0]
self.consensus.validators[proposer].role = ValidatorRole.PROPOSER
# Create mock block
block = Mock(spec=Block)
block.hash = "0xblockhash"
block.height = 1
result = self.consensus.validate_block(block, proposer)
assert result is True
def test_validate_block_invalid_proposer(self):
"""Test block validation with invalid proposer"""
from aitbc_chain.models import Block
# Create mock block
block = Mock(spec=Block)
block.hash = "0xblockhash"
block.height = 1
# Try to validate with non-existent validator
result = self.consensus.validate_block(block, "0xnonexistent")
assert result is False
def test_get_consensus_participants(self):
"""Test getting consensus participants"""
# Set first 3 validators as active
for i, address in enumerate(self.validator_addresses[:3]):
self.consensus.validators[address].role = ValidatorRole.PROPOSER if i == 0 else ValidatorRole.VALIDATOR
self.consensus.validators[address].is_active = True
# Set remaining validators as standby
for address in self.validator_addresses[3:]:
self.consensus.validators[address].role = ValidatorRole.STANDBY
self.consensus.validators[address].is_active = False
participants = self.consensus.get_consensus_participants()
assert len(participants) == 3
assert self.validator_addresses[0] in participants
assert self.validator_addresses[1] in participants
assert self.validator_addresses[2] in participants
assert self.validator_addresses[3] not in participants
def test_update_validator_reputation(self):
"""Test updating validator reputation"""
validator = self.validator_addresses[0]
initial_reputation = self.consensus.validators[validator].reputation
# Increase reputation
result = self.consensus.update_validator_reputation(validator, 0.1)
assert result is True
assert self.consensus.validators[validator].reputation == initial_reputation + 0.1
# Decrease reputation
result = self.consensus.update_validator_reputation(validator, -0.2)
assert result is True
assert self.consensus.validators[validator].reputation == initial_reputation - 0.1
# Try to update non-existent validator
result = self.consensus.update_validator_reputation("0xnonexistent", 0.1)
assert result is False
def test_reputation_bounds(self):
"""Test reputation stays within bounds [0.0, 1.0]"""
validator = self.validator_addresses[0]
# Try to increase beyond 1.0
result = self.consensus.update_validator_reputation(validator, 0.5)
assert result is True
assert self.consensus.validators[validator].reputation == 1.0
# Try to decrease below 0.0
result = self.consensus.update_validator_reputation(validator, -1.5)
assert result is True
assert self.consensus.validators[validator].reputation == 0.0
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -0,0 +1,402 @@
"""
Tests for Escrow System
"""
import pytest
import asyncio
import time
from decimal import Decimal
from unittest.mock import Mock, patch
from aitbc_chain.contracts.escrow import EscrowManager, EscrowState, DisputeReason
class TestEscrowManager:
"""Test cases for escrow manager"""
def setup_method(self):
"""Setup test environment"""
self.escrow_manager = EscrowManager()
def test_create_contract(self):
"""Test escrow contract creation"""
success, message, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_001",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
assert success, f"Contract creation failed: {message}"
assert contract_id is not None
# Check contract details
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract is not None
assert contract.job_id == "job_001"
assert contract.client_address == "0x1234567890123456789012345678901234567890"
assert contract.agent_address == "0x2345678901234567890123456789012345678901"
assert contract.amount > Decimal('100.0') # Includes platform fee
assert contract.state == EscrowState.CREATED
def test_create_contract_invalid_inputs(self):
"""Test contract creation with invalid inputs"""
success, message, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="", # Empty job ID
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
assert not success
assert contract_id is None
assert "invalid" in message.lower()
def test_create_contract_with_milestones(self):
"""Test contract creation with milestones"""
milestones = [
{
'milestone_id': 'milestone_1',
'description': 'Initial setup',
'amount': Decimal('30.0')
},
{
'milestone_id': 'milestone_2',
'description': 'Main work',
'amount': Decimal('50.0')
},
{
'milestone_id': 'milestone_3',
'description': 'Final delivery',
'amount': Decimal('20.0')
}
]
success, message, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_002",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0'),
milestones=milestones
)
)
assert success
assert contract_id is not None
# Check milestones
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert len(contract.milestones) == 3
assert contract.milestones[0]['amount'] == Decimal('30.0')
assert contract.milestones[1]['amount'] == Decimal('50.0')
assert contract.milestones[2]['amount'] == Decimal('20.0')
def test_create_contract_invalid_milestones(self):
"""Test contract creation with invalid milestones"""
milestones = [
{
'milestone_id': 'milestone_1',
'description': 'Setup',
'amount': Decimal('30.0')
},
{
'milestone_id': 'milestone_2',
'description': 'Main work',
'amount': Decimal('80.0') # Total exceeds contract amount
}
]
success, message, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_003",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0'),
milestones=milestones
)
)
assert not success
assert "milestones" in message.lower()
def test_fund_contract(self):
"""Test funding contract"""
# Create contract first
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_004",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
assert success
# Fund contract
success, message = asyncio.run(
self.escrow_manager.fund_contract(contract_id, "tx_hash_001")
)
assert success, f"Contract funding failed: {message}"
# Check state
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract.state == EscrowState.FUNDED
def test_fund_already_funded_contract(self):
"""Test funding already funded contract"""
# Create and fund contract
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_005",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
# Try to fund again
success, message = asyncio.run(
self.escrow_manager.fund_contract(contract_id, "tx_hash_002")
)
assert not success
assert "state" in message.lower()
def test_start_job(self):
"""Test starting job"""
# Create and fund contract
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_006",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
# Start job
success, message = asyncio.run(self.escrow_manager.start_job(contract_id))
assert success, f"Job start failed: {message}"
# Check state
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract.state == EscrowState.JOB_STARTED
def test_complete_milestone(self):
"""Test completing milestone"""
milestones = [
{
'milestone_id': 'milestone_1',
'description': 'Setup',
'amount': Decimal('50.0')
},
{
'milestone_id': 'milestone_2',
'description': 'Delivery',
'amount': Decimal('50.0')
}
]
# Create contract with milestones
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_007",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0'),
milestones=milestones
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
asyncio.run(self.escrow_manager.start_job(contract_id))
# Complete milestone
success, message = asyncio.run(
self.escrow_manager.complete_milestone(contract_id, "milestone_1")
)
assert success, f"Milestone completion failed: {message}"
# Check milestone status
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
milestone = contract.milestones[0]
assert milestone['completed']
assert milestone['completed_at'] is not None
def test_verify_milestone(self):
"""Test verifying milestone"""
milestones = [
{
'milestone_id': 'milestone_1',
'description': 'Setup',
'amount': Decimal('50.0')
}
]
# Create contract with milestone
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_008",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0'),
milestones=milestones
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
asyncio.run(self.escrow_manager.start_job(contract_id))
asyncio.run(self.escrow_manager.complete_milestone(contract_id, "milestone_1"))
# Verify milestone
success, message = asyncio.run(
self.escrow_manager.verify_milestone(contract_id, "milestone_1", True, "Work completed successfully")
)
assert success, f"Milestone verification failed: {message}"
# Check verification status
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
milestone = contract.milestones[0]
assert milestone['verified']
assert milestone['verification_feedback'] == "Work completed successfully"
def test_create_dispute(self):
"""Test creating dispute"""
# Create and fund contract
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_009",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
asyncio.run(self.escrow_manager.start_job(contract_id))
# Create dispute
evidence = [
{
'type': 'screenshot',
'description': 'Poor quality work',
'timestamp': time.time()
}
]
success, message = asyncio.run(
self.escrow_manager.create_dispute(
contract_id, DisputeReason.QUALITY_ISSUES, "Work quality is poor", evidence
)
)
assert success, f"Dispute creation failed: {message}"
# Check dispute status
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract.state == EscrowState.DISPUTED
assert contract.dispute_reason == DisputeReason.QUALITY_ISSUES
def test_resolve_dispute(self):
"""Test resolving dispute"""
# Create and fund contract
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_010",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
asyncio.run(self.escrow_manager.start_job(contract_id))
# Create dispute
asyncio.run(
self.escrow_manager.create_dispute(
contract_id, DisputeReason.QUALITY_ISSUES, "Quality issues"
)
)
# Resolve dispute
resolution = {
'winner': 'client',
'client_refund': 0.8, # 80% refund
'agent_payment': 0.2 # 20% payment
}
success, message = asyncio.run(
self.escrow_manager.resolve_dispute(contract_id, resolution)
)
assert success, f"Dispute resolution failed: {message}"
# Check resolution
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract.state == EscrowState.RESOLVED
assert contract.resolution == resolution
def test_refund_contract(self):
"""Test refunding contract"""
# Create and fund contract
success, _, contract_id = asyncio.run(
self.escrow_manager.create_contract(
job_id="job_011",
client_address="0x1234567890123456789012345678901234567890",
agent_address="0x2345678901234567890123456789012345678901",
amount=Decimal('100.0')
)
)
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
# Refund contract
success, message = asyncio.run(
self.escrow_manager.refund_contract(contract_id, "Client requested refund")
)
assert success, f"Refund failed: {message}"
# Check refund status
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
assert contract.state == EscrowState.REFUNDED
assert contract.refunded_amount > 0
def test_get_escrow_statistics(self):
"""Test getting escrow statistics"""
# Create multiple contracts
for i in range(5):
asyncio.run(
self.escrow_manager.create_contract(
job_id=f"job_{i:03d}",
client_address=f"0x123456789012345678901234567890123456789{i}",
agent_address=f"0x234567890123456789012345678901234567890{i}",
amount=Decimal('100.0')
)
)
stats = asyncio.run(self.escrow_manager.get_escrow_statistics())
assert 'total_contracts' in stats
assert 'active_contracts' in stats
assert 'disputed_contracts' in stats
assert 'state_distribution' in stats
assert 'total_amount' in stats
assert stats['total_contracts'] >= 5
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -0,0 +1,239 @@
"""
Tests for Staking Mechanism
"""
import pytest
import time
from decimal import Decimal
from unittest.mock import Mock, patch
from aitbc_chain.economics.staking import StakingManager, StakingStatus
class TestStakingManager:
"""Test cases for staking manager"""
def setup_method(self):
"""Setup test environment"""
self.staking_manager = StakingManager(min_stake_amount=1000.0)
# Register a test validator
success, message = self.staking_manager.register_validator(
"0xvalidator1", 2000.0, 0.05
)
assert success, f"Failed to register validator: {message}"
def test_register_validator(self):
"""Test validator registration"""
# Valid registration
success, message = self.staking_manager.register_validator(
"0xvalidator2", 1500.0, 0.03
)
assert success, f"Validator registration failed: {message}"
# Check validator info
validator_info = self.staking_manager.get_validator_stake_info("0xvalidator2")
assert validator_info is not None
assert validator_info.validator_address == "0xvalidator2"
assert float(validator_info.self_stake) == 1500.0
assert validator_info.commission_rate == 0.03
def test_register_validator_insufficient_stake(self):
"""Test validator registration with insufficient stake"""
success, message = self.staking_manager.register_validator(
"0xvalidator3", 500.0, 0.05
)
assert not success
assert "insufficient stake" in message.lower()
def test_register_validator_invalid_commission(self):
"""Test validator registration with invalid commission"""
success, message = self.staking_manager.register_validator(
"0xvalidator4", 1500.0, 0.15 # Too high
)
assert not success
assert "commission" in message.lower()
def test_register_duplicate_validator(self):
"""Test registering duplicate validator"""
success, message = self.staking_manager.register_validator(
"0xvalidator1", 2000.0, 0.05
)
assert not success
assert "already registered" in message.lower()
def test_stake_to_validator(self):
"""Test staking to validator"""
success, message = self.staking_manager.stake(
"0xvalidator1", "0xdelegator1", 1200.0
)
assert success, f"Staking failed: {message}"
# Check stake position
position = self.staking_manager.get_stake_position("0xvalidator1", "0xdelegator1")
assert position is not None
assert position.validator_address == "0xvalidator1"
assert position.delegator_address == "0xdelegator1"
assert float(position.amount) == 1200.0
assert position.status == StakingStatus.ACTIVE
def test_stake_insufficient_amount(self):
"""Test staking insufficient amount"""
success, message = self.staking_manager.stake(
"0xvalidator1", "0xdelegator2", 500.0
)
assert not success
assert "at least" in message.lower()
def test_stake_to_nonexistent_validator(self):
"""Test staking to non-existent validator"""
success, message = self.staking_manager.stake(
"0xnonexistent", "0xdelegator3", 1200.0
)
assert not success
assert "not found" in message.lower() or "not active" in message.lower()
def test_unstake(self):
"""Test unstaking"""
# First stake
success, _ = self.staking_manager.stake("0xvalidator1", "0xdelegator4", 1200.0)
assert success
# Then unstake
success, message = self.staking_manager.unstake("0xvalidator1", "0xdelegator4")
assert success, f"Unstaking failed: {message}"
# Check position status
position = self.staking_manager.get_stake_position("0xvalidator1", "0xdelegator4")
assert position is not None
assert position.status == StakingStatus.UNSTAKING
def test_unstake_nonexistent_position(self):
"""Test unstaking non-existent position"""
success, message = self.staking_manager.unstake("0xvalidator1", "0xnonexistent")
assert not success
assert "not found" in message.lower()
def test_unstake_locked_position(self):
"""Test unstaking locked position"""
# Stake with long lock period
success, _ = self.staking_manager.stake("0xvalidator1", "0xdelegator5", 1200.0, 90)
assert success
# Try to unstake immediately
success, message = self.staking_manager.unstake("0xvalidator1", "0xdelegator5")
assert not success
assert "lock period" in message.lower()
def test_withdraw(self):
"""Test withdrawal after unstaking period"""
# Stake and unstake
success, _ = self.staking_manager.stake("0xvalidator1", "0xdelegator6", 1200.0, 1) # 1 day lock
assert success
success, _ = self.staking_manager.unstake("0xvalidator1", "0xdelegator6")
assert success
# Wait for unstaking period (simulate with direct manipulation)
position = self.staking_manager.get_stake_position("0xvalidator1", "0xdelegator6")
if position:
position.staked_at = time.time() - (2 * 24 * 3600) # 2 days ago
# Withdraw
success, message, amount = self.staking_manager.withdraw("0xvalidator1", "0xdelegator6")
assert success, f"Withdrawal failed: {message}"
assert amount == 1200.0 # Should get back the full amount
# Check position status
position = self.staking_manager.get_stake_position("0xvalidator1", "0xdelegator6")
assert position is not None
assert position.status == StakingStatus.WITHDRAWN
def test_withdraw_too_early(self):
"""Test withdrawal before unstaking period completes"""
# Stake and unstake
success, _ = self.staking_manager.stake("0xvalidator1", "0xdelegator7", 1200.0, 30) # 30 days
assert success
success, _ = self.staking_manager.unstake("0xvalidator1", "0xdelegator7")
assert success
# Try to withdraw immediately
success, message, amount = self.staking_manager.withdraw("0xvalidator1", "0xdelegator7")
assert not success
assert "not completed" in message.lower()
assert amount == 0.0
def test_slash_validator(self):
"""Test validator slashing"""
# Stake to validator
success, _ = self.staking_manager.stake("0xvalidator1", "0xdelegator8", 1200.0)
assert success
# Slash validator
success, message = self.staking_manager.slash_validator("0xvalidator1", 0.1, "Test slash")
assert success, f"Slashing failed: {message}"
# Check stake reduction
position = self.staking_manager.get_stake_position("0xvalidator1", "0xdelegator8")
assert position is not None
assert float(position.amount) == 1080.0 # 10% reduction
assert position.slash_count == 1
def test_get_validator_stake_info(self):
"""Test getting validator stake information"""
# Add delegators
self.staking_manager.stake("0xvalidator1", "0xdelegator9", 1000.0)
self.staking_manager.stake("0xvalidator1", "0xdelegator10", 1500.0)
info = self.staking_manager.get_validator_stake_info("0xvalidator1")
assert info is not None
assert float(info.self_stake) == 2000.0
assert float(info.delegated_stake) == 2500.0
assert float(info.total_stake) == 4500.0
assert info.delegators_count == 2
def test_get_all_validators(self):
"""Test getting all validators"""
# Register another validator
self.staking_manager.register_validator("0xvalidator5", 1800.0, 0.04)
validators = self.staking_manager.get_all_validators()
assert len(validators) >= 2
validator_addresses = [v.validator_address for v in validators]
assert "0xvalidator1" in validator_addresses
assert "0xvalidator5" in validator_addresses
def test_get_active_validators(self):
"""Test getting active validators only"""
# Unregister one validator
self.staking_manager.unregister_validator("0xvalidator1")
active_validators = self.staking_manager.get_active_validators()
validator_addresses = [v.validator_address for v in active_validators]
assert "0xvalidator1" not in validator_addresses
def test_get_total_staked(self):
"""Test getting total staked amount"""
# Add some stakes
self.staking_manager.stake("0xvalidator1", "0xdelegator11", 1000.0)
self.staking_manager.stake("0xvalidator1", "0xdelegator12", 2000.0)
total = self.staking_manager.get_total_staked()
expected = 2000.0 + 1000.0 + 2000.0 + 2000.0 # validator1 self-stake + delegators
assert float(total) == expected
def test_get_staking_statistics(self):
"""Test staking statistics"""
stats = self.staking_manager.get_staking_statistics()
assert 'total_validators' in stats
assert 'total_staked' in stats
assert 'total_delegators' in stats
assert 'average_stake_per_validator' in stats
assert stats['total_validators'] >= 1
assert stats['total_staked'] >= 2000.0 # At least the initial validator stake
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -0,0 +1,101 @@
"""
Tests for P2P Discovery Service
"""
import pytest
import asyncio
from unittest.mock import Mock, patch
from aitbc_chain.network.discovery import P2PDiscovery, PeerNode, NodeStatus
class TestP2PDiscovery:
"""Test cases for P2P discovery service"""
def setup_method(self):
"""Setup test environment"""
self.discovery = P2PDiscovery("test-node", "127.0.0.1", 8000)
# Add bootstrap nodes
self.discovery.add_bootstrap_node("127.0.0.1", 8001)
self.discovery.add_bootstrap_node("127.0.0.1", 8002)
def test_generate_node_id(self):
"""Test node ID generation"""
address = "127.0.0.1"
port = 8000
public_key = "test_public_key"
node_id = self.discovery.generate_node_id(address, port, public_key)
assert isinstance(node_id, str)
assert len(node_id) == 64 # SHA256 hex length
# Test consistency
node_id2 = self.discovery.generate_node_id(address, port, public_key)
assert node_id == node_id2
def test_add_bootstrap_node(self):
"""Test adding bootstrap node"""
initial_count = len(self.discovery.bootstrap_nodes)
self.discovery.add_bootstrap_node("127.0.0.1", 8003)
assert len(self.discovery.bootstrap_nodes) == initial_count + 1
assert ("127.0.0.1", 8003) in self.discovery.bootstrap_nodes
def test_generate_node_id_consistency(self):
"""Test node ID generation consistency"""
address = "192.168.1.1"
port = 9000
public_key = "test_key"
node_id1 = self.discovery.generate_node_id(address, port, public_key)
node_id2 = self.discovery.generate_node_id(address, port, public_key)
assert node_id1 == node_id2
# Different inputs should produce different IDs
node_id3 = self.discovery.generate_node_id("192.168.1.2", port, public_key)
assert node_id1 != node_id3
def test_get_peer_count_empty(self):
"""Test getting peer count with no peers"""
assert self.discovery.get_peer_count() == 0
def test_get_peer_list_empty(self):
"""Test getting peer list with no peers"""
assert self.discovery.get_peer_list() == []
def test_update_peer_reputation_new_peer(self):
"""Test updating reputation for non-existent peer"""
result = self.discovery.update_peer_reputation("nonexistent", 0.1)
assert result is False
def test_update_peer_reputation_bounds(self):
"""Test reputation bounds"""
# Add a test peer
peer = PeerNode(
node_id="test_peer",
address="127.0.0.1",
port=8001,
public_key="test_key",
last_seen=0,
status=NodeStatus.ONLINE,
capabilities=["test"],
reputation=0.5,
connection_count=0
)
self.discovery.peers["test_peer"] = peer
# Try to increase beyond 1.0
result = self.discovery.update_peer_reputation("test_peer", 0.6)
assert result is True
assert self.discovery.peers["test_peer"].reputation == 1.0
# Try to decrease below 0.0
result = self.discovery.update_peer_reputation("test_peer", -1.5)
assert result is True
assert self.discovery.peers["test_peer"].reputation == 0.0
if __name__ == "__main__":
pytest.main([__file__])