montana/Русский/Бот/time_bank.py

991 lines
41 KiB
Python
Raw Permalink Normal View History

#!/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 <addr> баланс
start <addr> начать присутствие
activity <addr> активность
end <addr> завершить
send <from> <to> <amount> перевод
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}")