diff --git a/tests/raid_test.py b/tests/raid_test.py index 4feb5fd..a26b9e1 100644 --- a/tests/raid_test.py +++ b/tests/raid_test.py @@ -12,6 +12,7 @@ from world_boss.app.kms import signer from world_boss.app.models import Transaction, WorldBossReward, WorldBossRewardAmount from world_boss.app.raid import ( + bulk_insert_transactions, create_unsigned_tx, get_assets, get_latest_raid_id, @@ -387,3 +388,58 @@ def test_get_next_month_last_day(): with patch("datetime.date") as m: m.today.return_value = date(2024, 9, 19) assert get_next_month_last_day() == datetime(2024, 10, 31, tzinfo=timezone.utc) + + +def test_bulk_insert_transactions(fx_session): + content = """3,25,0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4,5b65f5D0e23383FA18d74A62FbEa383c7D11F29d,150000,CRYSTAL,18,175 + 3,25,0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4,5b65f5D0e23383FA18d74A62FbEa383c7D11F29d,560,RUNESTONE_FENRIR1,0,175 + 3,25,0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4,5b65f5D0e23383FA18d74A62FbEa383c7D11F29d,150,RUNESTONE_FENRIR2,0,175 + 3,25,0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4,5b65f5D0e23383FA18d74A62FbEa383c7D11F29d,40,RUNESTONE_FENRIR3,0,175 + 3,26,5b65f5D0e23383FA18d74A62FbEa383c7D11F29d,0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4,560,RUNESTONE_FENRIR1,0,175""" + rows = [r.split(",") for r in content.split("\n")] + nonce_rows_map = {175: rows} + bulk_insert_transactions( + rows, + nonce_rows_map, + datetime(2024, 9, 24, tzinfo=timezone.utc), + fx_session, + signer, + "memo", + ) + + assert len(fx_session.query(Transaction).first().amounts) == 5 + + world_boss_rewards = fx_session.query(WorldBossReward) + for i, world_boss_reward in enumerate(world_boss_rewards): + agent_address = "0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4" + avatar_address = "5b65f5D0e23383FA18d74A62FbEa383c7D11F29d" + ranking = 25 + amounts = [ + ("CRYSTAL", 150000, 18), + ("RUNESTONE_FENRIR1", 560, 0), + ("RUNESTONE_FENRIR2", 150, 0), + ("RUNESTONE_FENRIR3", 40, 0), + ] + if i == 1: + agent_address = "5b65f5D0e23383FA18d74A62FbEa383c7D11F29d" + avatar_address = "0x01069aaf336e6aEE605a8A54D0734b43B62f8Fe4" + ranking = 26 + amounts = [ + ("RUNESTONE_FENRIR1", 560, 0), + ] + + assert world_boss_reward.raid_id == 3 + assert world_boss_reward.ranking == ranking + assert world_boss_reward.agent_address == agent_address + assert world_boss_reward.avatar_address == avatar_address + + assert len(world_boss_reward.amounts) == len(amounts) + + for ticker, amount, decimal_places in amounts: + world_boss_reward_amount = ( + fx_session.query(WorldBossRewardAmount) + .filter_by(reward_id=world_boss_reward.id, ticker=ticker) + .one() + ) + assert world_boss_reward_amount.decimal_places == decimal_places + assert world_boss_reward_amount.amount == amount diff --git a/tests/tasks_test.py b/tests/tasks_test.py index d310b05..615f2fa 100644 --- a/tests/tasks_test.py +++ b/tests/tasks_test.py @@ -15,7 +15,6 @@ RankingRewardWithAgentDictionary, ) from world_boss.app.tasks import ( - check_season, check_signer_balance, count_users, get_ranking_rewards, @@ -371,18 +370,6 @@ def test_stage_transactions_with_countdown( ) -def test_check_season(redisdb, celery_session_worker, fx_session, httpx_mock): - raid_id = 1 - network_type = NetworkType.MAIN - offset = 0 - check_season.delay().get(timeout=10) - httpx_mock.add_response( - method="POST", - url=config.data_provider_url, - json={"data": {"worldBossTotalUsers": 20000}}, - ) - - @pytest.mark.parametrize("exist", [True, False]) def test_save_ranking_rewards( redisdb, diff --git a/world_boss/app/kms.py b/world_boss/app/kms.py index 0cbcc8c..b3ccd2e 100644 --- a/world_boss/app/kms.py +++ b/world_boss/app/kms.py @@ -94,6 +94,20 @@ def _save_transaction( db.commit() return transaction + def sign(self, unsigned_transaction: bytes, nonce: int) -> bytes: + account = ethereum_kms_signer.kms.BasicKmsAccount(self._key_id, self.address) + msg_hash = hashlib.sha256(unsigned_transaction).digest() + _, r, s = account.sign_msg_hash(msg_hash).vrs + + n = int.from_bytes( + base64.b64decode("/////////////////////rqu3OavSKA7v9JejNA2QUE="), "big" + ) + + seq = SequenceOf(componentType=Integer()) + seq.extend([r, min(s, n - s)]) + signature = der_encode(seq) + return self._sign_transaction(unsigned_transaction, signature) + def transfer_assets( self, time_stamp: datetime.datetime, diff --git a/world_boss/app/raid.py b/world_boss/app/raid.py index 9004586..e845d93 100644 --- a/world_boss/app/raid.py +++ b/world_boss/app/raid.py @@ -1,6 +1,7 @@ import calendar import csv import datetime +import hashlib import json import typing from typing import List, Tuple, cast @@ -10,7 +11,7 @@ import jwt from fastapi import HTTPException from fastapi.encoders import jsonable_encoder -from sqlalchemy import func +from sqlalchemy import func, insert from sqlalchemy.orm import Session from starlette.responses import Response @@ -389,3 +390,88 @@ def get_next_month_last_day() -> datetime.datetime: next_month_year, next_month, last_day, tzinfo=datetime.timezone.utc ) return last_date + + +def bulk_insert_transactions( + rows: List[RecipientRow], + nonce_rows_map: dict[int, List[RecipientRow]], + time_stamp: datetime.datetime, + db: Session, + signer, + memo: typing.Optional[str] = None, +): + # ranking : world_boss_reward + world_boss_rewards: dict[int, dict] = {} + signer_address = signer.address + tx_values: List[dict] = [] + tx_ids: dict[int, str] = {} + for n in nonce_rows_map.keys(): + recipient_rows = nonce_rows_map[n] + recipients = [row_to_recipient(r) for r in recipient_rows] + pv = get_transfer_assets_plain_value(signer_address, recipients, memo) + unsigned_transaction = create_unsigned_tx( + config.planet_id, signer.public_key, signer_address, n, pv, time_stamp + ) + signed_transaction = signer.sign(unsigned_transaction, n) + tx_id = hashlib.sha256(signed_transaction).hexdigest() + tx_values.append( + { + "tx_id": tx_id, + "nonce": n, + "signer": signer_address, + "payload": signed_transaction.hex(), + "tx_result": "CREATED", + } + ) + tx_ids[n] = tx_id + db.execute(insert(Transaction), tx_values) + raid_id = int(rows[0][0]) + exist_rankings = [ + r for r, in db.query(WorldBossReward.ranking).filter_by(raid_id=raid_id) + ] + world_boss_reward_amounts: dict[int, list[dict]] = {} + # raid_id,ranking,agent_address,avatar_address,amount,ticker,decimal_places,target_nonce + for row in rows: + # parse row + ranking = int(row[1]) + agent_address = row[2] + avatar_address = row[3] + amount = int(row[4]) + ticker = row[5] + decimal_places = int(row[6]) + nonce = int(row[7]) + + # get or create world_boss_reward + if ranking not in exist_rankings and not world_boss_rewards.get(ranking): + world_boss_reward = { + "raid_id": raid_id, + "ranking": ranking, + "agent_address": agent_address, + "avatar_address": avatar_address, + } + world_boss_rewards[ranking] = world_boss_reward + + # create world_boss_reward_amount + world_boss_reward_amount = { + "amount": amount, + "decimal_places": decimal_places, + "ticker": ticker, + "tx_id": tx_ids[nonce], + } + if not world_boss_reward_amounts.get(ranking): + world_boss_reward_amounts[ranking] = [] + world_boss_reward_amounts[ranking].append(world_boss_reward_amount) + if world_boss_rewards: + db.execute(insert(WorldBossReward), world_boss_rewards.values()) + result = db.query(WorldBossReward).filter_by(raid_id=raid_id) + values = [] + for reward in result: + exist_tickers = [i.ticker for i in reward.amounts] + if world_boss_rewards.get(reward.ranking): + for amounts in world_boss_reward_amounts[reward.ranking]: + if amounts["ticker"] not in exist_tickers: + amounts["reward_id"] = reward.id + values.append(amounts) + if values: + db.execute(insert(WorldBossRewardAmount), values) + db.commit() diff --git a/world_boss/app/tasks.py b/world_boss/app/tasks.py index 87fc0ad..f909978 100644 --- a/world_boss/app/tasks.py +++ b/world_boss/app/tasks.py @@ -15,13 +15,13 @@ from world_boss.app.kms import signer from world_boss.app.models import Transaction, WorldBossReward, WorldBossRewardAmount from world_boss.app.raid import ( + bulk_insert_transactions, get_assets, get_latest_raid_id, get_next_month_last_day, get_next_tx_nonce, get_reward_count, get_tx_delay_factor, - row_to_recipient, update_agent_address, write_ranking_rewards_csv, write_tx_result_csv, @@ -262,7 +262,6 @@ def save_ranking_rewards(raid_id: int, size: int, offset: int, signer_address: s results: List[RankingRewardWithAgentDictionary] = [] payload_size = size time_stamp = get_next_month_last_day() - signed_transactions: List[Transaction] = [] memo = "world boss ranking rewards by world boss signer" with TaskSessionLocal() as db: start_nonce = get_next_tx_nonce(db) @@ -300,12 +299,7 @@ def save_ranking_rewards(raid_id: int, size: int, offset: int, signer_address: s rows.append(row) nonce_rows_map[nonce].append(row) i += 1 - for n in nonce_rows_map.keys(): - recipient_rows = nonce_rows_map[n] - recipients = [row_to_recipient(r) for r in recipient_rows] - tx = signer.transfer_assets(time_stamp, n, recipients, memo, db) - signed_transactions.append(tx) - insert_world_boss_rewards(rows, signer_address) + bulk_insert_transactions(rows, nonce_rows_map, time_stamp, db, memo) @celery.task()