Finish replace of old chainstate object in rewarder bot

This commit is contained in:
Ben 2024-06-17 15:34:08 +02:00
parent c38a2242ba
commit bed57dd35b
No known key found for this signature in database
GPG Key ID: 541B9D8C9F1426A1
18 changed files with 427 additions and 495 deletions

View File

@ -13,9 +13,9 @@ namespace DiscordRewards
public enum CheckType
{
Uninitialized,
FilledSlot,
FinishedSlot,
PostedContract,
StartedContract,
HostFilledSlot,
HostFinishedSlot,
ClientPostedContract,
ClientStartedContract,
}
}

View File

@ -5,6 +5,11 @@
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
public MarketAverage[] Averages { get; set; } = Array.Empty<MarketAverage>();
public string[] EventsOverview { get; set; } = Array.Empty<string>();
public bool HasAny()
{
return Rewards.Any() || Averages.Any() || EventsOverview.Any();
}
}
public class RewardUsersCommand

View File

@ -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),

View File

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

View File

@ -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<int, EthAddress> hosts = new Dictionary<int, EthAddress>();
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();
}
}
}

View File

@ -20,10 +20,6 @@ namespace CodexContractsPlugin.ChainMonitor
{
}
public void OnRequestStarted(IChainStateRequest request)
{
}
public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex)
{
}

View File

@ -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)
{

View File

@ -0,0 +1,52 @@
using Logging;
namespace TestNetRewarder
{
public class BufferLogger : ILog
{
private readonly List<string> lines = new List<string>();
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);
}
}
}
}

View File

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

View File

@ -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[]
{
"🔴",
"🟠",
"🟡",
"🟢",
"🔵",
"🟣",
"🟤",
"⚫",
"⚪",
"🟥",
"🟧",
"🟨",
"🟩",
"🟦",
"🟪",
"🟫",
"⬛",
"⬜",
"🔶",
"🔷"
};
}
}

View File

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

View File

@ -0,0 +1,70 @@
using CodexContractsPlugin.ChainMonitor;
using CodexContractsPlugin.Marketplace;
using DiscordRewards;
using System.Numerics;
namespace TestNetRewarder
{
public class MarketBuffer
{
private readonly List<IChainStateRequest> requests = new List<IChainStateRequest>();
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<IChainStateRequest, BigInteger> getValue)
{
return Average(s => Convert.ToInt32(getValue(s)));
}
private float Average(Func<IChainStateRequest, int> 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;
}
}
}

View File

@ -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<ChainState> buffer = new List<ChainState>();
private readonly List<MarketBuffer> buffers = new List<MarketBuffer>();
private readonly ILog log;
public MarketAverage[] ProcessChainState(ChainState chainState)
public MarketTracker(Configuration config, ILog log)
{
var intervalCounts = GetInsightCounts();
if (!intervalCounts.Any()) return Array.Empty<MarketAverage>();
var intervals = GetInsightCounts(config);
UpdateBuffer(chainState, intervalCounts.Max());
var result = intervalCounts
.Select(GenerateMarketAverage)
.Where(a => a != null)
.Cast<MarketAverage>()
.ToArray();
if (!result.Any()) result = Array.Empty<MarketAverage>();
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<ChainState>();
if (numberOfIntervals > buffer.Count) return Array.Empty<ChainState>();
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<StorageRequest, BigInteger> getValue)
{
return Average(states, s => Convert.ToInt32(getValue(s)));
}
private float Average(ChainState[] states, Func<StorageRequest, int> 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<int>();
return buffers.Select(b => b.GetAverage()).Where(a => a != null).Cast<MarketAverage>().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<int>();
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,40 @@
using DiscordRewards;
using GethPlugin;
namespace TestNetRewarder
{
public class RequestBuilder : IRewardGiver
{
private readonly Dictionary<ulong, List<EthAddress>> rewards = new Dictionary<ulong, List<EthAddress>>();
public void Give(RewardConfig reward, EthAddress receiver)
{
if (rewards.ContainsKey(reward.RoleId))
{
rewards[reward.RoleId].Add(receiver);
}
else
{
rewards.Add(reward.RoleId, new List<EthAddress> { 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;
}
}
}

View File

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

View File

@ -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<bool> SendRewardsCommand(List<RewardUsersCommand> 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<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();
}
public IChainStateChangeHandler Handler { get; }
}
}