diff --git a/Framework/NethereumWorkflow/BlockTimeFinder.cs b/Framework/NethereumWorkflow/BlockTimeFinder.cs index e7b8c4a..f7dd543 100644 --- a/Framework/NethereumWorkflow/BlockTimeFinder.cs +++ b/Framework/NethereumWorkflow/BlockTimeFinder.cs @@ -52,7 +52,13 @@ namespace NethereumWorkflow return closestBefore.BlockNumber; } - FetchBlocksAround(moment); + var newBlocks = FetchBlocksAround(moment); + if (newBlocks == 0) + { + log.Log("Didn't find any new blocks."); + if (closestBefore != null) return closestBefore.BlockNumber; + throw new Exception("Failed to find highest before."); + } return GetHighestBlockBefore(moment); } @@ -71,11 +77,17 @@ namespace NethereumWorkflow return closestAfter.BlockNumber; } - FetchBlocksAround(moment); + var newBlocks = FetchBlocksAround(moment); + if (newBlocks == 0) + { + log.Log("Didn't find any new blocks."); + if (closestAfter != null) return closestAfter.BlockNumber; + throw new Exception("Failed to find lowest before."); + } return GetLowestBlockAfter(moment); } - private void FetchBlocksAround(DateTime moment) + private int FetchBlocksAround(DateTime moment) { var timePerBlock = EstimateTimePerBlock(); log.Debug("Fetching blocks around " + moment.ToString("o") + " timePerBlock: " + timePerBlock.TotalSeconds); @@ -85,42 +97,55 @@ namespace NethereumWorkflow var max = entries.Keys.Max(); var blockDifference = CalculateBlockDifference(moment, timePerBlock, max); - FetchUp(max, blockDifference); - FetchDown(max, blockDifference); + return + FetchUp(max, blockDifference) + + FetchDown(max, blockDifference); } - private void FetchDown(ulong max, ulong blockDifference) + private int FetchDown(ulong max, ulong blockDifference) { - var target = max - blockDifference - 1; + var target = GetTarget(max, blockDifference); var fetchDown = FetchRange; + var newBlocks = 0; while (fetchDown > 0) { if (!entries.ContainsKey(target)) { - var newBlock = AddBlockNumber(target); - if (newBlock == null) return; + var newBlock = AddBlockNumber("FD" + fetchDown, target); + if (newBlock == null) return newBlocks; + newBlocks++; fetchDown--; } target--; - if (target <= 0) return; + if (target <= 0) return newBlocks; } + return newBlocks; } - private void FetchUp(ulong max, ulong blockDifference) + private int FetchUp(ulong max, ulong blockDifference) { - var target = max - blockDifference; + var target = GetTarget(max, blockDifference); var fetchUp = FetchRange; + var newBlocks = 0; while (fetchUp > 0) { if (!entries.ContainsKey(target)) { - var newBlock = AddBlockNumber(target); - if (newBlock == null) return; + var newBlock = AddBlockNumber("FU" + fetchUp, target); + if (newBlock == null) return newBlocks; + newBlocks++; fetchUp--; } target++; - if (target >= max) return; + if (target >= max) return newBlocks; } + return newBlocks; + } + + private ulong GetTarget(ulong max, ulong blockDifference) + { + if (max <= blockDifference) return 1; + return max - blockDifference; } private ulong CalculateBlockDifference(DateTime moment, TimeSpan timePerBlock, ulong max) @@ -155,13 +180,14 @@ namespace NethereumWorkflow } } - private BlockTimeEntry? AddBlockNumber(decimal blockNumber) + private BlockTimeEntry? AddBlockNumber(string a, decimal blockNumber) { - return AddBlockNumber(Convert.ToUInt64(blockNumber)); + return AddBlockNumber(a, Convert.ToUInt64(blockNumber)); } - private BlockTimeEntry? AddBlockNumber(ulong blockNumber) + private BlockTimeEntry? AddBlockNumber(string a, ulong blockNumber) { + log.Log(a + " - Adding blockNumber: " + blockNumber); if (entries.ContainsKey(blockNumber)) { return entries[blockNumber]; @@ -190,9 +216,10 @@ namespace NethereumWorkflow { var min = entries.Keys.Min(); var max = entries.Keys.Max(); + log.Log("min/max: " + min + " / " + max); var clippedMin = Math.Max(max - 100, min); var minTime = entries[min].Utc; - var clippedMinBlock = AddBlockNumber(clippedMin); + var clippedMinBlock = AddBlockNumber("EST", clippedMin); if (clippedMinBlock != null) minTime = clippedMinBlock.Utc; var maxTime = entries[max].Utc; @@ -212,7 +239,7 @@ namespace NethereumWorkflow if (!entries.Any()) { AddCurrentBlock(); - AddBlockNumber(entries.Single().Key - 1); + AddBlockNumber("INIT", entries.Single().Key - 1); } } @@ -225,7 +252,7 @@ namespace NethereumWorkflow { var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); var blockNumber = number.ToDecimal(); - return AddBlockNumber(blockNumber); + return AddBlockNumber("CUR", blockNumber); } private DateTime? GetTimestampFromBlock(ulong blockNumber) @@ -238,7 +265,7 @@ namespace NethereumWorkflow } catch (Exception ex) { - int i = 0; + log.Error(nameof(GetTimestampFromBlock) + " Exception: " + ex); throw; } } diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 0a8c2e4..0f56145 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -3,7 +3,6 @@ using Nethereum.ABI.FunctionEncoding.Attributes; using Nethereum.Contracts; using Nethereum.RPC.Eth.DTOs; using Nethereum.Web3; -using System.Runtime.CompilerServices; using Utils; namespace NethereumWorkflow @@ -90,14 +89,18 @@ namespace NethereumWorkflow { var blockTimeFinder = new BlockTimeFinder(web3, log); - var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From); - var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To); + var lowest = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From); + var highest = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To); + + var fromBlock = Math.Min(lowest, highest); + var toBlock = Math.Max(lowest, highest); return GetEvents(address, fromBlock, toBlock); } public List> GetEvents(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new() { + log.Debug($"Getting events of type [{typeof(TEvent).Name}] in block range [{fromBlockNumber} - {toBlockNumber}]"); var eventHandler = web3.Eth.GetEvent(address); var from = new BlockParameter(fromBlockNumber); var to = new BlockParameter(toBlockNumber); diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj index 19422e4..2f796a8 100644 --- a/Tools/BiblioTech/BiblioTech.csproj +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index f21e486..7907177 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -26,12 +26,10 @@ namespace BiblioTech Program.AdminChecker.SetGuild(guild); Program.Log.Log($"Initializing for guild: '{guild.Name}'"); - var roleController = new RoleController(client); - var rewardsApi = new RewardsApi(roleController); - var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray(); if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel"); Program.AdminChecker.SetAdminChannel(adminChannels.First()); + Program.RoleDriver = new RoleDriver(client); var builders = commands.Select(c => { @@ -62,8 +60,6 @@ namespace BiblioTech var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); Program.Log.Error(json); } - - rewardsApi.Start(); } private async Task SlashCommandHandler(SocketSlashCommand command) diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 7d6d690..8f7c98c 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,5 +1,6 @@ using ArgsUniform; using BiblioTech.Commands; +using BiblioTech.Rewards; using Discord; using Discord.WebSocket; using Logging; @@ -13,6 +14,7 @@ namespace BiblioTech public static Configuration Config { get; private set; } = null!; public static UserRepo UserRepo { get; } = new UserRepo(); public static AdminChecker AdminChecker { get; private set; } = null!; + public static IDiscordRoleDriver RoleDriver { get; set; } = null!; public static ILog Log { get; private set; } = null!; public static Task Main(string[] args) @@ -29,10 +31,10 @@ namespace BiblioTech EnsurePath(Config.UserDataPath); EnsurePath(Config.EndpointsPath); - return new Program().MainAsync(); + return new Program().MainAsync(args); } - public async Task MainAsync() + public async Task MainAsync(string[] args) { Log.Log("Starting Codex Discord Bot..."); client = new DiscordSocketClient(); @@ -52,10 +54,19 @@ namespace BiblioTech await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); - AdminChecker = new AdminChecker(); + var builder = WebApplication.CreateBuilder(args); + builder.WebHost.ConfigureKestrel((context, options) => + { + options.ListenAnyIP(Config.RewardApiPort); + }); + builder.Services.AddControllers(); + var app = builder.Build(); + app.MapControllers(); + Log.Log("Running..."); + await app.RunAsync(); await Task.Delay(-1); } diff --git a/Tools/BiblioTech/Properties/launchSettings.json b/Tools/BiblioTech/Properties/launchSettings.json new file mode 100644 index 0000000..74fbfe0 --- /dev/null +++ b/Tools/BiblioTech/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "BiblioTech": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:52960;http://localhost:52961" + } + } +} \ No newline at end of file diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs new file mode 100644 index 0000000..c9a19de --- /dev/null +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -0,0 +1,35 @@ +using DiscordRewards; +using Microsoft.AspNetCore.Mvc; + +namespace BiblioTech.Rewards +{ + public interface IDiscordRoleDriver + { + Task GiveRewards(GiveRewardsCommand rewards); + } + + [Route("api/[controller]")] + [ApiController] + public class RewardController : ControllerBase + { + [HttpGet] + public string Ping() + { + return "Pong"; + } + + [HttpPost] + public async Task Give(GiveRewardsCommand cmd) + { + try + { + await Program.RoleDriver.GiveRewards(cmd); + } + catch (Exception ex) + { + Program.Log.Error("Exception: " + ex); + } + return "OK"; + } + } +} diff --git a/Tools/BiblioTech/Rewards/RewardsApi.cs b/Tools/BiblioTech/Rewards/RewardsApi.cs deleted file mode 100644 index 71055f9..0000000 --- a/Tools/BiblioTech/Rewards/RewardsApi.cs +++ /dev/null @@ -1,91 +0,0 @@ -using DiscordRewards; -using Newtonsoft.Json; -using System.Net; -using TaskFactory = Utils.TaskFactory; - -namespace BiblioTech.Rewards -{ - public interface IDiscordRoleController - { - Task GiveRewards(GiveRewardsCommand rewards); - } - - public class RewardsApi - { - private readonly HttpListener listener = new HttpListener(); - private readonly TaskFactory taskFactory = new TaskFactory(); - private readonly IDiscordRoleController roleController; - private CancellationTokenSource cts = new CancellationTokenSource(); - - public RewardsApi(IDiscordRoleController roleController) - { - this.roleController = roleController; - } - - public void Start() - { - cts = new CancellationTokenSource(); - var uri = $"http://*:{Program.Config.RewardApiPort}/"; - listener.Prefixes.Add(uri); - listener.Start(); - taskFactory.Run(ConnectionDispatcher, nameof(ConnectionDispatcher)); - Program.Log.Log($"Reward API listening on '{uri}'"); - } - - public void Stop() - { - listener.Stop(); - cts.Cancel(); - taskFactory.WaitAll(); - } - - private void ConnectionDispatcher() - { - while (!cts.Token.IsCancellationRequested) - { - var wait = listener.GetContextAsync(); - wait.Wait(cts.Token); - if (wait.IsCompletedSuccessfully) - { - taskFactory.Run(() => - { - var context = wait.Result; - try - { - HandleConnection(context).Wait(); - } - catch (Exception ex) - { - Program.Log.Error("Exception during HTTP handler: " + ex); - } - }, nameof(HandleConnection)); - } - } - } - - private async Task HandleConnection(HttpListenerContext context) - { - using var reader = new StreamReader(context.Request.InputStream); - var content = reader.ReadToEnd(); - - if (content == "Ping") - { - using var writer = new StreamWriter(context.Response.OutputStream); - writer.Write("Pong"); - return; - } - - if (!content.StartsWith("{")) return; - var rewards = JsonConvert.DeserializeObject(content); - if (rewards != null) - { - await ProcessRewards(rewards); - } - } - - private async Task ProcessRewards(GiveRewardsCommand rewards) - { - await roleController.GiveRewards(rewards); - } - } -} diff --git a/Tools/BiblioTech/Rewards/RoleController.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs similarity index 93% rename from Tools/BiblioTech/Rewards/RoleController.cs rename to Tools/BiblioTech/Rewards/RoleDriver.cs index e59e804..2478f52 100644 --- a/Tools/BiblioTech/Rewards/RoleController.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -4,13 +4,13 @@ using DiscordRewards; namespace BiblioTech.Rewards { - public class RoleController : IDiscordRoleController + public class RoleDriver : IDiscordRoleDriver { private readonly DiscordSocketClient client; private readonly SocketTextChannel? rewardsChannel; private readonly RewardRepo repo = new RewardRepo(); - public RoleController(DiscordSocketClient client) + public RoleDriver(DiscordSocketClient client) { this.client = client; @@ -107,7 +107,13 @@ namespace BiblioTech.Rewards private SocketGuild GetGuild() { - return client.Guilds.Single(g => g.Name == Program.Config.ServerName); + var guild = client.Guilds.SingleOrDefault(g => g.Name == Program.Config.ServerName); + if (guild == null) + { + throw new Exception($"Unable to find guild by name: '{Program.Config.ServerName}'. " + + $"Known guilds: [{string.Join(",", client.Guilds.Select(g => g.Name))}]"); + } + return guild; } } diff --git a/Tools/TestNetRewarder/BotClient.cs b/Tools/TestNetRewarder/BotClient.cs index 91e6c83..e908f89 100644 --- a/Tools/TestNetRewarder/BotClient.cs +++ b/Tools/TestNetRewarder/BotClient.cs @@ -1,4 +1,5 @@ -using DiscordRewards; +using CodexContractsPlugin.Marketplace; +using DiscordRewards; using Logging; using Newtonsoft.Json; @@ -17,13 +18,30 @@ namespace TestNetRewarder public async Task IsOnline() { - return await HttpPost("Ping") == "Ping"; + var result = await HttpGet(); + log.Log("Is DiscordBot online: " + result); + return result == "Pong"; } - public async Task SendRewards(GiveRewardsCommand command) + public async Task SendRewards(GiveRewardsCommand command) { - if (command == null || command.Rewards == null || !command.Rewards.Any()) return; - await HttpPost(JsonConvert.SerializeObject(command)); + if (command == null || command.Rewards == null || !command.Rewards.Any()) return false; + return await HttpPost(JsonConvert.SerializeObject(command)) == "OK"; + } + + private async Task HttpGet() + { + try + { + var client = new HttpClient(); + var response = await client.GetAsync(GetUrl()); + return await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + log.Error(ex.ToString()); + return string.Empty; + } } private async Task HttpPost(string content) @@ -43,7 +61,7 @@ namespace TestNetRewarder private string GetUrl() { - return $"{configuration.DiscordHost}:{configuration.DiscordPort}"; + return $"{configuration.DiscordHost}:{configuration.DiscordPort}/api/reward"; } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index e56486b..bd1f20e 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -44,11 +44,14 @@ namespace TestNetRewarder if (outgoingRewards.Any()) { - await SendRewardsCommand(outgoingRewards); + if (!await SendRewardsCommand(outgoingRewards)) + { + log.Error("Failed to send reward command."); + } } } - private async Task SendRewardsCommand(List outgoingRewards) + private async Task SendRewardsCommand(List outgoingRewards) { var cmd = new GiveRewardsCommand { @@ -56,7 +59,7 @@ namespace TestNetRewarder }; log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); - await Program.BotClient.SendRewards(cmd); + return await Program.BotClient.SendRewards(cmd); } private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) diff --git a/Tools/TestNetRewarder/Program.cs b/Tools/TestNetRewarder/Program.cs index 3d10cff..c37c8a3 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -59,6 +59,7 @@ namespace TestNetRewarder var blockNumber = gc.GethNode.GetSyncedBlockNumber(); if (blockNumber == null || blockNumber < 1) throw new Exception("Geth connection failed."); + Log.Log("Geth OK. Block number: " + blockNumber); } private static async Task EnsureBotOnline() diff --git a/Tools/TestNetRewarder/TimeSegmenter.cs b/Tools/TestNetRewarder/TimeSegmenter.cs index a9ad71a..5a555f7 100644 --- a/Tools/TestNetRewarder/TimeSegmenter.cs +++ b/Tools/TestNetRewarder/TimeSegmenter.cs @@ -33,6 +33,7 @@ namespace TestNetRewarder // Wait for the entire time segment to be in the past. var delay = (end - now).Add(TimeSpan.FromSeconds(3)); waited = true; + log.Log($"Waiting till time segment is in the past... {Time.FormatDuration(delay)}"); await Task.Delay(delay, Program.CancellationToken); } diff --git a/Tools/TestNetRewarder/build-docker.bat b/Tools/TestNetRewarder/build-docker.bat new file mode 100644 index 0000000..32f207b --- /dev/null +++ b/Tools/TestNetRewarder/build-docker.bat @@ -0,0 +1,2 @@ +docker build -f docker/Dockerfile -t thatbenbierens/codex-rewardbot:initial ../.. +docker push thatbenbierens/codex-rewardbot:initial