refactor: consolidate blockchain explorer into single app and update backup ignore patterns

- Remove standalone explorer-web app (README, HTML, package files)
- Add /web endpoint to blockchain-explorer for web interface access
- Update .gitignore to exclude application backup archives (*.tar.gz, *.zip)
- Add backup documentation files to .gitignore (BACKUP_INDEX.md, README.md)
- Consolidate explorer functionality into main blockchain-explorer application
This commit is contained in:
oib
2026-03-06 18:14:49 +01:00
parent dc1561d457
commit bb5363bebc
295 changed files with 35501 additions and 3734 deletions

View File

@@ -0,0 +1,9 @@
"""Wallet daemon test configuration"""
import sys
from pathlib import Path
# Add src to path for imports
src_path = Path(__file__).parent.parent / "src"
if str(src_path) not in sys.path:
sys.path.insert(0, str(src_path))

View File

@@ -0,0 +1,38 @@
from __future__ import annotations
from pathlib import Path
from app.ledger_mock import SQLiteLedgerAdapter
def test_upsert_and_get_wallet(tmp_path: Path) -> None:
db_path = tmp_path / "ledger.db"
adapter = SQLiteLedgerAdapter(db_path)
adapter.upsert_wallet("wallet-1", "pubkey", {"label": "primary"})
record = adapter.get_wallet("wallet-1")
assert record is not None
assert record.wallet_id == "wallet-1"
assert record.public_key == "pubkey"
assert record.metadata["label"] == "primary"
# Update metadata and ensure persistence
adapter.upsert_wallet("wallet-1", "pubkey", {"label": "updated"})
updated = adapter.get_wallet("wallet-1")
assert updated is not None
assert updated.metadata["label"] == "updated"
def test_event_ordering(tmp_path: Path) -> None:
db_path = tmp_path / "ledger.db"
adapter = SQLiteLedgerAdapter(db_path)
adapter.upsert_wallet("wallet-1", "pubkey", {})
adapter.record_event("wallet-1", "created", {"step": 1})
adapter.record_event("wallet-1", "unlock", {"step": 2})
adapter.record_event("wallet-1", "sign", {"step": 3})
events = list(adapter.list_events("wallet-1"))
assert [event.event_type for event in events] == ["created", "unlock", "sign"]
assert [event.payload["step"] for event in events] == [1, 2, 3]

View File

@@ -0,0 +1,404 @@
"""
Multi-Chain Wallet Daemon Tests
Tests for multi-chain functionality including chain management,
chain-specific wallet operations, and cross-chain migrations.
"""
import pytest
import tempfile
import json
from pathlib import Path
from unittest.mock import Mock, patch
from datetime import datetime
from app.chain.manager import ChainManager, ChainConfig, ChainStatus
from app.chain.multichain_ledger import MultiChainLedgerAdapter, ChainWalletMetadata
from app.chain.chain_aware_wallet_service import ChainAwareWalletService
class TestChainManager:
"""Test the chain manager functionality"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.config_path = self.temp_dir / "test_chains.json"
self.chain_manager = ChainManager(self.config_path)
def teardown_method(self):
"""Clean up test environment"""
import shutil
shutil.rmtree(self.temp_dir)
def test_create_default_chain(self):
"""Test default chain creation"""
assert len(self.chain_manager.chains) == 1
assert "ait-devnet" in self.chain_manager.chains
assert self.chain_manager.default_chain_id == "ait-devnet"
def test_add_chain(self):
"""Test adding a new chain"""
chain_config = ChainConfig(
chain_id="test-chain",
name="Test Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
success = self.chain_manager.add_chain(chain_config)
assert success is True
assert "test-chain" in self.chain_manager.chains
assert len(self.chain_manager.chains) == 2
def test_add_duplicate_chain(self):
"""Test adding a duplicate chain"""
chain_config = ChainConfig(
chain_id="ait-devnet", # Already exists
name="Duplicate Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
success = self.chain_manager.add_chain(chain_config)
assert success is False
assert len(self.chain_manager.chains) == 1
def test_remove_chain(self):
"""Test removing a chain"""
# First add a test chain
chain_config = ChainConfig(
chain_id="test-chain",
name="Test Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
self.chain_manager.add_chain(chain_config)
# Remove it
success = self.chain_manager.remove_chain("test-chain")
assert success is True
assert "test-chain" not in self.chain_manager.chains
assert len(self.chain_manager.chains) == 1
def test_remove_default_chain(self):
"""Test removing the default chain (should fail)"""
success = self.chain_manager.remove_chain("ait-devnet")
assert success is False
assert "ait-devnet" in self.chain_manager.chains
def test_set_default_chain(self):
"""Test setting default chain"""
# Add a test chain first
chain_config = ChainConfig(
chain_id="test-chain",
name="Test Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
self.chain_manager.add_chain(chain_config)
# Set as default
success = self.chain_manager.set_default_chain("test-chain")
assert success is True
assert self.chain_manager.default_chain_id == "test-chain"
def test_validate_chain_id(self):
"""Test chain ID validation"""
# Valid active chain
assert self.chain_manager.validate_chain_id("ait-devnet") is True
# Invalid chain
assert self.chain_manager.validate_chain_id("nonexistent") is False
# Add inactive chain
chain_config = ChainConfig(
chain_id="inactive-chain",
name="Inactive Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key",
status=ChainStatus.INACTIVE
)
self.chain_manager.add_chain(chain_config)
# Inactive chain should be invalid
assert self.chain_manager.validate_chain_id("inactive-chain") is False
def test_get_chain_stats(self):
"""Test getting chain statistics"""
stats = self.chain_manager.get_chain_stats()
assert stats["total_chains"] == 1
assert stats["active_chains"] == 1
assert stats["default_chain"] == "ait-devnet"
assert len(stats["chain_list"]) == 1
class TestMultiChainLedger:
"""Test the multi-chain ledger adapter"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.chain_manager = ChainManager(self.temp_dir / "chains.json")
self.ledger = MultiChainLedgerAdapter(self.chain_manager, self.temp_dir)
def teardown_method(self):
"""Clean up test environment"""
import shutil
shutil.rmtree(self.temp_dir)
def test_create_wallet(self):
"""Test creating a wallet in a specific chain"""
success = self.ledger.create_wallet(
chain_id="ait-devnet",
wallet_id="test-wallet",
public_key="test-public-key",
address="test-address"
)
assert success is True
# Verify wallet exists
wallet = self.ledger.get_wallet("ait-devnet", "test-wallet")
assert wallet is not None
assert wallet.wallet_id == "test-wallet"
assert wallet.chain_id == "ait-devnet"
assert wallet.public_key == "test-public-key"
def test_create_wallet_invalid_chain(self):
"""Test creating wallet in invalid chain"""
success = self.ledger.create_wallet(
chain_id="invalid-chain",
wallet_id="test-wallet",
public_key="test-public-key"
)
assert success is False
def test_list_wallets(self):
"""Test listing wallets"""
# Create multiple wallets
self.ledger.create_wallet("ait-devnet", "wallet1", "pub1")
self.ledger.create_wallet("ait-devnet", "wallet2", "pub2")
wallets = self.ledger.list_wallets("ait-devnet")
assert len(wallets) == 2
wallet_ids = [wallet.wallet_id for wallet in wallets]
assert "wallet1" in wallet_ids
assert "wallet2" in wallet_ids
def test_record_event(self):
"""Test recording events"""
success = self.ledger.record_event(
chain_id="ait-devnet",
wallet_id="test-wallet",
event_type="test-event",
data={"test": "data"}
)
assert success is True
# Get events
events = self.ledger.get_wallet_events("ait-devnet", "test-wallet")
assert len(events) == 1
assert events[0].event_type == "test-event"
assert events[0].data["test"] == "data"
def test_get_chain_stats(self):
"""Test getting chain statistics"""
# Create a wallet first
self.ledger.create_wallet("ait-devnet", "test-wallet", "test-pub")
stats = self.ledger.get_chain_stats("ait-devnet")
assert stats["chain_id"] == "ait-devnet"
assert stats["wallet_count"] == 1
assert "database_path" in stats
class TestChainAwareWalletService:
"""Test the chain-aware wallet service"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.chain_manager = ChainManager(self.temp_dir / "chains.json")
self.ledger = MultiChainLedgerAdapter(self.chain_manager, self.temp_dir)
# Mock keystore service
with patch('app.chain.chain_aware_wallet_service.PersistentKeystoreService') as mock_keystore:
self.mock_keystore = mock_keystore.return_value
self.mock_keystore.create_wallet.return_value = Mock(
public_key="test-pub-key",
metadata={}
)
self.mock_keystore.sign_message.return_value = b"test-signature"
self.mock_keystore.unlock_wallet.return_value = True
self.mock_keystore.lock_wallet.return_value = True
self.wallet_service = ChainAwareWalletService(self.chain_manager, self.ledger)
def teardown_method(self):
"""Clean up test environment"""
import shutil
shutil.rmtree(self.temp_dir)
def test_create_wallet(self):
"""Test creating a wallet in a specific chain"""
wallet = self.wallet_service.create_wallet(
chain_id="ait-devnet",
wallet_id="test-wallet",
password="test-password"
)
assert wallet is not None
assert wallet.wallet_id == "test-wallet"
assert wallet.chain_id == "ait-devnet"
assert wallet.public_key == "test-pub-key"
def test_create_wallet_invalid_chain(self):
"""Test creating wallet in invalid chain"""
wallet = self.wallet_service.create_wallet(
chain_id="invalid-chain",
wallet_id="test-wallet",
password="test-password"
)
assert wallet is None
def test_sign_message(self):
"""Test signing a message"""
# First create a wallet
self.wallet_service.create_wallet("ait-devnet", "test-wallet", "test-password")
signature = self.wallet_service.sign_message(
chain_id="ait-devnet",
wallet_id="test-wallet",
password="test-password",
message=b"test message"
)
assert signature == "test-signature" # Mocked signature
def test_unlock_wallet(self):
"""Test unlocking a wallet"""
# First create a wallet
self.wallet_service.create_wallet("ait-devnet", "test-wallet", "test-password")
success = self.wallet_service.unlock_wallet(
chain_id="ait-devnet",
wallet_id="test-wallet",
password="test-password"
)
assert success is True
def test_list_wallets(self):
"""Test listing wallets"""
# Create wallets in different chains
self.wallet_service.create_wallet("ait-devnet", "wallet1", "password1")
# Add another chain
chain_config = ChainConfig(
chain_id="test-chain",
name="Test Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
self.chain_manager.add_chain(chain_config)
# Create wallet in new chain
self.wallet_service.create_wallet("test-chain", "wallet2", "password2")
# List all wallets
all_wallets = self.wallet_service.list_wallets()
assert len(all_wallets) == 2
# List specific chain wallets
devnet_wallets = self.wallet_service.list_wallets("ait-devnet")
assert len(devnet_wallets) == 1
assert devnet_wallets[0].wallet_id == "wallet1"
def test_get_chain_wallet_stats(self):
"""Test getting chain wallet statistics"""
# Create a wallet
self.wallet_service.create_wallet("ait-devnet", "test-wallet", "test-password")
stats = self.wallet_service.get_chain_wallet_stats("ait-devnet")
assert stats["chain_id"] == "ait-devnet"
assert "ledger_stats" in stats
assert "keystore_stats" in stats
class TestMultiChainIntegration:
"""Integration tests for multi-chain functionality"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.chain_manager = ChainManager(self.temp_dir / "chains.json")
self.ledger = MultiChainLedgerAdapter(self.chain_manager, self.temp_dir)
# Add a second chain
chain_config = ChainConfig(
chain_id="test-chain",
name="Test Chain",
coordinator_url="http://localhost:8001",
coordinator_api_key="test-key"
)
self.chain_manager.add_chain(chain_config)
def teardown_method(self):
"""Clean up test environment"""
import shutil
shutil.rmtree(self.temp_dir)
def test_cross_chain_wallet_isolation(self):
"""Test that wallets are properly isolated between chains"""
# Create wallet with same ID in different chains
self.ledger.create_wallet("ait-devnet", "same-wallet", "pub1", "addr1")
self.ledger.create_wallet("test-chain", "same-wallet", "pub2", "addr2")
# Verify they are different
wallet1 = self.ledger.get_wallet("ait-devnet", "same-wallet")
wallet2 = self.ledger.get_wallet("test-chain", "same-wallet")
assert wallet1.chain_id == "ait-devnet"
assert wallet2.chain_id == "test-chain"
assert wallet1.public_key != wallet2.public_key
assert wallet1.address != wallet2.address
def test_chain_specific_events(self):
"""Test that events are chain-specific"""
# Create wallets in different chains
self.ledger.create_wallet("ait-devnet", "wallet1", "pub1")
self.ledger.create_wallet("test-chain", "wallet2", "pub2")
# Record events
self.ledger.record_event("ait-devnet", "wallet1", "event1", {"chain": "devnet"})
self.ledger.record_event("test-chain", "wallet2", "event2", {"chain": "test"})
# Verify events are chain-specific
events1 = self.ledger.get_wallet_events("ait-devnet", "wallet1")
events2 = self.ledger.get_wallet_events("test-chain", "wallet2")
assert len(events1) == 1
assert len(events2) == 1
assert events1[0].data["chain"] == "devnet"
assert events2[0].data["chain"] == "test"
def test_all_chain_stats(self):
"""Test getting statistics for all chains"""
# Create wallets in different chains
self.ledger.create_wallet("ait-devnet", "wallet1", "pub1")
self.ledger.create_wallet("test-chain", "wallet2", "pub2")
stats = self.ledger.get_all_chain_stats()
assert stats["total_chains"] == 2
assert stats["total_wallets"] == 2
assert "ait-devnet" in stats["chain_stats"]
assert "test-chain" in stats["chain_stats"]
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -0,0 +1,81 @@
from __future__ import annotations
import pytest
from nacl.signing import SigningKey
from app.receipts import ReceiptValidationResult, ReceiptVerifierService
@pytest.fixture()
def sample_receipt() -> dict:
return {
"version": "1.0",
"receipt_id": "rcpt-1",
"job_id": "job-123",
"provider": "miner-abc",
"client": "client-xyz",
"units": 1.0,
"unit_type": "gpu_seconds",
"price": 3.5,
"started_at": 1700000000,
"completed_at": 1700000005,
"metadata": {},
}
class _DummyClient:
def __init__(self, latest=None, history=None):
self.latest = latest
self.history = history or []
def fetch_latest(self, job_id: str):
return self.latest
def fetch_history(self, job_id: str):
return list(self.history)
@pytest.fixture()
def signer():
return SigningKey.generate()
@pytest.fixture()
def signed_receipt(sample_receipt: dict, signer: SigningKey) -> dict:
from aitbc_crypto.signing import ReceiptSigner
receipt = dict(sample_receipt)
receipt["signature"] = ReceiptSigner(signer.encode()).sign(sample_receipt)
return receipt
def test_verify_latest_success(monkeypatch, signed_receipt: dict):
service = ReceiptVerifierService("http://coordinator", "api-key")
client = _DummyClient(latest=signed_receipt)
monkeypatch.setattr(service, "client", client)
result = service.verify_latest("job-123")
assert isinstance(result, ReceiptValidationResult)
assert result.job_id == "job-123"
assert result.receipt_id == "rcpt-1"
assert result.miner_valid is True
assert result.all_valid is True
def test_verify_latest_none(monkeypatch):
service = ReceiptVerifierService("http://coordinator", "api-key")
client = _DummyClient(latest=None)
monkeypatch.setattr(service, "client", client)
assert service.verify_latest("job-123") is None
def test_verify_history(monkeypatch, signed_receipt: dict):
service = ReceiptVerifierService("http://coordinator", "api-key")
client = _DummyClient(history=[signed_receipt])
monkeypatch.setattr(service, "client", client)
results = service.verify_history("job-123")
assert len(results) == 1
assert results[0].miner_valid is True
assert results[0].job_id == "job-123"

View File

@@ -0,0 +1,98 @@
from __future__ import annotations
import base64
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from app.deps import get_keystore, get_ledger, get_settings
from app.main import create_app
from app.keystore.service import KeystoreService
from app.ledger_mock import SQLiteLedgerAdapter
@pytest.fixture(name="client")
def client_fixture(tmp_path, monkeypatch):
# Override ledger path to temporary directory
from app.settings import Settings
test_settings = Settings(LEDGER_DB_PATH=str(tmp_path / "ledger.db"))
monkeypatch.setattr("app.settings.settings", test_settings)
from app import deps
deps.get_settings.cache_clear()
deps.get_keystore.cache_clear()
deps.get_ledger.cache_clear()
app = create_app()
keystore = KeystoreService()
ledger = SQLiteLedgerAdapter(Path(test_settings.ledger_db_path))
app.dependency_overrides[get_settings] = lambda: test_settings
app.dependency_overrides[get_keystore] = lambda: keystore
app.dependency_overrides[get_ledger] = lambda: ledger
return TestClient(app)
def _create_wallet(client: TestClient, wallet_id: str, password: str = "Password!234") -> None:
payload = {
"wallet_id": wallet_id,
"password": password,
}
response = client.post("/v1/wallets", json=payload)
assert response.status_code == 201, response.text
def test_wallet_workflow(client: TestClient):
wallet_id = "wallet-1"
password = "StrongPass!234"
# Create wallet
response = client.post(
"/v1/wallets",
json={
"wallet_id": wallet_id,
"password": password,
"metadata": {"label": "test"},
},
)
assert response.status_code == 201, response.text
data = response.json()["wallet"]
assert data["wallet_id"] == wallet_id
assert "public_key" in data
# List wallets
response = client.get("/v1/wallets")
assert response.status_code == 200
items = response.json()["items"]
assert any(item["wallet_id"] == wallet_id for item in items)
# Unlock wallet
response = client.post(f"/v1/wallets/{wallet_id}/unlock", json={"password": password})
assert response.status_code == 200
assert response.json()["unlocked"] is True
# Sign payload
message = base64.b64encode(b"hello").decode()
response = client.post(
f"/v1/wallets/{wallet_id}/sign",
json={"password": password, "message_base64": message},
)
assert response.status_code == 200, response.text
signature = response.json()["signature_base64"]
assert isinstance(signature, str) and len(signature) > 0
def test_wallet_password_rules(client: TestClient):
response = client.post(
"/v1/wallets",
json={"wallet_id": "weak", "password": "short"},
)
assert response.status_code == 400
body = response.json()
assert body["detail"]["reason"] == "password_too_weak"
assert "min_length" in body["detail"]