refactor: reorganize aitbc core library into subpackages
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 17s
CLI Tests / test-cli (push) Failing after 3s
Cross-Node Transaction Testing / transaction-test (push) Successful in 3s
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Integration Tests / test-service-integration (push) Successful in 2m39s
Package Tests / Python package - aitbc-agent-sdk (push) Failing after 12s
Package Tests / Python package - aitbc-core (push) Successful in 12s
Package Tests / Python package - aitbc-crypto (push) Successful in 10s
Package Tests / Python package - aitbc-sdk (push) Failing after 7s
Package Tests / JavaScript package - aitbc-sdk-js (push) Successful in 6s
Package Tests / JavaScript package - aitbc-token (push) Successful in 14s
Python Tests / test-python (push) Failing after 9s
Security Scanning / security-scan (push) Successful in 15s

- Create aitbc/crypto/ subpackage (crypto.py, security.py)
- Create aitbc/utils/ subpackage (validation, time_utils, json_utils, paths, env)
- Create aitbc/network/ subpackage (http_client, web3_utils)
- Update all import statements across codebase
- Maintain backward compatibility with __init__.py exports
- Improve code organization and modularity
This commit is contained in:
aitbc
2026-05-09 12:25:14 +02:00
parent e73ec88c69
commit 2713951a1b
30 changed files with 621 additions and 23 deletions

57
aitbc/crypto/__init__.py Normal file
View File

@@ -0,0 +1,57 @@
"""
Cryptographic utilities for AITBC
Provides encryption, signing, hashing, and security-related functions
"""
from .crypto import (
derive_ethereum_address,
sign_transaction_hash,
verify_signature,
encrypt_private_key,
decrypt_private_key,
generate_secure_random_bytes,
keccak256_hash,
sha256_hash,
validate_ethereum_address,
generate_ethereum_private_key
)
from .security import (
generate_token,
generate_api_key,
validate_token_format,
validate_api_key,
generate_secure_random_string,
generate_secure_random_int,
hash_password,
verify_password,
generate_nonce,
generate_hmac,
verify_hmac
)
__all__ = [
# Crypto functions
'derive_ethereum_address',
'sign_transaction_hash',
'verify_signature',
'encrypt_private_key',
'decrypt_private_key',
'generate_secure_random_bytes',
'keccak256_hash',
'sha256_hash',
'validate_ethereum_address',
'generate_ethereum_private_key',
# Security functions
'generate_token',
'generate_api_key',
'validate_token_format',
'validate_api_key',
'generate_secure_random_string',
'generate_secure_random_int',
'hash_password',
'verify_password',
'generate_nonce',
'generate_hmac',
'verify_hmac'
]

19
aitbc/network/__init__.py Normal file
View File

@@ -0,0 +1,19 @@
"""
Network utilities for AITBC
Provides HTTP client and Web3 utilities
"""
from .http_client import (
AITBCHTTPClient,
AsyncAITBCHTTPClient
)
from .web3_utils import (
create_web3_client
)
__all__ = [
'AITBCHTTPClient',
'AsyncAITBCHTTPClient',
'create_web3_client'
]

153
aitbc/utils/__init__.py Normal file
View File

@@ -0,0 +1,153 @@
"""
Utility functions for AITBC
Provides validation, time utilities, JSON utilities, path utilities, and environment variable utilities
"""
from .validation import (
validate_address,
validate_hash,
validate_url,
validate_port,
validate_email,
validate_non_empty,
validate_positive_number,
validate_range,
validate_chain_id,
validate_uuid
)
from .time_utils import (
get_utc_now,
get_timestamp_utc,
format_iso8601,
parse_iso8601,
timestamp_to_iso,
iso_to_timestamp,
format_duration,
format_duration_precise,
parse_duration,
add_duration,
subtract_duration,
get_time_until,
get_time_since,
calculate_deadline,
is_deadline_passed,
get_deadline_remaining,
format_time_ago,
format_time_in,
to_timezone,
get_timezone_offset,
is_business_hours,
get_start_of_day,
get_end_of_day,
get_start_of_week,
get_end_of_week,
get_start_of_month,
get_end_of_month,
sleep_until,
retry_until_deadline
)
from .json_utils import (
load_json,
save_json,
merge_json,
json_to_string,
string_to_json,
get_nested_value,
set_nested_value,
flatten_json
)
from .paths import (
get_data_path,
get_config_path,
get_log_path,
get_repo_path,
ensure_dir,
ensure_file_dir,
resolve_path,
get_keystore_path,
get_blockchain_data_path,
get_marketplace_data_path
)
from .env import (
get_env_var,
get_required_env_var,
get_bool_env_var,
get_int_env_var,
get_float_env_var,
get_list_env_var
)
__all__ = [
# Validation
'validate_address',
'validate_hash',
'validate_url',
'validate_port',
'validate_email',
'validate_non_empty',
'validate_positive_number',
'validate_range',
'validate_chain_id',
'validate_uuid',
# Time utils
'get_utc_now',
'get_timestamp_utc',
'format_iso8601',
'parse_iso8601',
'timestamp_to_iso',
'iso_to_timestamp',
'format_duration',
'format_duration_precise',
'parse_duration',
'add_duration',
'subtract_duration',
'get_time_until',
'get_time_since',
'calculate_deadline',
'is_deadline_passed',
'get_deadline_remaining',
'format_time_ago',
'format_time_in',
'to_timezone',
'get_timezone_offset',
'is_business_hours',
'get_start_of_day',
'get_end_of_day',
'get_start_of_week',
'get_end_of_week',
'get_start_of_month',
'get_end_of_month',
'sleep_until',
'retry_until_deadline',
# JSON utils
'load_json',
'save_json',
'merge_json',
'json_to_string',
'string_to_json',
'get_nested_value',
'set_nested_value',
'flatten_json',
# Paths
'get_data_path',
'get_config_path',
'get_log_path',
'get_repo_path',
'ensure_dir',
'ensure_file_dir',
'resolve_path',
'get_keystore_path',
'get_blockchain_data_path',
'get_marketplace_data_path',
# Environment
'get_env_var',
'get_required_env_var',
'get_bool_env_var',
'get_int_env_var',
'get_float_env_var',
'get_list_env_var'
]

View File

@@ -1,7 +1,7 @@
"""Agent daemon action handler for triggering autonomous agent responses."""
from typing import Any, Dict, Optional
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -1,7 +1,7 @@
"""Coordinator API action handler for triggering hermes agent actions."""
from typing import Any, Dict, List, Optional
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -1,7 +1,7 @@
"""Marketplace action handler for triggering marketplace state updates."""
from typing import Any, Dict, List
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -3,7 +3,7 @@
import asyncio
from typing import TYPE_CHECKING, Any, Dict, Optional
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -16,7 +16,7 @@ import os
import uuid
import hashlib
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -11,7 +11,7 @@ from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
import uvicorn
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.network.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError

View File

@@ -43,10 +43,10 @@ from typing import Optional, Dict, Any, List
from aitbc.aitbc_logging import get_logger
from aitbc.constants import BLOCKCHAIN_RPC_PORT, DATA_DIR, KEYSTORE_DIR
from aitbc.exceptions import ConfigurationError, NetworkError, ValidationError
from aitbc.http_client import AITBCHTTPClient
from aitbc.paths import get_blockchain_data_path, get_data_path
from aitbc.paths import ensure_dir, get_keystore_path
from aitbc.validation import validate_address, validate_url
from aitbc.network.http_client import AITBCHTTPClient
from aitbc.utils.paths import get_blockchain_data_path, get_data_path
from aitbc.utils.paths import ensure_dir, get_keystore_path
from aitbc.utils.validation import validate_address, validate_url
# Initialize logger
logger = get_logger(__name__)

View File

@@ -12,7 +12,7 @@ from pathlib import Path
from typing import Optional, Dict, Any, List
import requests
import getpass
from aitbc.paths import get_keystore_path
from aitbc.utils.paths import get_keystore_path
# Default paths
DEFAULT_KEYSTORE_DIR = get_keystore_path()

View File

@@ -2,7 +2,7 @@ import json
import os
import time
import uuid
from aitbc.paths import get_data_path
from aitbc.utils.paths import get_data_path
STATE_FILE = str(get_data_path("data/cli_extended_state.json"))

View File

@@ -3,7 +3,7 @@
import json
import requests
import sys
from aitbc.paths import get_data_path
from aitbc.utils.paths import get_data_path
def handle_wallet_create(args, create_wallet, read_password, first):

View File

@@ -13,7 +13,7 @@ import os
from pathlib import Path
from typing import Optional, Dict, Any
from aitbc.paths import get_keystore_path
from aitbc.utils.paths import get_keystore_path
from cryptography.fernet import Fernet

View File

@@ -16,7 +16,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError
from aitbc.http_client import AITBCHTTPClient
from aitbc.network.http_client import AITBCHTTPClient
from aitbc_agent.contract_integration import (
AgentContractIntegration,
ContractClient,

View File

@@ -14,7 +14,7 @@ from .agent import Agent, AgentCapabilities, AgentIdentity
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError
from aitbc.http_client import AITBCHTTPClient
from aitbc.network.http_client import AITBCHTTPClient
logger = get_logger(__name__)

View File

@@ -7,7 +7,7 @@ from typing import Any, Dict, Iterable, Iterator, List, Optional, cast
import base64
from aitbc.exceptions import NetworkError
from aitbc.http_client import AITBCHTTPClient
from aitbc.network.http_client import AITBCHTTPClient
from aitbc_crypto.signing import ReceiptVerifier

View File

@@ -0,0 +1,370 @@
"""
Contract tests for AITBC blockchain RPC interactions.
Tests verify that the blockchain RPC API maintains expected contracts and behaviors.
"""
import pytest
import httpx
from typing import Dict, Any, Optional
from unittest.mock import Mock, patch, AsyncMock
class BlockchainRPCContract:
"""
Contract definition for blockchain RPC API.
Defines expected behavior and response formats.
"""
BASE_URL = "http://localhost:8006"
# Expected response structures
BLOCK_RESPONSE_SCHEMA = {
"height": int,
"hash": str,
"parent_hash": str,
"timestamp": int,
"transactions": list
}
TRANSACTION_RESPONSE_SCHEMA = {
"hash": str,
"from": str,
"to": str,
"value": str,
"nonce": int,
"gas": int
}
ACCOUNT_RESPONSE_SCHEMA = {
"address": str,
"balance": int,
"nonce": int
}
STATUS_RESPONSE_SCHEMA = {
"syncing": bool,
"current_block": int,
"highest_block": int,
"peers": int
}
@pytest.mark.contract
class TestBlockchainRPCContracts:
"""Contract tests for blockchain RPC endpoints"""
@pytest.fixture
def client(self):
"""HTTP client for blockchain RPC"""
return httpx.Client(timeout=30.0)
@pytest.fixture
def rpc_url(self):
"""Blockchain RPC base URL"""
return BlockchainRPCContract.BASE_URL
def test_get_block_contract(self, client, rpc_url):
"""
Test contract for getting a block by height.
Contract:
- GET /rpc/blocks/{height} should return block data
- Response should contain required fields: height, hash, parent_hash, timestamp, transactions
- Height should be a non-negative integer
- Hash should be a valid hex string
"""
try:
response = client.get(f"{rpc_url}/rpc/blocks/0")
# Contract: Should return 200 for valid block height
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Response should be a dictionary
assert isinstance(data, dict), "Response should be a dictionary"
# Contract: Should contain required fields
required_fields = ["height", "hash", "timestamp"]
for field in required_fields:
assert field in data, f"Missing required field: {field}"
# Contract: Height should be integer
assert isinstance(data["height"], int), "Height should be an integer"
assert data["height"] >= 0, "Height should be non-negative"
# Contract: Hash should be hex string
assert isinstance(data["hash"], str), "Hash should be a string"
assert data["hash"].startswith("0x"), "Hash should start with 0x"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_get_head_block_contract(self, client, rpc_url):
"""
Test contract for getting the head block.
Contract:
- GET /rpc/head should return current head block
- Response should contain height and hash
- Head block should be the highest known block
"""
try:
response = client.get(f"{rpc_url}/rpc/head")
# Contract: Should return 200
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Should contain height and hash
assert "height" in data, "Missing height field"
assert "hash" in data, "Missing hash field"
# Contract: Height should be non-negative
assert data["height"] >= 0, "Height should be non-negative"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_get_transaction_contract(self, client, rpc_url):
"""
Test contract for getting a transaction by hash.
Contract:
- GET /rpc/transaction/{hash} should return transaction data
- Response should contain: hash, from, to, value, nonce
- Transaction hash should match request
"""
try:
# Use a sample transaction hash (this may not exist)
sample_hash = "0x" + "a" * 64
response = client.get(f"{rpc_url}/rpc/transaction/{sample_hash}")
# Contract: Should return 200 if found, 404 if not found
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Should contain transaction fields
assert "hash" in data, "Missing hash field"
assert data["hash"] == sample_hash, "Transaction hash should match request"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_get_account_balance_contract(self, client, rpc_url):
"""
Test contract for getting account balance.
Contract:
- GET /rpc/account/{address} should return account data
- Response should contain: address, balance, nonce
- Address should match request
- Balance should be non-negative integer
"""
try:
# Use a sample address
sample_address = "0x" + "a" * 40
response = client.get(f"{rpc_url}/rpc/account/{sample_address}")
# Contract: Should return 200 or 404
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Should contain account fields
assert "address" in data, "Missing address field"
assert data["address"] == sample_address, "Address should match request"
# Contract: Balance should be non-negative
if "balance" in data:
assert isinstance(data["balance"], (int, str)), "Balance should be integer or string"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_send_transaction_contract(self, client, rpc_url):
"""
Test contract for sending a transaction.
Contract:
- POST /rpc/sendTx should accept transaction payload
- Response should contain transaction hash if successful
- Should return error if transaction is invalid
"""
try:
# Create a sample transaction payload
tx_payload = {
"from": "0x" + "a" * 40,
"to": "0x" + "b" * 40,
"value": "1000000000000000000", # 1 ETH in wei
"nonce": 0,
"gas": 21000
}
response = client.post(f"{rpc_url}/rpc/sendTx", json=tx_payload)
# Contract: Should return 200, 400, or 500
assert response.status_code in (200, 400, 500), f"Unexpected status code: {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Should contain transaction hash on success
assert "hash" in data or "tx_hash" in data, "Missing transaction hash in response"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_get_peers_contract(self, client, rpc_url):
"""
Test contract for getting network peers.
Contract:
- GET /rpc/peers should return peer information
- Response should be a list of peers
- Each peer should have at least an ID or address
"""
try:
response = client.get(f"{rpc_url}/rpc/peers")
# Contract: Should return 200
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Response should be a list
assert isinstance(data, (list, dict)), "Response should be a list or dict"
if isinstance(data, list) and data:
# Contract: Each peer should have identifier
peer = data[0]
assert isinstance(peer, dict), "Peer should be a dictionary"
assert len(peer) > 0, "Peer should have at least one field"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_get_status_contract(self, client, rpc_url):
"""
Test contract for getting node status.
Contract:
- GET /rpc/status should return node status
- Response should contain: syncing status, current block, highest block
- Current block should not exceed highest block
"""
try:
response = client.get(f"{rpc_url}/rpc/status")
# Contract: Should return 200
assert response.status_code in (200, 404), f"Expected 200 or 404, got {response.status_code}"
if response.status_code == 200:
data = response.json()
# Contract: Should contain status fields
assert isinstance(data, dict), "Response should be a dictionary"
# Contract: If syncing info present, validate it
if "syncing" in data:
assert isinstance(data["syncing"], bool), "Syncing should be boolean"
if "current_block" in data and "highest_block" in data:
assert data["current_block"] <= data["highest_block"], \
"Current block should not exceed highest block"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_rpc_response_format_contract(self, client, rpc_url):
"""
Test contract for RPC response format.
Contract:
- All RPC responses should be JSON
- Content-Type should be application/json
- Response should be parseable as JSON
"""
try:
# Test multiple endpoints
endpoints = [
"/rpc/head",
"/rpc/status",
"/rpc/peers"
]
for endpoint in endpoints:
response = client.get(f"{rpc_url}{endpoint}")
if response.status_code == 200:
# Contract: Should have JSON content type
assert "application/json" in response.headers.get("content-type", ""), \
f"{endpoint} should return JSON content type"
# Contract: Should be parseable as JSON
try:
response.json()
except Exception as e:
pytest.fail(f"{endpoint} response should be valid JSON: {e}")
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
def test_rpc_error_handling_contract(self, client, rpc_url):
"""
Test contract for RPC error handling.
Contract:
- Invalid endpoints should return 404
- Invalid requests should return 400
- Server errors should return 500
- Error responses should contain error message
"""
try:
# Test invalid endpoint
response = client.get(f"{rpc_url}/rpc/invalid_endpoint")
assert response.status_code == 404, "Invalid endpoint should return 404"
# Test invalid method
response = client.post(f"{rpc_url}/rpc/head")
assert response.status_code in (405, 404), "Invalid method should return 405 or 404"
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")
@pytest.mark.contract
class TestBlockchainRPCTimeouts:
"""Contract tests for RPC timeout behavior"""
@pytest.fixture
def slow_client(self):
"""HTTP client with short timeout for testing"""
return httpx.Client(timeout=1.0)
def test_rpc_timeout_contract(self, slow_client, rpc_url):
"""
Test contract for RPC timeout handling.
Contract:
- Requests should respect timeout settings
- Timeout should raise appropriate exception
- Client should handle timeouts gracefully
"""
try:
# This should timeout if the endpoint is slow
response = slow_client.get(f"{rpc_url}/rpc/head")
# If it doesn't timeout, that's also valid
assert response.status_code in (200, 404)
except httpx.TimeoutException:
# Contract: Timeout should raise TimeoutException
pass # Expected behavior
except httpx.ConnectError:
pytest.skip("Blockchain RPC not available")

View File

@@ -8,7 +8,7 @@ from hypothesis import given, strategies as st, settings
from hypothesis.strategies import text, binary, integers
import json
from aitbc.crypto import (
from aitbc.crypto.crypto import (
derive_ethereum_address,
sign_transaction_hash,
verify_signature,

View File

@@ -7,7 +7,7 @@ import pytest
from hypothesis import given, strategies as st, settings
from hypothesis.strategies import text, integers, email, uuid, ip_addresses
from aitbc.validation import (
from aitbc.utils.validation import (
validate_address,
validate_hash,
validate_url,

View File

@@ -13,15 +13,14 @@ sys.path.insert(0, str(REPO_ROOT / "packages" / "py" / "aitbc-sdk" / "src"))
import aitbc
import aitbc_agent
import aitbc_sdk
import aitbc.paths as aitbc_paths
from aitbc.aitbc_logging import get_logger as direct_get_logger
from aitbc.constants import BLOCKCHAIN_RPC_PORT, DATA_DIR, ENV_FILE, KEYSTORE_DIR, LOG_DIR, NODE_ENV_FILE, PACKAGE_VERSION
from aitbc.exceptions import NetworkError, ValidationError
from aitbc.http_client import AITBCHTTPClient
from aitbc.paths import ensure_dir, get_keystore_path
from aitbc.network.http_client import AITBCHTTPClient
from aitbc.utils.paths import ensure_dir, get_keystore_path
from aitbc.utils.validation import validate_address, validate_url
from aitbc.testing import MockFactory
from aitbc.validation import validate_address, validate_url
def test_aitbc_root_exports_match_lightweight_submodules() -> None: