diff --git a/src/CAServer.Application.Contracts/Commons/HMACSHA256Helper.cs b/src/CAServer.Application.Contracts/Commons/HMACSHA256Helper.cs new file mode 100644 index 000000000..1f2261a44 --- /dev/null +++ b/src/CAServer.Application.Contracts/Commons/HMACSHA256Helper.cs @@ -0,0 +1,22 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace CAServer.Commons; + +public class HMACSHA256Helper +{ + + public static string ComputeHash(string data, string key) + { + var encoding = new UTF8Encoding(); + byte[] keyBytes = encoding.GetBytes(key); + byte[] messageBytes = encoding.GetBytes(data); + using (var hmacsha256 = new HMACSHA256(keyBytes)) + { + byte[] hashBytes = hmacsha256.ComputeHash(messageBytes); + return Convert.ToBase64String(hashBytes); + } + } + +} \ No newline at end of file diff --git a/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsRequestDto.cs b/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsRequestDto.cs new file mode 100644 index 000000000..e07a0260f --- /dev/null +++ b/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsRequestDto.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace CAServer.Growth.Dtos; + +public class TonGiftsRequestDto +{ + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("userIds")] + public List UserIds { get; set; } + + [JsonProperty("taskId")] + public string TaskId { get; set; } +} \ No newline at end of file diff --git a/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsResponseDto.cs b/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsResponseDto.cs new file mode 100644 index 000000000..9383b80ad --- /dev/null +++ b/src/CAServer.Application.Contracts/Growth/Dtos/TonGiftsResponseDto.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace CAServer.Growth.Dtos; + +public class TonGiftsResponseDto +{ + public List SuccessfulUpdates { get; set; } + + public List FailedUpdates { get; set; } +} + +public class SuccessfulUpdate : TonGiftsBase +{ +} + +public class FailedUpdate : TonGiftsBase +{ + public string Error { get; set; } +} + +public class TonGiftsBase +{ + public string UserId { get; set; } + public string TaskId { get; set; } + public string Status { get; set; } +} \ No newline at end of file diff --git a/src/CAServer.Application.Contracts/Growth/Dtos/ValidateHamsterScoreResponseDto.cs b/src/CAServer.Application.Contracts/Growth/Dtos/ValidateHamsterScoreResponseDto.cs new file mode 100644 index 000000000..ed0a773fc --- /dev/null +++ b/src/CAServer.Application.Contracts/Growth/Dtos/ValidateHamsterScoreResponseDto.cs @@ -0,0 +1,19 @@ +namespace CAServer.Growth.Dtos; + +public class ValidateHamsterScoreResponseDto +{ + public Result Result { get; set; } + + public ErrorMsg ErrorMsg { get; set; } +} + + +public class ErrorMsg +{ + public string Message { get; set; } +} + +public class Result +{ + public bool ValidateResult { get; set; } = false; +} \ No newline at end of file diff --git a/src/CAServer.Application.Contracts/Growth/IGrowthStatisticAppService.cs b/src/CAServer.Application.Contracts/Growth/IGrowthStatisticAppService.cs index 4f5eaa6d1..ec17cb214 100644 --- a/src/CAServer.Application.Contracts/Growth/IGrowthStatisticAppService.cs +++ b/src/CAServer.Application.Contracts/Growth/IGrowthStatisticAppService.cs @@ -14,5 +14,9 @@ public interface IGrowthStatisticAppService Task GetRewardProgressAsync(ActivityEnums activityEnum); Task GetBeInvitedConfigAsync(); Task ActivityBaseInfoAsync(); + + Task ValidateHamsterScoreAsync(string userId); Task RepairHamsterDataAsync(); + Task CollectHamsterUserIdsAsync(string userId); + Task TonGiftsValidateAsync(); } \ No newline at end of file diff --git a/src/CAServer.Application/CAActivity/Provider/ActivityProvider.cs b/src/CAServer.Application/CAActivity/Provider/ActivityProvider.cs index f0df94174..9350a58ad 100644 --- a/src/CAServer.Application/CAActivity/Provider/ActivityProvider.cs +++ b/src/CAServer.Application/CAActivity/Provider/ActivityProvider.cs @@ -186,4 +186,20 @@ public async Task GetNotSuccessTransactionAsync(string QueryContainer Filter(QueryContainerDescriptor f) => f.Bool(b => b.Must(mustQuery)); return await _transactionRepository.GetAsync(Filter); } + + public async Task GetCaHolderInfoAsync(string loginGuardianIdentifierHash, int skipCount = 0, int maxResultCount = 10) + { + return await _graphQlHelper.QueryAsync(new GraphQLRequest + { + Query = @" + query(loginGuardianIdentifierHash:[String],$skipCount:Int!,$maxResultCount:Int!) { + caHolderInfo(dto: {loginGuardianIdentifierHash:loginGuardianIdentifierHash,skipCount:$skipCount,maxResultCount:$maxResultCount}){ + id,chainId,caHash,caAddress,originChainId,managerInfos{address,extraData},guardianList{guardians{verifierId,identifierHash,salt,isLoginGuardian,type}}} + }", + Variables = new + { + loginGuardianIdentifierHash , skipCount, maxResultCount + } + }); + } } \ No newline at end of file diff --git a/src/CAServer.Application/CAActivity/Provider/IActivityProvider.cs b/src/CAServer.Application/CAActivity/Provider/IActivityProvider.cs index 11de2a1b4..658691cc5 100644 --- a/src/CAServer.Application/CAActivity/Provider/IActivityProvider.cs +++ b/src/CAServer.Application/CAActivity/Provider/IActivityProvider.cs @@ -35,4 +35,7 @@ Task> GetNotSuccessTransactionsAsync(string caAdd long endBlockHeight); Task GetNotSuccessTransactionAsync(string caAddress, string transactionId); + + Task GetCaHolderInfoAsync(string identifierHash, int skipCount = 0, + int maxResultCount = 10); } \ No newline at end of file diff --git a/src/CAServer.Application/Growth/GrowthStatisticAppService.cs b/src/CAServer.Application/Growth/GrowthStatisticAppService.cs index 3aa2f3fa5..4d22c0b2a 100644 --- a/src/CAServer.Application/Growth/GrowthStatisticAppService.cs +++ b/src/CAServer.Application/Growth/GrowthStatisticAppService.cs @@ -1,15 +1,22 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Net.Mime; using System.Reflection; +using System.Text; using System.Threading.Tasks; +using AElf; using AElf.Indexing.Elasticsearch; using CAServer.CAActivity.Provider; using CAServer.Cache; +using CAServer.Common; using CAServer.Commons; using CAServer.Entities.Es; using CAServer.EnumType; +using CAServer.Grains; using CAServer.Grains.Grain.ApplicationHandler; +using CAServer.Grains.Grain.Guardian; using CAServer.Growth.Dtos; using CAServer.Growth.Provider; using CAServer.Options; @@ -18,10 +25,12 @@ using Microsoft.Extensions.Options; using Nest; using Newtonsoft.Json; +using Orleans; using Volo.Abp; using Volo.Abp.Auditing; using Volo.Abp.Authorization; using Volo.Abp.Users; +using Result = CAServer.Growth.Dtos.Result; namespace CAServer.Growth; @@ -41,6 +50,10 @@ public class GrowthStatisticAppService : CAServerAppService, IGrowthStatisticApp private readonly HamsterOptions _hamsterOptions; private readonly BeInvitedConfigOptions _beInvitedConfigOptions; private const string RepairDataCache = "Hamster:DataRepairKey"; + private const string HamsterTonGiftsUserIdsKey = "Hamster:TonGifts:UserIdsKey"; + private readonly IClusterClient _clusterClient; + private readonly IHttpClientFactory _httpClientFactory; + private readonly TonGiftsOptions _tonGiftsOptions; public GrowthStatisticAppService(IGrowthProvider growthProvider, @@ -49,7 +62,8 @@ public GrowthStatisticAppService(IGrowthProvider growthProvider, IActivityProvider activityProvider, ILogger logger, IUserAssetsProvider userAssetsProvider, IOptionsSnapshot activityConfigOptions, IOptionsSnapshot hamsterOptions, - IOptionsSnapshot beInvitedConfigOptions) + IOptionsSnapshot beInvitedConfigOptions, IClusterClient clusterClient, + IHttpClientFactory httpClientFactory, IOptionsSnapshot tonGiftsOptions) { _growthProvider = growthProvider; _caHolderRepository = caHolderRepository; @@ -57,6 +71,9 @@ public GrowthStatisticAppService(IGrowthProvider growthProvider, _activityProvider = activityProvider; _logger = logger; _userAssetsProvider = userAssetsProvider; + _clusterClient = clusterClient; + _httpClientFactory = httpClientFactory; + _tonGiftsOptions = tonGiftsOptions.Value; _beInvitedConfigOptions = beInvitedConfigOptions.Value; _hamsterOptions = hamsterOptions.Value; _activityConfigOptions = activityConfigOptions.Value; @@ -447,6 +464,7 @@ await _growthProvider.GetReferralRecordListAsync(null, null, 0, Int16.MaxValue, { result.AddRange(scoreResult); } + index += length; } } @@ -645,6 +663,72 @@ public async Task ActivityBaseInfoAsync() }; } + public async Task ValidateHamsterScoreAsync(string userId) + { + var guardianGrainId = GrainIdHelper.GenerateGrainId("Guardian", userId); + var guardianGrain = _clusterClient.GetGrain(guardianGrainId); + var guardian = guardianGrain.GetGuardianAsync(userId).Result; + if (!guardian.Message.IsNullOrEmpty()) + { + return new ValidateHamsterScoreResponseDto + { + Result = new Result + { + ValidateResult = false + }, + ErrorMsg = + { + Message = guardian.Message + } + }; + } + + var identifierHash = guardian.Data.IdentifierHash; + var caHolderInfo = + await _activityProvider.GetCaHolderInfoAsync(identifierHash); + if (caHolderInfo == null || caHolderInfo.CaHolderInfo.Count == 0) + { + return new ValidateHamsterScoreResponseDto() + { + Result = + { + ValidateResult = false + }, + ErrorMsg = + { + Message = "Account not exist." + } + }; + } + + var address = caHolderInfo.CaHolderInfo.FirstOrDefault()?.CaAddress; + var hamsterScoreList = + await _growthProvider.GetHamsterScoreListAsync(new List { address }, DateTime.UtcNow.AddDays(-1), + DateTime.UtcNow); + if (hamsterScoreList == null || hamsterScoreList.GetScoreInfos.Count == 0) + { + return new ValidateHamsterScoreResponseDto() + { + Result = new Result + { + ValidateResult = false + }, + ErrorMsg = new ErrorMsg + { + Message = "Validate failed." + } + }; + } + + return new ValidateHamsterScoreResponseDto + { + Result = new Result + { + ValidateResult = true + } + }; + } + private ActivityConfig GetActivityDetails(ActivityEnums activityEnum) { _activityConfigOptions.ActivityConfigMap.TryGetValue(activityEnum.ToString(), out var config); @@ -673,10 +757,8 @@ public async Task RepairHamsterDataAsync() _logger.LogDebug("No data need to be repaired."); return; } - _logger.LogDebug("Total Count is {count}",repairList.Count); _logger.LogDebug("Total Count is {count}", repairList.Count); - var count = 0; foreach (var repair in repairList) { @@ -716,6 +798,67 @@ await _activityProvider.GetCaHolderInfoAsync(new List(), } } + public async Task CollectHamsterUserIdsAsync(string userId) + { + var expire = TimeSpan.FromDays(30); + await _cacheProvider.SetAddAsync(HamsterTonGiftsUserIdsKey, userId, expire); + } + + public async Task TonGiftsValidateAsync() + { + var userIds = await _cacheProvider.SetMembersAsync(HamsterTonGiftsUserIdsKey); + if (userIds.Length == 0) + { + _logger.LogDebug("No users need to be validate."); + return; + } + + var ids = new List(); + foreach (var id in userIds) + { + var guardianGrainId = GrainIdHelper.GenerateGrainId("Guardian", id); + var guardianGrain = _clusterClient.GetGrain(guardianGrainId); + var guardian = guardianGrain.GetGuardianAsync(id).Result; + if (!guardian.Message.IsNullOrEmpty()) + { + _logger.LogDebug("TonGift validate error : query user from grain error:{error}", guardian.Message); + continue; + } + + var identifierHash = guardian.Data.IdentifierHash; + var caHolderInfo = + await _activityProvider.GetCaHolderInfoAsync(identifierHash); + if (caHolderInfo == null || caHolderInfo.CaHolderInfo.Count == 0) + { + _logger.LogDebug("TonGift validate error : query user from graphQl error: user not exists"); + continue; + } + + ids.Add(id); + } + + var param = new TonGiftsRequestDto() + { + TaskId = _tonGiftsOptions.TaskId, + Status = "completed", + UserIds = ids + }; + var rawStr = JsonConvert.SerializeObject(param); + var t = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString(); + var Hash = HMACSHA256Helper.ComputeHash("rawStr=" + param + "&t=" + t, _tonGiftsOptions.ApiKey); + var apiKey = _tonGiftsOptions.ApiKey; + const string url = "https://devmini.tongifts.app/"; + var client = _httpClientFactory.CreateClient(); + var tokenParam = JsonConvert.SerializeObject(new + { rawStr, apiKey, Hash, t }); + var requestParam = new StringContent(tokenParam, + Encoding.UTF8, + MediaTypeNames.Application.Json); + + var response = await client.PostAsync(url, requestParam); + var result = await response.Content.ReadAsStringAsync(); + } + private async Task> GetNickNameByCaHashes(List caHashes) { var caHolderList = await GetCaHolderByCaHashAsync(caHashes); diff --git a/src/CAServer.Application/Options/TonGiftsOptions.cs b/src/CAServer.Application/Options/TonGiftsOptions.cs new file mode 100644 index 000000000..2335c7c14 --- /dev/null +++ b/src/CAServer.Application/Options/TonGiftsOptions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace CAServer.Options; + +public class TonGiftsOptions +{ + public string ApiKey { get; set; } + + + public string TaskId { get; set; } +} \ No newline at end of file diff --git a/src/CAServer.Application/Verifier/VerifierAppService.cs b/src/CAServer.Application/Verifier/VerifierAppService.cs index e30d88a54..e786ab130 100644 --- a/src/CAServer.Application/Verifier/VerifierAppService.cs +++ b/src/CAServer.Application/Verifier/VerifierAppService.cs @@ -385,6 +385,7 @@ public async Task VerifyTelegramTokenAsync(VerifyToken try { var userId = GetTelegramUserId(requestDto.AccessToken); + _logger.LogDebug("TeleGram userid is {uid}",userId); var hashInfo = await GetSaltAndHashAsync(userId); var response = await _verifierServerClient.VerifyTelegramTokenAsync(requestDto, hashInfo.Item1, hashInfo.Item2); diff --git a/src/CAServer.EntityEventHandler.Core/Worker/TonGiftsValidateWorker.cs b/src/CAServer.EntityEventHandler.Core/Worker/TonGiftsValidateWorker.cs new file mode 100644 index 000000000..72f432a83 --- /dev/null +++ b/src/CAServer.EntityEventHandler.Core/Worker/TonGiftsValidateWorker.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using CAServer.Growth; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.Threading; + +namespace CAServer.EntityEventHandler.Core.Worker; + +public class TonGiftsValidateWorker : AsyncPeriodicBackgroundWorkerBase +{ + private readonly IGrowthStatisticAppService _growthStatisticAppService; + private readonly ILogger _logger; + + public TonGiftsValidateWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, + IGrowthStatisticAppService growthStatisticAppService, ILogger logger) : base(timer, + serviceScopeFactory) + { + _growthStatisticAppService = growthStatisticAppService; + _logger = logger; + Timer.Period = WorkerConst.InitReferralTimePeriod; + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + _logger.LogDebug("Init referral data starting...."); + await _growthStatisticAppService.TonGiftsValidateAsync(); + } +} \ No newline at end of file diff --git a/src/CAServer.HttpApi/Controllers/GrowthController.cs b/src/CAServer.HttpApi/Controllers/GrowthController.cs index 80033cbfb..2cadb8200 100644 --- a/src/CAServer.HttpApi/Controllers/GrowthController.cs +++ b/src/CAServer.HttpApi/Controllers/GrowthController.cs @@ -82,6 +82,20 @@ public async Task GetActivityBaseInfos() { return await _statisticAppService.ActivityBaseInfoAsync(); } + + [HttpGet("collect-hamster-userid")] + public async Task CollectHamsterUserIds(string userId) + { + await _statisticAppService.CollectHamsterUserIdsAsync(userId); + } + + [HttpGet("validate-hamster-score")] + public async Task ValidateHamsterScore(string userId) + { + return await _statisticAppService.ValidateHamsterScoreAsync(userId); + } + +