Merge branch 'feature/bot-upgrade'
This commit is contained in:
commit
8a4d9fecea
26
.github/workflows/docker-rewarder.yml
vendored
Normal file
26
.github/workflows/docker-rewarder.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Docker - Rewarder Bot
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
paths:
|
||||||
|
- 'Tools/TestNetRewarder/**'
|
||||||
|
- '!Tools/TestNetRewarder/docker/docker-compose.yaml'
|
||||||
|
- 'Framework/**'
|
||||||
|
- 'ProjectPlugins/**'
|
||||||
|
- .github/workflows/docker-rewarder.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/TestNetRewarder/docker/Dockerfile
|
||||||
|
docker_repo: codexstorage/codex-rewarderbot
|
||||||
|
secrets: inherit
|
||||||
|
|
21
Framework/DiscordRewards/CheckConfig.cs
Normal file
21
Framework/DiscordRewards/CheckConfig.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace DiscordRewards
|
||||||
|
{
|
||||||
|
public class CheckConfig
|
||||||
|
{
|
||||||
|
public CheckType Type { get; set; }
|
||||||
|
public ulong MinNumberOfHosts { get; set; }
|
||||||
|
public ByteSize MinSlotSize { get; set; } = 0.Bytes();
|
||||||
|
public TimeSpan MinDuration { get; set; } = TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CheckType
|
||||||
|
{
|
||||||
|
Uninitialized,
|
||||||
|
FilledSlot,
|
||||||
|
FinishedSlot,
|
||||||
|
PostedContract,
|
||||||
|
StartedContract,
|
||||||
|
}
|
||||||
|
}
|
13
Framework/DiscordRewards/DiscordRewards.csproj
Normal file
13
Framework/DiscordRewards/DiscordRewards.csproj
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Framework\Utils\Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
13
Framework/DiscordRewards/GiveRewardsCommand.cs
Normal file
13
Framework/DiscordRewards/GiveRewardsCommand.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace DiscordRewards
|
||||||
|
{
|
||||||
|
public class GiveRewardsCommand
|
||||||
|
{
|
||||||
|
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RewardUsersCommand
|
||||||
|
{
|
||||||
|
public ulong RewardId { get; set; }
|
||||||
|
public string[] UserAddresses { get; set; } = Array.Empty<string>();
|
||||||
|
}
|
||||||
|
}
|
18
Framework/DiscordRewards/RewardConfig.cs
Normal file
18
Framework/DiscordRewards/RewardConfig.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace DiscordRewards
|
||||||
|
{
|
||||||
|
public class RewardConfig
|
||||||
|
{
|
||||||
|
public const string UsernameTag = "<USER>";
|
||||||
|
|
||||||
|
public RewardConfig(ulong roleId, string message, CheckConfig checkConfig)
|
||||||
|
{
|
||||||
|
RoleId = roleId;
|
||||||
|
Message = message;
|
||||||
|
CheckConfig = checkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong RoleId { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
public CheckConfig CheckConfig { get; }
|
||||||
|
}
|
||||||
|
}
|
53
Framework/DiscordRewards/RewardRepo.cs
Normal file
53
Framework/DiscordRewards/RewardRepo.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using Utils;
|
||||||
|
|
||||||
|
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.FilledSlot
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Finished any slot
|
||||||
|
new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig
|
||||||
|
{
|
||||||
|
Type = CheckType.FinishedSlot
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Finished a sizable slot
|
||||||
|
new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot!", new CheckConfig
|
||||||
|
{
|
||||||
|
Type = CheckType.FinishedSlot,
|
||||||
|
MinSlotSize = 1.GB(),
|
||||||
|
MinDuration = TimeSpan.FromHours(24.0),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Posted any contract
|
||||||
|
new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig
|
||||||
|
{
|
||||||
|
Type = CheckType.PostedContract
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Started any contract
|
||||||
|
new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig
|
||||||
|
{
|
||||||
|
Type = CheckType.StartedContract
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Started a sizable contract
|
||||||
|
new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time!", new CheckConfig
|
||||||
|
{
|
||||||
|
Type = CheckType.FinishedSlot,
|
||||||
|
MinNumberOfHosts = 4,
|
||||||
|
MinSlotSize = 1.GB(),
|
||||||
|
MinDuration = TimeSpan.FromHours(24.0),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
39
Framework/GethConnector/GethConnector.cs
Normal file
39
Framework/GethConnector/GethConnector.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using CodexContractsPlugin;
|
||||||
|
using GethPlugin;
|
||||||
|
using Logging;
|
||||||
|
|
||||||
|
namespace GethConnector
|
||||||
|
{
|
||||||
|
public class GethConnector
|
||||||
|
{
|
||||||
|
public IGethNode GethNode { get; }
|
||||||
|
public ICodexContracts CodexContracts { get; }
|
||||||
|
|
||||||
|
public static GethConnector? Initialize(ILog log)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(GethInput.LoadError))
|
||||||
|
{
|
||||||
|
var msg = "Geth input incorrect: " + GethInput.LoadError;
|
||||||
|
log.Error(msg);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contractsDeployment = new CodexContractsDeployment(
|
||||||
|
marketplaceAddress: GethInput.MarketplaceAddress,
|
||||||
|
abi: GethInput.ABI,
|
||||||
|
tokenAddress: GethInput.TokenAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey);
|
||||||
|
var contracts = new CodexContractsAccess(log, gethNode, contractsDeployment);
|
||||||
|
|
||||||
|
return new GethConnector(gethNode, contracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GethConnector(IGethNode gethNode, ICodexContracts codexContracts)
|
||||||
|
{
|
||||||
|
GethNode = gethNode;
|
||||||
|
CodexContracts = codexContracts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Framework/GethConnector/GethConnector.csproj
Normal file
15
Framework/GethConnector/GethConnector.csproj
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
|
||||||
|
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
|
||||||
|
<ProjectReference Include="..\..\ProjectPlugins\GethPlugin\GethPlugin.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
52
Framework/GethConnector/GethInput.cs
Normal file
52
Framework/GethConnector/GethInput.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
namespace GethConnector
|
||||||
|
{
|
||||||
|
public static class GethInput
|
||||||
|
{
|
||||||
|
private const string GethHostVar = "GETH_HOST";
|
||||||
|
private const string GethPortVar = "GETH_HTTP_PORT";
|
||||||
|
private const string GethPrivKeyVar = "GETH_PRIVATE_KEY";
|
||||||
|
private const string MarketplaceAddressVar = "CODEXCONTRACTS_MARKETPLACEADDRESS";
|
||||||
|
private const string TokenAddressVar = "CODEXCONTRACTS_TOKENADDRESS";
|
||||||
|
private const string AbiVar = "CODEXCONTRACTS_ABI";
|
||||||
|
|
||||||
|
static GethInput()
|
||||||
|
{
|
||||||
|
var error = new List<string>();
|
||||||
|
var gethHost = GetEnvVar(error, GethHostVar);
|
||||||
|
var gethPort = Convert.ToInt32(GetEnvVar(error, GethPortVar));
|
||||||
|
var privateKey = GetEnvVar(error, GethPrivKeyVar);
|
||||||
|
var marketplaceAddress = GetEnvVar(error, MarketplaceAddressVar);
|
||||||
|
var tokenAddress = GetEnvVar(error, TokenAddressVar);
|
||||||
|
var abi = GetEnvVar(error, AbiVar);
|
||||||
|
|
||||||
|
if (error.Any())
|
||||||
|
{
|
||||||
|
LoadError = string.Join(", ", error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GethHost = gethHost!;
|
||||||
|
GethPort = gethPort;
|
||||||
|
PrivateKey = privateKey!;
|
||||||
|
MarketplaceAddress = marketplaceAddress!;
|
||||||
|
TokenAddress = tokenAddress!;
|
||||||
|
ABI = abi!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GethHost { get; } = string.Empty;
|
||||||
|
public static int GethPort { get; }
|
||||||
|
public static string PrivateKey { get; } = string.Empty;
|
||||||
|
public static string MarketplaceAddress { get; } = string.Empty;
|
||||||
|
public static string TokenAddress { get; } = string.Empty;
|
||||||
|
public static string ABI { get; } = string.Empty;
|
||||||
|
public static string LoadError { get; } = string.Empty;
|
||||||
|
|
||||||
|
private static string? GetEnvVar(List<string> error, string name)
|
||||||
|
{
|
||||||
|
var result = Environment.GetEnvironmentVariable(name);
|
||||||
|
if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Framework/NethereumWorkflow/BlockTimeEntry.cs
Normal file
22
Framework/NethereumWorkflow/BlockTimeEntry.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace NethereumWorkflow
|
||||||
|
{
|
||||||
|
public partial class BlockTimeFinder
|
||||||
|
{
|
||||||
|
public class BlockTimeEntry
|
||||||
|
{
|
||||||
|
public BlockTimeEntry(ulong blockNumber, DateTime utc)
|
||||||
|
{
|
||||||
|
BlockNumber = blockNumber;
|
||||||
|
Utc = utc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong BlockNumber { get; }
|
||||||
|
public DateTime Utc { get; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[{BlockNumber}] @ {Utc.ToString("o")}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
280
Framework/NethereumWorkflow/BlockTimeFinder.cs
Normal file
280
Framework/NethereumWorkflow/BlockTimeFinder.cs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
using Logging;
|
||||||
|
using Nethereum.RPC.Eth.DTOs;
|
||||||
|
using Nethereum.Web3;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace NethereumWorkflow
|
||||||
|
{
|
||||||
|
public partial class BlockTimeFinder
|
||||||
|
{
|
||||||
|
private const ulong FetchRange = 6;
|
||||||
|
private const int MaxEntries = 1024;
|
||||||
|
private static readonly Dictionary<ulong, BlockTimeEntry> entries = new Dictionary<ulong, BlockTimeEntry>();
|
||||||
|
private readonly Web3 web3;
|
||||||
|
private readonly ILog log;
|
||||||
|
|
||||||
|
public BlockTimeFinder(Web3 web3, ILog log)
|
||||||
|
{
|
||||||
|
this.web3 = web3;
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetHighestBlockNumberBefore(DateTime moment)
|
||||||
|
{
|
||||||
|
log.Log("Looking for highest block before " + moment.ToString("o"));
|
||||||
|
AssertMomentIsInPast(moment);
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
return GetHighestBlockBefore(moment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetLowestBlockNumberAfter(DateTime moment)
|
||||||
|
{
|
||||||
|
log.Log("Looking for lowest block after " + moment.ToString("o"));
|
||||||
|
AssertMomentIsInPast(moment);
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
return GetLowestBlockAfter(moment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetHighestBlockBefore(DateTime moment)
|
||||||
|
{
|
||||||
|
var closestBefore = FindClosestBeforeEntry(moment);
|
||||||
|
var closestAfter = FindClosestAfterEntry(moment);
|
||||||
|
|
||||||
|
if (closestBefore != null &&
|
||||||
|
closestAfter != null &&
|
||||||
|
closestBefore.Utc < moment &&
|
||||||
|
closestAfter.Utc > moment &&
|
||||||
|
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
|
||||||
|
{
|
||||||
|
log.Log("Found highest-Before: " + closestBefore);
|
||||||
|
return closestBefore.BlockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchBlocksAround(moment);
|
||||||
|
return GetHighestBlockBefore(moment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetLowestBlockAfter(DateTime moment)
|
||||||
|
{
|
||||||
|
var closestBefore = FindClosestBeforeEntry(moment);
|
||||||
|
var closestAfter = FindClosestAfterEntry(moment);
|
||||||
|
|
||||||
|
if (closestBefore != null &&
|
||||||
|
closestAfter != null &&
|
||||||
|
closestBefore.Utc < moment &&
|
||||||
|
closestAfter.Utc > moment &&
|
||||||
|
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
|
||||||
|
{
|
||||||
|
log.Log("Found lowest-after: " + closestAfter);
|
||||||
|
return closestAfter.BlockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchBlocksAround(moment);
|
||||||
|
return GetLowestBlockAfter(moment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FetchBlocksAround(DateTime moment)
|
||||||
|
{
|
||||||
|
var timePerBlock = EstimateTimePerBlock();
|
||||||
|
log.Debug("Fetching blocks around " + moment.ToString("o") + " timePerBlock: " + timePerBlock.TotalSeconds);
|
||||||
|
|
||||||
|
EnsureRecentBlockIfNecessary(moment, timePerBlock);
|
||||||
|
|
||||||
|
var max = entries.Keys.Max();
|
||||||
|
var blockDifference = CalculateBlockDifference(moment, timePerBlock, max);
|
||||||
|
|
||||||
|
FetchUp(max, blockDifference);
|
||||||
|
FetchDown(max, blockDifference);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FetchDown(ulong max, ulong blockDifference)
|
||||||
|
{
|
||||||
|
var target = max - blockDifference - 1;
|
||||||
|
var fetchDown = FetchRange;
|
||||||
|
while (fetchDown > 0)
|
||||||
|
{
|
||||||
|
if (!entries.ContainsKey(target))
|
||||||
|
{
|
||||||
|
var newBlock = AddBlockNumber(target);
|
||||||
|
if (newBlock == null) return;
|
||||||
|
fetchDown--;
|
||||||
|
}
|
||||||
|
target--;
|
||||||
|
if (target <= 0) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FetchUp(ulong max, ulong blockDifference)
|
||||||
|
{
|
||||||
|
var target = max - blockDifference;
|
||||||
|
var fetchUp = FetchRange;
|
||||||
|
while (fetchUp > 0)
|
||||||
|
{
|
||||||
|
if (!entries.ContainsKey(target))
|
||||||
|
{
|
||||||
|
var newBlock = AddBlockNumber(target);
|
||||||
|
if (newBlock == null) return;
|
||||||
|
fetchUp--;
|
||||||
|
}
|
||||||
|
target++;
|
||||||
|
if (target >= max) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong CalculateBlockDifference(DateTime moment, TimeSpan timePerBlock, ulong max)
|
||||||
|
{
|
||||||
|
var latest = entries[max];
|
||||||
|
var timeDifference = latest.Utc - moment;
|
||||||
|
double secondsDifference = Math.Abs(timeDifference.TotalSeconds);
|
||||||
|
double secondsPerBlock = timePerBlock.TotalSeconds;
|
||||||
|
|
||||||
|
double numberOfBlocksDifference = secondsDifference / secondsPerBlock;
|
||||||
|
var blockDifference = Convert.ToUInt64(numberOfBlocksDifference);
|
||||||
|
if (blockDifference < 1) blockDifference = 1;
|
||||||
|
return blockDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureRecentBlockIfNecessary(DateTime moment, TimeSpan timePerBlock)
|
||||||
|
{
|
||||||
|
var max = entries.Keys.Max();
|
||||||
|
var latest = entries[max];
|
||||||
|
var maxRetry = 10;
|
||||||
|
while (moment > latest.Utc)
|
||||||
|
{
|
||||||
|
var newBlock = AddCurrentBlock();
|
||||||
|
if (newBlock == null || newBlock.BlockNumber == latest.BlockNumber)
|
||||||
|
{
|
||||||
|
maxRetry--;
|
||||||
|
if (maxRetry == 0) throw new Exception("Unable to fetch recent block after 10x tries.");
|
||||||
|
Thread.Sleep(timePerBlock);
|
||||||
|
}
|
||||||
|
max = entries.Keys.Max();
|
||||||
|
latest = entries[max];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockTimeEntry? AddBlockNumber(decimal blockNumber)
|
||||||
|
{
|
||||||
|
return AddBlockNumber(Convert.ToUInt64(blockNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockTimeEntry? AddBlockNumber(ulong blockNumber)
|
||||||
|
{
|
||||||
|
if (entries.ContainsKey(blockNumber))
|
||||||
|
{
|
||||||
|
return entries[blockNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Count > MaxEntries)
|
||||||
|
{
|
||||||
|
log.Debug("Entries cleared!");
|
||||||
|
entries.Clear();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = GetTimestampFromBlock(blockNumber);
|
||||||
|
if (time == null)
|
||||||
|
{
|
||||||
|
log.Log("Failed to get block for number: " + blockNumber);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var entry = new BlockTimeEntry(blockNumber, time.Value);
|
||||||
|
log.Debug("Found block " + entry.BlockNumber + " at " + entry.Utc.ToString("o"));
|
||||||
|
entries.Add(blockNumber, entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan EstimateTimePerBlock()
|
||||||
|
{
|
||||||
|
var min = entries.Keys.Min();
|
||||||
|
var max = entries.Keys.Max();
|
||||||
|
var clippedMin = Math.Max(max - 100, min);
|
||||||
|
var minTime = entries[min].Utc;
|
||||||
|
var clippedMinBlock = AddBlockNumber(clippedMin);
|
||||||
|
if (clippedMinBlock != null) minTime = clippedMinBlock.Utc;
|
||||||
|
|
||||||
|
var maxTime = entries[max].Utc;
|
||||||
|
var elapsedTime = maxTime - minTime;
|
||||||
|
|
||||||
|
double elapsedSeconds = elapsedTime.TotalSeconds;
|
||||||
|
double numberOfBlocks = max - min;
|
||||||
|
double secondsPerBlock = elapsedSeconds / numberOfBlocks;
|
||||||
|
|
||||||
|
var result = TimeSpan.FromSeconds(secondsPerBlock);
|
||||||
|
if (result.TotalSeconds < 1.0) result = TimeSpan.FromSeconds(1.0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
if (!entries.Any())
|
||||||
|
{
|
||||||
|
AddCurrentBlock();
|
||||||
|
AddBlockNumber(entries.Single().Key - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertMomentIsInPast(DateTime moment)
|
||||||
|
{
|
||||||
|
if (moment > DateTime.UtcNow) throw new Exception("Moment must be UTC and must be in the past.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockTimeEntry? AddCurrentBlock()
|
||||||
|
{
|
||||||
|
var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync());
|
||||||
|
var blockNumber = number.ToDecimal();
|
||||||
|
return AddBlockNumber(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? GetTimestampFromBlock(ulong blockNumber)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber)));
|
||||||
|
if (block == null) return null;
|
||||||
|
return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockTimeEntry? FindClosestBeforeEntry(DateTime moment)
|
||||||
|
{
|
||||||
|
BlockTimeEntry? result = null;
|
||||||
|
foreach (var entry in entries.Values)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
if (entry.Utc < moment) result = entry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (entry.Utc > result.Utc && entry.Utc < moment) result = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockTimeEntry? FindClosestAfterEntry(DateTime moment)
|
||||||
|
{
|
||||||
|
BlockTimeEntry? result = null;
|
||||||
|
foreach (var entry in entries.Values)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
if (entry.Utc > moment) result = entry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (entry.Utc < result.Utc && entry.Utc > moment) result = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
using Logging;
|
using Logging;
|
||||||
|
using Nethereum.ABI.FunctionEncoding.Attributes;
|
||||||
using Nethereum.Contracts;
|
using Nethereum.Contracts;
|
||||||
using Nethereum.RPC.Eth.DTOs;
|
using Nethereum.RPC.Eth.DTOs;
|
||||||
using Nethereum.Web3;
|
using Nethereum.Web3;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Utils;
|
using Utils;
|
||||||
|
|
||||||
namespace NethereumWorkflow
|
namespace NethereumWorkflow
|
||||||
@ -54,6 +56,12 @@ namespace NethereumWorkflow
|
|||||||
return receipt.TransactionHash;
|
return receipt.TransactionHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Transaction GetTransaction(string transactionHash)
|
||||||
|
{
|
||||||
|
log.Debug();
|
||||||
|
return Time.Wait(web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(transactionHash));
|
||||||
|
}
|
||||||
|
|
||||||
public decimal? GetSyncedBlockNumber()
|
public decimal? GetSyncedBlockNumber()
|
||||||
{
|
{
|
||||||
log.Debug();
|
log.Debug();
|
||||||
@ -77,5 +85,24 @@ namespace NethereumWorkflow
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
|
||||||
|
{
|
||||||
|
var blockTimeFinder = new BlockTimeFinder(web3, log);
|
||||||
|
|
||||||
|
var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From);
|
||||||
|
var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To);
|
||||||
|
|
||||||
|
return GetEvents<TEvent>(address, fromBlock, toBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
|
||||||
|
{
|
||||||
|
var eventHandler = web3.Eth.GetEvent<TEvent>(address);
|
||||||
|
var from = new BlockParameter(fromBlockNumber);
|
||||||
|
var to = new BlockParameter(toBlockNumber);
|
||||||
|
var blockFilter = Time.Wait(eventHandler.CreateFilterBlockRangeAsync(from, to));
|
||||||
|
return Time.Wait(eventHandler.GetAllChangesAsync(blockFilter));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace ContinuousTests
|
namespace Utils
|
||||||
{
|
{
|
||||||
public class TaskFactory
|
public class TaskFactory
|
||||||
{
|
{
|
@ -13,6 +13,11 @@
|
|||||||
return task.Result;
|
return task.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Wait(Task task)
|
||||||
|
{
|
||||||
|
task.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
public static string FormatDuration(TimeSpan d)
|
public static string FormatDuration(TimeSpan d)
|
||||||
{
|
{
|
||||||
var result = "";
|
var result = "";
|
||||||
|
24
Framework/Utils/TimeRange.cs
Normal file
24
Framework/Utils/TimeRange.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
public class TimeRange
|
||||||
|
{
|
||||||
|
public TimeRange(DateTime from, DateTime to)
|
||||||
|
{
|
||||||
|
if (from < to)
|
||||||
|
{
|
||||||
|
From = from;
|
||||||
|
To = to;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
From = to;
|
||||||
|
To = from;
|
||||||
|
}
|
||||||
|
Duration = To - From;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime From { get; }
|
||||||
|
public DateTime To { get; }
|
||||||
|
public TimeSpan Duration { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,11 @@
|
|||||||
using GethPlugin;
|
using CodexContractsPlugin.Marketplace;
|
||||||
|
using GethPlugin;
|
||||||
using Logging;
|
using Logging;
|
||||||
|
using Nethereum.ABI;
|
||||||
|
using Nethereum.Hex.HexTypes;
|
||||||
|
using Nethereum.Util;
|
||||||
|
using NethereumWorkflow;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace CodexContractsPlugin
|
namespace CodexContractsPlugin
|
||||||
{
|
{
|
||||||
@ -12,6 +18,23 @@ namespace CodexContractsPlugin
|
|||||||
string MintTestTokens(EthAddress ethAddress, TestToken testTokens);
|
string MintTestTokens(EthAddress ethAddress, TestToken testTokens);
|
||||||
TestToken GetTestTokenBalance(IHasEthAddress owner);
|
TestToken GetTestTokenBalance(IHasEthAddress owner);
|
||||||
TestToken GetTestTokenBalance(EthAddress ethAddress);
|
TestToken GetTestTokenBalance(EthAddress ethAddress);
|
||||||
|
|
||||||
|
Request[] GetStorageRequests(TimeRange timeRange);
|
||||||
|
EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex);
|
||||||
|
RequestState GetRequestState(Request request);
|
||||||
|
RequestFulfilledEventDTO[] GetRequestFulfilledEvents(TimeRange timeRange);
|
||||||
|
RequestCancelledEventDTO[] GetRequestCancelledEvents(TimeRange timeRange);
|
||||||
|
SlotFilledEventDTO[] GetSlotFilledEvents(TimeRange timeRange);
|
||||||
|
SlotFreedEventDTO[] GetSlotFreedEvents(TimeRange timeRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RequestState
|
||||||
|
{
|
||||||
|
New,
|
||||||
|
Started,
|
||||||
|
Cancelled,
|
||||||
|
Finished,
|
||||||
|
Failed
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CodexContractsAccess : ICodexContracts
|
public class CodexContractsAccess : ICodexContracts
|
||||||
@ -30,8 +53,7 @@ namespace CodexContractsPlugin
|
|||||||
|
|
||||||
public bool IsDeployed()
|
public bool IsDeployed()
|
||||||
{
|
{
|
||||||
var interaction = new ContractInteractions(log, gethNode);
|
return !string.IsNullOrEmpty(StartInteraction().GetTokenName(Deployment.TokenAddress));
|
||||||
return !string.IsNullOrEmpty(interaction.GetTokenName(Deployment.TokenAddress));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MintTestTokens(IHasEthAddress owner, TestToken testTokens)
|
public string MintTestTokens(IHasEthAddress owner, TestToken testTokens)
|
||||||
@ -41,8 +63,7 @@ namespace CodexContractsPlugin
|
|||||||
|
|
||||||
public string MintTestTokens(EthAddress ethAddress, TestToken testTokens)
|
public string MintTestTokens(EthAddress ethAddress, TestToken testTokens)
|
||||||
{
|
{
|
||||||
var interaction = new ContractInteractions(log, gethNode);
|
return StartInteraction().MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress);
|
||||||
return interaction.MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestToken GetTestTokenBalance(IHasEthAddress owner)
|
public TestToken GetTestTokenBalance(IHasEthAddress owner)
|
||||||
@ -52,9 +73,108 @@ namespace CodexContractsPlugin
|
|||||||
|
|
||||||
public TestToken GetTestTokenBalance(EthAddress ethAddress)
|
public TestToken GetTestTokenBalance(EthAddress ethAddress)
|
||||||
{
|
{
|
||||||
var interaction = new ContractInteractions(log, gethNode);
|
var balance = StartInteraction().GetBalance(Deployment.TokenAddress, ethAddress.Address);
|
||||||
var balance = interaction.GetBalance(Deployment.TokenAddress, ethAddress.Address);
|
|
||||||
return balance.TestTokens();
|
return balance.TestTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Request[] GetStorageRequests(TimeRange timeRange)
|
||||||
|
{
|
||||||
|
var events = gethNode.GetEvents<StorageRequestedEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||||
|
var i = StartInteraction();
|
||||||
|
return events
|
||||||
|
.Select(e =>
|
||||||
|
{
|
||||||
|
var requestEvent = i.GetRequest(Deployment.MarketplaceAddress, e.Event.RequestId);
|
||||||
|
var result = requestEvent.ReturnValue1;
|
||||||
|
result.BlockNumber = e.Log.BlockNumber.ToUlong();
|
||||||
|
result.RequestId = e.Event.RequestId;
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestFulfilledEventDTO[] GetRequestFulfilledEvents(TimeRange timeRange)
|
||||||
|
{
|
||||||
|
var events = gethNode.GetEvents<RequestFulfilledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||||
|
return events.Select(e =>
|
||||||
|
{
|
||||||
|
var result = e.Event;
|
||||||
|
result.BlockNumber = e.Log.BlockNumber.ToUlong();
|
||||||
|
return result;
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestCancelledEventDTO[] GetRequestCancelledEvents(TimeRange timeRange)
|
||||||
|
{
|
||||||
|
var events = gethNode.GetEvents<RequestCancelledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||||
|
return events.Select(e =>
|
||||||
|
{
|
||||||
|
var result = e.Event;
|
||||||
|
result.BlockNumber = e.Log.BlockNumber.ToUlong();
|
||||||
|
return result;
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotFilledEventDTO[] GetSlotFilledEvents(TimeRange timeRange)
|
||||||
|
{
|
||||||
|
var events = gethNode.GetEvents<SlotFilledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||||
|
return events.Select(e =>
|
||||||
|
{
|
||||||
|
var result = e.Event;
|
||||||
|
result.BlockNumber = e.Log.BlockNumber.ToUlong();
|
||||||
|
result.Host = GetEthAddressFromTransaction(e.Log.TransactionHash);
|
||||||
|
return result;
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotFreedEventDTO[] GetSlotFreedEvents(TimeRange timeRange)
|
||||||
|
{
|
||||||
|
var events = gethNode.GetEvents<SlotFreedEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||||
|
return events.Select(e =>
|
||||||
|
{
|
||||||
|
var result = e.Event;
|
||||||
|
result.BlockNumber = e.Log.BlockNumber.ToUlong();
|
||||||
|
return result;
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex)
|
||||||
|
{
|
||||||
|
var encoder = new ABIEncode();
|
||||||
|
var encoded = encoder.GetABIEncoded(
|
||||||
|
new ABIValue("bytes32", storageRequest.RequestId),
|
||||||
|
new ABIValue("uint256", slotIndex.ToBig())
|
||||||
|
);
|
||||||
|
|
||||||
|
var hashed = Sha3Keccack.Current.CalculateHash(encoded);
|
||||||
|
|
||||||
|
var func = new GetHostFunction
|
||||||
|
{
|
||||||
|
SlotId = hashed
|
||||||
|
};
|
||||||
|
var address = gethNode.Call<GetHostFunction, string>(Deployment.MarketplaceAddress, func);
|
||||||
|
if (string.IsNullOrEmpty(address)) return null;
|
||||||
|
return new EthAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestState GetRequestState(Request request)
|
||||||
|
{
|
||||||
|
var func = new RequestStateFunction
|
||||||
|
{
|
||||||
|
RequestId = request.RequestId
|
||||||
|
};
|
||||||
|
return gethNode.Call<RequestStateFunction, RequestState>(Deployment.MarketplaceAddress, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EthAddress GetEthAddressFromTransaction(string transactionHash)
|
||||||
|
{
|
||||||
|
var transaction = gethNode.GetTransaction(transactionHash);
|
||||||
|
return new EthAddress(transaction.From);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContractInteractions StartInteraction()
|
||||||
|
{
|
||||||
|
return new ContractInteractions(log, gethNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using GethPlugin;
|
using CodexContractsPlugin.Marketplace;
|
||||||
|
using GethPlugin;
|
||||||
using Logging;
|
using Logging;
|
||||||
using Nethereum.ABI.FunctionEncoding.Attributes;
|
using Nethereum.ABI.FunctionEncoding.Attributes;
|
||||||
using Nethereum.Contracts;
|
using Nethereum.Contracts;
|
||||||
|
using Nethereum.Hex.HexConvertors.Extensions;
|
||||||
using NethereumWorkflow;
|
using NethereumWorkflow;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@ -59,6 +61,17 @@ namespace CodexContractsPlugin
|
|||||||
return gethNode.Call<GetTokenBalanceFunction, BigInteger>(tokenAddress, function).ToDecimal();
|
return gethNode.Call<GetTokenBalanceFunction, BigInteger>(tokenAddress, function).ToDecimal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetRequestOutputDTO GetRequest(string marketplaceAddress, byte[] requestId)
|
||||||
|
{
|
||||||
|
|
||||||
|
log.Debug($"({marketplaceAddress}) {requestId.ToHex(true)}");
|
||||||
|
var func = new GetRequestFunction
|
||||||
|
{
|
||||||
|
RequestId = requestId
|
||||||
|
};
|
||||||
|
return gethNode.Call<GetRequestFunction, GetRequestOutputDTO>(marketplaceAddress, func);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsSynced(string marketplaceAddress, string marketplaceAbi)
|
public bool IsSynced(string marketplaceAddress, string marketplaceAbi)
|
||||||
{
|
{
|
||||||
log.Debug();
|
log.Debug();
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
|
using GethPlugin;
|
||||||
|
|
||||||
|
namespace CodexContractsPlugin.Marketplace
|
||||||
|
{
|
||||||
|
public partial class Request : RequestBase
|
||||||
|
{
|
||||||
|
public ulong BlockNumber { get; set; }
|
||||||
|
public byte[] RequestId { get; set; }
|
||||||
|
|
||||||
|
public EthAddress ClientAddress { get { return new EthAddress(Client); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestFulfilledEventDTO
|
||||||
|
{
|
||||||
|
public ulong BlockNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestCancelledEventDTO
|
||||||
|
{
|
||||||
|
public ulong BlockNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SlotFilledEventDTO
|
||||||
|
{
|
||||||
|
public ulong BlockNumber { get; set; }
|
||||||
|
public EthAddress Host { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SlotFreedEventDTO
|
||||||
|
{
|
||||||
|
public ulong BlockNumber { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
517
ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs
Normal file
517
ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
using Nethereum.ABI.FunctionEncoding.Attributes;
|
||||||
|
using Nethereum.Contracts;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
// Generated code, do not modify.
|
||||||
|
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
|
namespace CodexContractsPlugin.Marketplace
|
||||||
|
{
|
||||||
|
public partial class ConfigFunction : ConfigFunctionBase { }
|
||||||
|
|
||||||
|
[Function("config", typeof(ConfigOutputDTO))]
|
||||||
|
public class ConfigFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class FillSlotFunction : FillSlotFunctionBase { }
|
||||||
|
|
||||||
|
[Function("fillSlot")]
|
||||||
|
public class FillSlotFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
[Parameter("uint256", "slotIndex", 2)]
|
||||||
|
public virtual BigInteger SlotIndex { get; set; }
|
||||||
|
[Parameter("bytes", "proof", 3)]
|
||||||
|
public virtual byte[] Proof { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class FreeSlotFunction : FreeSlotFunctionBase { }
|
||||||
|
|
||||||
|
[Function("freeSlot")]
|
||||||
|
public class FreeSlotFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetActiveSlotFunction : GetActiveSlotFunctionBase { }
|
||||||
|
|
||||||
|
[Function("getActiveSlot", typeof(GetActiveSlotOutputDTO))]
|
||||||
|
public class GetActiveSlotFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetChallengeFunction : GetChallengeFunctionBase { }
|
||||||
|
|
||||||
|
[Function("getChallenge", "bytes32")]
|
||||||
|
public class GetChallengeFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetHostFunction : GetHostFunctionBase { }
|
||||||
|
|
||||||
|
[Function("getHost", "address")]
|
||||||
|
public class GetHostFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetPointerFunction : GetPointerFunctionBase { }
|
||||||
|
|
||||||
|
[Function("getPointer", "uint8")]
|
||||||
|
public class GetPointerFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetRequestFunction : GetRequestFunctionBase { }
|
||||||
|
|
||||||
|
[Function("getRequest", typeof(GetRequestOutputDTO))]
|
||||||
|
public class GetRequestFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class IsProofRequiredFunction : IsProofRequiredFunctionBase { }
|
||||||
|
|
||||||
|
[Function("isProofRequired", "bool")]
|
||||||
|
public class IsProofRequiredFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MarkProofAsMissingFunction : MarkProofAsMissingFunctionBase { }
|
||||||
|
|
||||||
|
[Function("markProofAsMissing")]
|
||||||
|
public class MarkProofAsMissingFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
[Parameter("uint256", "period", 2)]
|
||||||
|
public virtual BigInteger Period { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MissingProofsFunction : MissingProofsFunctionBase { }
|
||||||
|
|
||||||
|
[Function("missingProofs", "uint256")]
|
||||||
|
public class MissingProofsFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MyRequestsFunction : MyRequestsFunctionBase { }
|
||||||
|
|
||||||
|
[Function("myRequests", "bytes32[]")]
|
||||||
|
public class MyRequestsFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MySlotsFunction : MySlotsFunctionBase { }
|
||||||
|
|
||||||
|
[Function("mySlots", "bytes32[]")]
|
||||||
|
public class MySlotsFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestEndFunction : RequestEndFunctionBase { }
|
||||||
|
|
||||||
|
[Function("requestEnd", "uint256")]
|
||||||
|
public class RequestEndFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestStateFunction : RequestStateFunctionBase { }
|
||||||
|
|
||||||
|
[Function("requestState", "uint8")]
|
||||||
|
public class RequestStateFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestStorageFunction : RequestStorageFunctionBase { }
|
||||||
|
|
||||||
|
[Function("requestStorage")]
|
||||||
|
public class RequestStorageFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "request", 1)]
|
||||||
|
public virtual Request Request { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SlotStateFunction : SlotStateFunctionBase { }
|
||||||
|
|
||||||
|
[Function("slotState", "uint8")]
|
||||||
|
public class SlotStateFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "slotId", 1)]
|
||||||
|
public virtual byte[] SlotId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SubmitProofFunction : SubmitProofFunctionBase { }
|
||||||
|
|
||||||
|
[Function("submitProof")]
|
||||||
|
public class SubmitProofFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
[Parameter("bytes", "proof", 2)]
|
||||||
|
public virtual byte[] Proof { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class TokenFunction : TokenFunctionBase { }
|
||||||
|
|
||||||
|
[Function("token", "address")]
|
||||||
|
public class TokenFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class WillProofBeRequiredFunction : WillProofBeRequiredFunctionBase { }
|
||||||
|
|
||||||
|
[Function("willProofBeRequired", "bool")]
|
||||||
|
public class WillProofBeRequiredFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class WithdrawFundsFunction : WithdrawFundsFunctionBase { }
|
||||||
|
|
||||||
|
[Function("withdrawFunds")]
|
||||||
|
public class WithdrawFundsFunctionBase : FunctionMessage
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ProofSubmittedEventDTO : ProofSubmittedEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("ProofSubmitted")]
|
||||||
|
public class ProofSubmittedEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "id", 1, false)]
|
||||||
|
public virtual byte[] Id { get; set; }
|
||||||
|
[Parameter("bytes", "proof", 2, false)]
|
||||||
|
public virtual byte[] Proof { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestCancelledEventDTO : RequestCancelledEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("RequestCancelled")]
|
||||||
|
public class RequestCancelledEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, true)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestFailedEventDTO : RequestFailedEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("RequestFailed")]
|
||||||
|
public class RequestFailedEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, true)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestFulfilledEventDTO : RequestFulfilledEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("RequestFulfilled")]
|
||||||
|
public class RequestFulfilledEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, true)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SlotFilledEventDTO : SlotFilledEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("SlotFilled")]
|
||||||
|
public class SlotFilledEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, true)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
[Parameter("uint256", "slotIndex", 2, false)]
|
||||||
|
public virtual BigInteger SlotIndex { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SlotFreedEventDTO : SlotFreedEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("SlotFreed")]
|
||||||
|
public class SlotFreedEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, true)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
[Parameter("uint256", "slotIndex", 2, false)]
|
||||||
|
public virtual BigInteger SlotIndex { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class StorageRequestedEventDTO : StorageRequestedEventDTOBase { }
|
||||||
|
|
||||||
|
[Event("StorageRequested")]
|
||||||
|
public class StorageRequestedEventDTOBase : IEventDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "requestId", 1, false)]
|
||||||
|
public virtual byte[] RequestId { get; set; }
|
||||||
|
[Parameter("tuple", "ask", 2, false)]
|
||||||
|
public virtual Ask Ask { get; set; }
|
||||||
|
[Parameter("uint256", "expiry", 3, false)]
|
||||||
|
public virtual BigInteger Expiry { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ConfigOutputDTO : ConfigOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class ConfigOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "collateral", 1)]
|
||||||
|
public virtual CollateralConfig Collateral { get; set; }
|
||||||
|
[Parameter("tuple", "proofs", 2)]
|
||||||
|
public virtual ProofConfig Proofs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public partial class GetActiveSlotOutputDTO : GetActiveSlotOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class GetActiveSlotOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "", 1)]
|
||||||
|
public virtual ActiveSlot ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetChallengeOutputDTO : GetChallengeOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class GetChallengeOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32", "", 1)]
|
||||||
|
public virtual byte[] ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetHostOutputDTO : GetHostOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class GetHostOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("address", "", 1)]
|
||||||
|
public virtual string ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetPointerOutputDTO : GetPointerOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class GetPointerOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("uint8", "", 1)]
|
||||||
|
public virtual byte ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class GetRequestOutputDTO : GetRequestOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class GetRequestOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "", 1)]
|
||||||
|
public virtual Request ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class IsProofRequiredOutputDTO : IsProofRequiredOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class IsProofRequiredOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("bool", "", 1)]
|
||||||
|
public virtual bool ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public partial class MissingProofsOutputDTO : MissingProofsOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class MissingProofsOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("uint256", "", 1)]
|
||||||
|
public virtual BigInteger ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MyRequestsOutputDTO : MyRequestsOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class MyRequestsOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32[]", "", 1)]
|
||||||
|
public virtual List<byte[]> ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MySlotsOutputDTO : MySlotsOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class MySlotsOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("bytes32[]", "", 1)]
|
||||||
|
public virtual List<byte[]> ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestEndOutputDTO : RequestEndOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class RequestEndOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("uint256", "", 1)]
|
||||||
|
public virtual BigInteger ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RequestStateOutputDTO : RequestStateOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class RequestStateOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("uint8", "", 1)]
|
||||||
|
public virtual byte ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public partial class SlotStateOutputDTO : SlotStateOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class SlotStateOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("uint8", "", 1)]
|
||||||
|
public virtual byte ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public partial class TokenOutputDTO : TokenOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class TokenOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("address", "", 1)]
|
||||||
|
public virtual string ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class WillProofBeRequiredOutputDTO : WillProofBeRequiredOutputDTOBase { }
|
||||||
|
|
||||||
|
[FunctionOutput]
|
||||||
|
public class WillProofBeRequiredOutputDTOBase : IFunctionOutputDTO
|
||||||
|
{
|
||||||
|
[Parameter("bool", "", 1)]
|
||||||
|
public virtual bool ReturnValue1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public partial class CollateralConfig : CollateralConfigBase { }
|
||||||
|
|
||||||
|
public class CollateralConfigBase
|
||||||
|
{
|
||||||
|
[Parameter("uint8", "repairRewardPercentage", 1)]
|
||||||
|
public virtual byte RepairRewardPercentage { get; set; }
|
||||||
|
[Parameter("uint8", "maxNumberOfSlashes", 2)]
|
||||||
|
public virtual byte MaxNumberOfSlashes { get; set; }
|
||||||
|
[Parameter("uint16", "slashCriterion", 3)]
|
||||||
|
public virtual ushort SlashCriterion { get; set; }
|
||||||
|
[Parameter("uint8", "slashPercentage", 4)]
|
||||||
|
public virtual byte SlashPercentage { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ProofConfig : ProofConfigBase { }
|
||||||
|
|
||||||
|
public class ProofConfigBase
|
||||||
|
{
|
||||||
|
[Parameter("uint256", "period", 1)]
|
||||||
|
public virtual BigInteger Period { get; set; }
|
||||||
|
[Parameter("uint256", "timeout", 2)]
|
||||||
|
public virtual BigInteger Timeout { get; set; }
|
||||||
|
[Parameter("uint8", "downtime", 3)]
|
||||||
|
public virtual byte Downtime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MarketplaceConfig : MarketplaceConfigBase { }
|
||||||
|
|
||||||
|
public class MarketplaceConfigBase
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "collateral", 1)]
|
||||||
|
public virtual CollateralConfig Collateral { get; set; }
|
||||||
|
[Parameter("tuple", "proofs", 2)]
|
||||||
|
public virtual ProofConfig Proofs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Ask : AskBase { }
|
||||||
|
|
||||||
|
public class AskBase
|
||||||
|
{
|
||||||
|
[Parameter("uint64", "slots", 1)]
|
||||||
|
public virtual ulong Slots { get; set; }
|
||||||
|
[Parameter("uint256", "slotSize", 2)]
|
||||||
|
public virtual BigInteger SlotSize { get; set; }
|
||||||
|
[Parameter("uint256", "duration", 3)]
|
||||||
|
public virtual BigInteger Duration { get; set; }
|
||||||
|
[Parameter("uint256", "proofProbability", 4)]
|
||||||
|
public virtual BigInteger ProofProbability { get; set; }
|
||||||
|
[Parameter("uint256", "reward", 5)]
|
||||||
|
public virtual BigInteger Reward { get; set; }
|
||||||
|
[Parameter("uint256", "collateral", 6)]
|
||||||
|
public virtual BigInteger Collateral { get; set; }
|
||||||
|
[Parameter("uint64", "maxSlotLoss", 7)]
|
||||||
|
public virtual ulong MaxSlotLoss { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Content : ContentBase { }
|
||||||
|
|
||||||
|
public class ContentBase
|
||||||
|
{
|
||||||
|
[Parameter("string", "cid", 1)]
|
||||||
|
public virtual string Cid { get; set; }
|
||||||
|
[Parameter("bytes32", "merkleRoot", 2)]
|
||||||
|
public virtual byte[] MerkleRoot { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Request : RequestBase { }
|
||||||
|
|
||||||
|
public class RequestBase
|
||||||
|
{
|
||||||
|
[Parameter("address", "client", 1)]
|
||||||
|
public virtual string Client { get; set; }
|
||||||
|
[Parameter("tuple", "ask", 2)]
|
||||||
|
public virtual Ask Ask { get; set; }
|
||||||
|
[Parameter("tuple", "content", 3)]
|
||||||
|
public virtual Content Content { get; set; }
|
||||||
|
[Parameter("uint256", "expiry", 4)]
|
||||||
|
public virtual BigInteger Expiry { get; set; }
|
||||||
|
[Parameter("bytes32", "nonce", 5)]
|
||||||
|
public virtual byte[] Nonce { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ActiveSlot : ActiveSlotBase { }
|
||||||
|
|
||||||
|
public class ActiveSlotBase
|
||||||
|
{
|
||||||
|
[Parameter("tuple", "request", 1)]
|
||||||
|
public virtual Request Request { get; set; }
|
||||||
|
[Parameter("uint256", "slotIndex", 2)]
|
||||||
|
public virtual BigInteger SlotIndex { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
@ -0,0 +1 @@
|
|||||||
|
This code was generated using the Nethereum code generator, here: http://playground.nethereum.com
|
@ -27,6 +27,13 @@ namespace CodexPlugin
|
|||||||
|
|
||||||
public class CodexLogCustomTopics
|
public class CodexLogCustomTopics
|
||||||
{
|
{
|
||||||
|
public CodexLogCustomTopics(CodexLogLevel discV5, CodexLogLevel libp2p, CodexLogLevel blockExchange)
|
||||||
|
{
|
||||||
|
DiscV5 = discV5;
|
||||||
|
Libp2p = libp2p;
|
||||||
|
BlockExchange = blockExchange;
|
||||||
|
}
|
||||||
|
|
||||||
public CodexLogCustomTopics(CodexLogLevel discV5, CodexLogLevel libp2p)
|
public CodexLogCustomTopics(CodexLogLevel discV5, CodexLogLevel libp2p)
|
||||||
{
|
{
|
||||||
DiscV5 = discV5;
|
DiscV5 = discV5;
|
||||||
@ -35,6 +42,7 @@ namespace CodexPlugin
|
|||||||
|
|
||||||
public CodexLogLevel DiscV5 { get; set; }
|
public CodexLogLevel DiscV5 { get; set; }
|
||||||
public CodexLogLevel Libp2p { get; set; }
|
public CodexLogLevel Libp2p { get; set; }
|
||||||
|
public CodexLogLevel? BlockExchange { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CodexSetup : CodexStartupConfig, ICodexSetup
|
public class CodexSetup : CodexStartupConfig, ICodexSetup
|
||||||
|
@ -52,12 +52,31 @@ namespace CodexPlugin
|
|||||||
"connection",
|
"connection",
|
||||||
"connmanager",
|
"connmanager",
|
||||||
"websock",
|
"websock",
|
||||||
"ws-session"
|
"ws-session",
|
||||||
|
"dialer",
|
||||||
|
"muxedupgrade",
|
||||||
|
"upgrade",
|
||||||
|
"identify"
|
||||||
|
};
|
||||||
|
var blockExchangeTopics = new[]
|
||||||
|
{
|
||||||
|
"codex",
|
||||||
|
"pendingblocks",
|
||||||
|
"peerctxstore",
|
||||||
|
"discoveryengine",
|
||||||
|
"blockexcengine",
|
||||||
|
"blockexcnetwork",
|
||||||
|
"blockexcnetworkpeer"
|
||||||
};
|
};
|
||||||
|
|
||||||
level = $"{level};" +
|
level = $"{level};" +
|
||||||
$"{CustomTopics.DiscV5.ToString()!.ToLowerInvariant()}:{string.Join(",", discV5Topics)};" +
|
$"{CustomTopics.DiscV5.ToString()!.ToLowerInvariant()}:{string.Join(",", discV5Topics)};" +
|
||||||
$"{CustomTopics.Libp2p.ToString()!.ToLowerInvariant()}:{string.Join(",", libp2pTopics)}";
|
$"{CustomTopics.Libp2p.ToString()!.ToLowerInvariant()}:{string.Join(",", libp2pTopics)}";
|
||||||
|
|
||||||
|
if (CustomTopics.BlockExchange != null)
|
||||||
|
{
|
||||||
|
level += $";{CustomTopics.BlockExchange.ToString()!.ToLowerInvariant()}:{string.Join(",", blockExchangeTopics)}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,22 @@
|
|||||||
{
|
{
|
||||||
public EthAddress(string address)
|
public EthAddress(string address)
|
||||||
{
|
{
|
||||||
Address = address;
|
Address = address.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Address { get; }
|
public string Address { get; }
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is EthAddress address &&
|
||||||
|
Address == address.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Address);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Address;
|
return Address;
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
using Core;
|
using Core;
|
||||||
using KubernetesWorkflow.Types;
|
using KubernetesWorkflow.Types;
|
||||||
using Logging;
|
using Logging;
|
||||||
|
using Nethereum.ABI.FunctionEncoding.Attributes;
|
||||||
using Nethereum.Contracts;
|
using Nethereum.Contracts;
|
||||||
|
using Nethereum.RPC.Eth.DTOs;
|
||||||
using NethereumWorkflow;
|
using NethereumWorkflow;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace GethPlugin
|
namespace GethPlugin
|
||||||
{
|
{
|
||||||
@ -17,9 +20,12 @@ namespace GethPlugin
|
|||||||
string SendEth(EthAddress account, Ether eth);
|
string SendEth(EthAddress account, Ether eth);
|
||||||
TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
|
TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
|
||||||
string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
|
string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
|
||||||
|
Transaction GetTransaction(string transactionHash);
|
||||||
decimal? GetSyncedBlockNumber();
|
decimal? GetSyncedBlockNumber();
|
||||||
bool IsContractAvailable(string abi, string contractAddress);
|
bool IsContractAvailable(string abi, string contractAddress);
|
||||||
GethBootstrapNode GetBootstrapRecord();
|
GethBootstrapNode GetBootstrapRecord();
|
||||||
|
List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new();
|
||||||
|
List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeploymentGethNode : BaseGethNode, IGethNode
|
public class DeploymentGethNode : BaseGethNode, IGethNode
|
||||||
@ -123,6 +129,11 @@ namespace GethPlugin
|
|||||||
return StartInteraction().SendTransaction(contractAddress, function);
|
return StartInteraction().SendTransaction(contractAddress, function);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Transaction GetTransaction(string transactionHash)
|
||||||
|
{
|
||||||
|
return StartInteraction().GetTransaction(transactionHash);
|
||||||
|
}
|
||||||
|
|
||||||
public decimal? GetSyncedBlockNumber()
|
public decimal? GetSyncedBlockNumber()
|
||||||
{
|
{
|
||||||
return StartInteraction().GetSyncedBlockNumber();
|
return StartInteraction().GetSyncedBlockNumber();
|
||||||
@ -133,6 +144,16 @@ namespace GethPlugin
|
|||||||
return StartInteraction().IsContractAvailable(abi, contractAddress);
|
return StartInteraction().IsContractAvailable(abi, contractAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
|
||||||
|
{
|
||||||
|
return StartInteraction().GetEvents<TEvent>(address, fromBlockNumber, toBlockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
|
||||||
|
{
|
||||||
|
return StartInteraction().GetEvents<TEvent>(address, timeRange);
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract NethereumInteraction StartInteraction();
|
protected abstract NethereumInteraction StartInteraction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using DistTestCore.Logs;
|
|||||||
using Logging;
|
using Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Utils;
|
using Utils;
|
||||||
|
using TaskFactory = Utils.TaskFactory;
|
||||||
|
|
||||||
namespace ContinuousTests
|
namespace ContinuousTests
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using CodexPlugin;
|
|||||||
using DistTestCore.Logs;
|
using DistTestCore.Logs;
|
||||||
using Core;
|
using Core;
|
||||||
using KubernetesWorkflow.Types;
|
using KubernetesWorkflow.Types;
|
||||||
|
using TaskFactory = Utils.TaskFactory;
|
||||||
|
|
||||||
namespace ContinuousTests
|
namespace ContinuousTests
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using DistTestCore.Logs;
|
using DistTestCore.Logs;
|
||||||
using Logging;
|
using Logging;
|
||||||
|
using TaskFactory = Utils.TaskFactory;
|
||||||
|
|
||||||
namespace ContinuousTests
|
namespace ContinuousTests
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ using CodexPlugin;
|
|||||||
using DistTestCore;
|
using DistTestCore;
|
||||||
using GethPlugin;
|
using GethPlugin;
|
||||||
using MetricsPlugin;
|
using MetricsPlugin;
|
||||||
|
using Nethereum.Hex.HexConvertors.Extensions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Utils;
|
using Utils;
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ namespace CodexTests.BasicTests
|
|||||||
var contracts = Ci.StartCodexContracts(geth);
|
var contracts = Ci.StartCodexContracts(geth);
|
||||||
|
|
||||||
var seller = AddCodex(s => s
|
var seller = AddCodex(s => s
|
||||||
|
.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn))
|
||||||
.WithStorageQuota(11.GB())
|
.WithStorageQuota(11.GB())
|
||||||
.EnableMarketplace(geth, contracts, initialEth: 10.Eth(), initialTokens: sellerInitialBalance, isValidator: true)
|
.EnableMarketplace(geth, contracts, initialEth: 10.Eth(), initialTokens: sellerInitialBalance, isValidator: true)
|
||||||
.WithSimulateProofFailures(failEveryNProofs: 3));
|
.WithSimulateProofFailures(failEveryNProofs: 3));
|
||||||
@ -88,14 +90,40 @@ namespace CodexTests.BasicTests
|
|||||||
|
|
||||||
purchaseContract.WaitForStorageContractStarted(fileSize);
|
purchaseContract.WaitForStorageContractStarted(fileSize);
|
||||||
|
|
||||||
|
var requests = contracts.GetStorageRequests(GetTestRunTimeRange());
|
||||||
|
Assert.That(requests.Length, Is.EqualTo(1));
|
||||||
|
var request = requests.Single();
|
||||||
|
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Started));
|
||||||
|
Assert.That(request.ClientAddress, Is.EqualTo(buyer.EthAddress));
|
||||||
|
Assert.That(request.Ask.Slots, Is.EqualTo(1));
|
||||||
|
|
||||||
AssertBalance(contracts, seller, Is.LessThan(sellerInitialBalance), "Collateral was not placed.");
|
AssertBalance(contracts, seller, Is.LessThan(sellerInitialBalance), "Collateral was not placed.");
|
||||||
|
|
||||||
|
var requestFulfilledEvents = contracts.GetRequestFulfilledEvents(GetTestRunTimeRange());
|
||||||
|
Assert.That(requestFulfilledEvents.Length, Is.EqualTo(1));
|
||||||
|
CollectionAssert.AreEqual(request.RequestId, requestFulfilledEvents[0].RequestId);
|
||||||
|
var filledSlotEvents = contracts.GetSlotFilledEvents(GetTestRunTimeRange());
|
||||||
|
Assert.That(filledSlotEvents.Length, Is.EqualTo(1));
|
||||||
|
var filledSlotEvent = filledSlotEvents.Single();
|
||||||
|
Assert.That(filledSlotEvent.SlotIndex.IsZero);
|
||||||
|
Assert.That(filledSlotEvent.RequestId.ToHex(), Is.EqualTo(request.RequestId.ToHex()));
|
||||||
|
Assert.That(filledSlotEvent.Host, Is.EqualTo(seller.EthAddress));
|
||||||
|
|
||||||
|
var slotHost = contracts.GetSlotHost(request, 0);
|
||||||
|
Assert.That(slotHost, Is.EqualTo(seller.EthAddress));
|
||||||
|
|
||||||
purchaseContract.WaitForStorageContractFinished();
|
purchaseContract.WaitForStorageContractFinished();
|
||||||
|
|
||||||
AssertBalance(contracts, seller, Is.GreaterThan(sellerInitialBalance), "Seller was not paid for storage.");
|
AssertBalance(contracts, seller, Is.GreaterThan(sellerInitialBalance), "Seller was not paid for storage.");
|
||||||
AssertBalance(contracts, buyer, Is.LessThan(buyerInitialBalance), "Buyer was not charged for storage.");
|
AssertBalance(contracts, buyer, Is.LessThan(buyerInitialBalance), "Buyer was not charged for storage.");
|
||||||
|
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
|
||||||
|
|
||||||
CheckLogForErrors(seller, buyer);
|
var log = Ci.DownloadLog(seller);
|
||||||
|
log.AssertLogContains("Received a request to store a slot!");
|
||||||
|
log.AssertLogContains("Received proof challenge");
|
||||||
|
log.AssertLogContains("Collecting input for proof");
|
||||||
|
|
||||||
|
//CheckLogForErrors(seller, buyer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -143,6 +143,11 @@ namespace DistTestCore
|
|||||||
Stopwatch.Measure(Get().Log, name, action);
|
Stopwatch.Measure(Get().Log, name, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TimeRange GetTestRunTimeRange()
|
||||||
|
{
|
||||||
|
return new TimeRange(Get().TestStart, DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Initialize(FixtureLog fixtureLog)
|
protected virtual void Initialize(FixtureLog fixtureLog)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ namespace DistTestCore
|
|||||||
public class TestLifecycle : IK8sHooks
|
public class TestLifecycle : IK8sHooks
|
||||||
{
|
{
|
||||||
private const string TestsType = "dist-tests";
|
private const string TestsType = "dist-tests";
|
||||||
private readonly DateTime testStart;
|
|
||||||
private readonly EntryPoint entryPoint;
|
private readonly EntryPoint entryPoint;
|
||||||
private readonly Dictionary<string, string> metadata;
|
private readonly Dictionary<string, string> metadata;
|
||||||
private readonly List<RunningContainers> runningContainers = new List<RunningContainers>();
|
private readonly List<RunningContainers> runningContainers = new List<RunningContainers>();
|
||||||
@ -21,7 +20,7 @@ namespace DistTestCore
|
|||||||
Log = log;
|
Log = log;
|
||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
TimeSet = timeSet;
|
TimeSet = timeSet;
|
||||||
testStart = DateTime.UtcNow;
|
TestStart = DateTime.UtcNow;
|
||||||
|
|
||||||
entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(timeSet, this, testNamespace), configuration.GetFileManagerFolder(), timeSet);
|
entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(timeSet, this, testNamespace), configuration.GetFileManagerFolder(), timeSet);
|
||||||
metadata = entryPoint.GetPluginMetadata();
|
metadata = entryPoint.GetPluginMetadata();
|
||||||
@ -30,6 +29,7 @@ namespace DistTestCore
|
|||||||
log.WriteLogTag();
|
log.WriteLogTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DateTime TestStart { get; }
|
||||||
public TestLog Log { get; }
|
public TestLog Log { get; }
|
||||||
public Configuration Configuration { get; }
|
public Configuration Configuration { get; }
|
||||||
public ITimeSet TimeSet { get; }
|
public ITimeSet TimeSet { get; }
|
||||||
@ -60,7 +60,7 @@ namespace DistTestCore
|
|||||||
|
|
||||||
public TimeSpan GetTestDuration()
|
public TimeSpan GetTestDuration()
|
||||||
{
|
{
|
||||||
return DateTime.UtcNow - testStart;
|
return DateTime.UtcNow - TestStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnContainersStarted(RunningContainers rc)
|
public void OnContainersStarted(RunningContainers rc)
|
||||||
|
@ -1,87 +1,18 @@
|
|||||||
using BiblioTech.Options;
|
using BiblioTech.Options;
|
||||||
using CodexContractsPlugin;
|
using CodexContractsPlugin;
|
||||||
using GethPlugin;
|
using GethPlugin;
|
||||||
using Logging;
|
|
||||||
|
|
||||||
namespace BiblioTech
|
namespace BiblioTech
|
||||||
{
|
{
|
||||||
public static class GethInput
|
|
||||||
{
|
|
||||||
private const string GethHostVar = "GETH_HOST";
|
|
||||||
private const string GethPortVar = "GETH_HTTP_PORT";
|
|
||||||
private const string GethPrivKeyVar = "GETH_PRIVATE_KEY";
|
|
||||||
private const string MarketplaceAddressVar = "CODEXCONTRACTS_MARKETPLACEADDRESS";
|
|
||||||
private const string TokenAddressVar = "CODEXCONTRACTS_TOKENADDRESS";
|
|
||||||
private const string AbiVar = "CODEXCONTRACTS_ABI";
|
|
||||||
|
|
||||||
static GethInput()
|
|
||||||
{
|
|
||||||
var error = new List<string>();
|
|
||||||
var gethHost = GetEnvVar(error, GethHostVar);
|
|
||||||
var gethPort = Convert.ToInt32(GetEnvVar(error, GethPortVar));
|
|
||||||
var privateKey = GetEnvVar(error, GethPrivKeyVar);
|
|
||||||
var marketplaceAddress = GetEnvVar(error, MarketplaceAddressVar);
|
|
||||||
var tokenAddress = GetEnvVar(error, TokenAddressVar);
|
|
||||||
var abi = GetEnvVar(error, AbiVar);
|
|
||||||
|
|
||||||
if (error.Any())
|
|
||||||
{
|
|
||||||
LoadError = string.Join(", ", error);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GethHost = gethHost!;
|
|
||||||
GethPort = gethPort;
|
|
||||||
PrivateKey = privateKey!;
|
|
||||||
MarketplaceAddress = marketplaceAddress!;
|
|
||||||
TokenAddress = tokenAddress!;
|
|
||||||
ABI = abi!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GethHost { get; } = string.Empty;
|
|
||||||
public static int GethPort { get; }
|
|
||||||
public static string PrivateKey { get; } = string.Empty;
|
|
||||||
public static string MarketplaceAddress { get; } = string.Empty;
|
|
||||||
public static string TokenAddress { get; } = string.Empty;
|
|
||||||
public static string ABI { get; } = string.Empty;
|
|
||||||
public static string LoadError { get; } = string.Empty;
|
|
||||||
|
|
||||||
private static string? GetEnvVar(List<string> error, string name)
|
|
||||||
{
|
|
||||||
var result = Environment.GetEnvironmentVariable(name);
|
|
||||||
if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set.");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class BaseGethCommand : BaseCommand
|
public abstract class BaseGethCommand : BaseCommand
|
||||||
{
|
{
|
||||||
protected override async Task Invoke(CommandContext context)
|
protected override async Task Invoke(CommandContext context)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(GethInput.LoadError))
|
var gethConnector = GethConnector.GethConnector.Initialize(Program.Log);
|
||||||
{
|
|
||||||
var msg = "Geth input incorrect: " + GethInput.LoadError;
|
|
||||||
Program.Log.Error(msg);
|
|
||||||
if (IsInAdminChannel(context.Command))
|
|
||||||
{
|
|
||||||
await context.Followup(msg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await context.Followup("I'm sorry, there seems to be a configuration error.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contractsDeployment = new CodexContractsDeployment(
|
if (gethConnector == null) return;
|
||||||
marketplaceAddress: GethInput.MarketplaceAddress,
|
var gethNode = gethConnector.GethNode;
|
||||||
abi: GethInput.ABI,
|
var contracts = gethConnector.CodexContracts;
|
||||||
tokenAddress: GethInput.TokenAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
var gethNode = new CustomGethNode(Program.Log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey);
|
|
||||||
var contracts = new CodexContractsAccess(Program.Log, gethNode, contractsDeployment);
|
|
||||||
|
|
||||||
if (!contracts.IsDeployed())
|
if (!contracts.IsDeployed())
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net" Version="3.12.0" />
|
<PackageReference Include="Discord.Net" Version="3.12.0" />
|
||||||
<ProjectReference Include="..\..\Framework\ArgsUniform\ArgsUniform.csproj" />
|
<ProjectReference Include="..\..\Framework\ArgsUniform\ArgsUniform.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Framework\DiscordRewards\DiscordRewards.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Framework\GethConnector\GethConnector.csproj" />
|
||||||
<ProjectReference Include="..\..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
<ProjectReference Include="..\..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using BiblioTech.Rewards;
|
||||||
|
|
||||||
namespace BiblioTech
|
namespace BiblioTech
|
||||||
{
|
{
|
||||||
@ -25,6 +26,9 @@ namespace BiblioTech
|
|||||||
Program.AdminChecker.SetGuild(guild);
|
Program.AdminChecker.SetGuild(guild);
|
||||||
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
|
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
|
||||||
|
|
||||||
|
var roleController = new RoleController(client);
|
||||||
|
var rewardsApi = new RewardsApi(roleController);
|
||||||
|
|
||||||
var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray();
|
var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray();
|
||||||
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
|
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
|
||||||
Program.AdminChecker.SetAdminChannel(adminChannels.First());
|
Program.AdminChecker.SetAdminChannel(adminChannels.First());
|
||||||
@ -58,6 +62,8 @@ namespace BiblioTech
|
|||||||
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
||||||
Program.Log.Error(json);
|
Program.Log.Error(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rewardsApi.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SlashCommandHandler(SocketSlashCommand command)
|
private async Task SlashCommandHandler(SocketSlashCommand command)
|
||||||
|
24
Tools/BiblioTech/Commands/NotifyCommand.cs
Normal file
24
Tools/BiblioTech/Commands/NotifyCommand.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using BiblioTech.Options;
|
||||||
|
|
||||||
|
namespace BiblioTech.Commands
|
||||||
|
{
|
||||||
|
public class NotifyCommand : BaseCommand
|
||||||
|
{
|
||||||
|
private readonly BoolOption boolOption = new BoolOption(name: "enabled", description: "Controls whether the bot will @-mention you.", isRequired: false);
|
||||||
|
|
||||||
|
public override string Name => "notify";
|
||||||
|
public override string StartingMessage => RandomBusyMessage.Get();
|
||||||
|
public override string Description => "Enable or disable notifications from the bot.";
|
||||||
|
public override CommandOption[] Options => new CommandOption[] { boolOption };
|
||||||
|
|
||||||
|
protected override async Task Invoke(CommandContext context)
|
||||||
|
{
|
||||||
|
var user = context.Command.User;
|
||||||
|
var enabled = await boolOption.Parse(context);
|
||||||
|
if (enabled == null) return;
|
||||||
|
|
||||||
|
Program.UserRepo.SetUserNotificationPreference(user, enabled.Value);
|
||||||
|
await context.Followup("Done!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,12 @@ namespace BiblioTech.Commands
|
|||||||
{
|
{
|
||||||
public class UserAssociateCommand : BaseCommand
|
public class UserAssociateCommand : BaseCommand
|
||||||
{
|
{
|
||||||
|
public UserAssociateCommand(NotifyCommand notifyCommand)
|
||||||
|
{
|
||||||
|
this.notifyCommand = notifyCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly NotifyCommand notifyCommand;
|
||||||
private readonly EthAddressOption ethOption = new EthAddressOption(isRequired: false);
|
private readonly EthAddressOption ethOption = new EthAddressOption(isRequired: false);
|
||||||
private readonly UserOption optionalUser = new UserOption(
|
private readonly UserOption optionalUser = new UserOption(
|
||||||
description: "If set, associates Ethereum address for another user. (Optional, admin-only)",
|
description: "If set, associates Ethereum address for another user. (Optional, admin-only)",
|
||||||
@ -30,7 +36,12 @@ namespace BiblioTech.Commands
|
|||||||
var result = Program.UserRepo.AssociateUserWithAddress(user, data);
|
var result = Program.UserRepo.AssociateUserWithAddress(user, data);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
await context.Followup("Done! Thank you for joining the test net!");
|
await context.Followup(new string[]
|
||||||
|
{
|
||||||
|
"Done! Thank you for joining the test net!",
|
||||||
|
"By default, the bot will @-mention you with test-net reward related notifications.",
|
||||||
|
$"You can enable/disable this behavior with the '/{notifyCommand.Name}' command."
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,10 @@ namespace BiblioTech
|
|||||||
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
|
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
|
||||||
public string AdminChannelName { get; set; } = "admin-channel";
|
public string AdminChannelName { get; set; } = "admin-channel";
|
||||||
|
|
||||||
|
[Uniform("rewards-channel-name", "ac", "REWARDSCHANNELNAME", false, "Name of the Discord server channel where participation rewards will be announced.")]
|
||||||
|
public string RewardsChannelName { get; set; } = "";
|
||||||
|
|
||||||
|
|
||||||
public string EndpointsPath
|
public string EndpointsPath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
23
Tools/BiblioTech/Options/BoolOption.cs
Normal file
23
Tools/BiblioTech/Options/BoolOption.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Discord;
|
||||||
|
|
||||||
|
namespace BiblioTech.Options
|
||||||
|
{
|
||||||
|
public class BoolOption : CommandOption
|
||||||
|
{
|
||||||
|
public BoolOption(string name, string description, bool isRequired)
|
||||||
|
: base(name, description, type: ApplicationCommandOptionType.Boolean, isRequired)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool?> Parse(CommandContext context)
|
||||||
|
{
|
||||||
|
var bData = context.Options.SingleOrDefault(o => o.Name == Name);
|
||||||
|
if (bData == null || !(bData.Value is bool))
|
||||||
|
{
|
||||||
|
await context.Followup("Bool option not received.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (bool) bData.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ namespace BiblioTech
|
|||||||
Config = uniformArgs.Parse();
|
Config = uniformArgs.Parse();
|
||||||
|
|
||||||
Log = new LogSplitter(
|
Log = new LogSplitter(
|
||||||
new FileLog(Path.Combine(Config.LogPath, "discordbot.log")),
|
new FileLog(Path.Combine(Config.LogPath, "discordbot")),
|
||||||
new ConsoleLog()
|
new ConsoleLog()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -38,13 +38,15 @@ namespace BiblioTech
|
|||||||
client = new DiscordSocketClient();
|
client = new DiscordSocketClient();
|
||||||
client.Log += ClientLog;
|
client.Log += ClientLog;
|
||||||
|
|
||||||
var associateCommand = new UserAssociateCommand();
|
var notifyCommand = new NotifyCommand();
|
||||||
|
var associateCommand = new UserAssociateCommand(notifyCommand);
|
||||||
var sprCommand = new SprCommand();
|
var sprCommand = new SprCommand();
|
||||||
var handler = new CommandHandler(client,
|
var handler = new CommandHandler(client,
|
||||||
new GetBalanceCommand(associateCommand),
|
new GetBalanceCommand(associateCommand),
|
||||||
new MintCommand(associateCommand),
|
new MintCommand(associateCommand),
|
||||||
sprCommand,
|
sprCommand,
|
||||||
associateCommand,
|
associateCommand,
|
||||||
|
notifyCommand,
|
||||||
new AdminCommand(sprCommand)
|
new AdminCommand(sprCommand)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
92
Tools/BiblioTech/Rewards/RewardsApi.cs
Normal file
92
Tools/BiblioTech/Rewards/RewardsApi.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using DiscordRewards;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Net;
|
||||||
|
using TaskFactory = Utils.TaskFactory;
|
||||||
|
|
||||||
|
namespace BiblioTech.Rewards
|
||||||
|
{
|
||||||
|
public interface IDiscordRoleController
|
||||||
|
{
|
||||||
|
Task GiveRewards(GiveRewardsCommand rewards);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RewardsApi
|
||||||
|
{
|
||||||
|
private readonly HttpListener listener = new HttpListener();
|
||||||
|
private readonly TaskFactory taskFactory = new TaskFactory();
|
||||||
|
private readonly IDiscordRoleController roleController;
|
||||||
|
private CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
public RewardsApi(IDiscordRoleController roleController)
|
||||||
|
{
|
||||||
|
this.roleController = roleController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
cts = new CancellationTokenSource();
|
||||||
|
listener.Prefixes.Add($"http://*:31080/");
|
||||||
|
listener.Start();
|
||||||
|
taskFactory.Run(ConnectionDispatcher, nameof(ConnectionDispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
listener.Stop();
|
||||||
|
cts.Cancel();
|
||||||
|
taskFactory.WaitAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConnectionDispatcher()
|
||||||
|
{
|
||||||
|
while (!cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var wait = listener.GetContextAsync();
|
||||||
|
wait.Wait(cts.Token);
|
||||||
|
if (wait.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
taskFactory.Run(() =>
|
||||||
|
{
|
||||||
|
var context = wait.Result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HandleConnection(context).Wait();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Log.Error("Exception during HTTP handler: " + ex);
|
||||||
|
}
|
||||||
|
// Whatever happens, everything's always OK.
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
context.Response.OutputStream.Close();
|
||||||
|
}, nameof(HandleConnection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleConnection(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(context.Request.InputStream);
|
||||||
|
var content = reader.ReadToEnd();
|
||||||
|
|
||||||
|
if (content == "Ping")
|
||||||
|
{
|
||||||
|
using var writer = new StreamWriter(context.Response.OutputStream);
|
||||||
|
writer.Write("Pong");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content.StartsWith("{")) return;
|
||||||
|
var rewards = JsonConvert.DeserializeObject<GiveRewardsCommand>(content);
|
||||||
|
if (rewards != null)
|
||||||
|
{
|
||||||
|
await ProcessRewards(rewards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessRewards(GiveRewardsCommand rewards)
|
||||||
|
{
|
||||||
|
await roleController.GiveRewards(rewards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
Tools/BiblioTech/Rewards/RoleController.cs
Normal file
218
Tools/BiblioTech/Rewards/RoleController.cs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using DiscordRewards;
|
||||||
|
|
||||||
|
namespace BiblioTech.Rewards
|
||||||
|
{
|
||||||
|
public class RoleController : IDiscordRoleController
|
||||||
|
{
|
||||||
|
private readonly DiscordSocketClient client;
|
||||||
|
private readonly SocketTextChannel? rewardsChannel;
|
||||||
|
private readonly RewardRepo repo = new RewardRepo();
|
||||||
|
|
||||||
|
public RoleController(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Program.Config.RewardsChannelName))
|
||||||
|
{
|
||||||
|
rewardsChannel = GetGuild().TextChannels.SingleOrDefault(c => c.Name == Program.Config.RewardsChannelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GiveRewards(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);
|
||||||
|
|
||||||
|
await context.ProcessGiveRewardsCommand(LookUpUsers(rewards));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<ulong, IGuildUser>> LoadAllUsers(SocketGuild guild)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<ulong, IGuildUser>();
|
||||||
|
var users = guild.GetUsersAsync();
|
||||||
|
await foreach (var ulist in users)
|
||||||
|
{
|
||||||
|
foreach (var u in ulist)
|
||||||
|
{
|
||||||
|
result.Add(u.Id, u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<ulong, RoleReward> LookUpAllRoles(SocketGuild guild, GiveRewardsCommand rewards)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<ulong, RoleReward>();
|
||||||
|
foreach (var r in rewards.Rewards)
|
||||||
|
{
|
||||||
|
if (!result.ContainsKey(r.RewardId))
|
||||||
|
{
|
||||||
|
var rewardConfig = repo.Rewards.SingleOrDefault(rr => rr.RoleId == r.RewardId);
|
||||||
|
if (rewardConfig == null)
|
||||||
|
{
|
||||||
|
Program.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.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Add(r.RewardId, new RoleReward(socketRole, rewardConfig));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserReward[] LookUpUsers(GiveRewardsCommand rewards)
|
||||||
|
{
|
||||||
|
return rewards.Rewards.Select(LookUpUserData).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserReward LookUpUserData(RewardUsersCommand command)
|
||||||
|
{
|
||||||
|
return new UserReward(command,
|
||||||
|
command.UserAddresses
|
||||||
|
.Select(LookUpUserDataForAddress)
|
||||||
|
.Where(d => d != null)
|
||||||
|
.Cast<UserData>()
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserData? LookUpUserDataForAddress(string address)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(address));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Log.Error("Error during UserData lookup: " + ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SocketGuild GetGuild()
|
||||||
|
{
|
||||||
|
return client.Guilds.Single(g => g.Name == Program.Config.ServerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RoleReward
|
||||||
|
{
|
||||||
|
public RoleReward(SocketRole socketRole, RewardConfig reward)
|
||||||
|
{
|
||||||
|
SocketRole = socketRole;
|
||||||
|
Reward = reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketRole SocketRole { get; }
|
||||||
|
public RewardConfig Reward { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserReward
|
||||||
|
{
|
||||||
|
public UserReward(RewardUsersCommand rewardCommand, UserData[] users)
|
||||||
|
{
|
||||||
|
RewardCommand = rewardCommand;
|
||||||
|
Users = users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RewardUsersCommand RewardCommand { get; }
|
||||||
|
public UserData[] Users { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RewardContext
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ulong, IGuildUser> users;
|
||||||
|
private readonly Dictionary<ulong, RoleReward> roles;
|
||||||
|
private readonly SocketTextChannel? rewardsChannel;
|
||||||
|
|
||||||
|
public RewardContext(Dictionary<ulong, IGuildUser> users, Dictionary<ulong, RoleReward> roles, SocketTextChannel? rewardsChannel)
|
||||||
|
{
|
||||||
|
this.users = users;
|
||||||
|
this.roles = roles;
|
||||||
|
this.rewardsChannel = rewardsChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessGiveRewardsCommand(UserReward[] rewards)
|
||||||
|
{
|
||||||
|
foreach (var rewardCommand in rewards)
|
||||||
|
{
|
||||||
|
if (roles.ContainsKey(rewardCommand.RewardCommand.RewardId))
|
||||||
|
{
|
||||||
|
var role = roles[rewardCommand.RewardCommand.RewardId];
|
||||||
|
await ProcessRewardCommand(role, rewardCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessRewardCommand(RoleReward role, UserReward reward)
|
||||||
|
{
|
||||||
|
foreach (var user in reward.Users)
|
||||||
|
{
|
||||||
|
await GiveReward(role, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GiveReward(RoleReward role, UserData user)
|
||||||
|
{
|
||||||
|
if (!users.ContainsKey(user.DiscordId))
|
||||||
|
{
|
||||||
|
Program.Log.Log($"User by id '{user.DiscordId}' not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guildUser = users[user.DiscordId];
|
||||||
|
|
||||||
|
var alreadyHas = guildUser.RoleIds.ToArray();
|
||||||
|
if (alreadyHas.Any(r => r == role.Reward.RoleId)) return;
|
||||||
|
|
||||||
|
await GiveRole(guildUser, role.SocketRole);
|
||||||
|
await SendNotification(role, user, guildUser);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GiveRole(IGuildUser user, SocketRole role)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Program.Log.Log($"Giving role {role.Name}={role.Id} to user {user.DisplayName}");
|
||||||
|
await user.AddRoleAsync(role);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"Failed to give role '{role.Name}' to user '{user.DisplayName}': {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendNotification(RoleReward reward, UserData userData, IGuildUser user)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (userData.NotificationsEnabled && rewardsChannel != null)
|
||||||
|
{
|
||||||
|
var msg = reward.Reward.Message.Replace(RewardConfig.UsernameTag, $"<@{user.Id}>");
|
||||||
|
await rewardsChannel.SendMessageAsync(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"Failed to notify user '{user.DisplayName}' about role '{reward.SocketRole.Name}': {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,14 @@ namespace BiblioTech
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetUserNotificationPreference(IUser user, bool enableNotifications)
|
||||||
|
{
|
||||||
|
lock (repoLock)
|
||||||
|
{
|
||||||
|
SetUserNotification(user, enableNotifications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction<Ether>? eth, Transaction<TestToken>? tokens)
|
public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction<Ether>? eth, Transaction<TestToken>? tokens)
|
||||||
{
|
{
|
||||||
lock (repoLock)
|
lock (repoLock)
|
||||||
@ -96,6 +104,29 @@ namespace BiblioTech
|
|||||||
return userData.CreateOverview();
|
return userData.CreateOverview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserData? GetUserDataForAddress(EthAddress? address)
|
||||||
|
{
|
||||||
|
if (address == null) return null;
|
||||||
|
|
||||||
|
// If this becomes a performance problem, switch to in-memory cached list.
|
||||||
|
var files = Directory.GetFiles(Program.Config.UserDataPath);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
|
||||||
|
if (user.CurrentAddress != null &&
|
||||||
|
user.CurrentAddress.Address == address.Address)
|
||||||
|
{
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private bool SetUserAddress(IUser user, EthAddress? address)
|
private bool SetUserAddress(IUser user, EthAddress? address)
|
||||||
{
|
{
|
||||||
if (GetUserDataForAddress(address) != null)
|
if (GetUserDataForAddress(address) != null)
|
||||||
@ -110,6 +141,14 @@ namespace BiblioTech
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetUserNotification(IUser user, bool notifyEnabled)
|
||||||
|
{
|
||||||
|
var userData = GetUserData(user);
|
||||||
|
if (userData == null) return;
|
||||||
|
userData.NotificationsEnabled = notifyEnabled;
|
||||||
|
SaveUserData(userData);
|
||||||
|
}
|
||||||
|
|
||||||
private UserData? GetUserData(IUser user)
|
private UserData? GetUserData(IUser user)
|
||||||
{
|
{
|
||||||
var filename = GetFilename(user);
|
var filename = GetFilename(user);
|
||||||
@ -132,34 +171,11 @@ namespace BiblioTech
|
|||||||
|
|
||||||
private UserData CreateAndSaveNewUserData(IUser user)
|
private UserData CreateAndSaveNewUserData(IUser user)
|
||||||
{
|
{
|
||||||
var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>());
|
var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>(), true);
|
||||||
SaveUserData(newUser);
|
SaveUserData(newUser);
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserData? GetUserDataForAddress(EthAddress? address)
|
|
||||||
{
|
|
||||||
if (address == null) return null;
|
|
||||||
|
|
||||||
// If this becomes a performance problem, switch to in-memory cached list.
|
|
||||||
var files = Directory.GetFiles(Program.Config.UserDataPath);
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
|
|
||||||
if (user.CurrentAddress != null &&
|
|
||||||
user.CurrentAddress.Address == address.Address)
|
|
||||||
{
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveUserData(UserData userData)
|
private void SaveUserData(UserData userData)
|
||||||
{
|
{
|
||||||
var filename = GetFilename(userData);
|
var filename = GetFilename(userData);
|
||||||
@ -185,7 +201,7 @@ namespace BiblioTech
|
|||||||
|
|
||||||
public class UserData
|
public class UserData
|
||||||
{
|
{
|
||||||
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents)
|
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents, bool notificationsEnabled)
|
||||||
{
|
{
|
||||||
DiscordId = discordId;
|
DiscordId = discordId;
|
||||||
Name = name;
|
Name = name;
|
||||||
@ -193,6 +209,7 @@ namespace BiblioTech
|
|||||||
CurrentAddress = currentAddress;
|
CurrentAddress = currentAddress;
|
||||||
AssociateEvents = associateEvents;
|
AssociateEvents = associateEvents;
|
||||||
MintEvents = mintEvents;
|
MintEvents = mintEvents;
|
||||||
|
NotificationsEnabled = notificationsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DiscordId { get; }
|
public ulong DiscordId { get; }
|
||||||
@ -201,6 +218,7 @@ namespace BiblioTech
|
|||||||
public EthAddress? CurrentAddress { get; set; }
|
public EthAddress? CurrentAddress { get; set; }
|
||||||
public List<UserAssociateAddressEvent> AssociateEvents { get; }
|
public List<UserAssociateAddressEvent> AssociateEvents { get; }
|
||||||
public List<UserMintEvent> MintEvents { get; }
|
public List<UserMintEvent> MintEvents { get; }
|
||||||
|
public bool NotificationsEnabled { get; set; }
|
||||||
|
|
||||||
public string[] CreateOverview()
|
public string[] CreateOverview()
|
||||||
{
|
{
|
||||||
|
49
Tools/TestNetRewarder/BotClient.cs
Normal file
49
Tools/TestNetRewarder/BotClient.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using DiscordRewards;
|
||||||
|
using Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class BotClient
|
||||||
|
{
|
||||||
|
private readonly Configuration configuration;
|
||||||
|
private readonly ILog log;
|
||||||
|
|
||||||
|
public BotClient(Configuration configuration, ILog log)
|
||||||
|
{
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsOnline()
|
||||||
|
{
|
||||||
|
return await HttpPost("Ping") == "Ping";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendRewards(GiveRewardsCommand command)
|
||||||
|
{
|
||||||
|
if (command == null || command.Rewards == null || !command.Rewards.Any()) return;
|
||||||
|
await HttpPost(JsonConvert.SerializeObject(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> HttpPost(string content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = new HttpClient();
|
||||||
|
var response = await client.PostAsync(GetUrl(), new StringContent(content));
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log.Error(ex.ToString());
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUrl()
|
||||||
|
{
|
||||||
|
return $"{configuration.DiscordHost}:{configuration.DiscordPort}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Tools/TestNetRewarder/ChainState.cs
Normal file
35
Tools/TestNetRewarder/ChainState.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using CodexContractsPlugin;
|
||||||
|
using CodexContractsPlugin.Marketplace;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class ChainState
|
||||||
|
{
|
||||||
|
private readonly HistoricState historicState;
|
||||||
|
|
||||||
|
public ChainState(HistoricState historicState, ICodexContracts contracts, TimeRange timeRange)
|
||||||
|
{
|
||||||
|
NewRequests = contracts.GetStorageRequests(timeRange);
|
||||||
|
historicState.ProcessNewRequests(NewRequests);
|
||||||
|
historicState.UpdateStorageRequests(contracts);
|
||||||
|
|
||||||
|
StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray();
|
||||||
|
FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFininshed).ToArray();
|
||||||
|
RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(timeRange);
|
||||||
|
RequestCancelledEvents = contracts.GetRequestCancelledEvents(timeRange);
|
||||||
|
SlotFilledEvents = contracts.GetSlotFilledEvents(timeRange);
|
||||||
|
SlotFreedEvents = contracts.GetSlotFreedEvents(timeRange);
|
||||||
|
this.historicState = historicState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request[] NewRequests { get; }
|
||||||
|
public StorageRequest[] AllRequests => historicState.StorageRequests;
|
||||||
|
public StorageRequest[] StartedRequests { get; private set; }
|
||||||
|
public StorageRequest[] FinishedRequests { get; private set; }
|
||||||
|
public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; }
|
||||||
|
public RequestCancelledEventDTO[] RequestCancelledEvents { get; }
|
||||||
|
public SlotFilledEventDTO[] SlotFilledEvents { get; }
|
||||||
|
public SlotFreedEventDTO[] SlotFreedEvents { get; }
|
||||||
|
}
|
||||||
|
}
|
141
Tools/TestNetRewarder/Checks.cs
Normal file
141
Tools/TestNetRewarder/Checks.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
Tools/TestNetRewarder/Configuration.cs
Normal file
30
Tools/TestNetRewarder/Configuration.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using ArgsUniform;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class Configuration
|
||||||
|
{
|
||||||
|
[Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")]
|
||||||
|
public string DataPath { get; set; } = "datapath";
|
||||||
|
|
||||||
|
[Uniform("discordbot-host", "dh", "DISCORDBOTHOST", true, "http address of the discord bot.")]
|
||||||
|
public string DiscordHost { get; set; } = "host";
|
||||||
|
|
||||||
|
[Uniform("discordbot-port", "dp", "DISCORDBOTPORT", true, "port number of the discord bot reward API. (31080 by default)")]
|
||||||
|
public int DiscordPort { get; set; } = 31080;
|
||||||
|
|
||||||
|
[Uniform("interval-minutes", "im", "INTERVALMINUTES", false, "time in minutes between reward updates. (default 15)")]
|
||||||
|
public int Interval { get; set; } = 15;
|
||||||
|
|
||||||
|
[Uniform("check-history", "ch", "CHECKHISTORY", true, "Unix epoc timestamp of a moment in history on which processing begins. Required for hosting rewards. Should be 'launch of the testnet'.")]
|
||||||
|
public int CheckHistoryTimestamp { get; set; } = 0;
|
||||||
|
|
||||||
|
public string LogPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(DataPath, "logs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Tools/TestNetRewarder/HistoricState.cs
Normal file
68
Tools/TestNetRewarder/HistoricState.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using CodexContractsPlugin;
|
||||||
|
using CodexContractsPlugin.Marketplace;
|
||||||
|
using GethPlugin;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class HistoricState
|
||||||
|
{
|
||||||
|
private readonly List<StorageRequest> storageRequests = new List<StorageRequest>();
|
||||||
|
|
||||||
|
public StorageRequest[] StorageRequests { get { return storageRequests.ToArray(); } }
|
||||||
|
|
||||||
|
public void ProcessNewRequests(Request[] requests)
|
||||||
|
{
|
||||||
|
storageRequests.AddRange(requests.Select(r => new StorageRequest(r)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateStorageRequests(ICodexContracts contracts)
|
||||||
|
{
|
||||||
|
foreach (var r in storageRequests) r.Update(contracts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StorageRequest
|
||||||
|
{
|
||||||
|
public StorageRequest(Request request)
|
||||||
|
{
|
||||||
|
Request = request;
|
||||||
|
Hosts = Array.Empty<EthAddress>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request Request { get; }
|
||||||
|
public EthAddress[] Hosts { get; private set; }
|
||||||
|
public RequestState State { get; private set; }
|
||||||
|
public bool RecentlyStarted { get; private set; }
|
||||||
|
public bool RecentlyFininshed { get; private set; }
|
||||||
|
|
||||||
|
public void Update(ICodexContracts contracts)
|
||||||
|
{
|
||||||
|
Hosts = GetHosts(contracts);
|
||||||
|
|
||||||
|
var newState = contracts.GetRequestState(Request);
|
||||||
|
|
||||||
|
RecentlyStarted =
|
||||||
|
State == RequestState.New &&
|
||||||
|
newState == RequestState.Started;
|
||||||
|
|
||||||
|
RecentlyFininshed =
|
||||||
|
State == RequestState.Started &&
|
||||||
|
newState == RequestState.Finished;
|
||||||
|
|
||||||
|
State = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EthAddress[] GetHosts(ICodexContracts contracts)
|
||||||
|
{
|
||||||
|
var result = new List<EthAddress>();
|
||||||
|
|
||||||
|
for (decimal i = 0; i < Request.Ask.Slots; i++)
|
||||||
|
{
|
||||||
|
var host = contracts.GetSlotHost(Request, i);
|
||||||
|
if (host != null) result.Add(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
Tools/TestNetRewarder/Processor.cs
Normal file
98
Tools/TestNetRewarder/Processor.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using DiscordRewards;
|
||||||
|
using GethPlugin;
|
||||||
|
using Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class Processor
|
||||||
|
{
|
||||||
|
private static readonly HistoricState historicState = new HistoricState();
|
||||||
|
private static readonly RewardRepo rewardRepo = new RewardRepo();
|
||||||
|
private readonly ILog log;
|
||||||
|
|
||||||
|
public Processor(ILog log)
|
||||||
|
{
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessTimeSegment(TimeRange range)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var connector = GethConnector.GethConnector.Initialize(log);
|
||||||
|
if (connector == null) return;
|
||||||
|
|
||||||
|
var chainState = new ChainState(historicState, connector.CodexContracts, range);
|
||||||
|
await ProcessTimeSegment(chainState);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log.Error("Exception processing time segment: " + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessTimeSegment(ChainState chainState)
|
||||||
|
{
|
||||||
|
var outgoingRewards = new List<RewardUsersCommand>();
|
||||||
|
foreach (var reward in rewardRepo.Rewards)
|
||||||
|
{
|
||||||
|
ProcessReward(outgoingRewards, reward, chainState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outgoingRewards.Any())
|
||||||
|
{
|
||||||
|
await SendRewardsCommand(outgoingRewards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendRewardsCommand(List<RewardUsersCommand> outgoingRewards)
|
||||||
|
{
|
||||||
|
var cmd = new GiveRewardsCommand
|
||||||
|
{
|
||||||
|
Rewards = outgoingRewards.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd));
|
||||||
|
await Program.BotClient.SendRewards(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessReward(List<RewardUsersCommand> outgoingRewards, RewardConfig reward, ChainState chainState)
|
||||||
|
{
|
||||||
|
var winningAddresses = PerformCheck(reward, chainState);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
Tools/TestNetRewarder/Program.cs
Normal file
92
Tools/TestNetRewarder/Program.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using ArgsUniform;
|
||||||
|
using GethConnector;
|
||||||
|
using Logging;
|
||||||
|
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!;
|
||||||
|
private static Processor processor = null!;
|
||||||
|
|
||||||
|
public static Task Main(string[] args)
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
CancellationToken = cts.Token;
|
||||||
|
Console.CancelKeyPress += (sender, args) => cts.Cancel();
|
||||||
|
|
||||||
|
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
|
||||||
|
Config = uniformArgs.Parse(true);
|
||||||
|
|
||||||
|
Log = new LogSplitter(
|
||||||
|
new FileLog(Path.Combine(Config.LogPath, "testnetrewarder")),
|
||||||
|
new ConsoleLog()
|
||||||
|
);
|
||||||
|
|
||||||
|
BotClient = new BotClient(Config, Log);
|
||||||
|
processor = new Processor(Log);
|
||||||
|
|
||||||
|
EnsurePath(Config.DataPath);
|
||||||
|
EnsurePath(Config.LogPath);
|
||||||
|
|
||||||
|
return new Program().MainAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
|
EnsureGethOnline();
|
||||||
|
|
||||||
|
Log.Log("Starting TestNet Rewarder...");
|
||||||
|
var segmenter = new TimeSegmenter(Log, Config);
|
||||||
|
|
||||||
|
while (!CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await EnsureBotOnline();
|
||||||
|
await segmenter.WaitForNextSegment(processor.ProcessTimeSegment);
|
||||||
|
await Task.Delay(1000, CancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureGethOnline()
|
||||||
|
{
|
||||||
|
Log.Log("Checking Geth...");
|
||||||
|
var gc = GethConnector.GethConnector.Initialize(Log);
|
||||||
|
if (gc == null) throw new Exception("Geth input incorrect");
|
||||||
|
|
||||||
|
var blockNumber = gc.GethNode.GetSyncedBlockNumber();
|
||||||
|
if (blockNumber == null || blockNumber < 1) throw new Exception("Geth connection failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task EnsureBotOnline()
|
||||||
|
{
|
||||||
|
var start = DateTime.UtcNow;
|
||||||
|
while (! await BotClient.IsOnline() && !CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
var elapsed = DateTime.UtcNow - start;
|
||||||
|
if (elapsed.TotalMinutes > 10)
|
||||||
|
{
|
||||||
|
var msg = "Unable to connect to bot for " + Time.FormatDuration(elapsed);
|
||||||
|
Log.Error(msg);
|
||||||
|
throw new Exception(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintHelp()
|
||||||
|
{
|
||||||
|
Log.Log("TestNet Rewarder");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsurePath(string path)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(path)) return;
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
Tools/TestNetRewarder/TestNetRewarder.csproj
Normal file
17
Tools/TestNetRewarder/TestNetRewarder.csproj
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<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\DiscordRewards\DiscordRewards.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Framework\GethConnector\GethConnector.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
51
Tools/TestNetRewarder/TimeSegmenter.cs
Normal file
51
Tools/TestNetRewarder/TimeSegmenter.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using Logging;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
|
namespace TestNetRewarder
|
||||||
|
{
|
||||||
|
public class TimeSegmenter
|
||||||
|
{
|
||||||
|
private readonly ILog log;
|
||||||
|
private readonly TimeSpan segmentSize;
|
||||||
|
private DateTime start;
|
||||||
|
|
||||||
|
public TimeSegmenter(ILog log, Configuration configuration)
|
||||||
|
{
|
||||||
|
this.log = log;
|
||||||
|
|
||||||
|
if (configuration.Interval < 0) configuration.Interval = 15;
|
||||||
|
if (configuration.CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet.");
|
||||||
|
|
||||||
|
segmentSize = TimeSpan.FromSeconds(configuration.Interval);
|
||||||
|
start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime;
|
||||||
|
|
||||||
|
log.Log("Starting time segments at " + start);
|
||||||
|
log.Log("Segment size: " + Time.FormatDuration(segmentSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WaitForNextSegment(Func<TimeRange, Task> onSegment)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var end = start + segmentSize;
|
||||||
|
var waited = false;
|
||||||
|
if (end > now)
|
||||||
|
{
|
||||||
|
// Wait for the entire time segment to be in the past.
|
||||||
|
var delay = (end - now).Add(TimeSpan.FromSeconds(3));
|
||||||
|
waited = true;
|
||||||
|
await Task.Delay(delay, Program.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Program.CancellationToken.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
var postfix = "(Catching up...)";
|
||||||
|
if (waited) postfix = "(Real-time)";
|
||||||
|
|
||||||
|
log.Log($"Time segment [{start} to {end}] {postfix}");
|
||||||
|
var range = new TimeRange(start, end);
|
||||||
|
start = end;
|
||||||
|
|
||||||
|
await onSegment(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Tools/TestNetRewarder/docker/Dockerfile
Normal file
13
Tools/TestNetRewarder/docker/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Variables
|
||||||
|
ARG IMAGE=mcr.microsoft.com/dotnet/sdk:7.0
|
||||||
|
ARG APP_HOME=/app
|
||||||
|
|
||||||
|
# Create
|
||||||
|
FROM ${IMAGE}
|
||||||
|
ARG APP_HOME
|
||||||
|
|
||||||
|
WORKDIR ${APP_HOME}
|
||||||
|
COPY ./Tools/TestNetRewarder ./Tools/TestNetRewarder
|
||||||
|
COPY ./Framework ./Framework
|
||||||
|
COPY ./ProjectPlugins ./ProjectPlugins
|
||||||
|
CMD ["dotnet", "run", "--project", "Tools/TestNetRewarder"]
|
@ -53,6 +53,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployAndRunPlugin", "Proje
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrameworkTests", "Tests\FrameworkTests\FrameworkTests.csproj", "{25E72004-4D71-4D1E-A193-FC125D12FF96}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrameworkTests", "Tests\FrameworkTests\FrameworkTests.csproj", "{25E72004-4D71-4D1E-A193-FC125D12FF96}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestNetRewarder", "Tools\TestNetRewarder\TestNetRewarder.csproj", "{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GethConnector", "Framework\GethConnector\GethConnector.csproj", "{F730DA73-1C92-4107-BCFB-D33759DAB0C3}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordRewards", "Framework\DiscordRewards\DiscordRewards.csproj", "{B07820C4-309F-4454-BCC1-1D4902C9C67B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -143,6 +149,18 @@ Global
|
|||||||
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Release|Any CPU.Build.0 = Release|Any CPU
|
{25E72004-4D71-4D1E-A193-FC125D12FF96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F730DA73-1C92-4107-BCFB-D33759DAB0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F730DA73-1C92-4107-BCFB-D33759DAB0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F730DA73-1C92-4107-BCFB-D33759DAB0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F730DA73-1C92-4107-BCFB-D33759DAB0C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B07820C4-309F-4454-BCC1-1D4902C9C67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B07820C4-309F-4454-BCC1-1D4902C9C67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B07820C4-309F-4454-BCC1-1D4902C9C67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B07820C4-309F-4454-BCC1-1D4902C9C67B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -169,6 +187,9 @@ Global
|
|||||||
{3E38A906-C2FC-43DC-8CA2-FC07C79CF3CA} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
{3E38A906-C2FC-43DC-8CA2-FC07C79CF3CA} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
||||||
{1CC5AF82-8924-4C7E-BFF1-3125D86E53FB} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
|
{1CC5AF82-8924-4C7E-BFF1-3125D86E53FB} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
|
||||||
{25E72004-4D71-4D1E-A193-FC125D12FF96} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
|
{25E72004-4D71-4D1E-A193-FC125D12FF96} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
|
||||||
|
{570C0DBE-0EF1-47B5-9A3B-E1F7895722A5} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
||||||
|
{F730DA73-1C92-4107-BCFB-D33759DAB0C3} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||||
|
{B07820C4-309F-4454-BCC1-1D4902C9C67B} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}
|
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user