diff --git a/app/core/_config.py b/app/core/_config.py index 67c2d65..695f0a7 100644 --- a/app/core/_config.py +++ b/app/core/_config.py @@ -31,7 +31,7 @@ _now_str = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") LOG_FILEPATH = f"{LOG_DIR}/{_now_str}.log" WEB_APP_URLS = { - 'uploadContent': f"https://web2-client.vercel.app/uploadContent" + 'uploadContent': f"https://my-public-node-8.projscale.dev/uploadContent" } ALLOWED_CONTENT_TYPES = [ diff --git a/app/core/_secrets.py b/app/core/_secrets.py index 6a63273..4f6451f 100644 --- a/app/core/_secrets.py +++ b/app/core/_secrets.py @@ -1,11 +1,14 @@ from os import getenv, urandom import os +import time +import json from nacl.bindings import crypto_sign_seed_keypair from tonsdk.utils import Address from app.core._blockchain.ton.wallet_v3cr3 import WalletV3CR3 from app.core.logger import make_log +from sqlalchemy import create_engine, text def _load_seed_from_env_or_generate() -> bytes: @@ -17,13 +20,83 @@ def _load_seed_from_env_or_generate() -> bytes: return urandom(32) +def _init_seed_via_db() -> bytes: + """Store and read hot seed from PostgreSQL service_config (key='private_key'). + Primary node writes it once; workers wait until it appears. + """ + from app.core._config import DATABASE_URL + + engine = create_engine(DATABASE_URL, pool_pre_ping=True) + role = os.getenv("NODE_ROLE", "worker").lower() + + def db_ready(conn) -> bool: + try: + r = conn.execute(text("SELECT to_regclass('public.service_config')")).scalar() + return r is not None + except Exception: + return False + + # Wait for table to exist + start = time.time() + with engine.connect() as conn: + while not db_ready(conn): + time.sleep(0.5) + if time.time() - start > 120: + raise TimeoutError("service_config table not available") + + def read_seed() -> bytes | None: + row = conn.execute(text("SELECT packed_value FROM service_config WHERE key = :k LIMIT 1"), {"k": "private_key"}).first() + if not row: + return None + packed = row[0] or {} + try: + # packed_value is JSON; ensure dict + if isinstance(packed, str): + packed = json.loads(packed) + seed_hex = packed.get("value") + return bytes.fromhex(seed_hex) if seed_hex else None + except Exception: + return None + + seed = read_seed() + if seed: + return seed + + if role == "primary": + seed = _load_seed_from_env_or_generate() + # Try insert; if another primary raced, ignore + try: + conn.execute( + text("INSERT INTO service_config (key, packed_value) VALUES (:k, CAST(:v AS JSON))"), + {"k": "private_key", "v": json.dumps({"value": seed.hex()})} + ) + conn.commit() + make_log("HotWallet", "Seed saved in service_config by primary", level='info') + return seed + except Exception: + conn.rollback() + # Read again in case of race + seed2 = read_seed() + if seed2: + return seed2 + raise + else: + make_log("HotWallet", "Worker waiting for seed in service_config...", level='info') + while True: + seed = read_seed() + if seed: + return seed + time.sleep(0.5) + + _extra_ton_wallet_options = {} if getenv('TON_CUSTOM_WALLET_ADDRESS'): _extra_ton_wallet_options['address'] = Address(getenv('TON_CUSTOM_WALLET_ADDRESS')) def _init_wallet(): - hot_seed_bytes = _load_seed_from_env_or_generate() + # Primary writes to DB; workers wait and read from DB + hot_seed_bytes = _init_seed_via_db() pub, priv = crypto_sign_seed_keypair(hot_seed_bytes) wallet = WalletV3CR3( private_key=priv, diff --git a/app/core/background/indexer_service.py b/app/core/background/indexer_service.py index 496d7ac..1931cc1 100644 --- a/app/core/background/indexer_service.py +++ b/app/core/background/indexer_service.py @@ -47,9 +47,11 @@ async def indexer_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: except BaseException as e: make_log("TON_Daemon", f"Error while saving TON per STAR price: {e}" + '\n' + traceback.format_exc(), level="ERROR") + from sqlalchemy import cast + from sqlalchemy.dialects.postgresql import JSONB new_licenses = (await session.execute(select(UserContent).where( and_( - ~UserContent.meta.contains({'notification_sent': True}), + ~(cast(UserContent.meta, JSONB).contains({'notification_sent': True})), UserContent.type == 'nft/listen' ) ))).scalars().all() diff --git a/app/core/background/license_service.py b/app/core/background/license_service.py index a6d1a07..d2ed681 100644 --- a/app/core/background/license_service.py +++ b/app/core/background/license_service.py @@ -77,7 +77,7 @@ async def license_index_loop(memory, platform_found: bool, seqno: int) -> [bool, User.last_use > datetime.now() - timedelta(hours=4) ).order_by(User.updated.asc()))).scalars().all() for user in users: - user_wallet_address = user.wallet_address(session) + user_wallet_address = await user.wallet_address_async(session) if not user_wallet_address: make_log("LicenseIndex", f"User {user.id} has no wallet address", level="info") continue diff --git a/app/core/background/ton_service.py b/app/core/background/ton_service.py index de505f9..2814b84 100644 --- a/app/core/background/ton_service.py +++ b/app/core/background/ton_service.py @@ -97,7 +97,7 @@ async def main_fn(memory): service_wallet.create_transfer_message( [{ 'address': highload_wallet.address.to_string(1, 1, 0), - 'amount': int(0.08 * 10 ** 9), + 'amount': int(0.02 * 10 ** 9), 'send_mode': 1, 'payload': begin_cell().store_uint(0, 32).end_cell() }], sw_seqno_value