Files
aitbc/tests/integration/test_blockchain_node.py
oib c8be9d7414 feat: add marketplace metrics, privacy features, and service registry endpoints
- Add Prometheus metrics for marketplace API throughput and error rates with new dashboard panels
- Implement confidential transaction models with encryption support and access control
- Add key management system with registration, rotation, and audit logging
- Create services and registry routers for service discovery and management
- Integrate ZK proof generation for privacy-preserving receipts
- Add metrics instru
2025-12-22 10:33:23 +01:00

534 lines
19 KiB
Python

"""
Integration tests for AITBC Blockchain Node
"""
import pytest
import asyncio
import json
import websockets
from datetime import datetime, timedelta
from unittest.mock import Mock, patch, AsyncMock
import requests
from apps.blockchain_node.src.aitbc_chain.models import Block, Transaction, Receipt, Account
from apps.blockchain_node.src.aitbc_chain.consensus.poa import PoAConsensus
from apps.blockchain_node.src.aitbc_chain.rpc.router import router
from apps.blockchain_node.src.aitbc_chain.rpc.websocket import WebSocketManager
@pytest.mark.integration
class TestBlockchainNodeRPC:
"""Test blockchain node RPC endpoints"""
@pytest.fixture
def blockchain_client(self):
"""Create a test client for blockchain node"""
base_url = "http://localhost:8545"
return requests.Session()
# Note: In real tests, this would connect to a running test instance
def test_get_block_by_number(self, blockchain_client):
"""Test getting block by number"""
with patch('apps.blockchain_node.src.aitbc_chain.rpc.handlers.get_block_by_number') as mock_handler:
mock_handler.return_value = {
"number": 100,
"hash": "0x123",
"timestamp": datetime.utcnow().timestamp(),
"transactions": [],
}
response = blockchain_client.post(
"http://localhost:8545",
json={
"jsonrpc": "2.0",
"method": "eth_getBlockByNumber",
"params": ["0x64", True],
"id": 1
}
)
assert response.status_code == 200
data = response.json()
assert data["jsonrpc"] == "2.0"
assert "result" in data
assert data["result"]["number"] == 100
def test_get_transaction_by_hash(self, blockchain_client):
"""Test getting transaction by hash"""
with patch('apps.blockchain_node.src.aitbc_chain.rpc.handlers.get_transaction_by_hash') as mock_handler:
mock_handler.return_value = {
"hash": "0x456",
"blockNumber": 100,
"from": "0xabc",
"to": "0xdef",
"value": "1000",
"status": "0x1",
}
response = blockchain_client.post(
"http://localhost:8545",
json={
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",
"params": ["0x456"],
"id": 1
}
)
assert response.status_code == 200
data = response.json()
assert data["result"]["hash"] == "0x456"
def test_send_raw_transaction(self, blockchain_client):
"""Test sending raw transaction"""
with patch('apps.blockchain_node.src.aitbc_chain.rpc.handlers.send_raw_transaction') as mock_handler:
mock_handler.return_value = "0x789"
response = blockchain_client.post(
"http://localhost:8545",
json={
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0xrawtx"],
"id": 1
}
)
assert response.status_code == 200
data = response.json()
assert data["result"] == "0x789"
def test_get_balance(self, blockchain_client):
"""Test getting account balance"""
with patch('apps.blockchain_node.src.aitbc_chain.rpc.handlers.get_balance') as mock_handler:
mock_handler.return_value = "0x1520F41CC0B40000" # 100000 ETH in wei
response = blockchain_client.post(
"http://localhost:8545",
json={
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xabc", "latest"],
"id": 1
}
)
assert response.status_code == 200
data = response.json()
assert data["result"] == "0x1520F41CC0B40000"
def test_get_block_range(self, blockchain_client):
"""Test getting a range of blocks"""
with patch('apps.blockchain_node.src.aitbc_chain.rpc.handlers.get_block_range') as mock_handler:
mock_handler.return_value = [
{"number": 100, "hash": "0x100"},
{"number": 101, "hash": "0x101"},
{"number": 102, "hash": "0x102"},
]
response = blockchain_client.post(
"http://localhost:8545",
json={
"jsonrpc": "2.0",
"method": "aitbc_getBlockRange",
"params": [100, 102],
"id": 1
}
)
assert response.status_code == 200
data = response.json()
assert len(data["result"]) == 3
assert data["result"][0]["number"] == 100
@pytest.mark.integration
class TestWebSocketSubscriptions:
"""Test WebSocket subscription functionality"""
async def test_subscribe_new_blocks(self):
"""Test subscribing to new blocks"""
with patch('websockets.connect') as mock_connect:
mock_ws = AsyncMock()
mock_connect.return_value.__aenter__.return_value = mock_ws
# Mock subscription response
mock_ws.recv.side_effect = [
json.dumps({"id": 1, "result": "0xsubscription"}),
json.dumps({
"subscription": "0xsubscription",
"result": {
"number": 101,
"hash": "0xnewblock",
}
})
]
# Connect and subscribe
async with websockets.connect("ws://localhost:8546") as ws:
await ws.send(json.dumps({
"id": 1,
"method": "eth_subscribe",
"params": ["newHeads"]
}))
# Get subscription ID
response = await ws.recv()
sub_data = json.loads(response)
assert "result" in sub_data
# Get block notification
notification = await ws.recv()
block_data = json.loads(notification)
assert block_data["result"]["number"] == 101
async def test_subscribe_pending_transactions(self):
"""Test subscribing to pending transactions"""
with patch('websockets.connect') as mock_connect:
mock_ws = AsyncMock()
mock_connect.return_value.__aenter__.return_value = mock_ws
mock_ws.recv.side_effect = [
json.dumps({"id": 1, "result": "0xtxsub"}),
json.dumps({
"subscription": "0xtxsub",
"result": {
"hash": "0xtx123",
"from": "0xabc",
"to": "0xdef",
}
})
]
async with websockets.connect("ws://localhost:8546") as ws:
await ws.send(json.dumps({
"id": 1,
"method": "eth_subscribe",
"params": ["newPendingTransactions"]
}))
response = await ws.recv()
assert "result" in response
notification = await ws.recv()
tx_data = json.loads(notification)
assert tx_data["result"]["hash"] == "0xtx123"
async def test_subscribe_logs(self):
"""Test subscribing to event logs"""
with patch('websockets.connect') as mock_connect:
mock_ws = AsyncMock()
mock_connect.return_value.__aenter__.return_value = mock_ws
mock_ws.recv.side_effect = [
json.dumps({"id": 1, "result": "0xlogsub"}),
json.dumps({
"subscription": "0xlogsub",
"result": {
"address": "0xcontract",
"topics": ["0xevent"],
"data": "0xdata",
}
})
]
async with websockets.connect("ws://localhost:8546") as ws:
await ws.send(json.dumps({
"id": 1,
"method": "eth_subscribe",
"params": ["logs", {"address": "0xcontract"}]
}))
response = await ws.recv()
sub_data = json.loads(response)
notification = await ws.recv()
log_data = json.loads(notification)
assert log_data["result"]["address"] == "0xcontract"
@pytest.mark.integration
class TestPoAConsensus:
"""Test Proof of Authority consensus mechanism"""
@pytest.fixture
def poa_consensus(self):
"""Create PoA consensus instance for testing"""
validators = [
"0xvalidator1",
"0xvalidator2",
"0xvalidator3",
]
return PoAConsensus(validators=validators, block_time=1)
def test_proposer_selection(self, poa_consensus):
"""Test proposer selection algorithm"""
# Test deterministic proposer selection
proposer1 = poa_consensus.get_proposer(100)
proposer2 = poa_consensus.get_proposer(101)
assert proposer1 in poa_consensus.validators
assert proposer2 in poa_consensus.validators
# Should rotate based on block number
assert proposer1 != proposer2
def test_block_validation(self, poa_consensus):
"""Test block validation"""
block = Block(
number=100,
hash="0xblock123",
proposer="0xvalidator1",
timestamp=datetime.utcnow(),
transactions=[],
)
# Valid block
assert poa_consensus.validate_block(block) is True
# Invalid proposer
block.proposer = "0xinvalid"
assert poa_consensus.validate_block(block) is False
def test_validator_rotation(self, poa_consensus):
"""Test validator rotation schedule"""
proposers = []
for i in range(10):
proposer = poa_consensus.get_proposer(i)
proposers.append(proposer)
# Each validator should have proposed roughly equal times
for validator in poa_consensus.validators:
count = proposers.count(validator)
assert count >= 2 # At least 2 times in 10 blocks
@pytest.mark.asyncio
async def test_block_production_loop(self, poa_consensus):
"""Test block production loop"""
blocks_produced = []
async def mock_produce_block():
block = Block(
number=len(blocks_produced),
hash=f"0xblock{len(blocks_produced)}",
proposer=poa_consensus.get_proposer(len(blocks_produced)),
timestamp=datetime.utcnow(),
transactions=[],
)
blocks_produced.append(block)
return block
# Mock block production
with patch.object(poa_consensus, 'produce_block', side_effect=mock_produce_block):
# Produce 3 blocks
for _ in range(3):
block = await poa_consensus.produce_block()
assert block.number == len(blocks_produced) - 1
assert len(blocks_produced) == 3
@pytest.mark.integration
class TestCrossChainSettlement:
"""Test cross-chain settlement integration"""
@pytest.fixture
def bridge_manager(self):
"""Create bridge manager for testing"""
from apps.coordinator_api.src.app.services.bridge_manager import BridgeManager
return BridgeManager()
def test_bridge_registration(self, bridge_manager):
"""Test bridge registration"""
bridge_config = {
"bridge_id": "layerzero",
"source_chain": "ethereum",
"target_chain": "polygon",
"endpoint": "https://endpoint.layerzero.network",
}
result = bridge_manager.register_bridge(bridge_config)
assert result["success"] is True
assert result["bridge_id"] == "layerzero"
def test_cross_chain_transaction(self, bridge_manager):
"""Test cross-chain transaction execution"""
with patch.object(bridge_manager, 'execute_cross_chain_tx') as mock_execute:
mock_execute.return_value = {
"tx_hash": "0xcrosschain",
"status": "pending",
"source_tx": "0x123",
"target_tx": None,
}
result = bridge_manager.execute_cross_chain_tx({
"source_chain": "ethereum",
"target_chain": "polygon",
"amount": "1000",
"token": "USDC",
"recipient": "0xabc",
})
assert result["tx_hash"] is not None
assert result["status"] == "pending"
def test_settlement_verification(self, bridge_manager):
"""Test cross-chain settlement verification"""
with patch.object(bridge_manager, 'verify_settlement') as mock_verify:
mock_verify.return_value = {
"verified": True,
"source_tx": "0x123",
"target_tx": "0x456",
"amount": "1000",
"completed_at": datetime.utcnow().isoformat(),
}
result = bridge_manager.verify_settlement("0xcrosschain")
assert result["verified"] is True
assert result["target_tx"] is not None
@pytest.mark.integration
class TestNodePeering:
"""Test node peering and gossip"""
@pytest.fixture
def peer_manager(self):
"""Create peer manager for testing"""
from apps.blockchain_node.src.aitbc_chain.p2p.peer_manager import PeerManager
return PeerManager()
def test_peer_discovery(self, peer_manager):
"""Test peer discovery"""
with patch.object(peer_manager, 'discover_peers') as mock_discover:
mock_discover.return_value = [
"enode://1@localhost:30301",
"enode://2@localhost:30302",
"enode://3@localhost:30303",
]
peers = peer_manager.discover_peers()
assert len(peers) == 3
assert all(peer.startswith("enode://") for peer in peers)
def test_gossip_transaction(self, peer_manager):
"""Test transaction gossip"""
tx_data = {
"hash": "0xgossip",
"from": "0xabc",
"to": "0xdef",
"value": "100",
}
with patch.object(peer_manager, 'gossip_transaction') as mock_gossip:
mock_gossip.return_value = {"peers_notified": 5}
result = peer_manager.gossip_transaction(tx_data)
assert result["peers_notified"] > 0
def test_gossip_block(self, peer_manager):
"""Test block gossip"""
block_data = {
"number": 100,
"hash": "0xblock100",
"transactions": [],
}
with patch.object(peer_manager, 'gossip_block') as mock_gossip:
mock_gossip.return_value = {"peers_notified": 5}
result = peer_manager.gossip_block(block_data)
assert result["peers_notified"] > 0
@pytest.mark.integration
class TestNodeSynchronization:
"""Test node synchronization"""
@pytest.fixture
def sync_manager(self):
"""Create sync manager for testing"""
from apps.blockchain_node.src.aitbc_chain.sync.sync_manager import SyncManager
return SyncManager()
def test_sync_status(self, sync_manager):
"""Test synchronization status"""
with patch.object(sync_manager, 'get_sync_status') as mock_status:
mock_status.return_value = {
"syncing": False,
"current_block": 100,
"highest_block": 100,
"starting_block": 0,
}
status = sync_manager.get_sync_status()
assert status["syncing"] is False
assert status["current_block"] == status["highest_block"]
def test_sync_from_peer(self, sync_manager):
"""Test syncing from peer"""
with patch.object(sync_manager, 'sync_from_peer') as mock_sync:
mock_sync.return_value = {
"synced": True,
"blocks_synced": 10,
"time_taken": 5.0,
}
result = sync_manager.sync_from_peer("enode://peer@localhost:30301")
assert result["synced"] is True
assert result["blocks_synced"] > 0
@pytest.mark.integration
class TestNodeMetrics:
"""Test node metrics and monitoring"""
def test_block_metrics(self):
"""Test block production metrics"""
from apps.blockchain_node.src.aitbc_chain.metrics import block_metrics
# Record block metrics
block_metrics.record_block(100, 2.5)
block_metrics.record_block(101, 2.1)
# Get metrics
metrics = block_metrics.get_metrics()
assert metrics["block_count"] == 2
assert metrics["avg_block_time"] == 2.3
assert metrics["last_block_number"] == 101
def test_transaction_metrics(self):
"""Test transaction metrics"""
from apps.blockchain_node.src.aitbc_chain.metrics import tx_metrics
# Record transaction metrics
tx_metrics.record_transaction("0x123", 1000, True)
tx_metrics.record_transaction("0x456", 2000, False)
metrics = tx_metrics.get_metrics()
assert metrics["total_txs"] == 2
assert metrics["success_rate"] == 0.5
assert metrics["total_value"] == 3000
def test_peer_metrics(self):
"""Test peer connection metrics"""
from apps.blockchain_node.src.aitbc_chain.metrics import peer_metrics
# Record peer metrics
peer_metrics.record_peer_connected()
peer_metrics.record_peer_connected()
peer_metrics.record_peer_disconnected()
metrics = peer_metrics.get_metrics()
assert metrics["connected_peers"] == 1
assert metrics["total_connections"] == 2
assert metrics["disconnections"] == 1