From bed57dd35b6ad854301f22103c6bc9664d8e5a23 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Jun 2024 15:34:08 +0200 Subject: [PATCH] Finish replace of old chainstate object in rewarder bot --- Framework/DiscordRewards/CheckConfig.cs | 8 +- .../DiscordRewards/GiveRewardsCommand.cs | 5 + Framework/DiscordRewards/RewardRepo.cs | 12 +- .../ChainMonitor/ChainState.cs | 3 +- .../ChainMonitor/ChainStateRequest.cs | 34 +++++ .../DoNothingChainEventHandler.cs | 4 - .../UtilityTests/DiscordBotTests.cs | 11 +- Tools/TestNetRewarder/BufferLogger.cs | 52 +++++++ Tools/TestNetRewarder/ChainChangeMux.cs | 45 ++++++ Tools/TestNetRewarder/ChainState.cs | 35 ----- Tools/TestNetRewarder/Checks.cs | 141 ----------------- Tools/TestNetRewarder/MarketBuffer.cs | 70 +++++++++ Tools/TestNetRewarder/MarketTracker.cs | 143 ++++-------------- Tools/TestNetRewarder/Processor.cs | 113 ++++---------- Tools/TestNetRewarder/Program.cs | 11 +- Tools/TestNetRewarder/RequestBuilder.cs | 40 +++++ Tools/TestNetRewarder/RewardCheck.cs | 98 ++++++++++++ Tools/TestNetRewarder/RewardChecker.cs | 97 +----------- 18 files changed, 427 insertions(+), 495 deletions(-) create mode 100644 Tools/TestNetRewarder/BufferLogger.cs create mode 100644 Tools/TestNetRewarder/ChainChangeMux.cs delete mode 100644 Tools/TestNetRewarder/ChainState.cs delete mode 100644 Tools/TestNetRewarder/Checks.cs create mode 100644 Tools/TestNetRewarder/MarketBuffer.cs create mode 100644 Tools/TestNetRewarder/RequestBuilder.cs create mode 100644 Tools/TestNetRewarder/RewardCheck.cs diff --git a/Framework/DiscordRewards/CheckConfig.cs b/Framework/DiscordRewards/CheckConfig.cs index 9c4fccb0..34425cea 100644 --- a/Framework/DiscordRewards/CheckConfig.cs +++ b/Framework/DiscordRewards/CheckConfig.cs @@ -13,9 +13,9 @@ namespace DiscordRewards public enum CheckType { Uninitialized, - FilledSlot, - FinishedSlot, - PostedContract, - StartedContract, + HostFilledSlot, + HostFinishedSlot, + ClientPostedContract, + ClientStartedContract, } } diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/GiveRewardsCommand.cs index 48dabcca..dffb4ede 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/GiveRewardsCommand.cs @@ -5,6 +5,11 @@ public RewardUsersCommand[] Rewards { get; set; } = Array.Empty(); public MarketAverage[] Averages { get; set; } = Array.Empty(); public string[] EventsOverview { get; set; } = Array.Empty(); + + public bool HasAny() + { + return Rewards.Any() || Averages.Any() || EventsOverview.Any(); + } } public class RewardUsersCommand diff --git a/Framework/DiscordRewards/RewardRepo.cs b/Framework/DiscordRewards/RewardRepo.cs index 51ac3fcb..28f6173a 100644 --- a/Framework/DiscordRewards/RewardRepo.cs +++ b/Framework/DiscordRewards/RewardRepo.cs @@ -11,19 +11,19 @@ namespace DiscordRewards // Filled any slot new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig { - Type = CheckType.FilledSlot + Type = CheckType.HostFilledSlot }), // Finished any slot new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig { - Type = CheckType.FinishedSlot + Type = CheckType.HostFinishedSlot }), // Finished a sizable slot new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig { - Type = CheckType.FinishedSlot, + Type = CheckType.HostFinishedSlot, MinSlotSize = 10.MB(), MinDuration = TimeSpan.FromMinutes(5.0), }), @@ -31,19 +31,19 @@ namespace DiscordRewards // Posted any contract new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig { - Type = CheckType.PostedContract + Type = CheckType.ClientPostedContract }), // Started any contract new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig { - Type = CheckType.StartedContract + Type = CheckType.ClientStartedContract }), // Started a sizable contract new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig { - Type = CheckType.StartedContract, + Type = CheckType.ClientStartedContract, MinNumberOfHosts = 4, MinSlotSize = 10.MB(), MinDuration = TimeSpan.FromMinutes(5.0), diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 875ea8ea..0d6af676 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -8,7 +8,6 @@ namespace CodexContractsPlugin.ChainMonitor public interface IChainStateChangeHandler { void OnNewRequest(IChainStateRequest request); - void OnRequestStarted(IChainStateRequest request); void OnRequestFinished(IChainStateRequest request); void OnRequestFulfilled(IChainStateRequest request); void OnRequestCancelled(IChainStateRequest request); @@ -115,6 +114,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; + r.Hosts.Add(request.Host, (int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFilled"); handler.OnSlotFilled(r, request.SlotIndex); } @@ -123,6 +123,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; + r.Hosts.RemoveHost((int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFreed"); handler.OnSlotFreed(r, request.SlotIndex); } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs index 419e78c5..d908d193 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.Marketplace; +using GethPlugin; using Logging; namespace CodexContractsPlugin.ChainMonitor @@ -9,6 +10,8 @@ namespace CodexContractsPlugin.ChainMonitor RequestState State { get; } DateTime ExpiryUtc { get; } DateTime FinishedUtc { get; } + EthAddress Client { get; } + RequestHosts Hosts { get; } } public class ChainStateRequest : IChainStateRequest @@ -25,12 +28,17 @@ namespace CodexContractsPlugin.ChainMonitor FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); Log($"[{request.Block.BlockNumber}] Created as {State}."); + + Client = new EthAddress(request.Client); + Hosts = new RequestHosts(); } public Request Request { get; } public RequestState State { get; private set; } public DateTime ExpiryUtc { get; } public DateTime FinishedUtc { get; } + public EthAddress Client { get; } + public RequestHosts Hosts { get; } public void UpdateState(ulong blockNumber, RequestState newState) { @@ -43,4 +51,30 @@ namespace CodexContractsPlugin.ChainMonitor log.Log($"Request '{Request.Id}': {msg}"); } } + + public class RequestHosts + { + private readonly Dictionary hosts = new Dictionary(); + + public void Add(EthAddress host, int index) + { + hosts.Add(index, host); + } + + public void RemoveHost(int index) + { + hosts.Remove(index); + } + + public EthAddress? GetHost(int index) + { + if (!hosts.ContainsKey(index)) return null; + return hosts[index]; + } + + public EthAddress[] GetHosts() + { + return hosts.Values.ToArray(); + } + } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs index 65e038b2..8a3709cc 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -20,10 +20,6 @@ namespace CodexContractsPlugin.ChainMonitor { } - public void OnRequestStarted(IChainStateRequest request) - { - } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) { } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 3ae94741..7aafab6b 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -39,27 +39,24 @@ namespace CodexTests.UtilityTests var client = StartClient(geth, contracts); var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); - var chainState = ChainState.FromEvents( - GetTestLog(), - events, - new DoNothingChainEventHandler()); + var chainState = new ChainState(GetTestLog(), contracts, new DoNothingChainEventHandler(), GetTestRunTimeRange().From); var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); var purchaseContract = ClientPurchasesStorage(client); - chainState.Update(contracts); + chainState.Update(); Assert.That(chainState.Requests.Length, Is.EqualTo(1)); purchaseContract.WaitForStorageContractStarted(); - chainState.Update(contracts); + chainState.Update(); purchaseContract.WaitForStorageContractFinished(); Thread.Sleep(rewarderInterval * 2); apiCalls.Stop(); - chainState.Update(contracts); + chainState.Update(); foreach (var r in repo.Rewards) { diff --git a/Tools/TestNetRewarder/BufferLogger.cs b/Tools/TestNetRewarder/BufferLogger.cs new file mode 100644 index 00000000..f7f9925e --- /dev/null +++ b/Tools/TestNetRewarder/BufferLogger.cs @@ -0,0 +1,52 @@ +using Logging; + +namespace TestNetRewarder +{ + public class BufferLogger : ILog + { + private readonly List lines = new List(); + + public void AddStringReplace(string from, string to) + { + throw new NotImplementedException(); + } + + public LogFile CreateSubfile(string ext = "log") + { + throw new NotImplementedException(); + } + + public void Debug(string message = "", int skipFrames = 0) + { + lines.Add(message); + Trim(); + } + + public void Error(string message) + { + lines.Add($"Error: {message}"); + Trim(); + } + + public void Log(string message) + { + lines.Add(message); + Trim(); + } + + public string[] Get() + { + var result = lines.ToArray(); + lines.Clear(); + return result; + } + + private void Trim() + { + if (lines.Count > 100) + { + lines.RemoveRange(0, 50); + } + } + } +} diff --git a/Tools/TestNetRewarder/ChainChangeMux.cs b/Tools/TestNetRewarder/ChainChangeMux.cs new file mode 100644 index 00000000..f70490bf --- /dev/null +++ b/Tools/TestNetRewarder/ChainChangeMux.cs @@ -0,0 +1,45 @@ +using CodexContractsPlugin.ChainMonitor; +using System.Numerics; + +namespace TestNetRewarder +{ + public class ChainChangeMux : IChainStateChangeHandler + { + private readonly IChainStateChangeHandler[] handlers; + + public ChainChangeMux(params IChainStateChangeHandler[] handlers) + { + this.handlers = handlers; + } + + public void OnNewRequest(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnNewRequest(request); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestCancelled(request); + } + + public void OnRequestFinished(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestFinished(request); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestFulfilled(request); + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + foreach (var handler in handlers) handler.OnSlotFilled(request, slotIndex); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + foreach (var handler in handlers) handler.OnSlotFreed(request, slotIndex); + } + } +} diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs deleted file mode 100644 index a22537f0..00000000 --- a/Tools/TestNetRewarder/ChainState.cs +++ /dev/null @@ -1,35 +0,0 @@ -using CodexContractsPlugin; -using CodexContractsPlugin.Marketplace; -using NethereumWorkflow.BlockUtils; -using Newtonsoft.Json; -using Utils; - -namespace TestNetRewarder -{ - public class Keepers - { - private readonly string[] colorIcons = new[] - { - "🔴", - "🟠", - "🟡", - "🟢", - "🔵", - "🟣", - "🟤", - "⚫", - "⚪", - "🟥", - "🟧", - "🟨", - "🟩", - "🟦", - "🟪", - "🟫", - "⬛", - "⬜", - "🔶", - "🔷" - }; - } -} diff --git a/Tools/TestNetRewarder/Checks.cs b/Tools/TestNetRewarder/Checks.cs deleted file mode 100644 index 43102e06..00000000 --- a/Tools/TestNetRewarder/Checks.cs +++ /dev/null @@ -1,141 +0,0 @@ -using CodexContractsPlugin.Marketplace; -using GethPlugin; -using NethereumWorkflow; -using Utils; - -namespace TestNetRewarder -{ - public interface ICheck - { - EthAddress[] Check(ChainState state); - } - - public class FilledAnySlotCheck : ICheck - { - public EthAddress[] Check(ChainState state) - { - return state.SlotFilledEvents.Select(e => e.Host).ToArray(); - } - } - - public class FinishedSlotCheck : ICheck - { - private readonly ByteSize minSize; - private readonly TimeSpan minDuration; - - public FinishedSlotCheck(ByteSize minSize, TimeSpan minDuration) - { - this.minSize = minSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.FinishedRequests - .Where(r => - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .SelectMany(r => r.Hosts) - .ToArray(); - } - - private bool MeetsSizeRequirement(StorageRequest r) - { - var slotSize = r.Request.Ask.SlotSize.ToDecimal(); - decimal min = minSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(StorageRequest r) - { - var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); - return duration >= minDuration; - } - } - - public class PostedContractCheck : ICheck - { - private readonly ulong minNumberOfHosts; - private readonly ByteSize minSlotSize; - private readonly TimeSpan minDuration; - - public PostedContractCheck(ulong minNumberOfHosts, ByteSize minSlotSize, TimeSpan minDuration) - { - this.minNumberOfHosts = minNumberOfHosts; - this.minSlotSize = minSlotSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.NewRequests - .Where(r => - MeetsNumSlotsRequirement(r) && - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .Select(r => r.ClientAddress) - .ToArray(); - } - - private bool MeetsNumSlotsRequirement(Request r) - { - return r.Ask.Slots >= minNumberOfHosts; - } - - private bool MeetsSizeRequirement(Request r) - { - var slotSize = r.Ask.SlotSize.ToDecimal(); - decimal min = minSlotSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(Request r) - { - var duration = TimeSpan.FromSeconds((double)r.Ask.Duration); - return duration >= minDuration; - } - } - - public class StartedContractCheck : ICheck - { - private readonly ulong minNumberOfHosts; - private readonly ByteSize minSlotSize; - private readonly TimeSpan minDuration; - - public StartedContractCheck(ulong minNumberOfHosts, ByteSize minSlotSize, TimeSpan minDuration) - { - this.minNumberOfHosts = minNumberOfHosts; - this.minSlotSize = minSlotSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.StartedRequests - .Where(r => - MeetsNumSlotsRequirement(r) && - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .Select(r => r.Request.ClientAddress) - .ToArray(); - } - - private bool MeetsNumSlotsRequirement(StorageRequest r) - { - return r.Request.Ask.Slots >= minNumberOfHosts; - } - - private bool MeetsSizeRequirement(StorageRequest r) - { - var slotSize = r.Request.Ask.SlotSize.ToDecimal(); - decimal min = minSlotSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(StorageRequest r) - { - var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); - return duration >= minDuration; - } - } -} diff --git a/Tools/TestNetRewarder/MarketBuffer.cs b/Tools/TestNetRewarder/MarketBuffer.cs new file mode 100644 index 00000000..45573785 --- /dev/null +++ b/Tools/TestNetRewarder/MarketBuffer.cs @@ -0,0 +1,70 @@ +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; +using DiscordRewards; +using System.Numerics; + +namespace TestNetRewarder +{ + public class MarketBuffer + { + private readonly List requests = new List(); + private readonly TimeSpan bufferSpan; + + public MarketBuffer(TimeSpan bufferSpan) + { + this.bufferSpan = bufferSpan; + } + + public void Add(IChainStateRequest request) + { + requests.Add(request); + } + + public void Update() + { + var now = DateTime.UtcNow; + requests.RemoveAll(r => (now - r.FinishedUtc) > bufferSpan); + } + + public MarketAverage? GetAverage() + { + if (requests.Count == 0) return null; + + return new MarketAverage + { + NumberOfFinished = requests.Count, + TimeRangeSeconds = (int)bufferSpan.TotalSeconds, + Price = Average(s => s.Request.Ask.Reward), + Duration = Average(s => s.Request.Ask.Duration), + Size = Average(s => GetTotalSize(s.Request.Ask)), + Collateral = Average(s => s.Request.Ask.Collateral), + ProofProbability = Average(s => s.Request.Ask.ProofProbability) + }; + } + + private float Average(Func getValue) + { + return Average(s => Convert.ToInt32(getValue(s))); + } + + private float Average(Func getValue) + { + var sum = 0.0f; + float count = requests.Count; + foreach (var r in requests) + { + sum += getValue(r); + } + + if (count < 1.0f) return 0.0f; + return sum / count; + } + + private int GetTotalSize(Ask ask) + { + var nSlots = Convert.ToInt32(ask.Slots); + var slotSize = Convert.ToInt32(ask.SlotSize); + return nSlots * slotSize; + } + } +} diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index c0abad3b..669dd3aa 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,160 +1,73 @@ using CodexContractsPlugin.ChainMonitor; -using CodexContractsPlugin.Marketplace; using DiscordRewards; +using Logging; using System.Numerics; namespace TestNetRewarder { public class MarketTracker : IChainStateChangeHandler { - private readonly List buffer = new List(); + private readonly List buffers = new List(); + private readonly ILog log; - public MarketAverage[] ProcessChainState(ChainState chainState) + public MarketTracker(Configuration config, ILog log) { - var intervalCounts = GetInsightCounts(); - if (!intervalCounts.Any()) return Array.Empty(); + var intervals = GetInsightCounts(config); - UpdateBuffer(chainState, intervalCounts.Max()); - var result = intervalCounts - .Select(GenerateMarketAverage) - .Where(a => a != null) - .Cast() - .ToArray(); - - if (!result.Any()) result = Array.Empty(); - return result; - } - - private void UpdateBuffer(ChainState chainState, int maxNumberOfIntervals) - { - buffer.Add(chainState); - while (buffer.Count > maxNumberOfIntervals) + foreach (var i in intervals) { - buffer.RemoveAt(0); - } - } - - private MarketAverage? GenerateMarketAverage(int numberOfIntervals) - { - var states = SelectStates(numberOfIntervals); - return CreateAverage(states); - } - - private ChainState[] SelectStates(int numberOfIntervals) - { - if (numberOfIntervals < 1) return Array.Empty(); - if (numberOfIntervals > buffer.Count) return Array.Empty(); - return buffer.TakeLast(numberOfIntervals).ToArray(); - } - - private MarketAverage? CreateAverage(ChainState[] states) - { - try - { - return new MarketAverage - { - NumberOfFinished = CountNumberOfFinishedRequests(states), - TimeRangeSeconds = GetTotalTimeRange(states), - Price = Average(states, s => s.Request.Ask.Reward), - Duration = Average(states, s => s.Request.Ask.Duration), - Size = Average(states, s => GetTotalSize(s.Request.Ask)), - Collateral = Average(states, s => s.Request.Ask.Collateral), - ProofProbability = Average(states, s => s.Request.Ask.ProofProbability) - }; - } - catch (Exception ex) - { - Program.Log.Error($"Exception in CreateAverage: {ex}"); - return null; - } - } - - private int GetTotalSize(Ask ask) - { - var nSlots = Convert.ToInt32(ask.Slots); - var slotSize = Convert.ToInt32(ask.SlotSize); - return nSlots * slotSize; - } - - private float Average(ChainState[] states, Func getValue) - { - return Average(states, s => Convert.ToInt32(getValue(s))); - } - - private float Average(ChainState[] states, Func getValue) - { - var sum = 0.0f; - var count = 0.0f; - foreach (var state in states) - { - foreach (var finishedRequest in state.FinishedRequests) - { - sum += getValue(finishedRequest); - count++; - } + buffers.Add(new MarketBuffer( + config.Interval * i + )); } - if (count < 1.0f) return 0.0f; - return sum / count; + this.log = log; } - private int GetTotalTimeRange(ChainState[] states) + public MarketAverage[] GetAverages() { - return Convert.ToInt32((Program.Config.Interval * states.Length).TotalSeconds); - } + foreach (var b in buffers) b.Update(); - private int CountNumberOfFinishedRequests(ChainState[] states) - { - return states.Sum(s => s.FinishedRequests.Length); - } - - private int[] GetInsightCounts() - { - try - { - var tokens = Program.Config.MarketInsights.Split(';').ToArray(); - return tokens.Select(t => Convert.ToInt32(t)).ToArray(); - } - catch (Exception ex) - { - Program.Log.Error($"Exception when parsing MarketInsights config parameters: {ex}"); - } - return Array.Empty(); + return buffers.Select(b => b.GetAverage()).Where(a => a != null).Cast().ToArray(); } public void OnNewRequest(IChainStateRequest request) { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); } public void OnRequestFinished(IChainStateRequest request) { - throw new NotImplementedException(); + foreach (var b in buffers) b.Add(request); } public void OnRequestFulfilled(IChainStateRequest request) { - throw new NotImplementedException(); } public void OnRequestCancelled(IChainStateRequest request) { - throw new NotImplementedException(); } public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) { - throw new NotImplementedException(); } public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) { - throw new NotImplementedException(); + } + + private int[] GetInsightCounts(Configuration config) + { + try + { + var tokens = config.MarketInsights.Split(';').ToArray(); + return tokens.Select(t => Convert.ToInt32(t)).ToArray(); + } + catch (Exception ex) + { + log.Error($"Exception when parsing MarketInsights config parameters: {ex}"); + } + return Array.Empty(); } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index b3586659..8cbc4dbd 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -1,29 +1,36 @@ using CodexContractsPlugin; using CodexContractsPlugin.ChainMonitor; -using DiscordRewards; -using GethPlugin; using Logging; -using Newtonsoft.Json; -using System.Numerics; using Utils; namespace TestNetRewarder { - public class Processor : ITimeSegmentHandler, IChainStateChangeHandler + public class Processor : ITimeSegmentHandler { - private readonly RewardChecker rewardChecker = new RewardChecker(); - private readonly MarketTracker marketTracker = new MarketTracker(); + private readonly RequestBuilder builder; + private readonly RewardChecker rewardChecker; + private readonly MarketTracker marketTracker; + private readonly BufferLogger bufferLogger; private readonly ChainState chainState; - private readonly Configuration config; + private readonly BotClient client; private readonly ILog log; - private BlockInterval? lastBlockRange; - public Processor(Configuration config, ICodexContracts contracts, ILog log) + public Processor(Configuration config, BotClient client, ICodexContracts contracts, ILog log) { - this.config = config; + this.client = client; this.log = log; - chainState = new ChainState(log, contracts, this, config.HistoryStartUtc); + builder = new RequestBuilder(); + rewardChecker = new RewardChecker(builder); + marketTracker = new MarketTracker(config, log); + bufferLogger = new BufferLogger(); + + var handler = new ChainChangeMux( + rewardChecker.Handler, + marketTracker + ); + + chainState = new ChainState(new LogSplitter(log, bufferLogger), contracts, handler, config.HistoryStartUtc); } public async Task OnNewSegment(TimeRange timeRange) @@ -31,8 +38,15 @@ namespace TestNetRewarder try { chainState.Update(timeRange.To); - - await ProcessChainState(chainState); + + var averages = marketTracker.GetAverages(); + var lines = bufferLogger.Get(); + + var request = builder.Build(averages, lines); + if (request.HasAny()) + { + await client.SendRewards(request); + } } catch (Exception ex) { @@ -40,76 +54,5 @@ namespace TestNetRewarder throw; } } - - private async Task ProcessChainState(ChainState chainState) - { - log.Log(chainState.EntireString()); - - var outgoingRewards = new List(); - foreach (var reward in rewardRepo.Rewards) - { - ProcessReward(outgoingRewards, reward, chainState); - } - - var marketAverages = GetMarketAverages(chainState); - var eventsOverview = GenerateEventsOverview(chainState); - - log.Log($"Found {outgoingRewards.Count} rewards. " + - $"Found {marketAverages.Length} market averages. " + - $"Found {eventsOverview.Length} events."); - - if (outgoingRewards.Any() || marketAverages.Any() || eventsOverview.Any()) - { - if (!await SendRewardsCommand(outgoingRewards, marketAverages, eventsOverview)) - { - log.Error("Failed to send reward command."); - } - } - } - - private string[] GenerateEventsOverview(ChainState chainState) - { - return chainState.GenerateOverview(); - } - - private MarketAverage[] GetMarketAverages(ChainState chainState) - { - return marketTracker.ProcessChainState(chainState); - } - - public void OnNewRequest(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFinished(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFulfilled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestCancelled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } - - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } } } diff --git a/Tools/TestNetRewarder/Program.cs b/Tools/TestNetRewarder/Program.cs index 1106ff76..0056ffec 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -1,16 +1,15 @@ using ArgsUniform; using Logging; -using Nethereum.Model; using Utils; namespace TestNetRewarder { public class Program { - public static Configuration Config { get; private set; } = null!; - public static ILog Log { get; private set; } = null!; - public static CancellationToken CancellationToken { get; private set; } - public static BotClient BotClient { get; private set; } = null!; + public static CancellationToken CancellationToken; + private static Configuration Config = null!; + private static ILog Log = null!; + private static BotClient BotClient = null!; private static Processor processor = null!; private static DateTime lastCheck = DateTime.MinValue; @@ -32,7 +31,7 @@ namespace TestNetRewarder if (connector == null) throw new Exception("Invalid Geth information"); BotClient = new BotClient(Config, Log); - processor = new Processor(Config, connector.CodexContracts, Log); + processor = new Processor(Config, BotClient, connector.CodexContracts, Log); EnsurePath(Config.DataPath); EnsurePath(Config.LogPath); diff --git a/Tools/TestNetRewarder/RequestBuilder.cs b/Tools/TestNetRewarder/RequestBuilder.cs new file mode 100644 index 00000000..ea06eaac --- /dev/null +++ b/Tools/TestNetRewarder/RequestBuilder.cs @@ -0,0 +1,40 @@ +using DiscordRewards; +using GethPlugin; + +namespace TestNetRewarder +{ + public class RequestBuilder : IRewardGiver + { + private readonly Dictionary> rewards = new Dictionary>(); + + public void Give(RewardConfig reward, EthAddress receiver) + { + if (rewards.ContainsKey(reward.RoleId)) + { + rewards[reward.RoleId].Add(receiver); + } + else + { + rewards.Add(reward.RoleId, new List { receiver }); + } + } + + public GiveRewardsCommand Build(MarketAverage[] marketAverages, string[] lines) + { + var result = new GiveRewardsCommand + { + Rewards = rewards.Select(p => new RewardUsersCommand + { + RewardId = p.Key, + UserAddresses = p.Value.Select(v => v.Address).ToArray() + }).ToArray(), + Averages = marketAverages, + EventsOverview = lines + }; + + rewards.Clear(); + + return result; + } + } +} diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs new file mode 100644 index 00000000..9be87d22 --- /dev/null +++ b/Tools/TestNetRewarder/RewardCheck.cs @@ -0,0 +1,98 @@ +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; +using GethPlugin; +using NethereumWorkflow; +using System.Numerics; + +namespace TestNetRewarder +{ + public interface IRewardGiver + { + void Give(RewardConfig reward, EthAddress receiver); + } + + public class RewardCheck : IChainStateChangeHandler + { + private readonly RewardConfig reward; + private readonly IRewardGiver giver; + + public RewardCheck(RewardConfig reward, IRewardGiver giver) + { + this.reward = reward; + this.giver = giver; + } + + public void OnNewRequest(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.ClientPostedContract, request)) + { + GiveReward(reward, request.Client); + } + } + + public void OnRequestCancelled(IChainStateRequest request) + { + } + + public void OnRequestFinished(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.HostFinishedSlot, request)) + { + foreach (var host in request.Hosts.GetHosts()) + { + GiveReward(reward, host); + } + } + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.ClientStartedContract, request)) + { + GiveReward(reward, request.Client); + } + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + if (MeetsRequirements(CheckType.HostFilledSlot, request)) + { + var host = request.Hosts.GetHost((int)slotIndex); + if (host != null) + { + GiveReward(reward, host); + } + } + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + } + + private void GiveReward(RewardConfig reward, EthAddress receiver) + { + giver.Give(reward, receiver); + } + + private bool MeetsRequirements(CheckType type, IChainStateRequest request) + { + return + reward.CheckConfig.Type == type && + MeetsDurationRequirement(request) && + MeetsSizeRequirement(request); + } + + private bool MeetsSizeRequirement(IChainStateRequest r) + { + var slotSize = r.Request.Ask.SlotSize.ToDecimal(); + decimal min = reward.CheckConfig.MinSlotSize.SizeInBytes; + return slotSize >= min; + } + + private bool MeetsDurationRequirement(IChainStateRequest r) + { + var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); + return duration >= reward.CheckConfig.MinDuration; + } + } +} diff --git a/Tools/TestNetRewarder/RewardChecker.cs b/Tools/TestNetRewarder/RewardChecker.cs index 63151969..0d22baf4 100644 --- a/Tools/TestNetRewarder/RewardChecker.cs +++ b/Tools/TestNetRewarder/RewardChecker.cs @@ -1,102 +1,17 @@ using CodexContractsPlugin.ChainMonitor; using DiscordRewards; -using GethPlugin; -using Nethereum.Model; -using Newtonsoft.Json; -using System.Numerics; namespace TestNetRewarder { - public class RewardChecker : IChainStateChangeHandler + public class RewardChecker { - private static readonly RewardRepo rewardRepo = new RewardRepo(); - - private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + public RewardChecker(IRewardGiver giver) { - var cmd = new GiveRewardsCommand - { - Rewards = outgoingRewards.ToArray(), - Averages = marketAverages.ToArray(), - EventsOverview = eventsOverview - }; - - log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); - return await Program.BotClient.SendRewards(cmd); + var repo = new RewardRepo(); + var checks = repo.Rewards.Select(r => new RewardCheck(r, giver)).ToArray(); + Handler = new ChainChangeMux(checks); } - private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) - { - var winningAddresses = PerformCheck(reward, chainState); - foreach (var win in winningAddresses) - { - log.Log($"Address '{win.Address}' wins '{reward.Message}'"); - } - if (winningAddresses.Any()) - { - outgoingRewards.Add(new RewardUsersCommand - { - RewardId = reward.RoleId, - UserAddresses = winningAddresses.Select(a => a.Address).ToArray() - }); - } - } - - private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) - { - var check = GetCheck(reward.CheckConfig); - return check.Check(chainState).Distinct().ToArray(); - } - - private ICheck GetCheck(CheckConfig config) - { - switch (config.Type) - { - case CheckType.FilledSlot: - return new FilledAnySlotCheck(); - case CheckType.FinishedSlot: - return new FinishedSlotCheck(config.MinSlotSize, config.MinDuration); - case CheckType.PostedContract: - return new PostedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); - case CheckType.StartedContract: - return new StartedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); - } - - throw new Exception("Unknown check type: " + config.Type); - } - - public void OnNewRequest(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFinished(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFulfilled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestCancelled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } - - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } + public IChainStateChangeHandler Handler { get; } } }