This commit is contained in:
benbierens 2024-06-14 11:05:29 +02:00
parent d3488dc907
commit c38a2242ba
No known key found for this signature in database
GPG Key ID: 877D2C2E09A22F3A
8 changed files with 237 additions and 269 deletions

View File

@ -20,37 +20,29 @@ namespace CodexContractsPlugin.ChainMonitor
{
private readonly List<ChainStateRequest> requests = new List<ChainStateRequest>();
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);

View File

@ -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<StorageRequest>();
FinishedRequests = Array.Empty<StorageRequest>();
}
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<StringBlockNumberPair>();
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<StringBlockNumberPair>
{
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);
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<ChainState> buffer = new List<ChainState>();
@ -120,5 +121,40 @@ namespace TestNetRewarder
}
return Array.Empty<int>();
}
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();
}
}
}

View File

@ -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;
}
chainState.Update(timeRange.To);
var chainState = new ChainState(historicState, connector.CodexContracts, blockRange);
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<bool> SendRewardsCommand(List<RewardUsersCommand> 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<RewardUsersCommand> 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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<bool> SendRewardsCommand(List<RewardUsersCommand> 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<RewardUsersCommand> 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();
}
}
}

View File

@ -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<TimeRange, Task> 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}");
log.Log($"Time segment [{start} to {end}] {postfix}");
var range = new TimeRange(start, end);
start = end;
var range = new TimeRange(latest, end);
latest = end;
await onSegment(range);
await handler.OnNewSegment(range);
}
private async Task<bool> 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;
}
}
}