diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 952d4ec..875ea8e 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -20,37 +20,29 @@ namespace CodexContractsPlugin.ChainMonitor { private readonly List requests = new List(); private readonly ILog log; + private readonly ICodexContracts contracts; private readonly IChainStateChangeHandler handler; - private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) + public ChainState(ILog log, ICodexContracts contracts, IChainStateChangeHandler changeHandler, DateTime startUtc) { this.log = new LogPrefixer(log, "(ChainState) "); + this.contracts = contracts; handler = changeHandler; - TotalSpan = timeRange; - } - - public static ChainState FromTimeRange(ILog log, ICodexContracts contracts, TimeRange timeRange, IChainStateChangeHandler changeHandler) - { - var events = ChainEvents.FromTimeRange(contracts, timeRange); - return FromEvents(log, events, changeHandler); - } - - public static ChainState FromEvents(ILog log, ChainEvents events, IChainStateChangeHandler changeHandler) - { - var state = new ChainState(log, changeHandler, events.BlockInterval.TimeRange); - state.Apply(events); - return state; + StartUtc = startUtc; + TotalSpan = new TimeRange(startUtc, startUtc); } public TimeRange TotalSpan { get; private set; } public IChainStateRequest[] Requests => requests.ToArray(); - public void Update(ICodexContracts contracts) + public DateTime StartUtc { get; } + + public void Update() { - Update(contracts, DateTime.UtcNow); + Update(DateTime.UtcNow); } - public void Update(ICodexContracts contracts, DateTime toUtc) + public void Update(DateTime toUtc) { var span = new TimeRange(TotalSpan.To, toUtc); var events = ChainEvents.FromTimeRange(contracts, span); diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index cef0808..a22537f 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -6,9 +6,8 @@ using Utils; namespace TestNetRewarder { - public class ChainState + public class Keepers { - private HistoricState historicState; private readonly string[] colorIcons = new[] { "🔴", @@ -32,155 +31,5 @@ namespace TestNetRewarder "🔶", "🔷" }; - - public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange) - { - this.historicState = historicState; - - throw new Exception("This is getting a rewrite"); - - //NewRequests = contracts.GetStorageRequests(blockRange); - //historicState.CleanUpOldRequests(); - //historicState.ProcessNewRequests(NewRequests); - //historicState.UpdateStorageRequests(contracts); - - //StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); - //FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); - //RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); - //RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); - //SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); - //SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); - } - - public ChainState( - Request[] newRequests, - RequestFulfilledEventDTO[] requestFulfilledEvents, - RequestCancelledEventDTO[] requestCancelledEvents, - SlotFilledEventDTO[] slotFilledEvents, - SlotFreedEventDTO[] slotFreedEvents) - { - NewRequests = newRequests; - RequestFulfilledEvents = requestFulfilledEvents; - RequestCancelledEvents = requestCancelledEvents; - SlotFilledEvents = slotFilledEvents; - SlotFreedEvents = slotFreedEvents; - - historicState = new HistoricState(); - StartedRequests = Array.Empty(); - FinishedRequests = Array.Empty(); - } - - public Request[] NewRequests { get; } - [JsonIgnore] - public StorageRequest[] AllRequests => historicState.StorageRequests; - [JsonIgnore] - public StorageRequest[] StartedRequests { get; private set; } - [JsonIgnore] - public StorageRequest[] FinishedRequests { get; private set; } - public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; } - public RequestCancelledEventDTO[] RequestCancelledEvents { get; } - public SlotFilledEventDTO[] SlotFilledEvents { get; } - public SlotFreedEventDTO[] SlotFreedEvents { get; } - - public string EntireString() - { - return - $"ChainState=[{JsonConvert.SerializeObject(this)}]" + - $"HistoricState=[{historicState.EntireString()}]"; - } - - public void Set(HistoricState h) - { - historicState = h; - } - - public string[] GenerateOverview() - { - var entries = new List(); - - entries.AddRange(NewRequests.Select(ToPair)); - entries.AddRange(RequestFulfilledEvents.Select(ToPair)); - entries.AddRange(RequestCancelledEvents.Select(ToPair)); - entries.AddRange(SlotFilledEvents.Select(ToPair)); - entries.AddRange(SlotFreedEvents.Select(ToPair)); - entries.AddRange(FinishedRequests.Select(ToPair)); - - entries.Sort(new StringUtcComparer()); - - return entries.Select(ToLine).ToArray(); - } - - private StringBlockNumberPair ToPair(Request r) - { - return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - public StringBlockNumberPair ToPair(StorageRequest r) - { - return new StringBlockNumberPair("FinishedRequest", JsonConvert.SerializeObject(r), r.Request.Block, r.Request.RequestId); - } - - private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) - { - return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(RequestCancelledEventDTO r) - { - return new StringBlockNumberPair("Cancelled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(SlotFilledEventDTO r) - { - return new StringBlockNumberPair("SlotFilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(SlotFreedEventDTO r) - { - return new StringBlockNumberPair("SlotFreed", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private string ToLine(StringBlockNumberPair pair) - { - var nl = Environment.NewLine; - var colorIcon = GetColorIcon(pair.RequestId); - return $"{colorIcon} {pair.Block} ({pair.Name}){nl}" + - $"```json{nl}" + - $"{pair.Str}{nl}" + - $"```"; - } - - private string GetColorIcon(byte[] requestId) - { - var index = requestId[0] % colorIcons.Length; - return colorIcons[index]; - } - - public class StringBlockNumberPair - { - public StringBlockNumberPair(string name, string str, BlockTimeEntry block, byte[] requestId) - { - Name = name; - Str = str; - Block = block; - RequestId = requestId; - } - - public string Name { get; } - public string Str { get; } - public BlockTimeEntry Block { get; } - public byte[] RequestId { get; } - } - - public class StringUtcComparer : IComparer - { - public int Compare(StringBlockNumberPair? x, StringBlockNumberPair? y) - { - if (x == null && y == null) return 0; - if (x == null) return 1; - if (y == null) return -1; - return x.Block.BlockNumber.CompareTo(y.Block.BlockNumber); - } - } } } diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index c88a0aa..5d6ae51 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -40,5 +40,14 @@ namespace TestNetRewarder return TimeSpan.FromMinutes(IntervalMinutes); } } + + public DateTime HistoryStartUtc + { + get + { + if (CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet."); + return DateTimeOffset.FromUnixTimeSeconds(CheckHistoryTimestamp).UtcDateTime; + } + } } } diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index 17e5e82..c0abad3 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,10 +1,11 @@ -using CodexContractsPlugin.Marketplace; +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; using DiscordRewards; using System.Numerics; namespace TestNetRewarder { - public class MarketTracker + public class MarketTracker : IChainStateChangeHandler { private readonly List buffer = new List(); @@ -120,5 +121,40 @@ namespace TestNetRewarder } return Array.Empty(); } + + 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/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 678d4f8..b358665 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -1,39 +1,37 @@ -using DiscordRewards; +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; using GethPlugin; using Logging; using Newtonsoft.Json; +using System.Numerics; using Utils; namespace TestNetRewarder { - public class Processor + public class Processor : ITimeSegmentHandler, IChainStateChangeHandler { - private static readonly HistoricState historicState = new HistoricState(); - private static readonly RewardRepo rewardRepo = new RewardRepo(); - private static readonly MarketTracker marketTracker = new MarketTracker(); + private readonly RewardChecker rewardChecker = new RewardChecker(); + private readonly MarketTracker marketTracker = new MarketTracker(); + private readonly ChainState chainState; + private readonly Configuration config; private readonly ILog log; private BlockInterval? lastBlockRange; - public Processor(ILog log) + public Processor(Configuration config, ICodexContracts contracts, ILog log) { + this.config = config; this.log = log; + + chainState = new ChainState(log, contracts, this, config.HistoryStartUtc); } - public async Task ProcessTimeSegment(TimeRange timeRange) + public async Task OnNewSegment(TimeRange timeRange) { - var connector = GethConnector.GethConnector.Initialize(log); - if (connector == null) throw new Exception("Invalid Geth information"); - try { - var blockRange = connector.GethNode.ConvertTimeRangeToBlockRange(timeRange); - if (!IsNewBlockRange(blockRange)) - { - log.Log($"Block range {blockRange} was previously processed. Skipping..."); - return; - } - - var chainState = new ChainState(historicState, connector.CodexContracts, blockRange); + chainState.Update(timeRange.To); + await ProcessChainState(chainState); } catch (Exception ex) @@ -43,19 +41,6 @@ namespace TestNetRewarder } } - private bool IsNewBlockRange(BlockInterval blockRange) - { - if (lastBlockRange == null || - lastBlockRange.From != blockRange.From || - lastBlockRange.To != blockRange.To) - { - lastBlockRange = blockRange; - return true; - } - - return false; - } - private async Task ProcessChainState(ChainState chainState) { log.Log(chainState.EntireString()); @@ -92,57 +77,39 @@ namespace TestNetRewarder return marketTracker.ProcessChainState(chainState); } - private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + public void OnNewRequest(IChainStateRequest request) { - 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); + throw new NotImplementedException(); } - private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) + public void OnRequestStarted(IChainStateRequest request) { - 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() - }); - } + throw new NotImplementedException(); } - private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) + public void OnRequestFinished(IChainStateRequest request) { - var check = GetCheck(reward.CheckConfig); - return check.Check(chainState).Distinct().ToArray(); + throw new NotImplementedException(); } - private ICheck GetCheck(CheckConfig config) + public void OnRequestFulfilled(IChainStateRequest request) { - 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 NotImplementedException(); + } - throw new Exception("Unknown check type: " + config.Type); + 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 0aaa41c..1106ff7 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -1,5 +1,6 @@ using ArgsUniform; using Logging; +using Nethereum.Model; using Utils; namespace TestNetRewarder @@ -27,8 +28,11 @@ namespace TestNetRewarder new ConsoleLog() ); + var connector = GethConnector.GethConnector.Initialize(Log); + if (connector == null) throw new Exception("Invalid Geth information"); + BotClient = new BotClient(Config, Log); - processor = new Processor(Log); + processor = new Processor(Config, connector.CodexContracts, Log); EnsurePath(Config.DataPath); EnsurePath(Config.LogPath); @@ -41,12 +45,12 @@ namespace TestNetRewarder EnsureGethOnline(); Log.Log("Starting TestNet Rewarder..."); - var segmenter = new TimeSegmenter(Log, Config); + var segmenter = new TimeSegmenter(Log, Config, processor); while (!CancellationToken.IsCancellationRequested) { await EnsureBotOnline(); - await segmenter.WaitForNextSegment(processor.ProcessTimeSegment); + await segmenter.ProcessNextSegment(); await Task.Delay(100, CancellationToken); } } diff --git a/Tools/TestNetRewarder/RewardChecker.cs b/Tools/TestNetRewarder/RewardChecker.cs new file mode 100644 index 0000000..6315196 --- /dev/null +++ b/Tools/TestNetRewarder/RewardChecker.cs @@ -0,0 +1,102 @@ +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; +using GethPlugin; +using Nethereum.Model; +using Newtonsoft.Json; +using System.Numerics; + +namespace TestNetRewarder +{ + public class RewardChecker : IChainStateChangeHandler + { + private static readonly RewardRepo rewardRepo = new RewardRepo(); + + private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + { + 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); + } + + 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(); + } + } +} diff --git a/Tools/TestNetRewarder/TimeSegmenter.cs b/Tools/TestNetRewarder/TimeSegmenter.cs index 44062f8..24899ed 100644 --- a/Tools/TestNetRewarder/TimeSegmenter.cs +++ b/Tools/TestNetRewarder/TimeSegmenter.cs @@ -3,51 +3,60 @@ using Utils; namespace TestNetRewarder { + public interface ITimeSegmentHandler + { + Task OnNewSegment(TimeRange timeRange); + } + public class TimeSegmenter { private readonly ILog log; + private readonly ITimeSegmentHandler handler; private readonly TimeSpan segmentSize; - private DateTime start; + private DateTime latest; - public TimeSegmenter(ILog log, Configuration configuration) + public TimeSegmenter(ILog log, Configuration configuration, ITimeSegmentHandler handler) { this.log = log; - + this.handler = handler; if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 1; - if (configuration.CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet."); segmentSize = configuration.Interval; - start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime; + latest = configuration.HistoryStartUtc; - log.Log("Starting time segments at " + start); + log.Log("Starting time segments at " + latest); log.Log("Segment size: " + Time.FormatDuration(segmentSize)); } - public async Task WaitForNextSegment(Func onSegment) + public async Task ProcessNextSegment() { - var now = DateTime.UtcNow; - var end = start + segmentSize; - var waited = false; - if (end > now) - { - // Wait for the entire time segment to be in the past. - var delay = end - now; - waited = true; - log.Log($"Waiting till time segment is in the past... {Time.FormatDuration(delay)}"); - await Task.Delay(delay, Program.CancellationToken); - } - await Task.Delay(TimeSpan.FromSeconds(3), Program.CancellationToken); + var end = latest + segmentSize; + var waited = await WaitUntilTimeSegmentInPast(end); if (Program.CancellationToken.IsCancellationRequested) return; var postfix = "(Catching up...)"; if (waited) postfix = "(Real-time)"; + log.Log($"Time segment [{latest} to {end}] {postfix}"); + + var range = new TimeRange(latest, end); + latest = end; - log.Log($"Time segment [{start} to {end}] {postfix}"); - var range = new TimeRange(start, end); - start = end; + await handler.OnNewSegment(range); + } - await onSegment(range); + private async Task WaitUntilTimeSegmentInPast(DateTime end) + { + await Task.Delay(TimeSpan.FromSeconds(3), Program.CancellationToken); + + var now = DateTime.UtcNow; + while (end > now) + { + var delay = (end - now) + TimeSpan.FromSeconds(3); + await Task.Delay(delay, Program.CancellationToken); + return true; + } + return false; } } }