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 List<ChainStateRequest> requests = new List<ChainStateRequest>();
private readonly ILog log; private readonly ILog log;
private readonly ICodexContracts contracts;
private readonly IChainStateChangeHandler handler; 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.log = new LogPrefixer(log, "(ChainState) ");
this.contracts = contracts;
handler = changeHandler; handler = changeHandler;
TotalSpan = timeRange; StartUtc = startUtc;
} TotalSpan = new TimeRange(startUtc, startUtc);
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;
} }
public TimeRange TotalSpan { get; private set; } public TimeRange TotalSpan { get; private set; }
public IChainStateRequest[] Requests => requests.ToArray(); 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 span = new TimeRange(TotalSpan.To, toUtc);
var events = ChainEvents.FromTimeRange(contracts, span); var events = ChainEvents.FromTimeRange(contracts, span);

View File

@ -6,9 +6,8 @@ using Utils;
namespace TestNetRewarder namespace TestNetRewarder
{ {
public class ChainState public class Keepers
{ {
private HistoricState historicState;
private readonly string[] colorIcons = new[] 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); 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 DiscordRewards;
using System.Numerics; using System.Numerics;
namespace TestNetRewarder namespace TestNetRewarder
{ {
public class MarketTracker public class MarketTracker : IChainStateChangeHandler
{ {
private readonly List<ChainState> buffer = new List<ChainState>(); private readonly List<ChainState> buffer = new List<ChainState>();
@ -120,5 +121,40 @@ namespace TestNetRewarder
} }
return Array.Empty<int>(); 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 GethPlugin;
using Logging; using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Numerics;
using Utils; using Utils;
namespace TestNetRewarder namespace TestNetRewarder
{ {
public class Processor public class Processor : ITimeSegmentHandler, IChainStateChangeHandler
{ {
private static readonly HistoricState historicState = new HistoricState(); private readonly RewardChecker rewardChecker = new RewardChecker();
private static readonly RewardRepo rewardRepo = new RewardRepo(); private readonly MarketTracker marketTracker = new MarketTracker();
private static readonly MarketTracker marketTracker = new MarketTracker(); private readonly ChainState chainState;
private readonly Configuration config;
private readonly ILog log; private readonly ILog log;
private BlockInterval? lastBlockRange; private BlockInterval? lastBlockRange;
public Processor(ILog log) public Processor(Configuration config, ICodexContracts contracts, ILog log)
{ {
this.config = config;
this.log = log; 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 try
{ {
var blockRange = connector.GethNode.ConvertTimeRangeToBlockRange(timeRange); chainState.Update(timeRange.To);
if (!IsNewBlockRange(blockRange))
{
log.Log($"Block range {blockRange} was previously processed. Skipping...");
return;
}
var chainState = new ChainState(historicState, connector.CodexContracts, blockRange);
await ProcessChainState(chainState); await ProcessChainState(chainState);
} }
catch (Exception ex) 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) private async Task ProcessChainState(ChainState chainState)
{ {
log.Log(chainState.EntireString()); log.Log(chainState.EntireString());
@ -92,57 +77,39 @@ namespace TestNetRewarder
return marketTracker.ProcessChainState(chainState); 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 throw new NotImplementedException();
{
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) public void OnRequestStarted(IChainStateRequest request)
{ {
var winningAddresses = PerformCheck(reward, chainState); throw new NotImplementedException();
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) public void OnRequestFinished(IChainStateRequest request)
{ {
var check = GetCheck(reward.CheckConfig); throw new NotImplementedException();
return check.Check(chainState).Distinct().ToArray();
} }
private ICheck GetCheck(CheckConfig config) public void OnRequestFulfilled(IChainStateRequest request)
{ {
switch (config.Type) throw new NotImplementedException();
{ }
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 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 ArgsUniform;
using Logging; using Logging;
using Nethereum.Model;
using Utils; using Utils;
namespace TestNetRewarder namespace TestNetRewarder
@ -27,8 +28,11 @@ namespace TestNetRewarder
new ConsoleLog() new ConsoleLog()
); );
var connector = GethConnector.GethConnector.Initialize(Log);
if (connector == null) throw new Exception("Invalid Geth information");
BotClient = new BotClient(Config, Log); BotClient = new BotClient(Config, Log);
processor = new Processor(Log); processor = new Processor(Config, connector.CodexContracts, Log);
EnsurePath(Config.DataPath); EnsurePath(Config.DataPath);
EnsurePath(Config.LogPath); EnsurePath(Config.LogPath);
@ -41,12 +45,12 @@ namespace TestNetRewarder
EnsureGethOnline(); EnsureGethOnline();
Log.Log("Starting TestNet Rewarder..."); Log.Log("Starting TestNet Rewarder...");
var segmenter = new TimeSegmenter(Log, Config); var segmenter = new TimeSegmenter(Log, Config, processor);
while (!CancellationToken.IsCancellationRequested) while (!CancellationToken.IsCancellationRequested)
{ {
await EnsureBotOnline(); await EnsureBotOnline();
await segmenter.WaitForNextSegment(processor.ProcessTimeSegment); await segmenter.ProcessNextSegment();
await Task.Delay(100, CancellationToken); 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 namespace TestNetRewarder
{ {
public interface ITimeSegmentHandler
{
Task OnNewSegment(TimeRange timeRange);
}
public class TimeSegmenter public class TimeSegmenter
{ {
private readonly ILog log; private readonly ILog log;
private readonly ITimeSegmentHandler handler;
private readonly TimeSpan segmentSize; 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.log = log;
this.handler = handler;
if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 1; 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; 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)); 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 = latest + segmentSize;
var end = start + segmentSize; var waited = await WaitUntilTimeSegmentInPast(end);
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);
if (Program.CancellationToken.IsCancellationRequested) return; if (Program.CancellationToken.IsCancellationRequested) return;
var postfix = "(Catching up...)"; var postfix = "(Catching up...)";
if (waited) postfix = "(Real-time)"; 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}"); await handler.OnNewSegment(range);
var range = new TimeRange(start, end); }
start = end;
await onSegment(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;
} }
} }
} }