Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eed6160eb | ||
|
|
c57dc4daa1 | ||
|
|
4c75cebcd6 | ||
|
|
a820788c7d | ||
|
|
52a02abd3f | ||
|
|
a92455b2a5 | ||
|
|
933d5e7d4d | ||
|
|
ba43fd90c6 | ||
|
|
01ee514c73 | ||
|
|
1eb30329c6 | ||
|
|
0ef55abdf4 | ||
|
|
8341807d92 | ||
|
|
c2712daccb | ||
|
|
8033da1176 | ||
|
|
5afab577a7 | ||
|
|
362040bd3c | ||
|
|
40393c3a5b | ||
|
|
7f972bac85 | ||
|
|
d532d9505a |
@@ -0,0 +1,26 @@
|
||||
name: Docker - AutoClient
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
paths:
|
||||
- 'Tools/AutoClient/**'
|
||||
- '!Tools/AutoClient/docker/docker-compose.yaml'
|
||||
- 'Framework/**'
|
||||
- 'ProjectPlugins/**'
|
||||
- .github/workflows/docker-autoclient.yml
|
||||
- .github/workflows/docker-reusable.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push
|
||||
uses: ./.github/workflows/docker-reusable.yml
|
||||
with:
|
||||
docker_file: Tools/AutoClient/docker/Dockerfile
|
||||
docker_repo: codexstorage/codex-autoclient
|
||||
secrets: inherit
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
.vs
|
||||
obj
|
||||
bin
|
||||
.vscode
|
||||
.vscode
|
||||
Tools/AutoClient/datapath
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
using Utils;
|
||||
|
||||
namespace DiscordRewards
|
||||
namespace DiscordRewards
|
||||
{
|
||||
public class RewardRepo
|
||||
{
|
||||
private static string Tag => RewardConfig.UsernameTag;
|
||||
|
||||
public RewardConfig[] Rewards { get; } = new RewardConfig[]
|
||||
{
|
||||
// Filled any slot
|
||||
new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig
|
||||
{
|
||||
Type = CheckType.HostFilledSlot
|
||||
}),
|
||||
public RewardConfig[] Rewards { get; } = new RewardConfig[0];
|
||||
|
||||
// Finished any slot
|
||||
new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig
|
||||
{
|
||||
Type = CheckType.HostFinishedSlot
|
||||
}),
|
||||
// Example configuration, from test server:
|
||||
//{
|
||||
// // Filled any slot
|
||||
// new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig
|
||||
// {
|
||||
// Type = CheckType.HostFilledSlot
|
||||
// }),
|
||||
|
||||
// Finished a sizable slot
|
||||
new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig
|
||||
{
|
||||
Type = CheckType.HostFinishedSlot,
|
||||
MinSlotSize = 10.MB(),
|
||||
MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
}),
|
||||
// // Finished any slot
|
||||
// new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig
|
||||
// {
|
||||
// Type = CheckType.HostFinishedSlot
|
||||
// }),
|
||||
|
||||
// Posted any contract
|
||||
new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig
|
||||
{
|
||||
Type = CheckType.ClientPostedContract
|
||||
}),
|
||||
// // Finished a sizable slot
|
||||
// new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig
|
||||
// {
|
||||
// Type = CheckType.HostFinishedSlot,
|
||||
// MinSlotSize = 10.MB(),
|
||||
// MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
// }),
|
||||
|
||||
// Started any contract
|
||||
new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig
|
||||
{
|
||||
Type = CheckType.ClientStartedContract
|
||||
}),
|
||||
// // Posted any contract
|
||||
// new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig
|
||||
// {
|
||||
// Type = CheckType.ClientPostedContract
|
||||
// }),
|
||||
|
||||
// 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.ClientStartedContract,
|
||||
MinNumberOfHosts = 4,
|
||||
MinSlotSize = 10.MB(),
|
||||
MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
})
|
||||
};
|
||||
// // Started any contract
|
||||
// new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig
|
||||
// {
|
||||
// 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.ClientStartedContract,
|
||||
// MinNumberOfHosts = 4,
|
||||
// MinSlotSize = 10.MB(),
|
||||
// MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
// })
|
||||
//};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +57,27 @@ namespace FileUtils
|
||||
public void ScopedFiles(Action action)
|
||||
{
|
||||
PushFileSet();
|
||||
action();
|
||||
PopFileSet();
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PopFileSet();
|
||||
}
|
||||
}
|
||||
|
||||
public T ScopedFiles<T>(Func<T> action)
|
||||
{
|
||||
PushFileSet();
|
||||
var result = action();
|
||||
PopFileSet();
|
||||
return result;
|
||||
try
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PopFileSet();
|
||||
}
|
||||
}
|
||||
|
||||
private void PushFileSet()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using CodexContractsPlugin.Marketplace;
|
||||
using GethPlugin;
|
||||
using Logging;
|
||||
using NethereumWorkflow.BlockUtils;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
@@ -7,12 +9,24 @@ namespace CodexContractsPlugin.ChainMonitor
|
||||
{
|
||||
public interface IChainStateChangeHandler
|
||||
{
|
||||
void OnNewRequest(IChainStateRequest request);
|
||||
void OnRequestFinished(IChainStateRequest request);
|
||||
void OnRequestFulfilled(IChainStateRequest request);
|
||||
void OnRequestCancelled(IChainStateRequest request);
|
||||
void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex);
|
||||
void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex);
|
||||
void OnNewRequest(RequestEvent requestEvent);
|
||||
void OnRequestFinished(RequestEvent requestEvent);
|
||||
void OnRequestFulfilled(RequestEvent requestEvent);
|
||||
void OnRequestCancelled(RequestEvent requestEvent);
|
||||
void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex);
|
||||
void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex);
|
||||
}
|
||||
|
||||
public class RequestEvent
|
||||
{
|
||||
public RequestEvent(BlockTimeEntry block, IChainStateRequest request)
|
||||
{
|
||||
Block = block;
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public BlockTimeEntry Block { get; }
|
||||
public IChainStateRequest Request { get; }
|
||||
}
|
||||
|
||||
public class ChainState
|
||||
@@ -91,41 +105,41 @@ namespace CodexContractsPlugin.ChainMonitor
|
||||
var newRequest = new ChainStateRequest(log, request, RequestState.New);
|
||||
requests.Add(newRequest);
|
||||
|
||||
handler.OnNewRequest(newRequest);
|
||||
handler.OnNewRequest(new RequestEvent(request.Block, newRequest));
|
||||
}
|
||||
|
||||
private void ApplyEvent(RequestFulfilledEventDTO request)
|
||||
private void ApplyEvent(RequestFulfilledEventDTO @event)
|
||||
{
|
||||
var r = FindRequest(request.RequestId);
|
||||
var r = FindRequest(@event.RequestId);
|
||||
if (r == null) return;
|
||||
r.UpdateState(request.Block.BlockNumber, RequestState.Started);
|
||||
handler.OnRequestFulfilled(r);
|
||||
r.UpdateState(@event.Block.BlockNumber, RequestState.Started);
|
||||
handler.OnRequestFulfilled(new RequestEvent(@event.Block, r));
|
||||
}
|
||||
|
||||
private void ApplyEvent(RequestCancelledEventDTO request)
|
||||
private void ApplyEvent(RequestCancelledEventDTO @event)
|
||||
{
|
||||
var r = FindRequest(request.RequestId);
|
||||
var r = FindRequest(@event.RequestId);
|
||||
if (r == null) return;
|
||||
r.UpdateState(request.Block.BlockNumber, RequestState.Cancelled);
|
||||
handler.OnRequestCancelled(r);
|
||||
r.UpdateState(@event.Block.BlockNumber, RequestState.Cancelled);
|
||||
handler.OnRequestCancelled(new RequestEvent(@event.Block, r));
|
||||
}
|
||||
|
||||
private void ApplyEvent(SlotFilledEventDTO request)
|
||||
private void ApplyEvent(SlotFilledEventDTO @event)
|
||||
{
|
||||
var r = FindRequest(request.RequestId);
|
||||
var r = FindRequest(@event.RequestId);
|
||||
if (r == null) return;
|
||||
r.Hosts.Add(request.Host, (int)request.SlotIndex);
|
||||
r.Log($"[{request.Block.BlockNumber}] SlotFilled (host:'{request.Host}', slotIndex:{request.SlotIndex})");
|
||||
handler.OnSlotFilled(r, request.SlotIndex);
|
||||
r.Hosts.Add(@event.Host, (int)@event.SlotIndex);
|
||||
r.Log($"[{@event.Block.BlockNumber}] SlotFilled (host:'{@event.Host}', slotIndex:{@event.SlotIndex})");
|
||||
handler.OnSlotFilled(new RequestEvent(@event.Block, r), @event.Host, @event.SlotIndex);
|
||||
}
|
||||
|
||||
private void ApplyEvent(SlotFreedEventDTO request)
|
||||
private void ApplyEvent(SlotFreedEventDTO @event)
|
||||
{
|
||||
var r = FindRequest(request.RequestId);
|
||||
var r = FindRequest(@event.RequestId);
|
||||
if (r == null) return;
|
||||
r.Hosts.RemoveHost((int)request.SlotIndex);
|
||||
r.Log($"[{request.Block.BlockNumber}] SlotFreed (slotIndex:{request.SlotIndex})");
|
||||
handler.OnSlotFreed(r, request.SlotIndex);
|
||||
r.Hosts.RemoveHost((int)@event.SlotIndex);
|
||||
r.Log($"[{@event.Block.BlockNumber}] SlotFreed (slotIndex:{@event.SlotIndex})");
|
||||
handler.OnSlotFreed(new RequestEvent(@event.Block, r), @event.SlotIndex);
|
||||
}
|
||||
|
||||
private void ApplyTimeImplicitEvents(ulong blockNumber, DateTime eventsUtc)
|
||||
@@ -136,7 +150,7 @@ namespace CodexContractsPlugin.ChainMonitor
|
||||
&& r.FinishedUtc < eventsUtc)
|
||||
{
|
||||
r.UpdateState(blockNumber, RequestState.Finished);
|
||||
handler.OnRequestFinished(r);
|
||||
handler.OnRequestFinished(new RequestEvent(new BlockTimeEntry(blockNumber, eventsUtc), r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
using System.Numerics;
|
||||
using GethPlugin;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CodexContractsPlugin.ChainMonitor
|
||||
{
|
||||
public class DoNothingChainEventHandler : IChainStateChangeHandler
|
||||
{
|
||||
public void OnNewRequest(IChainStateRequest request)
|
||||
public void OnNewRequest(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestCancelled(IChainStateRequest request)
|
||||
public void OnRequestCancelled(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestFinished(IChainStateRequest request)
|
||||
public void OnRequestFinished(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestFulfilled(IChainStateRequest request)
|
||||
public void OnRequestFulfilled(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin
|
||||
public class DiscordBotContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
public override string AppName => "discordbot-bibliotech";
|
||||
public override string Image => "codexstorage/codex-discordbot:sha-22cf82b";
|
||||
public override string Image => "codexstorage/codex-discordbot:sha-8033da1";
|
||||
|
||||
public static string RewardsPort = "bot_rewards_port";
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ namespace CodexDiscordBotPlugin
|
||||
public class RewarderBotContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
public override string AppName => "discordbot-rewarder";
|
||||
public override string Image => "thatbenbierens/codex-rewardbot:newstate";
|
||||
//"codexstorage/codex-rewarderbot:sha-12dc7ef";
|
||||
public override string Image => "codexstorage/codex-rewarderbot:sha-8033da1";
|
||||
|
||||
protected override void Initialize(StartupConfig startupConfig)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace CodexPlugin
|
||||
{
|
||||
public class CodexContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
private const string DefaultDockerImage = "codexstorage/nim-codex:sha-b89493e-dist-tests";
|
||||
private const string DefaultDockerImage = "codexstorage/nim-codex:sha-471ebb2-dist-tests";
|
||||
|
||||
public const string ApiPortTag = "codex_api_port";
|
||||
public const string ListenPortTag = "codex_listen_port";
|
||||
|
||||
@@ -33,10 +33,9 @@ namespace CodexTests.BasicTests
|
||||
.AsStorageNode()
|
||||
.AsValidator()));
|
||||
|
||||
var expectedHostBalance = (numberOfHosts * hostInitialBalance.TstWei).TstWei();
|
||||
foreach (var host in hosts)
|
||||
{
|
||||
AssertBalance(contracts, host, Is.EqualTo(expectedHostBalance));
|
||||
AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance));
|
||||
|
||||
var availability = new StorageAvailability(
|
||||
totalSpace: 10.GB(),
|
||||
|
||||
@@ -49,6 +49,8 @@ namespace CodexTests.PeerDiscoveryTests
|
||||
|
||||
private void AssertAllNodesConnected(IEnumerable<ICodexNode> nodes)
|
||||
{
|
||||
nodes = nodes.Concat(new[] { BootstrapNode }).ToArray()!;
|
||||
|
||||
CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes);
|
||||
CheckRoutingTable(nodes);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace CodexTests.UtilityTests
|
||||
|
||||
[Test]
|
||||
[DontDownloadLogs]
|
||||
[Ignore("Used to debug testnet bots.")]
|
||||
public void BotRewardTest()
|
||||
{
|
||||
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Framework\ArgsUniform\ArgsUniform.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,45 @@
|
||||
using ArgsUniform;
|
||||
|
||||
namespace AutoClient
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
[Uniform("codex-host", "ch", "CODEXHOST", false, "Codex Host address. (default 'http://localhost')")]
|
||||
public string CodexHost { get; set; } = "http://localhost";
|
||||
|
||||
[Uniform("codex-port", "cp", "CODEXPORT", false, "port number of Codex API. (8080 by default)")]
|
||||
public int CodexPort { get; set; } = 8080;
|
||||
|
||||
[Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")]
|
||||
public string DataPath { get; set; } = "datapath";
|
||||
|
||||
[Uniform("purchases", "np", "PURCHASES", false, "Number of concurrent purchases.")]
|
||||
public int NumConcurrentPurchases { get; set; } = 10;
|
||||
|
||||
[Uniform("contract-duration", "cd", "CONTRACTDURATION", false, "contract duration in minutes. (default 30)")]
|
||||
public int ContractDurationMinutes { get; set; } = 30;
|
||||
|
||||
[Uniform("contract-expiry", "ce", "CONTRACTEXPIRY", false, "contract expiry in minutes. (default 15)")]
|
||||
public int ContractExpiryMinutes { get; set; } = 15;
|
||||
|
||||
[Uniform("num-hosts", "nh", "NUMHOSTS", false, "Number of hosts for contract. (default 5)")]
|
||||
public int NumHosts { get; set; } = 5;
|
||||
|
||||
[Uniform("num-hosts-tolerance", "nt", "NUMTOL", false, "Number of host tolerance for contract. (default 2)")]
|
||||
public int HostTolerance { get; set; } = 2;
|
||||
|
||||
[Uniform("price","p", "PRICE", false, "Price of contract. (default 10)")]
|
||||
public int Price { get; set; } = 10;
|
||||
|
||||
[Uniform("collateral", "c", "COLLATERAL", false, "Required collateral. (default 1)")]
|
||||
public int RequiredCollateral { get; set; } = 1;
|
||||
|
||||
public string LogPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(DataPath, "logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AutoClient
|
||||
{
|
||||
public class ImageGenerator
|
||||
{
|
||||
public async Task<string> GenerateImage()
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var thing = await httpClient.GetStreamAsync("https://picsum.photos/3840/2160");
|
||||
|
||||
var filename = $"{Guid.NewGuid().ToString().ToLowerInvariant()}.jpg";
|
||||
using var file = File.OpenWrite(filename);
|
||||
await thing.CopyToAsync(file);
|
||||
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using ArgsUniform;
|
||||
using AutoClient;
|
||||
using CodexOpenApi;
|
||||
using Core;
|
||||
using Logging;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var cancellationToken = cts.Token;
|
||||
Console.CancelKeyPress += (sender, args) => cts.Cancel();
|
||||
|
||||
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
|
||||
var config = uniformArgs.Parse(true);
|
||||
|
||||
if (config.NumConcurrentPurchases < 1)
|
||||
{
|
||||
throw new Exception("Number of concurrent purchases must be > 0");
|
||||
}
|
||||
|
||||
var log = new LogSplitter(
|
||||
new FileLog(Path.Combine(config.LogPath, "autoclient")),
|
||||
new ConsoleLog()
|
||||
);
|
||||
|
||||
var address = new Utils.Address(
|
||||
host: config.CodexHost,
|
||||
port: config.CodexPort
|
||||
);
|
||||
|
||||
log.Log($"Start. Address: {address}");
|
||||
|
||||
var imgGenerator = new ImageGenerator();
|
||||
|
||||
var client = new HttpClient();
|
||||
var codex = new CodexApi(client);
|
||||
codex.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
|
||||
|
||||
await CheckCodex(codex, log);
|
||||
|
||||
var purchasers = new List<Purchaser>();
|
||||
for (var i = 0; i < config.NumConcurrentPurchases; i++)
|
||||
{
|
||||
purchasers.Add(
|
||||
new Purchaser(new LogPrefixer(log, $"({i}) "), client, address, codex, cancellationToken, config, imgGenerator)
|
||||
);
|
||||
}
|
||||
|
||||
var delayPerPurchaser = TimeSpan.FromMinutes(config.ContractDurationMinutes) / config.NumConcurrentPurchases;
|
||||
foreach (var purchaser in purchasers)
|
||||
{
|
||||
purchaser.Start();
|
||||
await Task.Delay(delayPerPurchaser);
|
||||
}
|
||||
|
||||
log.Log("Done.");
|
||||
}
|
||||
|
||||
private static async Task CheckCodex(CodexApi codex, ILog log)
|
||||
{
|
||||
log.Log("Checking Codex...");
|
||||
try
|
||||
{
|
||||
var info = await codex.GetDebugInfoAsync();
|
||||
if (string.IsNullOrEmpty(info.Id)) throw new Exception("Failed to fetch Codex node id");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Log($"Codex not OK: {ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("Generates fake data and creates Codex storage contracts for it.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using CodexOpenApi;
|
||||
using CodexPlugin;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
|
||||
namespace AutoClient
|
||||
{
|
||||
public class Purchaser
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly HttpClient client;
|
||||
private readonly Address address;
|
||||
private readonly CodexApi codex;
|
||||
private readonly CancellationToken ct;
|
||||
private readonly Configuration config;
|
||||
private readonly ImageGenerator generator;
|
||||
|
||||
public Purchaser(ILog log, HttpClient client, Address address, CodexApi codex, CancellationToken ct, Configuration config, ImageGenerator generator)
|
||||
{
|
||||
this.log = log;
|
||||
this.client = client;
|
||||
this.address = address;
|
||||
this.codex = codex;
|
||||
this.ct = ct;
|
||||
this.config = config;
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(Worker);
|
||||
}
|
||||
|
||||
private async Task Worker()
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
var pid = await StartNewPurchase();
|
||||
await WaitTillFinished(pid);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> StartNewPurchase()
|
||||
{
|
||||
var file = await CreateFile();
|
||||
var cid = await UploadFile(file);
|
||||
return await RequestStorage(cid);
|
||||
}
|
||||
|
||||
private async Task<string> CreateFile()
|
||||
{
|
||||
return await generator.GenerateImage();
|
||||
}
|
||||
|
||||
private async Task<ContentId> UploadFile(string filename)
|
||||
{
|
||||
// Copied from CodexNode :/
|
||||
using var fileStream = File.OpenRead(filename);
|
||||
|
||||
log.Log($"Uploading file {filename}...");
|
||||
var response = await codex.UploadAsync(fileStream, ct);
|
||||
|
||||
if (string.IsNullOrEmpty(response)) FrameworkAssert.Fail("Received empty response.");
|
||||
if (response.StartsWith("Unable to store block")) FrameworkAssert.Fail("Node failed to store block.");
|
||||
|
||||
log.Log($"Uploaded file. Received contentId: '{response}'.");
|
||||
return new ContentId(response);
|
||||
}
|
||||
|
||||
private async Task<string> RequestStorage(ContentId cid)
|
||||
{
|
||||
log.Log("Requesting storage for " + cid.Id);
|
||||
var result = await codex.CreateStorageRequestAsync(cid.Id, new StorageRequestCreation()
|
||||
{
|
||||
Collateral = config.RequiredCollateral.ToString(),
|
||||
Duration = (config.ContractDurationMinutes * 60).ToString(),
|
||||
Expiry = (config.ContractExpiryMinutes * 60).ToString(),
|
||||
Nodes = config.NumHosts,
|
||||
Reward = config.Price.ToString(),
|
||||
ProofProbability = "15",
|
||||
Tolerance = config.HostTolerance
|
||||
}, ct);
|
||||
|
||||
log.Log("Purchase ID: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<string?> GetPurchaseState(string pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
// openapi still don't match code.
|
||||
var str = await client.GetStringAsync($"{address.Host}:{address.Port}/api/codex/v1/storage/purchases/{pid}");
|
||||
if (string.IsNullOrEmpty(str)) return null;
|
||||
var sp = JsonConvert.DeserializeObject<StoragePurchase>(str)!;
|
||||
log.Log($"Purchase {pid} is {sp.State}");
|
||||
if (!string.IsNullOrEmpty(sp.Error)) log.Log($"Purchase {pid} error is {sp.Error}");
|
||||
return sp.State;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WaitTillFinished(string pid)
|
||||
{
|
||||
log.Log("Waiting...");
|
||||
try
|
||||
{
|
||||
var emptyResponseTolerance = 10;
|
||||
while (true)
|
||||
{
|
||||
var status = (await GetPurchaseState(pid))?.ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(status))
|
||||
{
|
||||
emptyResponseTolerance--;
|
||||
if (emptyResponseTolerance == 0)
|
||||
{
|
||||
log.Log("Received 10 empty responses. Stop tracking this purchase.");
|
||||
await ExpiryTimeDelay();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status.Contains("cancel") ||
|
||||
status.Contains("error") ||
|
||||
status.Contains("finished"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (status.Contains("started"))
|
||||
{
|
||||
await FixedDurationDelay();
|
||||
}
|
||||
}
|
||||
|
||||
await FixedShortDelay();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Log($"Wait failed with exception: {ex}. Assume contract will expire: Wait expiry time.");
|
||||
await ExpiryTimeDelay();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FixedDurationDelay()
|
||||
{
|
||||
await Task.Delay(config.ContractDurationMinutes * 60 * 1000, ct);
|
||||
}
|
||||
|
||||
private async Task ExpiryTimeDelay()
|
||||
{
|
||||
await Task.Delay(config.ContractExpiryMinutes * 60 * 1000, ct);
|
||||
}
|
||||
|
||||
private async Task FixedShortDelay()
|
||||
{
|
||||
await Task.Delay(15 * 1000, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Variables
|
||||
ARG BUILDER=mcr.microsoft.com/dotnet/sdk:7.0
|
||||
ARG IMAGE=${BUILDER}
|
||||
ARG APP_HOME=/app
|
||||
|
||||
# Build
|
||||
FROM ${IMAGE} AS builder
|
||||
ARG APP_HOME
|
||||
|
||||
WORKDIR ${APP_HOME}
|
||||
COPY ./Tools/AutoClient ./Tools/AutoClient
|
||||
COPY ./Framework ./Framework
|
||||
COPY ./ProjectPlugins ./ProjectPlugins
|
||||
RUN dotnet restore Tools/AutoClient
|
||||
RUN dotnet publish Tools/AutoClient -c Release -o out
|
||||
|
||||
# Create
|
||||
FROM ${IMAGE}
|
||||
ARG APP_HOME
|
||||
ENV APP_HOME=${APP_HOME}
|
||||
|
||||
WORKDIR ${APP_HOME}
|
||||
COPY --from=builder ${APP_HOME}/out .
|
||||
CMD dotnet ${APP_HOME}/AutoClient.dll
|
||||
@@ -3,19 +3,23 @@ using Discord.WebSocket;
|
||||
using Discord;
|
||||
using Newtonsoft.Json;
|
||||
using BiblioTech.Rewards;
|
||||
using Logging;
|
||||
|
||||
namespace BiblioTech
|
||||
{
|
||||
public class CommandHandler
|
||||
{
|
||||
private readonly DiscordSocketClient client;
|
||||
private readonly CustomReplacement replacement;
|
||||
private readonly BaseCommand[] commands;
|
||||
private readonly ILog log;
|
||||
|
||||
public CommandHandler(DiscordSocketClient client, params BaseCommand[] commands)
|
||||
public CommandHandler(ILog log, DiscordSocketClient client, CustomReplacement replacement, params BaseCommand[] commands)
|
||||
{
|
||||
this.client = client;
|
||||
this.replacement = replacement;
|
||||
this.commands = commands;
|
||||
|
||||
this.log = log;
|
||||
client.Ready += Client_Ready;
|
||||
client.SlashCommandExecuted += SlashCommandHandler;
|
||||
}
|
||||
@@ -24,12 +28,12 @@ namespace BiblioTech
|
||||
{
|
||||
var guild = client.Guilds.Single(g => g.Id == Program.Config.ServerId);
|
||||
Program.AdminChecker.SetGuild(guild);
|
||||
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
|
||||
log.Log($"Initializing for guild: '{guild.Name}'");
|
||||
|
||||
var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray();
|
||||
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
|
||||
Program.AdminChecker.SetAdminChannel(adminChannels.First());
|
||||
Program.RoleDriver = new RoleDriver(client);
|
||||
Program.RoleDriver = new RoleDriver(client, log, replacement);
|
||||
|
||||
var builders = commands.Select(c =>
|
||||
{
|
||||
@@ -44,7 +48,7 @@ namespace BiblioTech
|
||||
builder.AddOption(option.Build());
|
||||
}
|
||||
|
||||
Program.Log.Log(msg);
|
||||
log.Log(msg);
|
||||
return builder;
|
||||
});
|
||||
|
||||
@@ -58,7 +62,7 @@ namespace BiblioTech
|
||||
catch (HttpException exception)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
||||
Program.Log.Error(json);
|
||||
log.Error(json);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BiblioTech.Options;
|
||||
using BiblioTech.Rewards;
|
||||
|
||||
namespace BiblioTech.Commands
|
||||
{
|
||||
@@ -10,12 +11,14 @@ namespace BiblioTech.Commands
|
||||
private readonly AddSprCommand addSprCommand;
|
||||
private readonly ClearSprsCommand clearSprsCommand;
|
||||
private readonly GetSprCommand getSprCommand;
|
||||
private readonly LogReplaceCommand logReplaceCommand;
|
||||
|
||||
public AdminCommand(SprCommand sprCommand)
|
||||
public AdminCommand(SprCommand sprCommand, CustomReplacement replacement)
|
||||
{
|
||||
addSprCommand = new AddSprCommand(sprCommand);
|
||||
clearSprsCommand = new ClearSprsCommand(sprCommand);
|
||||
getSprCommand = new GetSprCommand(sprCommand);
|
||||
logReplaceCommand = new LogReplaceCommand(replacement);
|
||||
}
|
||||
|
||||
public override string Name => "admin";
|
||||
@@ -29,7 +32,8 @@ namespace BiblioTech.Commands
|
||||
whoIsCommand,
|
||||
addSprCommand,
|
||||
clearSprsCommand,
|
||||
getSprCommand
|
||||
getSprCommand,
|
||||
logReplaceCommand
|
||||
};
|
||||
|
||||
protected override async Task Invoke(CommandContext context)
|
||||
@@ -52,6 +56,7 @@ namespace BiblioTech.Commands
|
||||
await addSprCommand.CommandHandler(context);
|
||||
await clearSprsCommand.CommandHandler(context);
|
||||
await getSprCommand.CommandHandler(context);
|
||||
await logReplaceCommand.CommandHandler(context);
|
||||
}
|
||||
|
||||
public class ClearUserAssociationCommand : SubCommandOption
|
||||
@@ -194,7 +199,7 @@ namespace BiblioTech.Commands
|
||||
}
|
||||
}
|
||||
|
||||
public class GetSprCommand: SubCommandOption
|
||||
public class GetSprCommand : SubCommandOption
|
||||
{
|
||||
private readonly SprCommand sprCommand;
|
||||
|
||||
@@ -210,5 +215,56 @@ namespace BiblioTech.Commands
|
||||
await context.Followup("SPRs: " + string.Join(", ", sprCommand.Get().Select(s => $"'{s}'")));
|
||||
}
|
||||
}
|
||||
|
||||
public class LogReplaceCommand : SubCommandOption
|
||||
{
|
||||
private readonly CustomReplacement replacement;
|
||||
private readonly StringOption fromOption = new StringOption("from", "string to replace", true);
|
||||
private readonly StringOption toOption = new StringOption("to", "string to replace with", false);
|
||||
|
||||
public LogReplaceCommand(CustomReplacement replacement)
|
||||
: base(name: "logreplace",
|
||||
description: "Replaces all occurances of 'from' with 'to' in ChainEvent messages. Leave 'to' empty to remove a replacement.")
|
||||
{
|
||||
this.replacement = replacement;
|
||||
}
|
||||
|
||||
public override CommandOption[] Options => new[] { fromOption, toOption };
|
||||
|
||||
protected override async Task onSubCommand(CommandContext context)
|
||||
{
|
||||
var from = await fromOption.Parse(context);
|
||||
var to = await toOption.Parse(context);
|
||||
|
||||
if (string.IsNullOrEmpty(from))
|
||||
{
|
||||
await context.Followup("'from' not received");
|
||||
return;
|
||||
}
|
||||
|
||||
if (from.Length < 5)
|
||||
{
|
||||
await context.Followup("'from' must be length 5 or greater.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(to))
|
||||
{
|
||||
replacement.Remove(from);
|
||||
await context.Followup($"Replace for '{from}' removed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (to.Length < 5)
|
||||
{
|
||||
await context.Followup("'to' must be length 5 or greater.");
|
||||
return;
|
||||
}
|
||||
|
||||
replacement.Add(from, to);
|
||||
await context.Followup($"Replace added '{from}' -->> '{to}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace BiblioTech
|
||||
public class Program
|
||||
{
|
||||
private DiscordSocketClient client = null!;
|
||||
private readonly CustomReplacement replacement = new CustomReplacement();
|
||||
|
||||
public static Configuration Config { get; private set; } = null!;
|
||||
public static UserRepo UserRepo { get; } = new UserRepo();
|
||||
@@ -73,13 +74,13 @@ namespace BiblioTech
|
||||
var notifyCommand = new NotifyCommand();
|
||||
var associateCommand = new UserAssociateCommand(notifyCommand);
|
||||
var sprCommand = new SprCommand();
|
||||
var handler = new CommandHandler(client,
|
||||
var handler = new CommandHandler(Log, client, replacement,
|
||||
new GetBalanceCommand(associateCommand),
|
||||
new MintCommand(associateCommand),
|
||||
sprCommand,
|
||||
associateCommand,
|
||||
notifyCommand,
|
||||
new AdminCommand(sprCommand),
|
||||
new AdminCommand(sprCommand, replacement),
|
||||
new MarketCommand()
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using Discord.WebSocket;
|
||||
using Logging;
|
||||
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public class ChainEventsSender
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly CustomReplacement replacement;
|
||||
private readonly SocketTextChannel? eventsChannel;
|
||||
|
||||
public ChainEventsSender(ILog log, CustomReplacement replacement, SocketTextChannel? eventsChannel)
|
||||
{
|
||||
this.log = log;
|
||||
this.replacement = replacement;
|
||||
this.eventsChannel = eventsChannel;
|
||||
}
|
||||
|
||||
public async Task ProcessChainEvents(string[] eventsOverview)
|
||||
{
|
||||
if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return;
|
||||
try
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
var users = Program.UserRepo.GetAllUserData();
|
||||
|
||||
foreach (var e in eventsOverview)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e))
|
||||
{
|
||||
var @event = ApplyReplacements(users, e);
|
||||
await eventsChannel.SendMessageAsync(@event);
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to process chain events: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string ApplyReplacements(UserData[] users, string msg)
|
||||
{
|
||||
var result = ApplyUserAddressReplacements(users, msg);
|
||||
result = ApplyCustomReplacements(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string ApplyUserAddressReplacements(UserData[] users, string msg)
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (user.CurrentAddress != null &&
|
||||
!string.IsNullOrEmpty(user.CurrentAddress.Address) &&
|
||||
!string.IsNullOrEmpty(user.Name))
|
||||
{
|
||||
msg = msg.Replace(user.CurrentAddress.Address, user.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private string ApplyCustomReplacements(string result)
|
||||
{
|
||||
return replacement.Apply(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public class CustomReplacement
|
||||
{
|
||||
private readonly Dictionary<string, string> replacements = new Dictionary<string, string>();
|
||||
|
||||
public void Add(string from, string to)
|
||||
{
|
||||
if (replacements.ContainsKey(from))
|
||||
{
|
||||
replacements[from] = to;
|
||||
}
|
||||
else
|
||||
{
|
||||
replacements.Add(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string from)
|
||||
{
|
||||
replacements.Remove(from);
|
||||
}
|
||||
|
||||
public string Apply(string msg)
|
||||
{
|
||||
var result = msg;
|
||||
foreach (var pair in replacements)
|
||||
{
|
||||
result.Replace(pair.Key, pair.Value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordRewards;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BiblioTech.Rewards
|
||||
@@ -8,41 +9,49 @@ namespace BiblioTech.Rewards
|
||||
public class RoleDriver : IDiscordRoleDriver
|
||||
{
|
||||
private readonly DiscordSocketClient client;
|
||||
private readonly ILog log;
|
||||
private readonly SocketTextChannel? rewardsChannel;
|
||||
private readonly SocketTextChannel? eventsChannel;
|
||||
private readonly ChainEventsSender eventsSender;
|
||||
private readonly RewardRepo repo = new RewardRepo();
|
||||
|
||||
public RoleDriver(DiscordSocketClient client)
|
||||
public RoleDriver(DiscordSocketClient client, ILog log, CustomReplacement replacement)
|
||||
{
|
||||
this.client = client;
|
||||
|
||||
this.log = log;
|
||||
rewardsChannel = GetChannel(Program.Config.RewardsChannelId);
|
||||
eventsChannel = GetChannel(Program.Config.ChainEventsChannelId);
|
||||
eventsSender = new ChainEventsSender(log, replacement, GetChannel(Program.Config.ChainEventsChannelId));
|
||||
}
|
||||
|
||||
public async Task GiveRewards(GiveRewardsCommand rewards)
|
||||
{
|
||||
Program.Log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'");
|
||||
log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'");
|
||||
|
||||
if (rewards.Rewards.Any())
|
||||
{
|
||||
await ProcessRewards(rewards);
|
||||
}
|
||||
|
||||
await ProcessChainEvents(rewards.EventsOverview);
|
||||
await eventsSender.ProcessChainEvents(rewards.EventsOverview);
|
||||
}
|
||||
|
||||
private async Task ProcessRewards(GiveRewardsCommand rewards)
|
||||
{
|
||||
var guild = GetGuild();
|
||||
// We load all role and user information first,
|
||||
// so we don't ask the server for the same info multiple times.
|
||||
var context = new RewardContext(
|
||||
await LoadAllUsers(guild),
|
||||
LookUpAllRoles(guild, rewards),
|
||||
rewardsChannel);
|
||||
try
|
||||
{
|
||||
var guild = GetGuild();
|
||||
// We load all role and user information first,
|
||||
// so we don't ask the server for the same info multiple times.
|
||||
var context = new RewardContext(
|
||||
await LoadAllUsers(guild),
|
||||
LookUpAllRoles(guild, rewards),
|
||||
rewardsChannel);
|
||||
|
||||
await context.ProcessGiveRewardsCommand(LookUpUsers(rewards));
|
||||
await context.ProcessGiveRewardsCommand(LookUpUsers(rewards));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to process rewards: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SocketTextChannel? GetChannel(ulong id)
|
||||
@@ -51,25 +60,9 @@ namespace BiblioTech.Rewards
|
||||
return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id);
|
||||
}
|
||||
|
||||
private async Task ProcessChainEvents(string[] eventsOverview)
|
||||
{
|
||||
if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return;
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var e in eventsOverview)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e))
|
||||
{
|
||||
await eventsChannel.SendMessageAsync(e);
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<Dictionary<ulong, IGuildUser>> LoadAllUsers(SocketGuild guild)
|
||||
{
|
||||
Program.Log.Log("Loading all users:");
|
||||
log.Log("Loading all users..");
|
||||
var result = new Dictionary<ulong, IGuildUser>();
|
||||
var users = guild.GetUsersAsync();
|
||||
await foreach (var ulist in users)
|
||||
@@ -77,8 +70,8 @@ namespace BiblioTech.Rewards
|
||||
foreach (var u in ulist)
|
||||
{
|
||||
result.Add(u.Id, u);
|
||||
var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray());
|
||||
Program.Log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]");
|
||||
//var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray());
|
||||
//log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -94,14 +87,14 @@ namespace BiblioTech.Rewards
|
||||
var rewardConfig = repo.Rewards.SingleOrDefault(rr => rr.RoleId == r.RewardId);
|
||||
if (rewardConfig == null)
|
||||
{
|
||||
Program.Log.Log($"No Reward is configured for id '{r.RewardId}'.");
|
||||
log.Log($"No Reward is configured for id '{r.RewardId}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var socketRole = guild.GetRole(r.RewardId);
|
||||
if (socketRole == null)
|
||||
{
|
||||
Program.Log.Log($"Guild Role by id '{r.RewardId}' not found.");
|
||||
log.Log($"Guild Role by id '{r.RewardId}' not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -134,13 +127,13 @@ namespace BiblioTech.Rewards
|
||||
try
|
||||
{
|
||||
var userData = Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(address));
|
||||
if (userData != null) Program.Log.Log($"User '{userData.Name}' was looked up.");
|
||||
else Program.Log.Log($"Lookup for user was unsuccessful. EthAddress: '{address}'");
|
||||
if (userData != null) log.Log($"User '{userData.Name}' was looked up.");
|
||||
else log.Log($"Lookup for user was unsuccessful. EthAddress: '{address}'");
|
||||
return userData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error("Error during UserData lookup: " + ex);
|
||||
log.Error("Error during UserData lookup: " + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using CodexContractsPlugin;
|
||||
using GethPlugin;
|
||||
|
||||
namespace BiblioTech
|
||||
{
|
||||
public class UserData
|
||||
{
|
||||
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents, bool notificationsEnabled)
|
||||
{
|
||||
DiscordId = discordId;
|
||||
Name = name;
|
||||
CreatedUtc = createdUtc;
|
||||
CurrentAddress = currentAddress;
|
||||
AssociateEvents = associateEvents;
|
||||
MintEvents = mintEvents;
|
||||
NotificationsEnabled = notificationsEnabled;
|
||||
}
|
||||
|
||||
public ulong DiscordId { get; }
|
||||
public string Name { get; }
|
||||
public DateTime CreatedUtc { get; }
|
||||
public EthAddress? CurrentAddress { get; set; }
|
||||
public List<UserAssociateAddressEvent> AssociateEvents { get; }
|
||||
public List<UserMintEvent> MintEvents { get; }
|
||||
public bool NotificationsEnabled { get; set; }
|
||||
|
||||
public string[] CreateOverview()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
$"name: '{Name}' - id:{DiscordId}",
|
||||
$"joined: {CreatedUtc.ToString("o")}",
|
||||
$"current address: {CurrentAddress}",
|
||||
$"{AssociateEvents.Count + MintEvents.Count} total bot events."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class UserAssociateAddressEvent
|
||||
{
|
||||
public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress)
|
||||
{
|
||||
Utc = utc;
|
||||
NewAddress = newAddress;
|
||||
}
|
||||
|
||||
public DateTime Utc { get; }
|
||||
public EthAddress? NewAddress { get; }
|
||||
}
|
||||
|
||||
public class UserMintEvent
|
||||
{
|
||||
public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction<Ether>? ethReceived, Transaction<TestToken>? testTokensMinted)
|
||||
{
|
||||
Utc = utc;
|
||||
UsedAddress = usedAddress;
|
||||
EthReceived = ethReceived;
|
||||
TestTokensMinted = testTokensMinted;
|
||||
}
|
||||
|
||||
public DateTime Utc { get; }
|
||||
public EthAddress UsedAddress { get; }
|
||||
public Transaction<Ether>? EthReceived { get; }
|
||||
public Transaction<TestToken>? TestTokensMinted { get; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace BiblioTech
|
||||
public class UserRepo
|
||||
{
|
||||
private readonly object repoLock = new object();
|
||||
private readonly Dictionary<ulong, UserData> cache = new Dictionary<ulong, UserData>();
|
||||
|
||||
public bool AssociateUserWithAddress(IUser user, EthAddress address)
|
||||
{
|
||||
@@ -33,6 +34,12 @@ namespace BiblioTech
|
||||
}
|
||||
}
|
||||
|
||||
public UserData[] GetAllUserData()
|
||||
{
|
||||
if (cache.Count == 0) LoadAllUserData();
|
||||
return cache.Values.ToArray();
|
||||
}
|
||||
|
||||
public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction<Ether>? eth, Transaction<TestToken>? tokens)
|
||||
{
|
||||
lock (repoLock)
|
||||
@@ -151,12 +158,19 @@ namespace BiblioTech
|
||||
|
||||
private UserData? GetUserData(IUser user)
|
||||
{
|
||||
if (cache.ContainsKey(user.Id))
|
||||
{
|
||||
return cache[user.Id];
|
||||
}
|
||||
|
||||
var filename = GetFilename(user);
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonConvert.DeserializeObject<UserData>(File.ReadAllText(filename))!;
|
||||
var userData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(filename))!;
|
||||
cache.Add(userData.DiscordId, userData);
|
||||
return userData;
|
||||
}
|
||||
|
||||
private UserData GetOrCreate(IUser user)
|
||||
@@ -181,6 +195,15 @@ namespace BiblioTech
|
||||
var filename = GetFilename(userData);
|
||||
if (File.Exists(filename)) File.Delete(filename);
|
||||
File.WriteAllText(filename, JsonConvert.SerializeObject(userData));
|
||||
|
||||
if (cache.ContainsKey(userData.DiscordId))
|
||||
{
|
||||
cache[userData.DiscordId] = userData;
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.Add(userData.DiscordId, userData);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFilename(IUser user)
|
||||
@@ -197,66 +220,29 @@ namespace BiblioTech
|
||||
{
|
||||
return Path.Combine(Program.Config.UserDataPath, discordId.ToString() + ".json");
|
||||
}
|
||||
}
|
||||
|
||||
public class UserData
|
||||
{
|
||||
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents, bool notificationsEnabled)
|
||||
private void LoadAllUserData()
|
||||
{
|
||||
DiscordId = discordId;
|
||||
Name = name;
|
||||
CreatedUtc = createdUtc;
|
||||
CurrentAddress = currentAddress;
|
||||
AssociateEvents = associateEvents;
|
||||
MintEvents = mintEvents;
|
||||
NotificationsEnabled = notificationsEnabled;
|
||||
}
|
||||
|
||||
public ulong DiscordId { get; }
|
||||
public string Name { get; }
|
||||
public DateTime CreatedUtc { get; }
|
||||
public EthAddress? CurrentAddress { get; set; }
|
||||
public List<UserAssociateAddressEvent> AssociateEvents { get; }
|
||||
public List<UserMintEvent> MintEvents { get; }
|
||||
public bool NotificationsEnabled { get; set; }
|
||||
|
||||
public string[] CreateOverview()
|
||||
{
|
||||
return new[]
|
||||
try
|
||||
{
|
||||
$"name: '{Name}' - id:{DiscordId}",
|
||||
$"joined: {CreatedUtc.ToString("o")}",
|
||||
$"current address: {CurrentAddress}",
|
||||
$"{AssociateEvents.Count + MintEvents.Count} total bot events."
|
||||
};
|
||||
var files = Directory.GetFiles(Program.Config.UserDataPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
|
||||
if (userData != null && userData.DiscordId > 0)
|
||||
{
|
||||
cache.Add(userData.DiscordId, userData);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error("Exception while trying to load all user data: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UserAssociateAddressEvent
|
||||
{
|
||||
public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress)
|
||||
{
|
||||
Utc = utc;
|
||||
NewAddress = newAddress;
|
||||
}
|
||||
|
||||
public DateTime Utc { get; }
|
||||
public EthAddress? NewAddress { get; }
|
||||
}
|
||||
|
||||
public class UserMintEvent
|
||||
{
|
||||
public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction<Ether>? ethReceived, Transaction<TestToken>? testTokensMinted)
|
||||
{
|
||||
Utc = utc;
|
||||
UsedAddress = usedAddress;
|
||||
EthReceived = ethReceived;
|
||||
TestTokensMinted = testTokensMinted;
|
||||
}
|
||||
|
||||
public DateTime Utc { get; }
|
||||
public EthAddress UsedAddress { get; }
|
||||
public Transaction<Ether>? EthReceived { get; }
|
||||
public Transaction<TestToken>? TestTokensMinted { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
lines.Add($"Error: {message}");
|
||||
}
|
||||
|
||||
public void Log(string message)
|
||||
{
|
||||
lines.Add(message);
|
||||
}
|
||||
|
||||
public string[] Get()
|
||||
{
|
||||
var result = lines.ToArray();
|
||||
lines.Clear();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using CodexContractsPlugin.ChainMonitor;
|
||||
using GethPlugin;
|
||||
using System.Numerics;
|
||||
|
||||
namespace TestNetRewarder
|
||||
@@ -12,34 +13,34 @@ namespace TestNetRewarder
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
public void OnNewRequest(IChainStateRequest request)
|
||||
public void OnNewRequest(RequestEvent requestEvent)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnNewRequest(request);
|
||||
foreach (var handler in handlers) handler.OnNewRequest(requestEvent);
|
||||
}
|
||||
|
||||
public void OnRequestCancelled(IChainStateRequest request)
|
||||
public void OnRequestCancelled(RequestEvent requestEvent)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnRequestCancelled(request);
|
||||
foreach (var handler in handlers) handler.OnRequestCancelled(requestEvent);
|
||||
}
|
||||
|
||||
public void OnRequestFinished(IChainStateRequest request)
|
||||
public void OnRequestFinished(RequestEvent requestEvent)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnRequestFinished(request);
|
||||
foreach (var handler in handlers) handler.OnRequestFinished(requestEvent);
|
||||
}
|
||||
|
||||
public void OnRequestFulfilled(IChainStateRequest request)
|
||||
public void OnRequestFulfilled(RequestEvent requestEvent)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnRequestFulfilled(request);
|
||||
foreach (var handler in handlers) handler.OnRequestFulfilled(requestEvent);
|
||||
}
|
||||
|
||||
public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnSlotFilled(request, slotIndex);
|
||||
foreach (var handler in handlers) handler.OnSlotFilled(requestEvent, host, slotIndex);
|
||||
}
|
||||
|
||||
public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
|
||||
{
|
||||
foreach (var handler in handlers) handler.OnSlotFreed(request, slotIndex);
|
||||
foreach (var handler in handlers) handler.OnSlotFreed(requestEvent, slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using CodexContractsPlugin;
|
||||
using CodexContractsPlugin.ChainMonitor;
|
||||
using GethPlugin;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
namespace TestNetRewarder
|
||||
{
|
||||
public class EventsFormatter : IChainStateChangeHandler
|
||||
{
|
||||
private static readonly string nl = Environment.NewLine;
|
||||
private readonly List<string> events = new List<string>();
|
||||
|
||||
public string[] GetEvents()
|
||||
{
|
||||
var result = events.ToArray();
|
||||
events.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void AddError(string error)
|
||||
{
|
||||
AddBlock("📢 **Error**", error);
|
||||
}
|
||||
|
||||
public void OnNewRequest(RequestEvent requestEvent)
|
||||
{
|
||||
var request = requestEvent.Request;
|
||||
AddRequestBlock(requestEvent, "New Request",
|
||||
$"Client: {request.Client}",
|
||||
$"Content: {request.Request.Content.Cid}",
|
||||
$"Duration: {BigIntToDuration(request.Request.Ask.Duration)}",
|
||||
$"Expiry: {BigIntToDuration(request.Request.Expiry)}",
|
||||
$"Collateral: {BitIntToTestTokens(request.Request.Ask.Collateral)}",
|
||||
$"Reward: {BitIntToTestTokens(request.Request.Ask.Reward)}",
|
||||
$"Number of Slots: {request.Request.Ask.Slots}",
|
||||
$"Slot Tolerance: {request.Request.Ask.MaxSlotLoss}",
|
||||
$"Slot Size: {BigIntToByteSize(request.Request.Ask.SlotSize)}"
|
||||
);
|
||||
}
|
||||
|
||||
public void OnRequestCancelled(RequestEvent requestEvent)
|
||||
{
|
||||
AddRequestBlock(requestEvent, "Cancelled");
|
||||
}
|
||||
|
||||
public void OnRequestFinished(RequestEvent requestEvent)
|
||||
{
|
||||
AddRequestBlock(requestEvent, "Finished");
|
||||
}
|
||||
|
||||
public void OnRequestFulfilled(RequestEvent requestEvent)
|
||||
{
|
||||
AddRequestBlock(requestEvent, "Started");
|
||||
}
|
||||
|
||||
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex)
|
||||
{
|
||||
AddRequestBlock(requestEvent, "Slot Filled",
|
||||
$"Host: {host}",
|
||||
$"Slot Index: {slotIndex}"
|
||||
);
|
||||
}
|
||||
|
||||
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
|
||||
{
|
||||
AddRequestBlock(requestEvent, "Slot Freed",
|
||||
$"Slot Index: {slotIndex}"
|
||||
);
|
||||
}
|
||||
|
||||
private void AddRequestBlock(RequestEvent requestEvent, string eventName, params string[] content)
|
||||
{
|
||||
var blockNumber = $"[{requestEvent.Block.BlockNumber}]";
|
||||
var title = $"{blockNumber} **{eventName}** `{requestEvent.Request.Request.Id}`";
|
||||
AddBlock(title, content);
|
||||
}
|
||||
|
||||
private void AddBlock(string title, params string[] content)
|
||||
{
|
||||
events.Add(FormatBlock(title, content));
|
||||
}
|
||||
|
||||
private string FormatBlock(string title, params string[] content)
|
||||
{
|
||||
if (content == null || !content.Any())
|
||||
{
|
||||
return $"{title}{nl}{nl}";
|
||||
}
|
||||
|
||||
return string.Join(nl,
|
||||
new string[]
|
||||
{
|
||||
title,
|
||||
"```"
|
||||
}
|
||||
.Concat(content)
|
||||
.Concat(new string[]
|
||||
{
|
||||
"```"
|
||||
})
|
||||
) + nl + nl;
|
||||
}
|
||||
|
||||
private string BigIntToDuration(BigInteger big)
|
||||
{
|
||||
var span = TimeSpan.FromSeconds((int)big);
|
||||
return Time.FormatDuration(span);
|
||||
}
|
||||
|
||||
private string BigIntToByteSize(BigInteger big)
|
||||
{
|
||||
var size = new ByteSize((long)big);
|
||||
return size.ToString();
|
||||
}
|
||||
|
||||
private string BitIntToTestTokens(BigInteger big)
|
||||
{
|
||||
var tt = new TestToken(big);
|
||||
return tt.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace TestNetRewarder
|
||||
{
|
||||
public class MarketBuffer
|
||||
{
|
||||
private readonly List<IChainStateRequest> requests = new List<IChainStateRequest>();
|
||||
private readonly List<RequestEvent> requestEvents = new List<RequestEvent>();
|
||||
private readonly TimeSpan bufferSpan;
|
||||
|
||||
public MarketBuffer(TimeSpan bufferSpan)
|
||||
@@ -15,24 +15,24 @@ namespace TestNetRewarder
|
||||
this.bufferSpan = bufferSpan;
|
||||
}
|
||||
|
||||
public void Add(IChainStateRequest request)
|
||||
public void Add(RequestEvent requestEvent)
|
||||
{
|
||||
requests.Add(request);
|
||||
requestEvents.Add(requestEvent);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
requests.RemoveAll(r => (now - r.FinishedUtc) > bufferSpan);
|
||||
requestEvents.RemoveAll(r => (now - r.Request.FinishedUtc) > bufferSpan);
|
||||
}
|
||||
|
||||
public MarketAverage? GetAverage()
|
||||
{
|
||||
if (requests.Count == 0) return null;
|
||||
if (requestEvents.Count == 0) return null;
|
||||
|
||||
return new MarketAverage
|
||||
{
|
||||
NumberOfFinished = requests.Count,
|
||||
NumberOfFinished = requestEvents.Count,
|
||||
TimeRangeSeconds = (int)bufferSpan.TotalSeconds,
|
||||
Price = Average(s => s.Request.Ask.Reward),
|
||||
Duration = Average(s => s.Request.Ask.Duration),
|
||||
@@ -54,10 +54,10 @@ namespace TestNetRewarder
|
||||
private float Average(Func<IChainStateRequest, int> getValue)
|
||||
{
|
||||
var sum = 0.0f;
|
||||
float count = requests.Count;
|
||||
foreach (var r in requests)
|
||||
float count = requestEvents.Count;
|
||||
foreach (var r in requestEvents)
|
||||
{
|
||||
sum += getValue(r);
|
||||
sum += getValue(r.Request);
|
||||
}
|
||||
|
||||
if (count < 1.0f) return 0.0f;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CodexContractsPlugin.ChainMonitor;
|
||||
using DiscordRewards;
|
||||
using GethPlugin;
|
||||
using Logging;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -31,28 +32,28 @@ namespace TestNetRewarder
|
||||
return buffers.Select(b => b.GetAverage()).Where(a => a != null).Cast<MarketAverage>().ToArray();
|
||||
}
|
||||
|
||||
public void OnNewRequest(IChainStateRequest request)
|
||||
public void OnNewRequest(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestFinished(IChainStateRequest request)
|
||||
public void OnRequestFinished(RequestEvent requestEvent)
|
||||
{
|
||||
foreach (var b in buffers) b.Add(request);
|
||||
foreach (var b in buffers) b.Add(requestEvent);
|
||||
}
|
||||
|
||||
public void OnRequestFulfilled(IChainStateRequest request)
|
||||
public void OnRequestFulfilled(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestCancelled(IChainStateRequest request)
|
||||
public void OnRequestCancelled(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace TestNetRewarder
|
||||
private readonly RequestBuilder builder;
|
||||
private readonly RewardChecker rewardChecker;
|
||||
private readonly MarketTracker marketTracker;
|
||||
private readonly BufferLogger bufferLogger;
|
||||
private readonly EventsFormatter eventsFormatter;
|
||||
private readonly ChainState chainState;
|
||||
private readonly BotClient client;
|
||||
private readonly ILog log;
|
||||
@@ -23,14 +23,15 @@ namespace TestNetRewarder
|
||||
builder = new RequestBuilder();
|
||||
rewardChecker = new RewardChecker(builder);
|
||||
marketTracker = new MarketTracker(config, log);
|
||||
bufferLogger = new BufferLogger();
|
||||
eventsFormatter = new EventsFormatter();
|
||||
|
||||
var handler = new ChainChangeMux(
|
||||
rewardChecker.Handler,
|
||||
marketTracker
|
||||
marketTracker,
|
||||
eventsFormatter
|
||||
);
|
||||
|
||||
chainState = new ChainState(new LogSplitter(log, bufferLogger), contracts, handler, config.HistoryStartUtc);
|
||||
chainState = new ChainState(log, contracts, handler, config.HistoryStartUtc);
|
||||
}
|
||||
|
||||
public async Task OnNewSegment(TimeRange timeRange)
|
||||
@@ -40,9 +41,9 @@ namespace TestNetRewarder
|
||||
chainState.Update(timeRange.To);
|
||||
|
||||
var averages = marketTracker.GetAverages();
|
||||
var lines = RemoveFirstLine(bufferLogger.Get());
|
||||
var events = eventsFormatter.GetEvents();
|
||||
|
||||
var request = builder.Build(averages, lines);
|
||||
var request = builder.Build(averages, events);
|
||||
if (request.HasAny())
|
||||
{
|
||||
await client.SendRewards(request);
|
||||
@@ -52,16 +53,9 @@ namespace TestNetRewarder
|
||||
{
|
||||
var msg = "Exception processing time segment: " + ex;
|
||||
log.Error(msg);
|
||||
bufferLogger.Error(msg);
|
||||
eventsFormatter.AddError(msg);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] RemoveFirstLine(string[] lines)
|
||||
{
|
||||
//if (!lines.Any()) return Array.Empty<string>();
|
||||
//return lines.Skip(1).ToArray();
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,42 +22,41 @@ namespace TestNetRewarder
|
||||
this.giver = giver;
|
||||
}
|
||||
|
||||
public void OnNewRequest(IChainStateRequest request)
|
||||
public void OnNewRequest(RequestEvent requestEvent)
|
||||
{
|
||||
if (MeetsRequirements(CheckType.ClientPostedContract, request))
|
||||
if (MeetsRequirements(CheckType.ClientPostedContract, requestEvent))
|
||||
{
|
||||
GiveReward(reward, request.Client);
|
||||
GiveReward(reward, requestEvent.Request.Client);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRequestCancelled(IChainStateRequest request)
|
||||
public void OnRequestCancelled(RequestEvent requestEvent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnRequestFinished(IChainStateRequest request)
|
||||
public void OnRequestFinished(RequestEvent requestEvent)
|
||||
{
|
||||
if (MeetsRequirements(CheckType.HostFinishedSlot, request))
|
||||
if (MeetsRequirements(CheckType.HostFinishedSlot, requestEvent))
|
||||
{
|
||||
foreach (var host in request.Hosts.GetHosts())
|
||||
foreach (var host in requestEvent.Request.Hosts.GetHosts())
|
||||
{
|
||||
GiveReward(reward, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRequestFulfilled(IChainStateRequest request)
|
||||
public void OnRequestFulfilled(RequestEvent requestEvent)
|
||||
{
|
||||
if (MeetsRequirements(CheckType.ClientStartedContract, request))
|
||||
if (MeetsRequirements(CheckType.ClientStartedContract, requestEvent))
|
||||
{
|
||||
GiveReward(reward, request.Client);
|
||||
GiveReward(reward, requestEvent.Request.Client);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex)
|
||||
{
|
||||
if (MeetsRequirements(CheckType.HostFilledSlot, request))
|
||||
if (MeetsRequirements(CheckType.HostFilledSlot, requestEvent))
|
||||
{
|
||||
var host = request.Hosts.GetHost((int)slotIndex);
|
||||
if (host != null)
|
||||
{
|
||||
GiveReward(reward, host);
|
||||
@@ -65,7 +64,7 @@ namespace TestNetRewarder
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex)
|
||||
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -74,12 +73,12 @@ namespace TestNetRewarder
|
||||
giver.Give(reward, receiver);
|
||||
}
|
||||
|
||||
private bool MeetsRequirements(CheckType type, IChainStateRequest request)
|
||||
private bool MeetsRequirements(CheckType type, RequestEvent requestEvent)
|
||||
{
|
||||
return
|
||||
reward.CheckConfig.Type == type &&
|
||||
MeetsDurationRequirement(request) &&
|
||||
MeetsSizeRequirement(request);
|
||||
MeetsDurationRequirement(requestEvent.Request) &&
|
||||
MeetsSizeRequirement(requestEvent.Request);
|
||||
}
|
||||
|
||||
private bool MeetsSizeRequirement(IChainStateRequest r)
|
||||
|
||||
@@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoClient", "Tools\AutoClient\AutoClient.csproj", "{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyMaker", "Tools\KeyMaker\KeyMaker.csproj", "{B57A4789-D8EF-42E0-8D20-581C4057FFD3}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -174,6 +176,10 @@ Global
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -208,6 +214,7 @@ Global
|
||||
{F730DA73-1C92-4107-BCFB-D33759DAB0C3} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||
{B07820C4-309F-4454-BCC1-1D4902C9C67B} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
|
||||
{73599F9C-98BB-4C6A-9D7D-7C50FBF2993B} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
||||
{B57A4789-D8EF-42E0-8D20-581C4057FFD3} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
Reference in New Issue
Block a user