Merge branch 'feature/bot-upgrade'
# Conflicts: # Tests/CodexTests/BasicTests/MarketplaceTests.cs
This commit is contained in:
commit
f5291517c1
|
@ -21,11 +21,11 @@ namespace DiscordRewards
|
|||
}),
|
||||
|
||||
// Finished a sizable slot
|
||||
new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot!", new CheckConfig
|
||||
new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig
|
||||
{
|
||||
Type = CheckType.FinishedSlot,
|
||||
MinSlotSize = 1.GB(),
|
||||
MinDuration = TimeSpan.FromHours(24.0),
|
||||
MinSlotSize = 10.MB(),
|
||||
MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
}),
|
||||
|
||||
// Posted any contract
|
||||
|
@ -41,12 +41,12 @@ namespace DiscordRewards
|
|||
}),
|
||||
|
||||
// Started a sizable contract
|
||||
new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time!", new CheckConfig
|
||||
new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig
|
||||
{
|
||||
Type = CheckType.FinishedSlot,
|
||||
Type = CheckType.StartedContract,
|
||||
MinNumberOfHosts = 4,
|
||||
MinSlotSize = 1.GB(),
|
||||
MinDuration = TimeSpan.FromHours(24.0),
|
||||
MinSlotSize = 10.MB(),
|
||||
MinDuration = TimeSpan.FromMinutes(5.0),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,5 +35,7 @@
|
|||
if (!entries.TryGetValue(number, out BlockTimeEntry? value)) return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
public int Size { get { return entries.Count; } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace NethereumWorkflow.BlockUtils
|
|||
if (moment <= bounds.Genesis.Utc) return null;
|
||||
if (moment >= bounds.Current.Utc) return bounds.Current.BlockNumber;
|
||||
|
||||
return Search(bounds.Genesis, bounds.Current, moment, HighestBeforeSelector);
|
||||
return Log(() => Search(bounds.Genesis, bounds.Current, moment, HighestBeforeSelector));
|
||||
}
|
||||
|
||||
public ulong? GetLowestBlockNumberAfter(DateTime moment)
|
||||
|
@ -33,7 +33,16 @@ namespace NethereumWorkflow.BlockUtils
|
|||
if (moment >= bounds.Current.Utc) return null;
|
||||
if (moment <= bounds.Genesis.Utc) return bounds.Genesis.BlockNumber;
|
||||
|
||||
return Search(bounds.Genesis, bounds.Current, moment, LowestAfterSelector);
|
||||
return Log(()=> Search(bounds.Genesis, bounds.Current, moment, LowestAfterSelector)); ;
|
||||
}
|
||||
|
||||
private ulong Log(Func<ulong> operation)
|
||||
{
|
||||
var sw = Stopwatch.Begin(log, nameof(BlockTimeFinder));
|
||||
var result = operation();
|
||||
sw.End($"(Bounds: [{bounds.Genesis.BlockNumber}-{bounds.Current.BlockNumber}] Cache: {cache.Size})");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ulong Search(BlockTimeEntry lower, BlockTimeEntry upper, DateTime target, Func<DateTime, BlockTimeEntry, bool> isWhatIwant)
|
||||
|
@ -70,7 +79,7 @@ namespace NethereumWorkflow.BlockUtils
|
|||
{
|
||||
var next = GetBlock(entry.BlockNumber + 1);
|
||||
return
|
||||
entry.Utc < target &&
|
||||
entry.Utc <= target &&
|
||||
next.Utc > target;
|
||||
}
|
||||
|
||||
|
@ -78,7 +87,7 @@ namespace NethereumWorkflow.BlockUtils
|
|||
{
|
||||
var previous = GetBlock(entry.BlockNumber - 1);
|
||||
return
|
||||
entry.Utc > target &&
|
||||
entry.Utc >= target &&
|
||||
previous.Utc < target;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,26 +89,9 @@ namespace NethereumWorkflow
|
|||
}
|
||||
}
|
||||
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new()
|
||||
{
|
||||
var wrapper = new Web3Wrapper(web3, log);
|
||||
var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log);
|
||||
|
||||
var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From);
|
||||
var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To);
|
||||
|
||||
if (!fromBlock.HasValue)
|
||||
{
|
||||
log.Error("Failed to find lowest block for time range: " + timeRange);
|
||||
throw new Exception("Failed");
|
||||
}
|
||||
if (!toBlock.HasValue)
|
||||
{
|
||||
log.Error("Failed to find highest block for time range: " + timeRange);
|
||||
throw new Exception("Failed");
|
||||
}
|
||||
|
||||
return GetEvents<TEvent>(address, fromBlock.Value, toBlock.Value);
|
||||
return GetEvents<TEvent>(address, blockRange.From, blockRange.To);
|
||||
}
|
||||
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
|
||||
|
@ -119,5 +102,24 @@ namespace NethereumWorkflow
|
|||
var blockFilter = Time.Wait(eventHandler.CreateFilterBlockRangeAsync(from, to));
|
||||
return Time.Wait(eventHandler.GetAllChangesAsync(blockFilter));
|
||||
}
|
||||
|
||||
public BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange)
|
||||
{
|
||||
var wrapper = new Web3Wrapper(web3, log);
|
||||
var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log);
|
||||
|
||||
var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From);
|
||||
var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To);
|
||||
|
||||
if (fromBlock == null || toBlock == null)
|
||||
{
|
||||
throw new Exception("Failed to convert time range to block range.");
|
||||
}
|
||||
|
||||
return new BlockInterval(
|
||||
from: fromBlock.Value,
|
||||
to: toBlock.Value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
namespace Utils
|
||||
{
|
||||
public class BlockInterval
|
||||
{
|
||||
public BlockInterval(ulong from, ulong to)
|
||||
{
|
||||
if (from < to)
|
||||
{
|
||||
From = from;
|
||||
To = to;
|
||||
}
|
||||
else
|
||||
{
|
||||
From = to;
|
||||
To = from;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong From { get; }
|
||||
public ulong To { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{From} - {To}]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,13 +19,13 @@ namespace CodexContractsPlugin
|
|||
TestToken GetTestTokenBalance(IHasEthAddress owner);
|
||||
TestToken GetTestTokenBalance(EthAddress ethAddress);
|
||||
|
||||
Request[] GetStorageRequests(TimeRange timeRange);
|
||||
Request[] GetStorageRequests(BlockInterval blockRange);
|
||||
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);
|
||||
RequestFulfilledEventDTO[] GetRequestFulfilledEvents(BlockInterval blockRange);
|
||||
RequestCancelledEventDTO[] GetRequestCancelledEvents(BlockInterval blockRange);
|
||||
SlotFilledEventDTO[] GetSlotFilledEvents(BlockInterval blockRange);
|
||||
SlotFreedEventDTO[] GetSlotFreedEvents(BlockInterval blockRange);
|
||||
}
|
||||
|
||||
public enum RequestState
|
||||
|
@ -77,9 +77,9 @@ namespace CodexContractsPlugin
|
|||
return balance.TestTokens();
|
||||
}
|
||||
|
||||
public Request[] GetStorageRequests(TimeRange timeRange)
|
||||
public Request[] GetStorageRequests(BlockInterval blockRange)
|
||||
{
|
||||
var events = gethNode.GetEvents<StorageRequestedEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||
var events = gethNode.GetEvents<StorageRequestedEventDTO>(Deployment.MarketplaceAddress, blockRange);
|
||||
var i = StartInteraction();
|
||||
return events
|
||||
.Select(e =>
|
||||
|
@ -93,9 +93,9 @@ namespace CodexContractsPlugin
|
|||
.ToArray();
|
||||
}
|
||||
|
||||
public RequestFulfilledEventDTO[] GetRequestFulfilledEvents(TimeRange timeRange)
|
||||
public RequestFulfilledEventDTO[] GetRequestFulfilledEvents(BlockInterval blockRange)
|
||||
{
|
||||
var events = gethNode.GetEvents<RequestFulfilledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||
var events = gethNode.GetEvents<RequestFulfilledEventDTO>(Deployment.MarketplaceAddress, blockRange);
|
||||
return events.Select(e =>
|
||||
{
|
||||
var result = e.Event;
|
||||
|
@ -104,9 +104,9 @@ namespace CodexContractsPlugin
|
|||
}).ToArray();
|
||||
}
|
||||
|
||||
public RequestCancelledEventDTO[] GetRequestCancelledEvents(TimeRange timeRange)
|
||||
public RequestCancelledEventDTO[] GetRequestCancelledEvents(BlockInterval blockRange)
|
||||
{
|
||||
var events = gethNode.GetEvents<RequestCancelledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||
var events = gethNode.GetEvents<RequestCancelledEventDTO>(Deployment.MarketplaceAddress, blockRange);
|
||||
return events.Select(e =>
|
||||
{
|
||||
var result = e.Event;
|
||||
|
@ -115,9 +115,9 @@ namespace CodexContractsPlugin
|
|||
}).ToArray();
|
||||
}
|
||||
|
||||
public SlotFilledEventDTO[] GetSlotFilledEvents(TimeRange timeRange)
|
||||
public SlotFilledEventDTO[] GetSlotFilledEvents(BlockInterval blockRange)
|
||||
{
|
||||
var events = gethNode.GetEvents<SlotFilledEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||
var events = gethNode.GetEvents<SlotFilledEventDTO>(Deployment.MarketplaceAddress, blockRange);
|
||||
return events.Select(e =>
|
||||
{
|
||||
var result = e.Event;
|
||||
|
@ -127,9 +127,9 @@ namespace CodexContractsPlugin
|
|||
}).ToArray();
|
||||
}
|
||||
|
||||
public SlotFreedEventDTO[] GetSlotFreedEvents(TimeRange timeRange)
|
||||
public SlotFreedEventDTO[] GetSlotFreedEvents(BlockInterval blockRange)
|
||||
{
|
||||
var events = gethNode.GetEvents<SlotFreedEventDTO>(Deployment.MarketplaceAddress, timeRange);
|
||||
var events = gethNode.GetEvents<SlotFreedEventDTO>(Deployment.MarketplaceAddress, blockRange);
|
||||
return events.Select(e =>
|
||||
{
|
||||
var result = e.Event;
|
||||
|
|
|
@ -35,6 +35,12 @@ namespace CodexDiscordBotPlugin
|
|||
return StartContainer(workflow, config);
|
||||
}
|
||||
|
||||
public RunningContainers DeployRewarder(RewarderBotStartupConfig config)
|
||||
{
|
||||
var workflow = tools.CreateWorkflow();
|
||||
return StartRewarderContainer(workflow, config);
|
||||
}
|
||||
|
||||
private RunningContainers StartContainer(IStartupWorkflow workflow, DiscordBotStartupConfig config)
|
||||
{
|
||||
var startupConfig = new StartupConfig();
|
||||
|
@ -42,5 +48,12 @@ namespace CodexDiscordBotPlugin
|
|||
startupConfig.Add(config);
|
||||
return workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig);
|
||||
}
|
||||
|
||||
private RunningContainers StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config)
|
||||
{
|
||||
var startupConfig = new StartupConfig();
|
||||
startupConfig.Add(config);
|
||||
return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ namespace CodexDiscordBotPlugin
|
|||
return Plugin(ci).Deploy(config);
|
||||
}
|
||||
|
||||
public static RunningContainers DeployRewarderBot(this CoreInterface ci, RewarderBotStartupConfig config)
|
||||
{
|
||||
return Plugin(ci).DeployRewarder(config);
|
||||
}
|
||||
|
||||
private static CodexDiscordBotPlugin Plugin(CoreInterface ci)
|
||||
{
|
||||
return ci.GetPlugin<CodexDiscordBotPlugin>();
|
||||
|
|
|
@ -7,7 +7,9 @@ namespace CodexDiscordBotPlugin
|
|||
public class DiscordBotContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
public override string AppName => "discordbot-bibliotech";
|
||||
public override string Image => "thatbenbierens/codex-discordbot:initial";
|
||||
public override string Image => "codexstorage/codex-discordbot:sha-8c64352";
|
||||
|
||||
public static string RewardsPort = "bot_rewards_port";
|
||||
|
||||
protected override void Initialize(StartupConfig startupConfig)
|
||||
{
|
||||
|
@ -19,6 +21,7 @@ namespace CodexDiscordBotPlugin
|
|||
AddEnvVar("SERVERNAME", config.ServerName);
|
||||
AddEnvVar("ADMINROLE", config.AdminRoleName);
|
||||
AddEnvVar("ADMINCHANNELNAME", config.AdminChannelName);
|
||||
AddEnvVar("REWARDSCHANNELNAME", config.RewardChannelName);
|
||||
AddEnvVar("KUBECONFIG", "/opt/kubeconfig.yaml");
|
||||
AddEnvVar("KUBENAMESPACE", config.KubeNamespace);
|
||||
|
||||
|
@ -30,13 +33,13 @@ namespace CodexDiscordBotPlugin
|
|||
AddEnvVar("CODEXCONTRACTS_TOKENADDRESS", gethInfo.TokenAddress);
|
||||
AddEnvVar("CODEXCONTRACTS_ABI", gethInfo.Abi);
|
||||
|
||||
AddInternalPortAndVar("REWARDAPIPORT", RewardsPort);
|
||||
|
||||
if (!string.IsNullOrEmpty(config.DataPath))
|
||||
{
|
||||
AddEnvVar("DATAPATH", config.DataPath);
|
||||
AddVolume(config.DataPath, 1.GB());
|
||||
}
|
||||
|
||||
AddVolume(name: "kubeconfig", mountPath: "/opt/kubeconfig.yaml", subPath: "kubeconfig.yaml", secret: "discordbot-sa-kubeconfig");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public class DiscordBotStartupConfig
|
||||
{
|
||||
public DiscordBotStartupConfig(string name, string token, string serverName, string adminRoleName, string adminChannelName, string kubeNamespace, DiscordBotGethInfo gethInfo)
|
||||
public DiscordBotStartupConfig(string name, string token, string serverName, string adminRoleName, string adminChannelName, string kubeNamespace, DiscordBotGethInfo gethInfo, string rewardChannelName)
|
||||
{
|
||||
Name = name;
|
||||
Token = token;
|
||||
|
@ -11,6 +11,7 @@
|
|||
AdminChannelName = adminChannelName;
|
||||
KubeNamespace = kubeNamespace;
|
||||
GethInfo = gethInfo;
|
||||
RewardChannelName = rewardChannelName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
@ -18,11 +19,32 @@
|
|||
public string ServerName { get; }
|
||||
public string AdminRoleName { get; }
|
||||
public string AdminChannelName { get; }
|
||||
public string RewardChannelName { get; }
|
||||
public string KubeNamespace { get; }
|
||||
public DiscordBotGethInfo GethInfo { get; }
|
||||
public string? DataPath { get; set; }
|
||||
}
|
||||
|
||||
public class RewarderBotStartupConfig
|
||||
{
|
||||
public RewarderBotStartupConfig(string discordBotHost, int discordBotPort, string interval, DateTime historyStartUtc, DiscordBotGethInfo gethInfo, string? dataPath)
|
||||
{
|
||||
DiscordBotHost = discordBotHost;
|
||||
DiscordBotPort = discordBotPort;
|
||||
Interval = interval;
|
||||
HistoryStartUtc = historyStartUtc;
|
||||
GethInfo = gethInfo;
|
||||
DataPath = dataPath;
|
||||
}
|
||||
|
||||
public string DiscordBotHost { get; }
|
||||
public int DiscordBotPort { get; }
|
||||
public string Interval { get; }
|
||||
public DateTime HistoryStartUtc { get; }
|
||||
public DiscordBotGethInfo GethInfo { get; }
|
||||
public string? DataPath { get; set; }
|
||||
}
|
||||
|
||||
public class DiscordBotGethInfo
|
||||
{
|
||||
public DiscordBotGethInfo(string host, int port, string privKey, string marketplaceAddress, string tokenAddress, string abi)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
using KubernetesWorkflow.Recipe;
|
||||
using KubernetesWorkflow;
|
||||
using Utils;
|
||||
|
||||
namespace CodexDiscordBotPlugin
|
||||
{
|
||||
public class RewarderBotContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
public override string AppName => "discordbot-rewarder";
|
||||
public override string Image => "codexstorage/codex-rewarderbot:sha-2ab84e2";
|
||||
|
||||
protected override void Initialize(StartupConfig startupConfig)
|
||||
{
|
||||
var config = startupConfig.Get<RewarderBotStartupConfig>();
|
||||
|
||||
SetSchedulingAffinity(notIn: "false");
|
||||
|
||||
AddEnvVar("DISCORDBOTHOST", config.DiscordBotHost);
|
||||
AddEnvVar("DISCORDBOTPORT", config.DiscordBotPort.ToString());
|
||||
AddEnvVar("INTERVALMINUTES", config.Interval);
|
||||
var offset = new DateTimeOffset(config.HistoryStartUtc);
|
||||
AddEnvVar("CHECKHISTORY", offset.ToUnixTimeSeconds().ToString());
|
||||
|
||||
var gethInfo = config.GethInfo;
|
||||
AddEnvVar("GETH_HOST", gethInfo.Host);
|
||||
AddEnvVar("GETH_HTTP_PORT", gethInfo.Port.ToString());
|
||||
AddEnvVar("GETH_PRIVATE_KEY", gethInfo.PrivKey);
|
||||
AddEnvVar("CODEXCONTRACTS_MARKETPLACEADDRESS", gethInfo.MarketplaceAddress);
|
||||
AddEnvVar("CODEXCONTRACTS_TOKENADDRESS", gethInfo.TokenAddress);
|
||||
AddEnvVar("CODEXCONTRACTS_ABI", gethInfo.Abi);
|
||||
|
||||
if (!string.IsNullOrEmpty(config.DataPath))
|
||||
{
|
||||
AddEnvVar("DATAPATH", config.DataPath);
|
||||
AddVolume(config.DataPath, 1.GB());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,8 +24,9 @@ namespace GethPlugin
|
|||
decimal? GetSyncedBlockNumber();
|
||||
bool IsContractAvailable(string abi, string contractAddress);
|
||||
GethBootstrapNode GetBootstrapRecord();
|
||||
List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new();
|
||||
List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new();
|
||||
List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new();
|
||||
BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange);
|
||||
}
|
||||
|
||||
public class DeploymentGethNode : BaseGethNode, IGethNode
|
||||
|
@ -144,14 +145,19 @@ namespace GethPlugin
|
|||
return StartInteraction().IsContractAvailable(abi, contractAddress);
|
||||
}
|
||||
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new()
|
||||
{
|
||||
return StartInteraction().GetEvents<TEvent>(address, fromBlockNumber, toBlockNumber);
|
||||
return StartInteraction().GetEvents<TEvent>(address, blockRange);
|
||||
}
|
||||
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
|
||||
{
|
||||
return StartInteraction().GetEvents<TEvent>(address, timeRange);
|
||||
return StartInteraction().GetEvents<TEvent>(address, ConvertTimeRangeToBlockRange(timeRange));
|
||||
}
|
||||
|
||||
public BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange)
|
||||
{
|
||||
return StartInteraction().ConvertTimeRangeToBlockRange(timeRange);
|
||||
}
|
||||
|
||||
protected abstract NethereumInteraction StartInteraction();
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
using CodexContractsPlugin;
|
||||
using CodexDiscordBotPlugin;
|
||||
using CodexPlugin;
|
||||
using GethPlugin;
|
||||
using NUnit.Framework;
|
||||
using Utils;
|
||||
|
||||
namespace CodexTests.BasicTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DiscordBotTests : AutoBootstrapDistTest
|
||||
{
|
||||
[Test]
|
||||
public void BotRewardTest()
|
||||
{
|
||||
var myAccount = EthAccount.GenerateNew();
|
||||
|
||||
var sellerInitialBalance = 234.TestTokens();
|
||||
var buyerInitialBalance = 100000.TestTokens();
|
||||
var fileSize = 11.MB();
|
||||
|
||||
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
|
||||
var contracts = Ci.StartCodexContracts(geth);
|
||||
|
||||
// start bot and rewarder
|
||||
var gethInfo = new DiscordBotGethInfo(
|
||||
host: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Host,
|
||||
port: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Port,
|
||||
privKey: geth.StartResult.Account.PrivateKey,
|
||||
marketplaceAddress: contracts.Deployment.MarketplaceAddress,
|
||||
tokenAddress: contracts.Deployment.TokenAddress,
|
||||
abi: contracts.Deployment.Abi
|
||||
);
|
||||
var bot = Ci.DeployCodexDiscordBot(new DiscordBotStartupConfig(
|
||||
name: "bot",
|
||||
token: "aaa",
|
||||
serverName: "ThatBen's server",
|
||||
adminRoleName: "bottest-admins",
|
||||
adminChannelName: "admin-channel",
|
||||
rewardChannelName: "rewards-channel",
|
||||
kubeNamespace: "notneeded",
|
||||
gethInfo: gethInfo
|
||||
));
|
||||
var botContainer = bot.Containers.Single();
|
||||
Ci.DeployRewarderBot(new RewarderBotStartupConfig(
|
||||
//discordBotHost: "http://" + botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Host,
|
||||
//discordBotPort: botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Port,
|
||||
discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host,
|
||||
discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port,
|
||||
interval: "60",
|
||||
historyStartUtc: GetTestRunTimeRange().From - TimeSpan.FromMinutes(3),
|
||||
gethInfo: gethInfo,
|
||||
dataPath: null
|
||||
));
|
||||
|
||||
var numberOfHosts = 3;
|
||||
|
||||
for (var i = 0; i < numberOfHosts; i++)
|
||||
{
|
||||
var seller = AddCodex(s => s
|
||||
.WithName("Seller")
|
||||
.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn)
|
||||
{
|
||||
ContractClock = CodexLogLevel.Trace,
|
||||
})
|
||||
.WithStorageQuota(11.GB())
|
||||
.EnableMarketplace(geth, contracts, m => m
|
||||
.WithAccount(myAccount)
|
||||
.WithInitial(10.Eth(), sellerInitialBalance)
|
||||
.AsStorageNode()
|
||||
.AsValidator()));
|
||||
|
||||
var availability = new StorageAvailability(
|
||||
totalSpace: 10.GB(),
|
||||
maxDuration: TimeSpan.FromMinutes(30),
|
||||
minPriceForTotalSpace: 1.TestTokens(),
|
||||
maxCollateral: 20.TestTokens()
|
||||
);
|
||||
seller.Marketplace.MakeStorageAvailable(availability);
|
||||
}
|
||||
|
||||
var testFile = GenerateTestFile(fileSize);
|
||||
|
||||
var buyer = AddCodex(s => s
|
||||
.WithName("Buyer")
|
||||
.EnableMarketplace(geth, contracts, m => m
|
||||
.WithAccount(myAccount)
|
||||
.WithInitial(10.Eth(), buyerInitialBalance)));
|
||||
|
||||
var contentId = buyer.UploadFile(testFile);
|
||||
|
||||
var purchase = new StoragePurchaseRequest(contentId)
|
||||
{
|
||||
PricePerSlotPerSecond = 2.TestTokens(),
|
||||
RequiredCollateral = 10.TestTokens(),
|
||||
MinRequiredNumberOfNodes = 5,
|
||||
NodeFailureTolerance = 2,
|
||||
ProofProbability = 5,
|
||||
Duration = TimeSpan.FromMinutes(6),
|
||||
Expiry = TimeSpan.FromMinutes(5)
|
||||
};
|
||||
|
||||
var purchaseContract = buyer.Marketplace.RequestStorage(purchase);
|
||||
|
||||
purchaseContract.WaitForStorageContractStarted();
|
||||
|
||||
purchaseContract.WaitForStorageContractFinished();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
using CodexContractsPlugin.Marketplace;
|
||||
using CodexPlugin;
|
||||
using GethPlugin;
|
||||
using Nethereum.Hex.HexConvertors.Extensions;
|
||||
using NUnit.Framework;
|
||||
using Utils;
|
||||
|
||||
|
@ -71,29 +70,26 @@ namespace CodexTests.BasicTests
|
|||
|
||||
var purchaseContract = client.Marketplace.RequestStorage(purchase);
|
||||
|
||||
WaitForAllSlotFilledEvents(contracts, purchase);
|
||||
WaitForAllSlotFilledEvents(contracts, purchase, geth);
|
||||
|
||||
purchaseContract.WaitForStorageContractStarted();
|
||||
|
||||
//AssertBalance(contracts, host, Is.LessThan(hostInitialBalance), "Collateral was not placed.");
|
||||
|
||||
var request = GetOnChainStorageRequest(contracts);
|
||||
var request = GetOnChainStorageRequest(contracts, geth);
|
||||
AssertStorageRequest(request, purchase, contracts, client);
|
||||
//AssertSlotFilledEvents(contracts, purchase, request, host);
|
||||
//AssertContractSlot(contracts, request, 0, host);
|
||||
AssertContractSlot(contracts, request, 0);
|
||||
|
||||
purchaseContract.WaitForStorageContractFinished();
|
||||
|
||||
//AssertBalance(contracts, host, Is.GreaterThan(hostInitialBalance), "Seller was not paid for storage.");
|
||||
AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage.");
|
||||
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
|
||||
}
|
||||
|
||||
private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase)
|
||||
private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth)
|
||||
{
|
||||
Time.Retry(() =>
|
||||
{
|
||||
var slotFilledEvents = contracts.GetSlotFilledEvents(GetTestRunTimeRange());
|
||||
var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange());
|
||||
var slotFilledEvents = contracts.GetSlotFilledEvents(blockRange);
|
||||
|
||||
Log($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}");
|
||||
|
||||
|
@ -101,24 +97,6 @@ namespace CodexTests.BasicTests
|
|||
}, Convert.ToInt32(purchase.Duration.TotalSeconds / 5) + 10, TimeSpan.FromSeconds(5), "Checking SlotFilled events");
|
||||
}
|
||||
|
||||
private void AssertSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, Request request, ICodexNode seller)
|
||||
{
|
||||
// Expect 1 fulfilled event for the purchase.
|
||||
var requestFulfilledEvents = contracts.GetRequestFulfilledEvents(GetTestRunTimeRange());
|
||||
Assert.That(requestFulfilledEvents.Length, Is.EqualTo(1));
|
||||
CollectionAssert.AreEqual(request.RequestId, requestFulfilledEvents[0].RequestId);
|
||||
|
||||
// Expect 1 filled-slot event for each slot in the purchase.
|
||||
var filledSlotEvents = contracts.GetSlotFilledEvents(GetTestRunTimeRange());
|
||||
Assert.That(filledSlotEvents.Length, Is.EqualTo(purchase.MinRequiredNumberOfNodes));
|
||||
for (var i = 0; i < purchase.MinRequiredNumberOfNodes; i++)
|
||||
{
|
||||
var filledSlotEvent = filledSlotEvents.Single(e => e.SlotIndex == i);
|
||||
Assert.That(filledSlotEvent.RequestId.ToHex(), Is.EqualTo(request.RequestId.ToHex()));
|
||||
Assert.That(filledSlotEvent.Host, Is.EqualTo(seller.EthAddress));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertStorageRequest(Request request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer)
|
||||
{
|
||||
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Started));
|
||||
|
@ -126,17 +104,17 @@ namespace CodexTests.BasicTests
|
|||
Assert.That(request.Ask.Slots, Is.EqualTo(purchase.MinRequiredNumberOfNodes));
|
||||
}
|
||||
|
||||
private Request GetOnChainStorageRequest(ICodexContracts contracts)
|
||||
private Request GetOnChainStorageRequest(ICodexContracts contracts, IGethNode geth)
|
||||
{
|
||||
var requests = contracts.GetStorageRequests(GetTestRunTimeRange());
|
||||
var requests = contracts.GetStorageRequests(geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()));
|
||||
Assert.That(requests.Length, Is.EqualTo(1));
|
||||
return requests.Single();
|
||||
}
|
||||
|
||||
private void AssertContractSlot(ICodexContracts contracts, Request request, int contractSlotIndex, ICodexNode expectedSeller)
|
||||
private void AssertContractSlot(ICodexContracts contracts, Request request, int contractSlotIndex)
|
||||
{
|
||||
var slotHost = contracts.GetSlotHost(request, contractSlotIndex);
|
||||
Assert.That(slotHost, Is.EqualTo(expectedSeller.EthAddress));
|
||||
Assert.That(slotHost?.Address, Is.Not.Null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\CodexDiscordBotPlugin\CodexDiscordBotPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\GethPlugin\GethPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\MetricsPlugin\MetricsPlugin.csproj" />
|
||||
|
|
|
@ -117,6 +117,41 @@ namespace FrameworkTests.NethereumWorkflow
|
|||
|
||||
Assert.That(notFound, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FailsToFindBlockBeforeFrontOfChain_history()
|
||||
{
|
||||
var first = blocks.First().Value;
|
||||
|
||||
var notFound = finder.GetHighestBlockNumberBefore(first.JustBefore);
|
||||
|
||||
Assert.That(notFound, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FailsToFindBlockAfterTailOfChain_future()
|
||||
{
|
||||
var last = blocks.Last().Value;
|
||||
|
||||
var notFound = finder.GetLowestBlockNumberAfter(last.JustAfter);
|
||||
|
||||
Assert.That(notFound, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunThrough()
|
||||
{
|
||||
foreach (var pair in blocks)
|
||||
{
|
||||
finder.GetHighestBlockNumberBefore(pair.Value.JustBefore);
|
||||
finder.GetHighestBlockNumberBefore(pair.Value.Time);
|
||||
finder.GetHighestBlockNumberBefore(pair.Value.JustAfter);
|
||||
|
||||
finder.GetLowestBlockNumberAfter(pair.Value.JustBefore);
|
||||
finder.GetLowestBlockNumberAfter(pair.Value.Time);
|
||||
finder.GetLowestBlockNumberAfter(pair.Value.JustAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Block
|
||||
|
@ -131,5 +166,10 @@ namespace FrameworkTests.NethereumWorkflow
|
|||
public DateTime Time { get; }
|
||||
public DateTime JustBefore { get { return Time.AddSeconds(-1); } }
|
||||
public DateTime JustAfter { get { return Time.AddSeconds(1); } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Number}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
|
|
@ -26,12 +26,10 @@ namespace BiblioTech
|
|||
Program.AdminChecker.SetGuild(guild);
|
||||
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();
|
||||
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
|
||||
Program.AdminChecker.SetAdminChannel(adminChannels.First());
|
||||
Program.RoleDriver = new RoleDriver(client);
|
||||
|
||||
var builders = commands.Select(c =>
|
||||
{
|
||||
|
@ -62,8 +60,6 @@ namespace BiblioTech
|
|||
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
||||
Program.Log.Error(json);
|
||||
}
|
||||
|
||||
rewardsApi.Start();
|
||||
}
|
||||
|
||||
private async Task SlashCommandHandler(SocketSlashCommand command)
|
||||
|
|
|
@ -6,8 +6,6 @@ namespace BiblioTech.Commands
|
|||
{
|
||||
public class MintCommand : BaseGethCommand
|
||||
{
|
||||
private readonly Ether defaultEthToSend = 10.Eth();
|
||||
private readonly TestToken defaultTestTokensToMint = 1024.TestTokens();
|
||||
private readonly UserOption optionalUser = new UserOption(
|
||||
description: "If set, mint tokens for this user. (Optional, admin-only)",
|
||||
isRequired: false);
|
||||
|
@ -47,9 +45,10 @@ namespace BiblioTech.Commands
|
|||
{
|
||||
if (ShouldMintTestTokens(contracts, addr))
|
||||
{
|
||||
var transaction = contracts.MintTestTokens(addr, defaultTestTokensToMint);
|
||||
report.Add($"Minted {defaultTestTokensToMint} {FormatTransactionLink(transaction)}");
|
||||
return new Transaction<TestToken>(defaultTestTokensToMint, transaction);
|
||||
var tokens = Program.Config.MintTT.TestTokens();
|
||||
var transaction = contracts.MintTestTokens(addr, tokens);
|
||||
report.Add($"Minted {tokens} {FormatTransactionLink(transaction)}");
|
||||
return new Transaction<TestToken>(tokens, transaction);
|
||||
}
|
||||
|
||||
report.Add("TestToken balance over threshold. (No TestTokens minted.)");
|
||||
|
@ -60,9 +59,10 @@ namespace BiblioTech.Commands
|
|||
{
|
||||
if (ShouldSendEth(gethNode, addr))
|
||||
{
|
||||
var transaction = gethNode.SendEth(addr, defaultEthToSend);
|
||||
report.Add($"Sent {defaultEthToSend} {FormatTransactionLink(transaction)}");
|
||||
return new Transaction<Ether>(defaultEthToSend, transaction);
|
||||
var eth = Program.Config.SendEth.Eth();
|
||||
var transaction = gethNode.SendEth(addr, eth);
|
||||
report.Add($"Sent {eth} {FormatTransactionLink(transaction)}");
|
||||
return new Transaction<Ether>(eth, transaction);
|
||||
}
|
||||
report.Add("Eth balance is over threshold. (No Eth sent.)");
|
||||
return null;
|
||||
|
@ -71,13 +71,13 @@ namespace BiblioTech.Commands
|
|||
private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr)
|
||||
{
|
||||
var testTokens = contracts.GetTestTokenBalance(addr);
|
||||
return testTokens.Amount < 64m;
|
||||
return testTokens.Amount < Program.Config.MintTT;
|
||||
}
|
||||
|
||||
private bool ShouldSendEth(IGethNode gethNode, EthAddress addr)
|
||||
{
|
||||
var eth = gethNode.GetEthBalance(addr);
|
||||
return eth.Eth < 1.0m;
|
||||
return eth.Eth < Program.Config.SendEth;
|
||||
}
|
||||
|
||||
private string FormatTransactionLink(string transaction)
|
||||
|
|
|
@ -19,9 +19,17 @@ namespace BiblioTech
|
|||
[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";
|
||||
|
||||
[Uniform("rewards-channel-name", "ac", "REWARDSCHANNELNAME", false, "Name of the Discord server channel where participation rewards will be announced.")]
|
||||
[Uniform("rewards-channel-name", "rc", "REWARDSCHANNELNAME", false, "Name of the Discord server channel where participation rewards will be announced.")]
|
||||
public string RewardsChannelName { get; set; } = "";
|
||||
|
||||
[Uniform("reward-api-port", "rp", "REWARDAPIPORT", false, "TCP listen port for the reward API.")]
|
||||
public int RewardApiPort { get; set; } = 31080;
|
||||
|
||||
[Uniform("send-eth", "se", "SENDETH", false, "Amount of Eth send by the mint command. Default: 10.")]
|
||||
public int SendEth { get; set; } = 10;
|
||||
|
||||
[Uniform("mint-tt", "mt", "MINTTT", false, "Amount of TestTokens minted by the mint command. Default: 1073741824")]
|
||||
public int MintTT { get; set; } = 1073741824;
|
||||
|
||||
public string EndpointsPath
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using ArgsUniform;
|
||||
using BiblioTech.Commands;
|
||||
using BiblioTech.Rewards;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Logging;
|
||||
|
@ -13,6 +14,7 @@ namespace BiblioTech
|
|||
public static Configuration Config { get; private set; } = null!;
|
||||
public static UserRepo UserRepo { get; } = new UserRepo();
|
||||
public static AdminChecker AdminChecker { get; private set; } = null!;
|
||||
public static IDiscordRoleDriver RoleDriver { get; set; } = null!;
|
||||
public static ILog Log { get; private set; } = null!;
|
||||
|
||||
public static Task Main(string[] args)
|
||||
|
@ -29,10 +31,10 @@ namespace BiblioTech
|
|||
EnsurePath(Config.UserDataPath);
|
||||
EnsurePath(Config.EndpointsPath);
|
||||
|
||||
return new Program().MainAsync();
|
||||
return new Program().MainAsync(args);
|
||||
}
|
||||
|
||||
public async Task MainAsync()
|
||||
public async Task MainAsync(string[] args)
|
||||
{
|
||||
Log.Log("Starting Codex Discord Bot...");
|
||||
client = new DiscordSocketClient();
|
||||
|
@ -52,10 +54,19 @@ namespace BiblioTech
|
|||
|
||||
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);
|
||||
await client.StartAsync();
|
||||
|
||||
AdminChecker = new AdminChecker();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.WebHost.ConfigureKestrel((context, options) =>
|
||||
{
|
||||
options.ListenAnyIP(Config.RewardApiPort);
|
||||
});
|
||||
builder.Services.AddControllers();
|
||||
var app = builder.Build();
|
||||
app.MapControllers();
|
||||
|
||||
Log.Log("Running...");
|
||||
await app.RunAsync();
|
||||
await Task.Delay(-1);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"profiles": {
|
||||
"BiblioTech": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:52960;http://localhost:52961"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using DiscordRewards;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public interface IDiscordRoleDriver
|
||||
{
|
||||
Task GiveRewards(GiveRewardsCommand rewards);
|
||||
}
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class RewardController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public string Ping()
|
||||
{
|
||||
return "Pong";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<string> Give(GiveRewardsCommand cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Program.RoleDriver.GiveRewards(cmd);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error("Exception: " + ex);
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,13 +4,13 @@ using DiscordRewards;
|
|||
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public class RoleController : IDiscordRoleController
|
||||
public class RoleDriver : IDiscordRoleDriver
|
||||
{
|
||||
private readonly DiscordSocketClient client;
|
||||
private readonly SocketTextChannel? rewardsChannel;
|
||||
private readonly RewardRepo repo = new RewardRepo();
|
||||
|
||||
public RoleController(DiscordSocketClient client)
|
||||
public RoleDriver(DiscordSocketClient client)
|
||||
{
|
||||
this.client = client;
|
||||
|
||||
|
@ -107,7 +107,13 @@ namespace BiblioTech.Rewards
|
|||
|
||||
private SocketGuild GetGuild()
|
||||
{
|
||||
return client.Guilds.Single(g => g.Name == Program.Config.ServerName);
|
||||
var guild = client.Guilds.SingleOrDefault(g => g.Name == Program.Config.ServerName);
|
||||
if (guild == null)
|
||||
{
|
||||
throw new Exception($"Unable to find guild by name: '{Program.Config.ServerName}'. " +
|
||||
$"Known guilds: [{string.Join(",", client.Guilds.Select(g => g.Name))}]");
|
||||
}
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +116,9 @@ namespace CodexNetDeployer
|
|||
|
||||
[Uniform("dbot-adminchannelname", "dbotacn", "DBOTADMINCHANNELNAME", false, "Required if discord-bot is true. Name of the Discord channel in which admin commands are allowed.")]
|
||||
public string DiscordBotAdminChannelName { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("dbot-rewardchannelname", "dbotrcn", "DBOTREWARDCHANNELNAME", false, "Required if discord-bot is true. Name of the Discord channel in which reward updates are posted.")]
|
||||
public string DiscordBotRewardChannelName { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("dbot-datapath", "dbotdp", "DBOTDATAPATH", false, "Optional. Path in container where bot will save all data.")]
|
||||
public string DiscordBotDataPath { get; set; } = string.Empty;
|
||||
|
@ -163,6 +166,7 @@ namespace CodexNetDeployer
|
|||
StringIsSet(nameof(DiscordBotServerName), DiscordBotServerName, errors);
|
||||
StringIsSet(nameof(DiscordBotAdminRoleName), DiscordBotAdminRoleName, errors);
|
||||
StringIsSet(nameof(DiscordBotAdminChannelName), DiscordBotAdminChannelName, errors);
|
||||
StringIsSet(nameof(DiscordBotRewardChannelName), DiscordBotRewardChannelName, errors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
|
|
@ -145,7 +145,8 @@ namespace CodexNetDeployer
|
|||
adminRoleName: config.DiscordBotAdminRoleName,
|
||||
adminChannelName: config.DiscordBotAdminChannelName,
|
||||
kubeNamespace: config.KubeNamespace,
|
||||
gethInfo: info)
|
||||
gethInfo: info,
|
||||
rewardChannelName: config.DiscordBotRewardChannelName)
|
||||
{
|
||||
DataPath = config.DiscordBotDataPath
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using DiscordRewards;
|
||||
using CodexContractsPlugin.Marketplace;
|
||||
using DiscordRewards;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace TestNetRewarder
|
||||
{
|
||||
|
@ -17,21 +19,41 @@ namespace TestNetRewarder
|
|||
|
||||
public async Task<bool> IsOnline()
|
||||
{
|
||||
return await HttpPost("Ping") == "Ping";
|
||||
var result = await HttpGet();
|
||||
log.Log("Is DiscordBot online: " + result);
|
||||
return result == "Pong";
|
||||
}
|
||||
|
||||
public async Task SendRewards(GiveRewardsCommand command)
|
||||
public async Task<bool> SendRewards(GiveRewardsCommand command)
|
||||
{
|
||||
if (command == null || command.Rewards == null || !command.Rewards.Any()) return;
|
||||
await HttpPost(JsonConvert.SerializeObject(command));
|
||||
if (command == null || command.Rewards == null || !command.Rewards.Any()) return false;
|
||||
var result = await HttpPostJson(command);
|
||||
log.Log("Reward response: " + result);
|
||||
return result == "OK";
|
||||
}
|
||||
|
||||
private async Task<string> HttpPost(string content)
|
||||
private async Task<string> HttpGet()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new HttpClient();
|
||||
var response = await client.PostAsync(GetUrl(), new StringContent(content));
|
||||
var response = await client.GetAsync(GetUrl());
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex.ToString());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> HttpPostJson<T>(T body)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
using var content = JsonContent.Create(body);
|
||||
using var response = await client.PostAsync(GetUrl(), content);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -43,7 +65,7 @@ namespace TestNetRewarder
|
|||
|
||||
private string GetUrl()
|
||||
{
|
||||
return $"{configuration.DiscordHost}:{configuration.DiscordPort}";
|
||||
return $"{configuration.DiscordHost}:{configuration.DiscordPort}/api/reward";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,18 @@ namespace TestNetRewarder
|
|||
{
|
||||
private readonly HistoricState historicState;
|
||||
|
||||
public ChainState(HistoricState historicState, ICodexContracts contracts, TimeRange timeRange)
|
||||
public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange)
|
||||
{
|
||||
NewRequests = contracts.GetStorageRequests(timeRange);
|
||||
NewRequests = contracts.GetStorageRequests(blockRange);
|
||||
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);
|
||||
RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange);
|
||||
RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange);
|
||||
SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange);
|
||||
SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange);
|
||||
this.historicState = historicState;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,30 +11,51 @@ namespace TestNetRewarder
|
|||
private static readonly HistoricState historicState = new HistoricState();
|
||||
private static readonly RewardRepo rewardRepo = new RewardRepo();
|
||||
private readonly ILog log;
|
||||
private BlockInterval? lastBlockRange;
|
||||
|
||||
public Processor(ILog log)
|
||||
{
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public async Task ProcessTimeSegment(TimeRange range)
|
||||
public async Task ProcessTimeSegment(TimeRange timeRange)
|
||||
{
|
||||
var connector = GethConnector.GethConnector.Initialize(log);
|
||||
if (connector == null) throw new Exception("Invalid Geth information");
|
||||
|
||||
try
|
||||
{
|
||||
var connector = GethConnector.GethConnector.Initialize(log);
|
||||
if (connector == null) return;
|
||||
|
||||
var chainState = new ChainState(historicState, connector.CodexContracts, range);
|
||||
await ProcessTimeSegment(chainState);
|
||||
var blockRange = connector.GethNode.ConvertTimeRangeToBlockRange(timeRange);
|
||||
if (!IsNewBlockRange(blockRange))
|
||||
{
|
||||
log.Log($"Block range {blockRange} was previously processed. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
var chainState = new ChainState(historicState, connector.CodexContracts, blockRange);
|
||||
await ProcessChainState(chainState);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Exception processing time segment: " + ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessTimeSegment(ChainState chainState)
|
||||
private bool IsNewBlockRange(BlockInterval blockRange)
|
||||
{
|
||||
if (lastBlockRange == null ||
|
||||
lastBlockRange.From != blockRange.From ||
|
||||
lastBlockRange.To != blockRange.To)
|
||||
{
|
||||
lastBlockRange = blockRange;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task ProcessChainState(ChainState chainState)
|
||||
{
|
||||
var outgoingRewards = new List<RewardUsersCommand>();
|
||||
foreach (var reward in rewardRepo.Rewards)
|
||||
|
@ -42,13 +63,17 @@ namespace TestNetRewarder
|
|||
ProcessReward(outgoingRewards, reward, chainState);
|
||||
}
|
||||
|
||||
log.Log($"Found {outgoingRewards.Count} rewards to send.");
|
||||
if (outgoingRewards.Any())
|
||||
{
|
||||
await SendRewardsCommand(outgoingRewards);
|
||||
if (!await SendRewardsCommand(outgoingRewards))
|
||||
{
|
||||
log.Error("Failed to send reward command.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendRewardsCommand(List<RewardUsersCommand> outgoingRewards)
|
||||
private async Task<bool> SendRewardsCommand(List<RewardUsersCommand> outgoingRewards)
|
||||
{
|
||||
var cmd = new GiveRewardsCommand
|
||||
{
|
||||
|
@ -56,12 +81,16 @@ namespace TestNetRewarder
|
|||
};
|
||||
|
||||
log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd));
|
||||
await Program.BotClient.SendRewards(cmd);
|
||||
return await Program.BotClient.SendRewards(cmd);
|
||||
}
|
||||
|
||||
private void ProcessReward(List<RewardUsersCommand> outgoingRewards, RewardConfig reward, ChainState chainState)
|
||||
{
|
||||
var winningAddresses = PerformCheck(reward, chainState);
|
||||
foreach (var win in winningAddresses)
|
||||
{
|
||||
log.Log($"Address '{win.Address}' wins '{reward.Message}'");
|
||||
}
|
||||
if (winningAddresses.Any())
|
||||
{
|
||||
outgoingRewards.Add(new RewardUsersCommand
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using ArgsUniform;
|
||||
using GethConnector;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
|
@ -12,6 +11,7 @@ namespace TestNetRewarder
|
|||
public static CancellationToken CancellationToken { get; private set; }
|
||||
public static BotClient BotClient { get; private set; } = null!;
|
||||
private static Processor processor = null!;
|
||||
private static DateTime lastCheck = DateTime.MinValue;
|
||||
|
||||
public static Task Main(string[] args)
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ namespace TestNetRewarder
|
|||
{
|
||||
await EnsureBotOnline();
|
||||
await segmenter.WaitForNextSegment(processor.ProcessTimeSegment);
|
||||
await Task.Delay(1000, CancellationToken);
|
||||
await Task.Delay(100, CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,11 +59,15 @@ namespace TestNetRewarder
|
|||
|
||||
var blockNumber = gc.GethNode.GetSyncedBlockNumber();
|
||||
if (blockNumber == null || blockNumber < 1) throw new Exception("Geth connection failed.");
|
||||
Log.Log("Geth OK. Block number: " + blockNumber);
|
||||
}
|
||||
|
||||
private static async Task EnsureBotOnline()
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
var timeSince = start - lastCheck;
|
||||
if (timeSince.TotalSeconds < 30.0) return;
|
||||
|
||||
while (! await BotClient.IsOnline() && !CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
|
@ -76,6 +80,8 @@ namespace TestNetRewarder
|
|||
throw new Exception(msg);
|
||||
}
|
||||
}
|
||||
|
||||
lastCheck = start;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
|
|
|
@ -31,10 +31,12 @@ namespace TestNetRewarder
|
|||
if (end > now)
|
||||
{
|
||||
// Wait for the entire time segment to be in the past.
|
||||
var delay = (end - now).Add(TimeSpan.FromSeconds(3));
|
||||
var delay = end - now;
|
||||
waited = true;
|
||||
log.Log($"Waiting till time segment is in the past... {Time.FormatDuration(delay)}");
|
||||
await Task.Delay(delay, Program.CancellationToken);
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(3), Program.CancellationToken);
|
||||
|
||||
if (Program.CancellationToken.IsCancellationRequested) return;
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
docker build -f docker/Dockerfile -t thatbenbierens/codex-rewardbot:initial ../..
|
||||
docker push thatbenbierens/codex-rewardbot:initial
|
Loading…
Reference in New Issue