From d85291369273cc76b1ef3f4c02652d4c68e581f2 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 13 Mar 2025 18:34:08 +0300 Subject: [PATCH] free upload & player --- app/api/routes/_blockchain.py | 90 ++++++++++ app/core/background/ton_service.py | 164 ++++++++++++++++-- app/core/models/_telegram/templates/player.py | 2 +- app/core/models/tasks.py | 4 +- locale/en/LC_MESSAGES/sanic_telegram_bot.mo | Bin 5767 -> 6396 bytes locale/en/LC_MESSAGES/sanic_telegram_bot.po | 9 + 6 files changed, 254 insertions(+), 15 deletions(-) diff --git a/app/api/routes/_blockchain.py b/app/api/routes/_blockchain.py index 565cd45..2446bc2 100644 --- a/app/api/routes/_blockchain.py +++ b/app/api/routes/_blockchain.py @@ -3,6 +3,7 @@ from datetime import datetime import traceback from sanic import response +from sqlalchemy import and_ from tonsdk.boc import begin_cell, begin_dict from tonsdk.utils import Address @@ -18,6 +19,8 @@ from app.core.models.content.user_content import UserContent from app.core.models.node_storage import StoredContent from app.core.models._telegram import Wrapped_CBotChat from app.core._keyboards import get_inline_keyboard +from app.core.models.promo import PromoAction +from app.core.models.tasks import BlockchainTask def valid_royalty_params(royalty_params): @@ -101,6 +104,92 @@ async def s_api_v1_blockchain_send_new_content_message(request): ) i += 1 + promo_free_upload_available = ( + 3 - (request.ctx.db_session.query(PromoAction).filter( + PromoAction.user_internal_id == request.ctx.user.id, + PromoAction.action_type == 'freeUpload', + ).count()) + ) + if request.ctx.db_session.query(BlockchainTask).filter( + and_( + BlockchainTask.user_id == request.ctx.user.id, + BlockchainTask.status != 'done', + ) + ).first(): + make_log("Blockchain", f"User {request.ctx.user.id} already has a pending task", level='warning') + promo_free_upload_available = 0 + + make_log("Blockchain", f"User {request.ctx.user.id} has {promo_free_upload_available} free uploads available", level='info') + + if promo_free_upload_available > 0: + promo_action = PromoAction( + user_id = str(request.ctx.user.id), + user_internal_id=request.ctx.user.id, + action_type='freeUpload', + action_ref=str(encrypted_content_cid.content_hash), + created=datetime.now() + ) + request.ctx.db_session.add(promo_action) + + blockchain_task = BlockchainTask( + destination=platform.address.to_string(1, 1, 1), + payload=b64encode( + begin_cell() + .store_uint(0x5491d08c, 32) + .store_uint(int.from_bytes(encrypted_content_cid.content_hash, "big", signed=False), 256) + .store_address(Address(request.ctx.user.wallet_address(request.ctx.db_session))) + .store_ref( + begin_cell() + .store_ref( + begin_cell() + .store_coins(int(0)) + .store_coins(int(0)) + .store_coins(int(request.json['price'])) + .end_cell() + ) + .store_maybe_ref(royalties_dict.end_dict()) + .store_uint(0, 1) + .end_cell() + ) + .store_ref( + begin_cell() + .store_ref( + begin_cell() + .store_bytes(f"{PROJECT_HOST}/api/v1.5/storage/{metadata_content.cid.serialize_v2(include_accept_type=True)}".encode()) + .end_cell() + ) + .store_ref( + begin_cell() + .store_ref(begin_cell().store_bytes(f"{encrypted_content_cid.serialize_v2()}".encode()).end_cell()) + .store_ref(begin_cell().store_bytes(f"{image_content_cid.serialize_v2() if image_content_cid else ''}".encode()).end_cell()) + .store_ref(begin_cell().store_bytes(f"{metadata_content.cid.serialize_v2()}".encode()).end_cell()) + .end_cell() + ) + .end_cell() + ) + .end_cell().to_boc(False) + ).decode(), + epoch=None, seqno=None, + created = datetime.now(), + status='wait', + user_id = request.ctx.user.id + ) + request.ctx.db_session.add(blockchain_task) + request.ctx.db_session.commit() + + await request.ctx.user_uploader_wrapper.send_message( + request.ctx.user.translated('p_uploadContentTxPromo').format( + title=content_title, + free_count=(promo_free_upload_available - 1) + ), message_type='hint', message_meta={ + 'encrypted_content_hash': b58encode(encrypted_content_cid.content_hash).decode(), + 'hint_type': 'uploadContentTxRequested' + } + ) + return response.json({ + 'promoUpload': True, + }) + await request.ctx.user_uploader_wrapper.send_message( request.ctx.user.translated('p_uploadContentTxRequested').format( title=content_title, @@ -117,6 +206,7 @@ async def s_api_v1_blockchain_send_new_content_message(request): begin_cell() .store_uint(0x5491d08c, 32) .store_uint(int.from_bytes(encrypted_content_cid.content_hash, "big", signed=False), 256) + .store_uint(0, 2) .store_ref( begin_cell() .store_ref( diff --git a/app/core/background/ton_service.py b/app/core/background/ton_service.py index 65f0be8..ef6f899 100644 --- a/app/core/background/ton_service.py +++ b/app/core/background/ton_service.py @@ -1,8 +1,11 @@ import asyncio +from base64 import b64decode import os +import traceback +import httpx -from sqlalchemy import and_ -from tonsdk.boc import begin_cell +from sqlalchemy import and_, func +from tonsdk.boc import begin_cell, Cell from tonsdk.contract.wallet import Wallets from tonsdk.utils import HighloadQueryId from datetime import datetime, timedelta @@ -93,7 +96,7 @@ async def main_fn(memory): await toncenter.send_boc( service_wallet.create_transfer_message( [{ - 'address': highload_wallet.address.to_string(1, 1, 1), + 'address': highload_wallet.address.to_string(1, 1, 0), 'amount': int(0.08 * 10 ** 9), 'send_mode': 1, 'payload': begin_cell().store_uint(0, 32).end_cell() @@ -122,18 +125,153 @@ async def main_fn(memory): sw_seqno_value = await get_sw_seqno() make_log("TON", f"Service running ({sw_seqno_value})", level="debug") - # with db_session() as session: - # next_task = session.query(BlockchainTask).filter( - # BlockchainTask.status == 'wait' - # ).first() - # if next_task: - # make_log("TON", f"Processing task {next_task.id}", level="info") - - # next_task.status = 'processing' + with db_session() as session: + # Проверка отправленных сообщений + async def process_incoming_transaction(transaction: dict): + transaction_hash = transaction['transaction_id']['hash'] + transaction_lt = str(transaction['transaction_id']['lt']) + # transaction_success = bool(transaction['success']) - # session.commit() + async def process_incoming_message(blockchain_message: dict): + in_msg_cell = Cell.one_from_boc(b64decode(blockchain_message['msg_data']['body'])) + in_msg_slice = in_msg_cell.refs[0].begin_parse() + in_msg_slice.read_uint(32) + in_msg_slice.read_uint(8) + in_msg_query_id = in_msg_slice.read_uint(23) + in_msg_created_at = in_msg_slice.read_uint(64) + in_msg_epoch = int(in_msg_created_at // (60 * 60)) + in_msg_seqno = HighloadQueryId.from_query_id(in_msg_query_id).to_seqno() + in_msg_blockchain_task = ( + session.query(BlockchainTask).filter( + and_( + BlockchainTask.seqno == in_msg_seqno, + BlockchainTask.epoch == in_msg_epoch, + ) + ) + ).first() + if not in_msg_blockchain_task: + return - await asyncio.sleep(5) + if not (in_msg_blockchain_task.status in ['done']) or in_msg_blockchain_task.transaction_hash != transaction_hash: + in_msg_blockchain_task.status = 'done' + in_msg_blockchain_task.transaction_hash = transaction_hash + in_msg_blockchain_task.transaction_lt = transaction_lt + await session.commit() + + for blockchain_message in [transaction['in_msg']]: + try: + await process_incoming_message(blockchain_message) + except BaseException as e: + pass # make_log("TON_Daemon", f"Error while processing incoming message: {e}" + '\n' + traceback.format_exc(), level='debug') + + try: + sw_transactions = await toncenter.get_transactions(highload_wallet.address.to_string(1, 1, 1), limit=100) + for sw_transaction in sw_transactions: + try: + await process_incoming_transaction(sw_transaction) + except BaseException as e: + make_log("TON_Daemon", f"Error while processing incoming transaction: {e}", level="debug") + except BaseException as e: + make_log("TON_Daemon", f"Error while getting service wallet transactions: {e}", level="ERROR") + + # Отправка подписанных сообщений + for blockchain_task in ( + session.query(BlockchainTask).filter( + BlockchainTask.status == 'processing', + ).order_by(BlockchainTask.updated.asc()).all() + ): + make_log("TON_Daemon", f"Processing task (processing) {blockchain_task.id}") + query_boc = bytes.fromhex(blockchain_task.meta['signed_message']) + errors_list = [] + + try: + await toncenter.send_boc(query_boc) + except BaseException as e: + errors_list.append(f"{e}") + + try: + make_log("TON_Daemon", str( + httpx.post( + 'https://tonapi.io/v2/blockchain/message', + json={ + 'boc': query_boc.hex() + } + ).text + )) + except BaseException as e: + make_log("TON_Daemon", f"Error while pushing task to tonkeeper ({blockchain_task.id}): {e}", level="ERROR") + errors_list.append(f"{e}") + + blockchain_task.updated = datetime.utcnow() + + if blockchain_task.meta['sign_created'] + 10 * 60 < datetime.utcnow().timestamp(): + # or sum([int("terminating vm with exit code 36" in e) for e in errors_list]) > 0: + make_log("TON_Daemon", f"Task {blockchain_task.id} done", level="DEBUG") + blockchain_task.status = 'done' + await session.commit() + continue + + await asyncio.sleep(0.5) + + # Создание новых подписей + for blockchain_task in ( + session.query(BlockchainTask).filter(BlockchainTask.status == 'wait').all() + ): + try: + # Check processing tasks in current epoch < 3_000_000 + if ( + session.query(BlockchainTask).filter( + BlockchainTask.epoch == blockchain_task.epoch, + ).count() > 3_000_000 + ): + make_log("TON", f"Too many processing tasks in epoch {blockchain_task.epoch}", level="error") + await send_status("ton_daemon", f"working: too many tasks in epoch {blockchain_task.epoch}") + await asyncio.sleep(5) + continue + + sign_created = int(datetime.utcnow().timestamp()) - 60 + try: + current_epoch = int(datetime.utcnow().timestamp() // (60 * 60)) + max_epoch_seqno = ( + session.query(func.max(BlockchainTask.seqno)).filter( + BlockchainTask.epoch == current_epoch + ).scalar() or 0 + ) + current_epoch_shift = 3_000_000 if current_epoch % 2 == 0 else 0 + current_seqno = max_epoch_seqno + 1 + (current_epoch_shift if max_epoch_seqno == 0 else 0) + except BaseException as e: + make_log("CRITICAL", f"Error calculating epoch,seqno: {e}", level="error") + current_epoch = 0 + current_seqno = 0 + + blockchain_task.seqno = current_seqno + blockchain_task.epoch = current_epoch + blockchain_task.status = 'processing' + try: + query = highload_wallet.create_transfer_message( + blockchain_task.destination, int(blockchain_task.amount), HighloadQueryId.from_seqno(current_seqno), + sign_created, send_mode=1, + payload=Cell.one_from_boc(b64decode(blockchain_task.payload)) + ) + query_boc = query['message'].to_boc(False) + except BaseException as e: + make_log("TON", f"Error creating transfer message: {e}", level="error") + query_boc = begin_cell().end_cell().to_boc(False) + + blockchain_task.meta = { + **blockchain_task.meta, + 'sign_created': sign_created, + 'signed_message': query_boc, + } + await session.commit() + make_log("TON", f"Created signed message for task {blockchain_task.id}" + '\n' + traceback.format_exc(), level="info") + except BaseException as e: + make_log("TON", f"Error processing task {blockchain_task.id}: {e}" + '\n' + traceback.format_exc(), level="error") + continue + + await asyncio.sleep(1) + + await asyncio.sleep(1) await send_status("ton_daemon", f"working (seqno={sw_seqno_value})") except BaseException as e: make_log("TON", f"Error: {e}", level="error") diff --git a/app/core/models/_telegram/templates/player.py b/app/core/models/_telegram/templates/player.py index 9945963..3235df1 100644 --- a/app/core/models/_telegram/templates/player.py +++ b/app/core/models/_telegram/templates/player.py @@ -182,7 +182,7 @@ class PlayerTemplates: ) ).first()) ) - if have_access: + if False and have_access: full_content = self.db_session.query(StoredContent).filter_by( hash=content.meta.get('converted_content', {}).get('low') # TODO: support high quality ).first() diff --git a/app/core/models/tasks.py b/app/core/models/tasks.py index ec0a80a..dd7621a 100644 --- a/app/core/models/tasks.py +++ b/app/core/models/tasks.py @@ -9,7 +9,8 @@ class BlockchainTask: id = Column(Integer, autoincrement=True, primary_key=True) destination = Column(String(1024), nullable=False) - payload = Column(String(1024), nullable=False) + amount = Column(String(256), nullable=False) + payload = Column(String(4096), nullable=False) epoch = Column(Integer, nullable=True) seqno = Column(Integer, nullable=True) @@ -17,6 +18,7 @@ class BlockchainTask: created = Column(DateTime, nullable=False, default=datetime.now) updated = Column(DateTime, nullable=False, default=datetime.now) + user_id = Column(Integer, ForeignKey('users.id'), nullable=True) meta = Column(JSON, nullable=False, default={}) status = Column(String(256), nullable=False) diff --git a/locale/en/LC_MESSAGES/sanic_telegram_bot.mo b/locale/en/LC_MESSAGES/sanic_telegram_bot.mo index fc1e8074db6ab25ac1b78b72380682f1efb6f615..241008ca59fa4b5335619f334bb2321603adc8db 100644 GIT binary patch delta 1316 zcmZvbOGs2v7{`x|Q>OW7eC4CMTDHi1MMaQFVbm%SX(4SQZ4||vunA0w@qvmMW)2m& zFgJypV04@u$7YP&%eh6hh+0USC`bsRMMS^v&h0^T;LPuS=R4>BJM-os#1G?yL#Y&a5{myU%!chS9rnRoIAL?v_CJL#)?dLa_yKN*KcMJk zBr3I4DW59jLmc~{2pxqIpdMzz7Te!r+c%*I&%oU<2<6-hSOh=9VwmcT@2iA4*tO7r z=WV+e<`Z8{@{xh#5tKw?HZ_z4)?oqs3U@%)=J@_nDC^ZQm##HJIe%q~Qk8H3?twx0 z2EK+Nv6Cnkwo(2;;;RWh&cIdJ3R9Bf6qvPRDRUdb6j~>+bZ{dAtqoiKI#MdcNGh<|@ozG5av)%t5^*Ck?%V zxu_rOP%tCIaQHUvkB=I9QU~-CLoaZ)-yC4`3SzNe!qrVRy)9jC_l0K9wNCGi>WZcs zL(dVlpMA?ZfN<0tGlvi-T3FBH8W17*LdbS55+rE$p}t5C5kp6Gto=_xI#BdaiG~FB zws||;@tB+>Z>ck24k59i!&V$xGaru~GlwND5@Xxn@gh3xU(3vCCGBMiZjO-Il8zb} zb0i(WA$ISN^6tyHGXok&k+B|QL#rwBx4kAEs!qlBUSB7R-B3mceTOO>)Y;^nt DGc98E delta 808 zcmX}qJuE{}6u|M*zM?HkTUDP`UT6@MG!g>?5(bGu6BY@BFp0!qFmzK^jZG{KiBVp< z*(4@bk%%rBL}En5;D6qG=}q4I-Sa-~$2s?JG(VIpM|>wjGr2mrG9Hl;T<~(E`3;d; z44}S`VGPHx0p~G}E3P~4`$LTI{0y7$3d4AdI^H7&L~`=#zW78PNPNx&Ef^&nM*VTx zjkBl&Z=rs_kKI_n9xS=}M`Rs!!d+~~JhosF^@nTs z`8~$T(mU$HJT)R|jG~FN_=HMfos}2!A;3V-ItJQ^?q%VV6%w5nqqVvOt zX|~@=+)shk8P`?ZAl}1Xe0DX+@3?-Cy2CYO7`q&r3Eh;oYA&i0Nck0uH z{BNh4gxH5n4~{lUC}htriylI4B&gCt^hlO{uo9??*G9tZ5^7&dr3Y3^tmnXK97W0` dlv3)gN#np%c=26Y#j0}9N`@1Lbsk$b{sE*hK9c|d diff --git a/locale/en/LC_MESSAGES/sanic_telegram_bot.po b/locale/en/LC_MESSAGES/sanic_telegram_bot.po index 2a16c31..2bfa581 100644 --- a/locale/en/LC_MESSAGES/sanic_telegram_bot.po +++ b/locale/en/LC_MESSAGES/sanic_telegram_bot.po @@ -194,3 +194,12 @@ msgstr "🔗 Поделиться" msgid "p_shareLinkContext" msgstr "🎉 Наслаждайтесь {title} на MY!" + +msgid "p_uploadContentTxPromo" +msgstr "" +"🎉 Вам доступно ещё {free_count} бесплатных приветственных загрузок контента! " +"Контент {title} уже находится в процессе загрузки. Как только блокчейн обработает транзакцию, " +"вы получите NFT-лицензию." + +msgid "p_playerContext_contentNotReady" +msgstr "⚠️ Контент, который вы хотите просмотреть, ещё не готов. Пожалуйста, попробуйте позже."