diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a2bb0599..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,32 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Changed -- **Python Version Baseline**: Updated minimum supported Python version from 3.8 to 3.11 - - Root CLI package now requires Python >=3.11 - - Added Python 3.12 support to CI and package classifiers - - Updated documentation to reflect 3.11+ minimum requirement - - Services and shared libraries already required Python 3.11+ - -### CI/CD -- Added Python 3.12 to CLI test matrix alongside 3.11 -- Updated CI workflows to test on newer Python versions - -### Documentation -- Updated infrastructure documentation to consistently state Python 3.11+ minimum -- Aligned all Python version references across docs - -## [0.1.0] - 2024-XX-XX - -Initial release with core AITBC functionality including: -- CLI tools for blockchain operations -- Coordinator API for job submission and management -- Blockchain node implementation -- GPU mining client support -- SDK packages for integration diff --git a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py index bb10c227..13984a1e 100644 --- a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py +++ b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py @@ -1,23 +1,19 @@ -from __future__ import annotations - import asyncio import hashlib -import time -from dataclasses import dataclass -from datetime import datetime import re +from datetime import datetime from typing import Callable, ContextManager, Optional +import httpx from sqlmodel import Session, select from ..logger import get_logger from ..metrics import metrics_registry -from ..models import Block, Transaction +from ..config import ProposerConfig +from ..models import Block from ..gossip import gossip_broker -from ..mempool import get_mempool - -_METRIC_KEY_SANITIZE = re.compile(r"[^0-9a-zA-Z]+") +_METRIC_KEY_SANITIZE = re.compile(r"[^a-zA-Z0-9_]") def _sanitize_metric_suffix(value: str) -> str: @@ -25,61 +21,19 @@ def _sanitize_metric_suffix(value: str) -> str: return sanitized or "unknown" -@dataclass -class ProposerConfig: - chain_id: str - proposer_id: str - interval_seconds: int - max_block_size_bytes: int = 1_000_000 - max_txs_per_block: int = 500 - - -class CircuitBreaker: - """Circuit breaker for graceful degradation on repeated failures.""" - - def __init__(self, threshold: int = 5, timeout: int = 30) -> None: - self._threshold = threshold - self._timeout = timeout - self._failure_count = 0 - self._last_failure_time: float = 0 - self._state = "closed" # closed, open, half-open - - @property - def state(self) -> str: - if self._state == "open": - if time.time() - self._last_failure_time >= self._timeout: - self._state = "half-open" - return self._state - - def record_success(self) -> None: - self._failure_count = 0 - self._state = "closed" - metrics_registry.set_gauge("circuit_breaker_state", 0.0) - - def record_failure(self) -> None: - self._failure_count += 1 - self._last_failure_time = time.time() - if self._failure_count >= self._threshold: - self._state = "open" - metrics_registry.set_gauge("circuit_breaker_state", 1.0) - metrics_registry.increment("circuit_breaker_trips_total") - - def allow_request(self) -> bool: - state = self.state - if state == "closed": - return True - if state == "half-open": - return True - return False - - class PoAProposer: + """Proof-of-Authority block proposer. + + Responsible for periodically proposing blocks if this node is configured as a proposer. + In the real implementation, this would involve checking the mempool, validating transactions, + and signing the block. + """ + def __init__( self, *, config: ProposerConfig, session_factory: Callable[[], ContextManager[Session]], - circuit_breaker: Optional[CircuitBreaker] = None, ) -> None: self._config = config self._session_factory = session_factory @@ -87,7 +41,6 @@ class PoAProposer: self._stop_event = asyncio.Event() self._task: Optional[asyncio.Task[None]] = None self._last_proposer_id: Optional[str] = None - self._circuit_breaker = circuit_breaker or CircuitBreaker() async def start(self) -> None: if self._task is not None: @@ -95,7 +48,7 @@ class PoAProposer: self._logger.info("Starting PoA proposer loop", extra={"interval": self._config.interval_seconds}) self._ensure_genesis_block() self._stop_event.clear() - self._task = asyncio.create_task(self._run_loop(), name="poa-proposer-loop") + self._task = asyncio.create_task(self._run_loop()) async def stop(self) -> None: if self._task is None: @@ -105,31 +58,15 @@ class PoAProposer: await self._task self._task = None - @property - def is_healthy(self) -> bool: - return self._circuit_breaker.state != "open" - async def _run_loop(self) -> None: - metrics_registry.set_gauge("poa_proposer_running", 1.0) - try: - while not self._stop_event.is_set(): - await self._wait_until_next_slot() - if self._stop_event.is_set(): - break - if not self._circuit_breaker.allow_request(): - self._logger.warning("Circuit breaker open, skipping block proposal") - metrics_registry.increment("blocks_skipped_circuit_breaker_total") - continue - try: - self._propose_block() - self._circuit_breaker.record_success() - except Exception as exc: - self._circuit_breaker.record_failure() - self._logger.exception("Failed to propose block", extra={"error": str(exc)}) - metrics_registry.increment("poa_propose_errors_total") - finally: - metrics_registry.set_gauge("poa_proposer_running", 0.0) - self._logger.info("PoA proposer loop exited") + while not self._stop_event.is_set(): + await self._wait_until_next_slot() + if self._stop_event.is_set(): + break + try: + self._propose_block() + except Exception as exc: # pragma: no cover - defensive logging + self._logger.exception("Failed to propose block", extra={"error": str(exc)}) async def _wait_until_next_slot(self) -> None: head = self._fetch_chain_head() @@ -146,7 +83,24 @@ class PoAProposer: return def _propose_block(self) -> None: - start_time = time.perf_counter() + # Check RPC mempool for transactions + try: + response = httpx.get("http://localhost:8082/metrics") + if response.status_code == 200: + has_transactions = False + for line in response.text.split("\n"): + if line.startswith("mempool_size"): + size = float(line.split(" ")[1]) + if size > 0: + has_transactions = True + break + + if not has_transactions: + return + except Exception as exc: + self._logger.error(f"Error checking RPC mempool: {exc}") + return + with self._session_factory() as session: head = session.exec(select(Block).order_by(Block.height.desc()).limit(1)).first() next_height = 0 @@ -157,13 +111,6 @@ class PoAProposer: parent_hash = head.hash interval_seconds = (datetime.utcnow() - head.timestamp).total_seconds() - # Drain transactions from mempool - mempool = get_mempool() - pending_txs = mempool.drain( - max_count=self._config.max_txs_per_block, - max_bytes=self._config.max_block_size_bytes, - ) - timestamp = datetime.utcnow() block_hash = self._compute_block_hash(next_height, parent_hash, timestamp) @@ -173,33 +120,14 @@ class PoAProposer: parent_hash=parent_hash, proposer=self._config.proposer_id, timestamp=timestamp, - tx_count=len(pending_txs), + tx_count=0, state_root=None, ) session.add(block) - - # Batch-insert transactions into the block - total_fees = 0 - for ptx in pending_txs: - tx = Transaction( - tx_hash=ptx.tx_hash, - block_height=next_height, - sender=ptx.content.get("sender", ""), - recipient=ptx.content.get("recipient", ptx.content.get("payload", {}).get("recipient", "")), - payload=ptx.content, - ) - session.add(tx) - total_fees += ptx.fee - session.commit() - # Metrics - build_duration = time.perf_counter() - start_time metrics_registry.increment("blocks_proposed_total") metrics_registry.set_gauge("chain_head_height", float(next_height)) - metrics_registry.set_gauge("last_block_tx_count", float(len(pending_txs))) - metrics_registry.set_gauge("last_block_total_fees", float(total_fees)) - metrics_registry.observe("block_build_duration_seconds", build_duration) if interval_seconds is not None and interval_seconds >= 0: metrics_registry.observe("block_interval_seconds", interval_seconds) metrics_registry.set_gauge("poa_last_block_interval_seconds", float(interval_seconds)) @@ -207,34 +135,31 @@ class PoAProposer: proposer_suffix = _sanitize_metric_suffix(self._config.proposer_id) metrics_registry.increment(f"poa_blocks_proposed_total_{proposer_suffix}") if self._last_proposer_id is not None and self._last_proposer_id != self._config.proposer_id: - metrics_registry.increment("poa_proposer_rotations_total") + metrics_registry.increment("poa_proposer_switches_total") self._last_proposer_id = self._config.proposer_id - asyncio.create_task( - gossip_broker.publish( - "blocks", - { - "height": block.height, - "hash": block.hash, - "parent_hash": block.parent_hash, - "timestamp": block.timestamp.isoformat(), - "tx_count": block.tx_count, - }, - ) - ) - self._logger.info( "Proposed block", extra={ - "height": next_height, - "hash": block_hash, - "parent_hash": parent_hash, - "timestamp": timestamp.isoformat(), - "tx_count": len(pending_txs), - "total_fees": total_fees, - "build_ms": round(build_duration * 1000, 2), + "height": block.height, + "hash": block.hash, + "proposer": block.proposer, }, ) + + # Broadcast the new block + gossip_broker.publish( + "blocks", + { + "height": block.height, + "hash": block.hash, + "parent_hash": block.parent_hash, + "proposer": block.proposer, + "timestamp": block.timestamp.isoformat(), + "tx_count": block.tx_count, + "state_root": block.state_root, + } + ) def _ensure_genesis_block(self) -> None: with self._session_factory() as session: @@ -243,44 +168,36 @@ class PoAProposer: return timestamp = datetime.utcnow() - genesis_hash = self._compute_block_hash(0, "0x00", timestamp) + block_hash = self._compute_block_hash(0, "0x00", timestamp) genesis = Block( height=0, - hash=genesis_hash, + hash=block_hash, parent_hash="0x00", - proposer=self._config.proposer_id, + proposer="genesis", timestamp=timestamp, tx_count=0, state_root=None, ) session.add(genesis) session.commit() - asyncio.create_task( - gossip_broker.publish( - "blocks", - { - "height": genesis.height, - "hash": genesis.hash, - "parent_hash": genesis.parent_hash, - "timestamp": genesis.timestamp.isoformat(), - "tx_count": genesis.tx_count, - }, - ) + + # Broadcast genesis block for initial sync + gossip_broker.publish( + "blocks", + { + "height": genesis.height, + "hash": genesis.hash, + "parent_hash": genesis.parent_hash, + "proposer": genesis.proposer, + "timestamp": genesis.timestamp.isoformat(), + "tx_count": genesis.tx_count, + "state_root": genesis.state_root, + } ) - self._logger.info("Created genesis block", extra={"hash": genesis_hash}) - def _fetch_chain_head(self) -> Optional[Block]: - for attempt in range(3): - try: - with self._session_factory() as session: - return session.exec(select(Block).order_by(Block.height.desc()).limit(1)).first() - except Exception as exc: - if attempt == 2: - self._logger.error("Failed to fetch chain head after 3 attempts", extra={"error": str(exc)}) - metrics_registry.increment("poa_db_errors_total") - return None - time.sleep(0.1 * (attempt + 1)) + with self._session_factory() as session: + return session.exec(select(Block).order_by(Block.height.desc()).limit(1)).first() def _compute_block_hash(self, height: int, parent_hash: str, timestamp: datetime) -> str: payload = f"{self._config.chain_id}|{height}|{parent_hash}|{timestamp.isoformat()}".encode() diff --git a/apps/marketplace-web/src/main.ts b/apps/marketplace-web/src/main.ts index 9e8cf5f2..8fcbb44b 100644 --- a/apps/marketplace-web/src/main.ts +++ b/apps/marketplace-web/src/main.ts @@ -271,89 +271,3 @@ selectors.bidForm?.addEventListener('submit', async (event) => { }); loadDashboard(); - -// Dark mode functionality with system preference detection -function toggleDarkMode() { - const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - setTheme(newTheme); -} - -function setTheme(theme: string) { - // Apply theme immediately - if (theme === 'dark') { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - - // Save to localStorage for persistence - localStorage.setItem('marketplaceTheme', theme); - - // Update button display - updateThemeButton(theme); - - // Send analytics event if available - if (typeof window !== 'undefined' && window.analytics) { - window.analytics.track('marketplace_theme_changed', { theme }); - } -} - -function updateThemeButton(theme: string) { - const emoji = document.getElementById('darkModeEmoji'); - const text = document.getElementById('darkModeText'); - - if (emoji && text) { - if (theme === 'dark') { - emoji.textContent = '🌙'; - text.textContent = 'Dark'; - } else { - emoji.textContent = '☀️'; - text.textContent = 'Light'; - } - } -} - -function getPreferredTheme(): string { - // 1. Check localStorage first (user preference for marketplace) - const saved = localStorage.getItem('marketplaceTheme'); - if (saved) { - return saved; - } - - // 2. Check main site preference for consistency - const mainSiteTheme = localStorage.getItem('theme'); - if (mainSiteTheme) { - return mainSiteTheme; - } - - // 3. Check system preference - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - return 'dark'; - } - - // 4. Default to dark (AITBC brand preference) - return 'dark'; -} - -function initializeTheme() { - const theme = getPreferredTheme(); - setTheme(theme); - - // Listen for system preference changes - if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { - // Only auto-switch if user hasn't manually set a preference - if (!localStorage.getItem('marketplaceTheme') && !localStorage.getItem('theme')) { - setTheme(e.matches ? 'dark' : 'light'); - } - }); - } -} - -// Initialize theme immediately (before DOM loads) -initializeTheme(); - -// Reference to suppress TypeScript "never used" warning -// @ts-ignore - function called from HTML onclick -window.toggleDarkMode = toggleDarkMode; diff --git a/apps/trade-exchange/index.html b/apps/trade-exchange/index.html index 5f9f9ef8..8a399056 100644 --- a/apps/trade-exchange/index.html +++ b/apps/trade-exchange/index.html @@ -428,17 +428,9 @@ }); // Enhanced Dark mode functionality with system preference detection - function toggleDarkMode() { - const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - setTheme(newTheme); - } + - function setTheme(theme) { - // Apply theme immediately - if (theme === 'dark') { - document.documentElement.classList.add('dark'); - } else { + else { document.documentElement.classList.remove('dark'); } @@ -454,12 +446,7 @@ } } - function getPreferredTheme() { - // 1. Check localStorage first (user preference for exchange) - const saved = localStorage.getItem('exchangeTheme'); - if (saved) { - return saved; - } + // 2. Check main site preference for consistency const mainSiteTheme = localStorage.getItem('theme'); @@ -476,17 +463,7 @@ return 'dark'; } - function initializeTheme() { - const theme = getPreferredTheme(); - setTheme(theme); - - // Listen for system preference changes - if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { - // Only auto-switch if user hasn't manually set a preference - if (!localStorage.getItem('exchangeTheme') && !localStorage.getItem('theme')) { - setTheme(e.matches ? 'dark' : 'light'); - } + }); } } diff --git a/ENHANCED_SERVICES_IMPLEMENTATION_GUIDE.md b/docs/0_getting_started/ENHANCED_SERVICES_IMPLEMENTATION_GUIDE.md similarity index 100% rename from ENHANCED_SERVICES_IMPLEMENTATION_GUIDE.md rename to docs/0_getting_started/ENHANCED_SERVICES_IMPLEMENTATION_GUIDE.md diff --git a/DEPLOYMENT_READINESS_REPORT.md b/docs/13_tasks/completed_phases/DEPLOYMENT_READINESS_REPORT.md similarity index 100% rename from DEPLOYMENT_READINESS_REPORT.md rename to docs/13_tasks/completed_phases/DEPLOYMENT_READINESS_REPORT.md diff --git a/RELEASE_NOTES_v0.1.0.md b/docs/RELEASE_NOTES_v0.1.0.md similarity index 96% rename from RELEASE_NOTES_v0.1.0.md rename to docs/RELEASE_NOTES_v0.1.0.md index 54245989..9e0b5ed7 100644 --- a/RELEASE_NOTES_v0.1.0.md +++ b/docs/RELEASE_NOTES_v0.1.0.md @@ -116,17 +116,10 @@ python3 -m aitbc_agent.agent start ## 🔮 Roadmap -### Next Releases -- **v0.2.0**: Windows/macOS support -- **v0.3.0**: AMD GPU support -- **v0.4.0**: Advanced agent capabilities -- **v1.0.0**: Production-ready ecosystem - ### Future Features - Multi-modal processing capabilities - Advanced swarm intelligence - Edge computing integration -- Quantum computing preparation ## 🤝 Contributing diff --git a/test/contracts/Integration.test.js b/tests/contracts/Integration.test.js similarity index 100% rename from test/contracts/Integration.test.js rename to tests/contracts/Integration.test.js diff --git a/test/contracts/MockERC20.sol b/tests/contracts/MockERC20.sol similarity index 100% rename from test/contracts/MockERC20.sol rename to tests/contracts/MockERC20.sol diff --git a/test/contracts/MockGroth16Verifier.sol b/tests/contracts/MockGroth16Verifier.sol similarity index 100% rename from test/contracts/MockGroth16Verifier.sol rename to tests/contracts/MockGroth16Verifier.sol diff --git a/test/contracts/MockZKVerifier.sol b/tests/contracts/MockZKVerifier.sol similarity index 100% rename from test/contracts/MockZKVerifier.sol rename to tests/contracts/MockZKVerifier.sol diff --git a/website/assets/css/site-header.css b/website/assets/css/site-header.css index 5420056d..8e3341b9 100644 --- a/website/assets/css/site-header.css +++ b/website/assets/css/site-header.css @@ -21,16 +21,6 @@ body.dark { --global-header-cta-text: #0f172a; } -body.light { - --global-header-bg: rgba(255, 255, 255, 0.97); - --global-header-border: rgba(15, 23, 42, 0.08); - --global-header-text: #111827; - --global-header-muted: #6b7280; - --global-header-pill: rgba(37, 99, 235, 0.07); - --global-header-pill-hover: rgba(37, 99, 235, 0.15); - --global-header-accent: #2563eb; - --global-header-cta-text: #fff; -} .global-header { height: 90px; @@ -45,7 +35,7 @@ body.light { } .global-header__inner { - max-width: 1200px; + max-width: 1160px; margin: 0 auto; padding: 0 1.25rem; height: 100%; @@ -137,27 +127,10 @@ body.light { flex-wrap: wrap; } -.global-dark-toggle { - border: 1px solid var(--global-header-border); - background: transparent; - color: var(--global-header-text); - padding: 0.35rem 0.9rem; - border-radius: 999px; - font-size: 0.9rem; - display: inline-flex; - align-items: center; - gap: 0.35rem; - cursor: pointer; - transition: all 0.2s ease; -} -.global-dark-toggle:hover { - border-color: var(--global-header-accent); - color: var(--global-header-accent); -} .global-subnav { - max-width: 1200px; + max-width: 1160px; margin: 0 auto; padding: 0 1.25rem 0.75rem; display: flex; diff --git a/website/assets/js/global-header.js b/website/assets/js/global-header.js index 7cb0e97f..dddb80ec 100644 --- a/website/assets/js/global-header.js +++ b/website/assets/js/global-header.js @@ -1,15 +1,11 @@ (function () { - // Immediately restore theme on script load - const savedTheme = localStorage.getItem('theme') || localStorage.getItem('exchangeTheme'); - if (savedTheme) { - if (savedTheme === 'dark') { - document.documentElement.setAttribute('data-theme', 'dark'); - document.documentElement.classList.add('dark'); - } else { - document.documentElement.removeAttribute('data-theme'); - document.documentElement.classList.remove('dark'); - } - } + // Always enforce dark theme + document.documentElement.setAttribute('data-theme', 'dark'); + document.documentElement.classList.add('dark'); + + // Clean up any old user preferences + if (localStorage.getItem('theme')) localStorage.removeItem('theme'); + if (localStorage.getItem('exchangeTheme')) localStorage.removeItem('exchangeTheme'); const NAV_ITEMS = [ { key: 'home', label: 'Home', href: '/' }, @@ -19,8 +15,6 @@ { key: 'docs', label: 'Docs', href: '/docs/index.html' }, ]; - const CTA = { label: 'Launch Marketplace', href: '/marketplace/' }; - function determineActiveKey(pathname) { if (pathname.startsWith('/explorer')) return 'explorer'; if (pathname.startsWith('/marketplace')) return 'marketplace'; @@ -48,81 +42,11 @@ -