#!/usr/bin/env python3 """ TIME_BANK v3.0 — Протокол начисления монет времени Montana =========================================================== ЭМИССИЯ: - Динамическая: зависит от количества участников - 1 секунда присутствия = 1 Ɉ × halving_coefficient - Халвинг каждые τ₄ (4 года) РЕЗЕРВ TIME_BANK: - 21,000,000 минут (~40 лет) - Банк тратит 10 мин/T2, подтверждая время - После исчерпания → Oracle Mode Привязка: Montana address База данных: SQLite (montana.db) """ import time import threading import hashlib from datetime import datetime, timezone from typing import Dict, Optional, Any, List, Tuple import logging from montana_db import get_db, MontanaDB # EVENT SOURCING — идеальная синхронизация между узлами try: from event_ledger import get_event_ledger, EventLedger EVENT_SOURCING_ENABLED = True except ImportError: EVENT_SOURCING_ENABLED = False # ML-DSA-65 для криптографических доказательств присутствия try: from node_crypto import sign_message, verify_signature, get_node_crypto_system ML_DSA_AVAILABLE = True except ImportError: ML_DSA_AVAILABLE = False # TIMECHAIN — Immutable Time Ledger (append-only, hash chaining) try: from timechain import get_timechain, TimeChain TIMECHAIN_ENABLED = True except ImportError: TIMECHAIN_ENABLED = False logging.basicConfig(level=logging.INFO) logger = logging.getLogger("TIME_BANK") # ============================================================ # КОНСТАНТЫ ПРОТОКОЛА v3.0 # ============================================================ class Protocol: """Константы протокола TIME_BANK v3.0""" VERSION = "3.0" # Сеть NODES_COUNT = 5 # 5 узлов Montana BANK_PRESENCE_PER_T2 = 600 # Банк всегда присутствует 600 сек (10 мин) # КОГНИТИВНЫЙ ГЕНЕЗИС: 9 января 2026 00:00:00 МСК # = 8 января 2026 21:00:00 UTC GENESIS_YEAR = 2026 GENESIS_MONTH = 1 GENESIS_DAY = 9 GENESIS_HOUR = 0 # по МСК GENESIS_MINUTE = 0 GENESIS_SECOND = 0 GENESIS_TIMEZONE = "Europe/Moscow" # TIME_BANK RESERVE — ровно 40 лет в секундах (с реальными високосными) # Високосные: 2028,2032,2036,2040,2044,2048,2052,2056,2060,2064 = 10 лет # Обычные: 30 лет # 30 × 365 × 86400 = 946,080,000 # 10 × 366 × 86400 = 316,224,000 # ИТОГО: 1,262,304,000 секунд # Конец: 9 января 2066 00:00:00 МСК BANK_TOTAL_SECONDS = 1_262_304_000 # Ровно 40 лет BANK_TOTAL_MINUTES = BANK_TOTAL_SECONDS // 60 # 21,038,400 минут # Эмиссия (динамическая, зависит от участников) # Нет фиксированной эмиссии — каждый получает свои секунды × halving # Временные координаты (Temporal Coordinates) TAU1_INTERVAL_SEC = 60 # τ₁ = 1 минута — интервал подписи присутствия T2_DURATION_SEC = 10 * 60 # τ₂ = 10 минут = 600 секунд (slice/block) TAU3_DURATION_SEC = 14 * 24 * 60 * 60 # τ₃ = 14 дней = 1,209,600 сек (checkpoint) TAU4_DURATION_SEC = 4 * 365 * 24 * 60 * 60 # τ₄ = 4 года = 126,144,000 сек (epoch) # Иерархия T2_PER_TAU3 = 2016 # 2016 × τ₂ в τ₃ (14 дней / 10 минут) TAU3_PER_YEAR = 26 # 26 × τ₃ в году (365 / 14) TAU3_PER_TAU4 = 104 # 104 × τ₃ в τ₄ (4 года) # Другие временные параметры INACTIVITY_LIMIT_SEC = 1 * 60 # τ₁ = 1 минута без активности = пауза TICK_INTERVAL_SEC = 1 # Интервал обновления # Монеты COINS_PER_SECOND = 1 # 1 секунда = 1 монета (без лотереи) # Presence Proof PRESENCE_PROOF_VERSION = "MONTANA_PRESENCE_V1" GENESIS_HASH = "0" * 64 # Genesis prev_hash @classmethod def get_genesis_datetime(cls) -> datetime: """Возвращает дату когнитивного генезиса (9 января 2026 00:00:00 МСК)""" import pytz tz = pytz.timezone(cls.GENESIS_TIMEZONE) return tz.localize(datetime( cls.GENESIS_YEAR, cls.GENESIS_MONTH, cls.GENESIS_DAY, cls.GENESIS_HOUR, cls.GENESIS_MINUTE, cls.GENESIS_SECOND )) @classmethod def get_end_datetime(cls) -> datetime: """Возвращает дату конца эмиссии (9 января 2066 00:00:00 МСК)""" from datetime import timedelta return cls.get_genesis_datetime() + timedelta(seconds=cls.BANK_TOTAL_SECONDS) @classmethod def seconds_since_genesis(cls) -> float: """Секунды с момента генезиса""" import pytz now = datetime.now(pytz.timezone(cls.GENESIS_TIMEZONE)) genesis = cls.get_genesis_datetime() return max(0, (now - genesis).total_seconds()) @classmethod def seconds_until_end(cls) -> float: """Секунды до конца эмиссии""" return max(0, cls.BANK_TOTAL_SECONDS - cls.seconds_since_genesis()) # ============================================================ # HALVING — Деление эмиссии на 2 каждые τ₄ # ============================================================ def halving_coefficient(tau4_count: int) -> float: """ Коэффициент халвинга — деление на 2 каждые τ₄ (4 года) Эмиссия уменьшается в 2 раза каждую эпоху τ₄ Args: tau4_count: Количество пройденных τ₄ эпох Returns: Коэффициент эмиссии (1.0, 0.5, 0.25, 0.125...) Формула: emission_per_second = 1.0 / (2 ** tau4_count) Пример: >>> halving_coefficient(0) # τ₄ #0 (первые 4 года) 1.0 >>> halving_coefficient(1) # τ₄ #1 (4-8 лет) 0.5 >>> halving_coefficient(2) # τ₄ #2 (8-12 лет) 0.25 >>> halving_coefficient(3) # τ₄ #3 (12-16 лет) 0.125 """ return 1.0 / (2 ** tau4_count) # ============================================================ # КЭШ СЕССИЙ # ============================================================ class PresenceCache: """Кэш присутствия по адресам (address или ip)""" def __init__(self): self.entries: Dict[str, Dict[str, Any]] = {} self._lock = threading.Lock() def get(self, address: str) -> Optional[Dict[str, Any]]: with self._lock: return self.entries.get(address) def set(self, address: str, data: Dict[str, Any]): with self._lock: self.entries[address] = data def remove(self, address: str): with self._lock: self.entries.pop(address, None) def all(self) -> Dict[str, Dict[str, Any]]: """Возвращает копию (для безопасного итерирования снаружи lock)""" with self._lock: return dict(self.entries) def items_snapshot(self) -> list: """ ULTRA-LIGHTWEIGHT: Снимок items для итерации. Создаёт список один раз, не копирует весь dict. """ with self._lock: return list(self.entries.items()) def count_active(self) -> int: with self._lock: return sum(1 for e in self.entries.values() if e.get("is_active")) # ============================================================ # ОСНОВНОЙ КЛАСС # ============================================================ class TimeBank: """ TIME_BANK v3.0 — Банк Времени Montana Эмиссия: динамическая, 1 сек = 1 Ɉ × halving Резерв: 21,000,000 минут (~40 лет) Халвинг: каждые τ₄ (4 года) """ def __init__(self, db: Optional[MontanaDB] = None): self.db = db or get_db() self.presence = PresenceCache() # Все адреса (tg_id или ip) # EVENT SOURCING — идеальная синхронизация self.ledger: Optional[EventLedger] = None if EVENT_SOURCING_ENABLED: self.ledger = get_event_ledger() # TIMECHAIN — Immutable Time Ledger (append-only, hash chaining) self.timechain: Optional[TimeChain] = None if TIMECHAIN_ENABLED: self.timechain = get_timechain() logger.info("⛓️ TimeChain: ENABLED (immutable ledger)") # Счётчики T2 self.current_t2_start = time.time() self.t2_emission = 0 self.t2_distributed = 0 self.total_reserve = 0 self.total_emitted = 0 self.total_distributed = 0 self.t2_count = 0 # Счётчики τ₃ и τ₄ self.tau3_count = 0 # Количество пройденных τ₃ (14 дней) self.tau4_count = 0 # Количество пройденных τ₄ (4 года) self.current_halving_coefficient = 1.0 # Текущий коэффициент халвинга # TIME_BANK RESERVE — отслеживание расхода 21 млн минут self.bank_seconds_spent = 0 # Сколько секунд банк уже потратил self.bank_exhausted = False # True когда 21 млн минут исчерпаны self._running = False self._thread: Optional[threading.Thread] = None # ML-DSA-65 Presence Proof self._presence_proofs: List[Dict[str, Any]] = [] # Подписанные доказательства self._last_proof_hash = Protocol.GENESIS_HASH # Prev hash для цепочки self._tau1_counter = 0 # Счётчик секунд до τ₁ self._node_private_key: Optional[str] = None # Private key узла self._node_public_key: Optional[str] = None # Public key узла logger.info(f"TIME_BANK v{Protocol.VERSION}") logger.info(f"📡 Эмиссия: динамическая (1 сек = 1 Ɉ × halving)") logger.info(f"⏳ Резерв: {Protocol.BANK_TOTAL_MINUTES:,} минут (~40 лет)") logger.info(f"🔐 ML-DSA-65: {'✅' if ML_DSA_AVAILABLE else '❌'}") logger.info(f"📊 Event Sourcing: {'✅' if EVENT_SOURCING_ENABLED else '❌'}") logger.info(f"⛓️ TimeChain: {'✅ IMMUTABLE' if TIMECHAIN_ENABLED else '❌'}") # -------------------------------------------------------- # ПРИСУТСТВИЕ (по адресу = ключу) # -------------------------------------------------------- def start(self, address: str, addr_type: str = "unknown") -> Dict[str, Any]: """ Начинает присутствие по адресу. address = address (str) или ip_address """ self.db.wallet(address, addr_type) entry = { "address": address, "addr_type": addr_type, "presence_seconds": 0, "last_activity": time.time(), "t2_seconds": 0, "is_active": True } self.presence.set(address, entry) logger.info(f"📍 Присутствие: {address} [{addr_type}]") return entry def activity(self, address: str, addr_type: str = "unknown") -> dict: """ Регистрирует активность по адресу. Returns: { "is_new": True если новая сессия, "was_paused": True если возобновлена после паузы, "presence_seconds": текущие секунды присутствия } """ entry = self.presence.get(address) is_new = False was_paused = False if not entry: self.start(address, addr_type) entry = self.presence.get(address) is_new = True logger.info(f"📍 Новое присутствие: {address}") entry["last_activity"] = time.time() if not entry.get("is_active") and not is_new: entry["is_active"] = True was_paused = True logger.info(f"▶️ Возобновлено: {address}") return { "is_new": is_new, "was_paused": was_paused, "presence_seconds": entry.get("presence_seconds", 0), "t2_seconds": entry.get("t2_seconds", 0) } def end(self, address: str) -> Optional[Dict[str, Any]]: """ Завершает присутствие. Монеты НЕ начисляются сразу — они будут начислены при финализации T2. Накопленные t2_seconds остаются в кэше до закрытия окна времени (раз в 10 минут). """ entry = self.presence.get(address) if not entry: return None # Помечаем как неактивный, но НЕ удаляем из кэша # t2_seconds будут начислены при следующей финализации T2 entry["is_active"] = False logger.info(f"🏁 Завершено: {address}, {entry['presence_seconds']} сек, pending T2: {entry['t2_seconds']} сек") return entry def get(self, address: str) -> Optional[Dict[str, Any]]: """Информация о присутствии""" entry = self.presence.get(address) if not entry: return None return { "address": address, "presence_seconds": entry["presence_seconds"], "t2_seconds": entry["t2_seconds"], "is_active": entry["is_active"], "balance": self.db.balance(address) } # -------------------------------------------------------- # ML-DSA-65 PRESENCE PROOF # -------------------------------------------------------- def set_node_keys(self, private_key_hex: str, public_key_hex: str): """ Устанавливает ключи узла для подписи присутствия POST-QUANTUM: ML-DSA-65 (FIPS 204) Args: private_key_hex: Приватный ключ (4032 байта в hex) public_key_hex: Публичный ключ (1952 байта в hex) """ self._node_private_key = private_key_hex self._node_public_key = public_key_hex logger.info(f"🔑 Node keys set (ML-DSA-65)") # Передаём ключи в TimeChain для подписи блоков if self.timechain: self.timechain.set_node_keys(private_key_hex, public_key_hex) logger.info(f"⛓️🔐 TimeChain keys set (ML-DSA-65)") def _sign_presence_proof(self) -> Optional[Dict[str, Any]]: """ Подписывает доказательство присутствия каждую τ₁ (1 минуту) Формат сообщения: MONTANA_PRESENCE_V1:{timestamp}:{prev_hash}:{pubkey}:{t2_index} Returns: Signed proof dict или None если нет ключей """ if not ML_DSA_AVAILABLE: logger.warning("ML-DSA-65 недоступен") return None if not self._node_private_key or not self._node_public_key: logger.debug("Node keys не установлены, пропуск подписи") return None timestamp = int(time.time()) t2_index = self.t2_count # Формируем сообщение для подписи message = f"{Protocol.PRESENCE_PROOF_VERSION}:{timestamp}:{self._last_proof_hash}:{self._node_public_key}:{t2_index}" # Подписываем ML-DSA-65 signature = sign_message(self._node_private_key, message) # Вычисляем hash этого proof для цепочки proof_hash = hashlib.sha256( f"{message}:{signature}".encode('utf-8') ).hexdigest() proof = { "version": Protocol.PRESENCE_PROOF_VERSION, "timestamp": timestamp, "prev_hash": self._last_proof_hash, "pubkey": self._node_public_key, "t2_index": t2_index, "message": message, "signature": signature, "proof_hash": proof_hash, "active_addresses": self.presence.count_active() } # Обновляем prev_hash для следующего proof self._last_proof_hash = proof_hash # Сохраняем proof self._presence_proofs.append(proof) # Ограничиваем хранимые proofs (последние 100) if len(self._presence_proofs) > 100: self._presence_proofs = self._presence_proofs[-100:] logger.info(f"✍️ Presence Proof #{len(self._presence_proofs)} signed (τ₁)") return proof def get_presence_proofs(self, limit: int = 10) -> List[Dict[str, Any]]: """ Получает последние подписанные доказательства присутствия Returns: List of signed proofs (newest first) """ return list(reversed(self._presence_proofs[-limit:])) def verify_presence_proof(self, proof: Dict[str, Any]) -> bool: """ Верифицирует подпись доказательства присутствия ML-DSA-65 Args: proof: Proof dict с message и signature Returns: True если подпись валидна """ if not ML_DSA_AVAILABLE: return False try: pubkey = proof.get("pubkey") message = proof.get("message") signature = proof.get("signature") if not all([pubkey, message, signature]): return False return verify_signature(pubkey, message, signature) except Exception as e: logger.error(f"Verify error: {e}") return False def get_proof_chain_status(self) -> Dict[str, Any]: """ Статус цепочки доказательств присутствия Returns: Chain status dict """ return { "ml_dsa_available": ML_DSA_AVAILABLE, "node_keys_set": bool(self._node_private_key), "total_proofs": len(self._presence_proofs), "last_proof_hash": self._last_proof_hash, "tau1_interval_sec": Protocol.TAU1_INTERVAL_SEC, "genesis_hash": Protocol.GENESIS_HASH } # -------------------------------------------------------- # КОШЕЛЁК API # -------------------------------------------------------- def balance(self, address: str) -> int: """Баланс по адресу (ключу) — EVENT SOURCING приоритет""" if self.ledger: return self.ledger.balance(address) return self.db.balance(address) def timechain_balance(self, address: str) -> int: """ IMMUTABLE баланс из TimeChain (сумма всех секунд) TimeChain — единственный источник истины. Каждый блок хэшируется с предыдущим. Без права перезаписи, отката, восстановления. """ if self.timechain: return self.timechain.balance(address) return 0 def verify_timechain(self, address: str) -> bool: """Проверяет целостность цепочки для адреса""" if self.timechain: return self.timechain.verify_chain(address) return False def get_timechain_blocks(self, address: str, limit: int = 100) -> list: """Получает блоки timechain для адреса""" if self.timechain: return self.timechain.get_blocks(address, limit) return [] def get_balance_with_pending(self, address: str) -> Dict[str, Any]: """ Баланс с учётом pending монет (ещё не подтверждённых в T2) Args: address: Адрес кошелька Returns: Dict с тремя значениями: - confirmed: Подтверждённый баланс (в DB) - pending: Накапливается в текущем T2 (в cache) - total: Сумма confirmed + pending """ # Подтверждённый баланс (в БД) confirmed = self.db.balance(address) # Pending монеты (в кэше присутствия) entry = self.presence.get(address) pending_seconds = entry.get("t2_seconds", 0) if entry else 0 # Умножаем на текущий коэффициент халвинга pending = int(pending_seconds * self.current_halving_coefficient) return { "confirmed": confirmed, "pending": pending, "total": confirmed + pending } def send(self, from_addr: str, to_addr: str, amount: int) -> Dict[str, Any]: """Перевод — EVENT SOURCING приоритет""" if self.ledger: ok, msg, event = self.ledger.transfer(from_addr, to_addr, amount) if ok: return {"success": True, "proof": event.event_id, "event_hash": event.event_hash} return {"success": False, "error": msg} # Fallback на старый метод proof = self.db.send(from_addr, to_addr, amount) if proof: return {"success": True, "proof": proof} return {"success": False} def tx_feed(self, limit: int = 50) -> List[Dict[str, Any]]: """Публичная лента TX""" return self.db.tx_feed(limit) def tx_verify(self, proof: str) -> Dict[str, Any]: """Верификация TX""" return self.db.tx_verify(proof) def my_txs(self, address: str, limit: int = 50) -> List[Dict[str, Any]]: """Личная история TX""" return self.db.my_txs(address, limit) def get_reserve_info(self) -> Dict[str, Any]: """ Информация о резерве TIME_BANK (21 млн минут). Returns: { "reserve_minutes": int, # Осталось минут "reserve_years": float, # Осталось лет (~40 изначально) "total_minutes": int, # Всего (21,000,000) "spent_minutes": int, # Потрачено "halving_coefficient": float, # Текущий коэффициент халвинга "exhausted": bool # True если резерв исчерпан } """ spent_minutes = self.bank_seconds_spent // 60 remaining_minutes = max(0, Protocol.BANK_TOTAL_MINUTES - spent_minutes) remaining_years = remaining_minutes / (60 * 24 * 365) return { "reserve_minutes": remaining_minutes, "reserve_years": remaining_years, "total_minutes": Protocol.BANK_TOTAL_MINUTES, "spent_minutes": spent_minutes, "halving_coefficient": self.current_halving_coefficient, "exhausted": self.bank_exhausted } def wallets(self, addr_type: str = None) -> List[Dict[str, Any]]: """Все кошельки""" return self.db.wallets(addr_type) def stats(self) -> Dict[str, Any]: """Статистика TIME_BANK""" t2_elapsed = int(time.time() - self.current_t2_start) return { "version": Protocol.VERSION, "emission_model": "dynamic", # Динамическая эмиссия "last_t2_emission": self.t2_emission, "t2_count": self.t2_count, "t2_elapsed_sec": t2_elapsed, "t2_remaining_sec": max(0, Protocol.T2_DURATION_SEC - t2_elapsed), "total_emitted": self.total_emitted, "total_distributed": self.total_distributed, "active_presence": self.presence.count_active(), "wallets": len(self.db.wallets()), # Temporal Coordinates "tau3_count": self.tau3_count, "tau4_count": self.tau4_count, "current_year": self.tau3_count // Protocol.TAU3_PER_YEAR, "halving_coefficient": self.current_halving_coefficient, "t2_to_next_tau3": Protocol.T2_PER_TAU3 - (self.t2_count % Protocol.T2_PER_TAU3), # TIME_BANK RESERVE (21 млн минут) "bank_total_minutes": Protocol.BANK_TOTAL_MINUTES, "bank_seconds_spent": self.bank_seconds_spent, "bank_minutes_spent": self.bank_seconds_spent // 60, "bank_minutes_remaining": max(0, Protocol.BANK_TOTAL_MINUTES - self.bank_seconds_spent // 60), "bank_exhausted": self.bank_exhausted, "bank_years_remaining": max(0, (Protocol.BANK_TOTAL_SECONDS - self.bank_seconds_spent) / (525600 * 60)), # ML-DSA-65 Presence Proof "ml_dsa_65": ML_DSA_AVAILABLE, "presence_proofs": len(self._presence_proofs), "tau1_counter": self._tau1_counter, "node_keys_set": bool(self._node_private_key), # Event Sourcing "event_sourcing": EVENT_SOURCING_ENABLED, "ledger_stats": self.ledger.stats() if self.ledger else None, # TimeChain — Immutable Ledger "timechain_enabled": TIMECHAIN_ENABLED, "timechain_blocks": self.timechain.total_blocks() if self.timechain else 0 } # -------------------------------------------------------- # ФОНОВЫЙ ПРОЦЕСС # -------------------------------------------------------- def run(self): """Запускает фоновый процесс""" if self._running: return self._running = True self._thread = threading.Thread(target=self._tick_loop, daemon=True) self._thread.start() logger.info("⏱️ TIME_BANK запущен") def stop(self): """Останавливает""" self._running = False if self._thread: self._thread.join(timeout=2) logger.info("⏹️ TIME_BANK остановлен") def _tick_loop(self): """Основной цикл""" while self._running: self._tick() time.sleep(Protocol.TICK_INTERVAL_SEC) def _tick(self): """Обновление каждую секунду""" now = time.time() # Проверяем окончание T2 if now - self.current_t2_start >= Protocol.T2_DURATION_SEC: self._finalize_t2() # τ₁ — подпись присутствия каждую минуту (ML-DSA-65) self._tau1_counter += 1 if self._tau1_counter >= Protocol.TAU1_INTERVAL_SEC: self._sign_presence_proof() self._tau1_counter = 0 # Обновляем все адреса for address, entry in self.presence.items_snapshot(): inactive = now - entry["last_activity"] if inactive > Protocol.INACTIVITY_LIMIT_SEC: if entry["is_active"]: entry["is_active"] = False logger.debug(f"⏸️ Пауза: {address}") else: entry["presence_seconds"] += 1 entry["t2_seconds"] += 1 def _finalize_t2(self): """ Завершает T2, начисляет монеты с халвингом Механизм эмиссии: 1. Считаем сумму всех секунд присутствия 2. Банк всегда присутствует 600 секунд (подтверждает что прошло 10 минут) 3. Эмиссия = (total_seconds - bank_seconds) × halving_coefficient 4. Распределяем каждому: user_seconds × halving_coefficient """ self.t2_count += 1 # Вычисляем коэффициент халвинга self.current_halving_coefficient = halving_coefficient(self.tau4_count) # Считаем общую сумму секунд присутствия всех участников total_users_seconds = 0 for address, entry in self.presence.items_snapshot(): total_users_seconds += entry["t2_seconds"] # Банк подтверждает что прошло 10 минут (600 секунд) bank_seconds = Protocol.BANK_PRESENCE_PER_T2 # TIME_BANK RESERVE — отслеживание расхода 21 млн минут # Банк всегда тратит 10 мин/T2, независимо от халвинга self.bank_seconds_spent += bank_seconds # Проверяем исчерпание резерва (21 млн минут = ~40 лет) if not self.bank_exhausted and self.bank_seconds_spent >= Protocol.BANK_TOTAL_SECONDS: self.bank_exhausted = True logger.info(f"") logger.info(f"╔═══════════════════════════════════════════════════════════╗") logger.info(f"║ ⏳ TIME_BANK RESERVE EXHAUSTED — ORACLE MODE ║") logger.info(f"╚═══════════════════════════════════════════════════════════╝") logger.info(f"🏦 Банк потратил все 21 млн минут") logger.info(f"📡 Теперь чистый оракул — продолжает верифицировать время") # Эмиссия = сумма секунд участников × халвинг # Банк НЕ получает монеты — только тратит резерв времени emission = int(total_users_seconds * self.current_halving_coefficient) self.t2_emission = emission self.total_emitted += emission # Распределяем по адресам (каждый получает свои секунды × halving) # EVENT SOURCING: используем ledger.emit() для неизменяемого лога distributed = 0 for address, entry in self.presence.items_snapshot(): if entry["t2_seconds"] > 0: seconds_earned = entry["t2_seconds"] coins = int(seconds_earned * self.current_halving_coefficient) if self.ledger: # EVENT SOURCING — создаём событие EMISSION self.ledger.emit( to_addr=address, amount=coins, metadata={ "t2_index": self.t2_count, "seconds": seconds_earned, "halving": self.current_halving_coefficient, "addr_type": entry.get("addr_type", "unknown") } ) else: # Fallback на старый метод self.db.credit(address, coins, entry.get("addr_type", "unknown")) # TIMECHAIN — IMMUTABLE LEDGER (append-only, hash chaining) # Записываем секунды (не монеты) — это сырое время присутствия if self.timechain: self.timechain.append(address, seconds_earned) distributed += coins entry["t2_seconds"] = 0 self.t2_distributed = distributed self.total_distributed += distributed # Очищаем неактивные записи после финализации inactive_addresses = [ addr for addr, entry in self.presence.items_snapshot() if not entry.get("is_active") and entry.get("t2_seconds", 0) == 0 ] for addr in inactive_addresses: self.presence.remove(addr) logger.debug(f"🗑️ Очистка неактивной записи: {addr}") # Проверяем τ₃ checkpoint (каждые 2016 T2 = 14 дней) if self.t2_count % Protocol.T2_PER_TAU3 == 0: self.tau3_count += 1 logger.info(f"") logger.info(f"╔═══════════════════════════════════════════════════════════╗") logger.info(f"║ τ₃ CHECKPOINT #{self.tau3_count} — 14 ДНЕЙ ПРОЙДЕНО ║") logger.info(f"╚═══════════════════════════════════════════════════════════╝") logger.info(f"⏰ τ₃ Index: {self.tau3_count}") logger.info(f"📅 Year: {self.tau3_count // Protocol.TAU3_PER_YEAR}") logger.info(f"📊 Halving: {self.current_halving_coefficient:.4f}x") logger.info(f"💰 Total Emitted: {self.total_emitted:,} Ɉ") # Проверяем τ₄ epoch (каждые 104 τ₃ = 4 года) — ХАЛВИНГ! if self.tau3_count > 0 and self.tau3_count % Protocol.TAU3_PER_TAU4 == 0: self.tau4_count += 1 logger.info(f"") logger.info(f"╔═══════════════════════════════════════════════════════════╗") logger.info(f"║ 🔥 τ₄ HALVING #{self.tau4_count} — ЭМИССИЯ ÷ 2 ║") logger.info(f"╚═══════════════════════════════════════════════════════════╝") logger.info(f"🎉 τ₄ Epoch: {self.tau4_count}") logger.info(f"📊 Новый коэффициент: {halving_coefficient(self.tau4_count):.4f}x") logger.info(f"💰 Total Emitted: {self.total_emitted:,} Ɉ") logger.info(f"═══ T2 #{self.t2_count} ═══") logger.info(f"👥 Участники: {total_users_seconds} сек") logger.info(f"🏦 Банк: {bank_seconds} сек (из резерва 21M)") logger.info(f"📊 Халвинг: {self.current_halving_coefficient:.4f}x") logger.info(f"📡 Эмиссия: {emission} Ɉ") logger.info(f"💰 Распределено: {distributed} Ɉ") # TIME_BANK RESERVE status bank_minutes_remaining = Protocol.BANK_TOTAL_MINUTES - self.bank_seconds_spent // 60 bank_years_remaining = bank_minutes_remaining / 525600 logger.info(f"⏳ Резерв: {bank_minutes_remaining:,} мин (~{bank_years_remaining:.1f} лет)") self.current_t2_start = time.time() # ============================================================ # SINGLETON # ============================================================ _instance: Optional[TimeBank] = None _lock = threading.Lock() def get_time_bank() -> TimeBank: """Возвращает глобальный экземпляр TimeBank""" global _instance with _lock: if _instance is None: _instance = TimeBank() _instance.run() return _instance # ============================================================ # CLI # ============================================================ if __name__ == "__main__": import sys import json bank = get_time_bank() if len(sys.argv) < 2: print(f""" TIME_BANK v{Protocol.VERSION} — Банк Времени Montana ═══════════════════════════════════════════════════ ЭМИССИЯ: • Динамическая: 1 сек = 1 Ɉ × halving • T2 = {Protocol.T2_DURATION_SEC // 60} мин • Халвинг каждые 4 года (τ₄) РЕЗЕРВ: • {Protocol.BANK_TOTAL_MINUTES:,} минут (~40 лет) Команды: balance — баланс start — начать присутствие activity — активность end — завершить send — перевод wallets — все кошельки stats — статистика proofs — ML-DSA-65 presence proofs proof-status — статус цепочки proofs demo — демо """) sys.exit(0) cmd = sys.argv[1] if cmd == "demo": print(f"🎬 Демо TIME_BANK v{Protocol.VERSION}") print("=" * 50) addr = "demo_123" bank.start(addr, "demo") print(f"▶️ Присутствие: {addr}") print(f"💰 Баланс: {bank.balance(addr)} Ɉ") print("\n⏱️ Симуляция 15 секунд...") for i in range(15): bank.activity(addr, "demo") bank._tick() time.sleep(0.1) info = bank.get(addr) print(f"📊 Присутствие: {info['presence_seconds']} сек") print(f"📊 T2: {info['t2_seconds']} сек") bank.end(addr) print(f"\n🏁 Завершено") print(f"💰 Итого: {bank.balance(addr)} Ɉ") elif cmd == "stats": s = bank.stats() print("📊 Статистика TIME_BANK:") print("=" * 50) for k, v in s.items(): print(f"{k}: {v}") elif cmd == "balance" and len(sys.argv) > 2: addr = sys.argv[2] print(f"💰 {addr}: {bank.balance(addr)} Ɉ") elif cmd == "start" and len(sys.argv) > 2: addr = sys.argv[2] addr_type = sys.argv[3] if len(sys.argv) > 3 else "cli" bank.start(addr, addr_type) print(f"▶️ Присутствие: {addr}") elif cmd == "activity" and len(sys.argv) > 2: addr = sys.argv[2] bank.activity(addr) print(f"✓ Активность: {addr}") elif cmd == "end" and len(sys.argv) > 2: addr = sys.argv[2] result = bank.end(addr) if result: print(f"🏁 Завершено") print(f"💰 Баланс: {bank.balance(addr)} Ɉ") else: print(f"Нет присутствия: {addr}") elif cmd == "send" and len(sys.argv) > 4: from_addr = sys.argv[2] to_addr = sys.argv[3] amount = int(sys.argv[4]) result = bank.send(from_addr, to_addr, amount) if result.get("success"): print(f"✓ TX: {result['proof'][:16]}...") else: print("❌ Ошибка") elif cmd == "wallets": ws = bank.wallets() print("💼 Кошельки:") print("-" * 40) for w in ws[:20]: print(f"{w['address']}: {w['balance']} Ɉ [{w['address_type']}]") elif cmd == "proofs": proofs = bank.get_presence_proofs(10) print("🔐 ML-DSA-65 Presence Proofs:") print("=" * 50) if not proofs: print("Нет подписанных proofs") for p in proofs: print(f"\n#{p['t2_index']} @ {p['timestamp']}") print(f" hash: {p['proof_hash'][:32]}...") print(f" prev: {p['prev_hash'][:32]}...") print(f" sig: {p['signature'][:32]}...") print(f" active: {p['active_addresses']} addresses") elif cmd == "proof-status": status = bank.get_proof_chain_status() print("🔐 Presence Proof Chain Status:") print("=" * 50) print(f"ML-DSA-65: {'✅' if status['ml_dsa_available'] else '❌'}") print(f"Node keys: {'✅' if status['node_keys_set'] else '❌'}") print(f"Total proofs: {status['total_proofs']}") print(f"τ₁ interval: {status['tau1_interval_sec']} sec") print(f"Last hash: {status['last_proof_hash'][:32]}...") else: print(f"Неизвестная команда: {cmd}")