From 5511f8ed328cbf93c1a7e340287f9c3c8e428aad Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 2 Apr 2024 13:32:06 +0200 Subject: [PATCH 001/142] Extra logging for discordbot reward API --- Tools/BiblioTech/Rewards/RewardContext.cs | 102 ++++++++++++++++++++++ Tools/BiblioTech/Rewards/RoleDriver.cs | 92 +++---------------- 2 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/RewardContext.cs diff --git a/Tools/BiblioTech/Rewards/RewardContext.cs b/Tools/BiblioTech/Rewards/RewardContext.cs new file mode 100644 index 00000000..d05d5f6d --- /dev/null +++ b/Tools/BiblioTech/Rewards/RewardContext.cs @@ -0,0 +1,102 @@ +using Discord.WebSocket; +using Discord; +using DiscordRewards; + +namespace BiblioTech.Rewards +{ + public class RewardContext + { + private readonly Dictionary users; + private readonly Dictionary roles; + private readonly SocketTextChannel? rewardsChannel; + + public RewardContext(Dictionary users, Dictionary 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); + } + else + { + Program.Log.Error($"RoleID not found on guild: {rewardCommand.RewardCommand.RewardId}"); + } + } + } + + 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(); + var logMessage = $"Giving reward '{role.SocketRole.Id}' to user '{user.DiscordId}'({user.Name})[" + + $"alreadyHas:{string.Join(",", alreadyHas.Select(a => a.ToString()))}]: "; + + + if (alreadyHas.Any(r => r == role.Reward.RoleId)) + { + logMessage += "Already has role"; + Program.Log.Log(logMessage); + return; + } + + await GiveRole(guildUser, role.SocketRole); + await SendNotification(role, user, guildUser); + await Task.Delay(1000); + logMessage += "Role given. Notification sent."; + Program.Log.Log(logMessage); + } + + 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}"); + } + } + } +} diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 2478f520..182dbf80 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using DiscordRewards; +using Newtonsoft.Json; namespace BiblioTech.Rewards { @@ -22,6 +23,8 @@ namespace BiblioTech.Rewards public async Task GiveRewards(GiveRewardsCommand rewards) { + Program.Log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(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. @@ -35,6 +38,7 @@ namespace BiblioTech.Rewards private async Task> LoadAllUsers(SocketGuild guild) { + Program.Log.Log("Loading all users:"); var result = new Dictionary(); var users = guild.GetUsersAsync(); await foreach (var ulist in users) @@ -42,6 +46,8 @@ namespace BiblioTech.Rewards foreach (var u in ulist) { result.Add(u.Id, u); + var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray()); + Program.Log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]"); } } return result; @@ -96,7 +102,10 @@ namespace BiblioTech.Rewards { try { - return Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(address)); + var userData = Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(address)); + if (userData != null) Program.Log.Log($"User '{userData.Name}' was looked up."); + else Program.Log.Log($"Lookup for user was unsuccessful. EthAddress: '{address}'"); + return userData; } catch (Exception ex) { @@ -140,85 +149,4 @@ namespace BiblioTech.Rewards public RewardUsersCommand RewardCommand { get; } public UserData[] Users { get; } } - - public class RewardContext - { - private readonly Dictionary users; - private readonly Dictionary roles; - private readonly SocketTextChannel? rewardsChannel; - - public RewardContext(Dictionary users, Dictionary 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}"); - } - } - } } From 29344451d6a7e662ae98fc384ef60359c2c737ff Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 2 Apr 2024 14:53:45 +0200 Subject: [PATCH 002/142] workaround for getpurchases api call --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index bd2582ef..697ff2cd 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -2,6 +2,7 @@ using Core; using KubernetesWorkflow; using KubernetesWorkflow.Types; +using Newtonsoft.Json; using Utils; namespace CodexPlugin @@ -90,7 +91,16 @@ namespace CodexPlugin public StoragePurchase GetPurchaseStatus(string purchaseId) { - return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId))); + var endpoint = GetEndpoint(); + return Time.Retry(() => + { + var str = endpoint.HttpGetString($"storage/purchases/{purchaseId}"); + if (string.IsNullOrEmpty(str)) throw new Exception("Empty response."); + return JsonConvert.DeserializeObject(str)!; + }, nameof(GetPurchaseStatus)); + + // TODO: current getpurchase api does not line up with its openapi spec. + // return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId))); } public string GetName() From b805c5f004e6d49086f653d1dde7f3c35da3841a Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 3 Apr 2024 09:21:10 +0200 Subject: [PATCH 003/142] Prevents duplicate addresses to be sent in the same reward request --- Tools/TestNetRewarder/Processor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 032b0ef7..d710a90d 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -104,7 +104,7 @@ namespace TestNetRewarder private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) { var check = GetCheck(reward.CheckConfig); - return check.Check(chainState); + return check.Check(chainState).Distinct().ToArray(); } private ICheck GetCheck(CheckConfig config) From 43aaed225d67f003db64cc70f185fd3681dcb88c Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 3 Apr 2024 20:40:13 +0200 Subject: [PATCH 004/142] Updates codex image --- ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- .../CodexPlugin/CodexContainerRecipe.cs | 2 +- ProjectPlugins/CodexPlugin/openapi.yaml | 53 ++++++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 498aeb1d..212fa193 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -9,7 +9,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "63-7F-46-5E-2C-60-7A-BD-0C-EC-32-87-61-1B-79-FA-C2-EF-73-81-BA-FA-28-77-33-02-81-30-80-5D-00-97"; + private const string OpenApiYamlHash = "5A-B0-2A-AC-42-B1-A2-49-6F-9D-4E-D8-56-40-10-A6-67-F4-0D-2A-9F-E0-84-5C-EB-B8-2D-4F-D8-56-79-6C"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 4e2cc8f9..7203aa3d 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-f2f1dd5-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1524803-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; diff --git a/ProjectPlugins/CodexPlugin/openapi.yaml b/ProjectPlugins/CodexPlugin/openapi.yaml index 6099ed55..cf9b6b6c 100644 --- a/ProjectPlugins/CodexPlugin/openapi.yaml +++ b/ProjectPlugins/CodexPlugin/openapi.yaml @@ -65,6 +65,22 @@ components: description: A timestamp as seconds since unix epoch at which this request expires if the Request does not find requested amount of nodes to host the data. default: 10 minutes + SPR: + type: string + description: Signed Peer Record (libp2p) + + SPRRead: + type: object + properties: + spr: + $ref: "#/components/schemas/SPR" + + PeerIdRead: + type: object + properties: + id: + $ref: "#/components/schemas/PeerId" + ErasureParameters: type: object properties: @@ -106,8 +122,7 @@ components: type: string description: Path of the data repository where all nodes data are stored spr: - type: string - description: Signed Peer Record to advertise DHT connection information + $ref: "#/components/schemas/SPR" SalesAvailability: type: object @@ -685,6 +700,40 @@ paths: "503": description: Purchasing is unavailable + "/node/spr": + get: + summary: "Get Node's SPR" + operationId: getSPR + tags: [ Node ] + responses: + "200": + description: Node's SPR + content: + plain/text: + schema: + $ref: "#/components/schemas/SPR" + application/json: + schema: + $ref: "#/components/schemas/SPRRead" + "503": + description: Node SPR not ready, try again later + + "/node/peerid": + get: + summary: "Get Node's PeerID" + operationId: getPeerId + tags: [ Node ] + responses: + "200": + description: Node's Peer ID + content: + plain/text: + schema: + $ref: "#/components/schemas/PeerId" + application/json: + schema: + $ref: "#/components/schemas/PeerIdRead" + "/debug/chronicles/loglevel": post: summary: "Set log level at run time" From 9c8151bdb7956e3a54f6196c2add9112dffd04d3 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 7 Apr 2024 14:04:31 +0200 Subject: [PATCH 005/142] Adds Market command --- .../DiscordRewards/GiveRewardsCommand.cs | 12 ++ Tools/BiblioTech/Commands/MarketCommand.cs | 57 ++++++++ Tools/BiblioTech/Program.cs | 5 +- Tools/BiblioTech/Rewards/RewardController.cs | 1 + Tools/TestNetRewarder/ChainState.cs | 2 +- Tools/TestNetRewarder/Configuration.cs | 11 ++ Tools/TestNetRewarder/HistoricState.cs | 4 +- Tools/TestNetRewarder/MarketTracker.cs | 122 ++++++++++++++++++ Tools/TestNetRewarder/Processor.cs | 18 ++- Tools/TestNetRewarder/TimeSegmenter.cs | 4 +- 10 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 Tools/BiblioTech/Commands/MarketCommand.cs create mode 100644 Tools/TestNetRewarder/MarketTracker.cs diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/GiveRewardsCommand.cs index 03bc835c..0fd7a0fd 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/GiveRewardsCommand.cs @@ -3,6 +3,7 @@ public class GiveRewardsCommand { public RewardUsersCommand[] Rewards { get; set; } = Array.Empty(); + public MarketAverage[] Averages { get; set; } = Array.Empty(); } public class RewardUsersCommand @@ -10,4 +11,15 @@ public ulong RewardId { get; set; } public string[] UserAddresses { get; set; } = Array.Empty(); } + + public class MarketAverage + { + public int NumberOfFinished { get; set; } + public TimeSpan TimeRange { get; set; } + public float Price { get; set; } + public float Size { get; set; } + public float Duration { get; set; } + public float Collateral { get; set; } + public float ProofProbability { get; set; } + } } diff --git a/Tools/BiblioTech/Commands/MarketCommand.cs b/Tools/BiblioTech/Commands/MarketCommand.cs new file mode 100644 index 00000000..92891811 --- /dev/null +++ b/Tools/BiblioTech/Commands/MarketCommand.cs @@ -0,0 +1,57 @@ +using BiblioTech.Options; +using DiscordRewards; +using System.Globalization; +using Utils; + +namespace BiblioTech.Commands +{ + public class MarketCommand : BaseCommand + { + public override string Name => "market"; + public override string StartingMessage => RandomBusyMessage.Get(); + public override string Description => "Fetch some insights about current market conditions."; + + protected override async Task Invoke(CommandContext context) + { + await context.Followup(GetInsights()); + } + + private string[] GetInsights() + { + var result = Program.Averages.SelectMany(GetInsight).ToArray(); + if (result.Length > 0) + { + result = new[] + { + "No market insights available." + }; + } + return result; + } + + private string[] GetInsight(MarketAverage avg) + { + var headerLine = $"[Last {Time.FormatDuration(avg.TimeRange)}] ({avg.NumberOfFinished} Contracts finished)"; + + if (avg.NumberOfFinished == 0) + { + return new[] { headerLine }; + } + + return new[] + { + headerLine, + $"Price: {Format(avg.Price)}", + $"Size: {Format(avg.Size)}", + $"Duration: {Format(avg.Duration)}", + $"Collateral: {Format(avg.Collateral)}", + $"ProofProbability: {Format(avg.ProofProbability)}" + }; + } + + private string Format(float f) + { + return f.ToString("F3", CultureInfo.InvariantCulture); + } + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 8f7c98ce..a604812a 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -3,6 +3,7 @@ using BiblioTech.Commands; using BiblioTech.Rewards; using Discord; using Discord.WebSocket; +using DiscordRewards; using Logging; namespace BiblioTech @@ -16,6 +17,7 @@ namespace BiblioTech 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 MarketAverage[] Averages { get; set; } = Array.Empty(); public static Task Main(string[] args) { @@ -49,7 +51,8 @@ namespace BiblioTech sprCommand, associateCommand, notifyCommand, - new AdminCommand(sprCommand) + new AdminCommand(sprCommand), + new MarketCommand() ); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index c9a19dea..6bf7b9f3 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -23,6 +23,7 @@ namespace BiblioTech.Rewards { try { + Program.Averages = cmd.Averages; await Program.RoleDriver.GiveRewards(cmd); } catch (Exception ex) diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index 40e4bf68..b57c39b5 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -15,7 +15,7 @@ namespace TestNetRewarder historicState.UpdateStorageRequests(contracts); StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); - FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFininshed).ToArray(); + FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index d19edb30..ed885d13 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -19,6 +19,9 @@ namespace TestNetRewarder [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; + [Uniform("market-insights", "mi", "MARKETINSIGHTS", false, "Semi-colon separated integers. Each represents a multiple of intervals, for which a market insights average will be generated.")] + public string MarketInsights { get; set; } = "1;96"; + public string LogPath { get @@ -26,5 +29,13 @@ namespace TestNetRewarder return Path.Combine(DataPath, "logs"); } } + + public TimeSpan Interval + { + get + { + return TimeSpan.FromMinutes(IntervalMinutes); + } + } } } diff --git a/Tools/TestNetRewarder/HistoricState.cs b/Tools/TestNetRewarder/HistoricState.cs index b1ac6996..53fd1bae 100644 --- a/Tools/TestNetRewarder/HistoricState.cs +++ b/Tools/TestNetRewarder/HistoricState.cs @@ -33,7 +33,7 @@ namespace TestNetRewarder 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 bool RecentlyFinished { get; private set; } public void Update(ICodexContracts contracts) { @@ -45,7 +45,7 @@ namespace TestNetRewarder State == RequestState.New && newState == RequestState.Started; - RecentlyFininshed = + RecentlyFinished = State == RequestState.Started && newState == RequestState.Finished; diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs new file mode 100644 index 00000000..47957c8e --- /dev/null +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -0,0 +1,122 @@ +using CodexContractsPlugin.Marketplace; +using DiscordRewards; +using System.Numerics; + +namespace TestNetRewarder +{ + public class MarketTracker + { + private readonly List buffer = new List(); + + public MarketAverage[] ProcessChainState(ChainState chainState) + { + var intervalCounts = GetInsightCounts(); + if (!intervalCounts.Any()) return Array.Empty(); + + UpdateBuffer(chainState, intervalCounts.Max()); + var result = intervalCounts + .Select(GenerateMarketAverage) + .Where(a => a != null) + .Cast() + .ToArray(); + + if (!result.Any()) result = Array.Empty(); + return result; + } + + private void UpdateBuffer(ChainState chainState, int maxNumberOfIntervals) + { + buffer.Add(chainState); + while (buffer.Count > maxNumberOfIntervals) + { + buffer.RemoveAt(0); + } + } + + private MarketAverage? GenerateMarketAverage(int numberOfIntervals) + { + var states = SelectStates(numberOfIntervals); + return CreateAverage(states); + } + + private ChainState[] SelectStates(int numberOfIntervals) + { + if (numberOfIntervals < 1) return Array.Empty(); + return buffer.TakeLast(numberOfIntervals).ToArray(); + } + + private MarketAverage? CreateAverage(ChainState[] states) + { + try + { + return new MarketAverage + { + NumberOfFinished = CountNumberOfFinishedRequests(states), + TimeRange = GetTotalTimeRange(states), + Price = Average(states, s => s.Request.Ask.Reward), + Duration = Average(states, s => s.Request.Ask.Duration), + Size = Average(states, s => GetTotalSize(s.Request.Ask)), + Collateral = Average(states, s => s.Request.Ask.Collateral), + ProofProbability = Average(states, s => s.Request.Ask.ProofProbability) + }; + } + catch (Exception ex) + { + Program.Log.Error($"Exception in CreateAverage: {ex}"); + return null; + } + } + + private int GetTotalSize(Ask ask) + { + var nSlots = Convert.ToInt32(ask.Slots); + var slotSize = Convert.ToInt32(ask.SlotSize); + return nSlots * slotSize; + } + + private float Average(ChainState[] states, Func getValue) + { + return Average(states, s => Convert.ToInt32(getValue(s))); + } + + private float Average(ChainState[] states, Func getValue) + { + var sum = 0.0f; + var count = 0.0f; + foreach (var state in states) + { + foreach (var finishedRequest in state.FinishedRequests) + { + sum += getValue(finishedRequest); + count++; + } + } + + return sum / count; + } + + private TimeSpan GetTotalTimeRange(ChainState[] states) + { + return Program.Config.Interval * states.Length; + } + + private int CountNumberOfFinishedRequests(ChainState[] states) + { + return states.Sum(s => s.FinishedRequests.Length); + } + + private int[] GetInsightCounts() + { + try + { + var tokens = Program.Config.MarketInsights.Split(';').ToArray(); + return tokens.Select(t => Convert.ToInt32(t)).ToArray(); + } + catch (Exception ex) + { + Program.Log.Error($"Exception when parsing MarketInsights config parameters: {ex}"); + } + return Array.Empty(); + } + } +} diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index d710a90d..85303302 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -10,6 +10,7 @@ namespace TestNetRewarder { private static readonly HistoricState historicState = new HistoricState(); private static readonly RewardRepo rewardRepo = new RewardRepo(); + private static readonly MarketTracker marketTracker = new MarketTracker(); private readonly ILog log; private BlockInterval? lastBlockRange; @@ -63,21 +64,30 @@ namespace TestNetRewarder ProcessReward(outgoingRewards, reward, chainState); } - log.Log($"Found {outgoingRewards.Count} rewards to send."); + var marketAverages = GetMarketAverages(chainState); + + log.Log($"Found {outgoingRewards.Count} rewards to send. Found {marketAverages.Length} market averages."); + if (outgoingRewards.Any()) { - if (!await SendRewardsCommand(outgoingRewards)) + if (!await SendRewardsCommand(outgoingRewards, marketAverages)) { log.Error("Failed to send reward command."); } } } - private async Task SendRewardsCommand(List outgoingRewards) + private MarketAverage[] GetMarketAverages(ChainState chainState) + { + return marketTracker.ProcessChainState(chainState); + } + + private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages) { var cmd = new GiveRewardsCommand { - Rewards = outgoingRewards.ToArray() + Rewards = outgoingRewards.ToArray(), + Averages = marketAverages.ToArray() }; log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); diff --git a/Tools/TestNetRewarder/TimeSegmenter.cs b/Tools/TestNetRewarder/TimeSegmenter.cs index d14bcc73..44062f8f 100644 --- a/Tools/TestNetRewarder/TimeSegmenter.cs +++ b/Tools/TestNetRewarder/TimeSegmenter.cs @@ -13,10 +13,10 @@ namespace TestNetRewarder { this.log = log; - if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 15; + if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 1; if (configuration.CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet."); - segmentSize = TimeSpan.FromMinutes(configuration.IntervalMinutes); + segmentSize = configuration.Interval; start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime; log.Log("Starting time segments at " + start); From 9f7e95c5159bf603fb49863cba87e2b804717299 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 8 Apr 2024 13:36:55 +0200 Subject: [PATCH 006/142] Applies async/await to getbalance and mint commands --- Tools/BiblioTech/Commands/GetBalanceCommand.cs | 10 ++++++++-- Tools/BiblioTech/Commands/MintCommand.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Tools/BiblioTech/Commands/GetBalanceCommand.cs b/Tools/BiblioTech/Commands/GetBalanceCommand.cs index 964d7c27..3c983359 100644 --- a/Tools/BiblioTech/Commands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/Commands/GetBalanceCommand.cs @@ -31,8 +31,14 @@ namespace BiblioTech.Commands return; } - var eth = gethNode.GetEthBalance(addr); - var testTokens = contracts.GetTestTokenBalance(addr); + var eth = 0.Eth(); + var testTokens = 0.TestTokens(); + + await Task.Run(() => + { + eth = gethNode.GetEthBalance(addr); + testTokens = contracts.GetTestTokenBalance(addr); + }); await context.Followup($"{context.Command.User.Username} has {eth} and {testTokens}."); } diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index faf62c8b..a017ee2f 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -33,8 +33,14 @@ namespace BiblioTech.Commands var report = new List(); - var sentEth = ProcessEth(gethNode, addr, report); - var mintedTokens = ProcessTokens(contracts, addr, report); + Transaction? sentEth = null; + Transaction? mintedTokens = null; + + await Task.Run(() => + { + sentEth = ProcessEth(gethNode, addr, report); + mintedTokens = ProcessTokens(contracts, addr, report); + }); Program.UserRepo.AddMintEventForUser(userId, addr, sentEth, mintedTokens); From 0b2dcef57e05331600ec5d42121950aa0d2d6980 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 8 Apr 2024 13:55:39 +0200 Subject: [PATCH 007/142] Sets up showing of chain events in discord bot --- .../DiscordRewards/GiveRewardsCommand.cs | 1 + Tools/BiblioTech/Configuration.cs | 3 +++ Tools/BiblioTech/Rewards/RoleDriver.cs | 23 +++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/GiveRewardsCommand.cs index 0fd7a0fd..96176334 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/GiveRewardsCommand.cs @@ -4,6 +4,7 @@ { public RewardUsersCommand[] Rewards { get; set; } = Array.Empty(); public MarketAverage[] Averages { get; set; } = Array.Empty(); + public string[] EventsOverview { get; set; } = Array.Empty(); } public class RewardUsersCommand diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 2183a540..37e1cc33 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -22,6 +22,9 @@ namespace BiblioTech [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("chain-events-channel-name", "cc", "CHAINEVENTSCHANNELNAME", false, "Name of the Discord server channel where chain events will be posted.")] + public string ChainEventsChannelName { get; set; } = ""; + [Uniform("reward-api-port", "rp", "REWARDAPIPORT", false, "TCP listen port for the reward API.")] public int RewardApiPort { get; set; } = 31080; diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 182dbf80..2afff6ec 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -9,16 +9,15 @@ namespace BiblioTech.Rewards { private readonly DiscordSocketClient client; private readonly SocketTextChannel? rewardsChannel; + private readonly SocketTextChannel? eventsChannel; private readonly RewardRepo repo = new RewardRepo(); public RoleDriver(DiscordSocketClient client) { this.client = client; - if (!string.IsNullOrEmpty(Program.Config.RewardsChannelName)) - { - rewardsChannel = GetGuild().TextChannels.SingleOrDefault(c => c.Name == Program.Config.RewardsChannelName); - } + rewardsChannel = GetChannel(Program.Config.RewardsChannelName); + eventsChannel = GetChannel(Program.Config.ChainEventsChannelName); } public async Task GiveRewards(GiveRewardsCommand rewards) @@ -34,6 +33,22 @@ namespace BiblioTech.Rewards rewardsChannel); await context.ProcessGiveRewardsCommand(LookUpUsers(rewards)); + await ProcessChainEvents(rewards.EventsOverview); + } + + private SocketTextChannel? GetChannel(string name) + { + if (string.IsNullOrEmpty(name)) return null; + return GetGuild().TextChannels.SingleOrDefault(c => c.Name == name); + } + + private async Task ProcessChainEvents(string[] eventsOverview) + { + if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; + foreach (var e in eventsOverview) + { + await eventsChannel.SendMessageAsync(e); + } } private async Task> LoadAllUsers(SocketGuild guild) From bbc975141fc90cb15238ea6a5d1fc89c330120aa Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 8 Apr 2024 16:07:52 +0200 Subject: [PATCH 008/142] Creates events overview in rewarder bot --- .../Marketplace/Customizations.cs | 6 ++ Tools/TestNetRewarder/ChainState.cs | 72 +++++++++++++++++++ Tools/TestNetRewarder/Configuration.cs | 3 + Tools/TestNetRewarder/HistoricState.cs | 41 ++++++++++- Tools/TestNetRewarder/Processor.cs | 13 +++- 5 files changed, 131 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 96490b75..8e8edb2c 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -1,10 +1,12 @@ #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. using GethPlugin; +using Newtonsoft.Json; namespace CodexContractsPlugin.Marketplace { public partial class Request : RequestBase { + [JsonIgnore] public ulong BlockNumber { get; set; } public byte[] RequestId { get; set; } @@ -13,22 +15,26 @@ namespace CodexContractsPlugin.Marketplace public partial class RequestFulfilledEventDTO { + [JsonIgnore] public ulong BlockNumber { get; set; } } public partial class RequestCancelledEventDTO { + [JsonIgnore] public ulong BlockNumber { get; set; } } public partial class SlotFilledEventDTO { + [JsonIgnore] public ulong BlockNumber { get; set; } public EthAddress Host { get; set; } } public partial class SlotFreedEventDTO { + [JsonIgnore] public ulong BlockNumber { get; set; } } } diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index b57c39b5..ce7f8503 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -1,5 +1,6 @@ using CodexContractsPlugin; using CodexContractsPlugin.Marketplace; +using Newtonsoft.Json; using Utils; namespace TestNetRewarder @@ -11,11 +12,13 @@ namespace TestNetRewarder public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange) { NewRequests = contracts.GetStorageRequests(blockRange); + historicState.CleanUpOldRequests(); historicState.ProcessNewRequests(NewRequests); historicState.UpdateStorageRequests(contracts); StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); + ChangedRequests = historicState.StorageRequests.Where(r => r.RecentlyChanged).ToArray(); RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); @@ -27,9 +30,78 @@ namespace TestNetRewarder public StorageRequest[] AllRequests => historicState.StorageRequests; public StorageRequest[] StartedRequests { get; private set; } public StorageRequest[] FinishedRequests { get; private set; } + public StorageRequest[] ChangedRequests { get; private set; } public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; } public RequestCancelledEventDTO[] RequestCancelledEvents { get; } public SlotFilledEventDTO[] SlotFilledEvents { get; } public SlotFreedEventDTO[] SlotFreedEvents { get; } + + public string[] GenerateOverview() + { + var entries = new List(); + + entries.AddRange(ChangedRequests.Select(ToPair)); + entries.AddRange(RequestFulfilledEvents.Select(ToPair)); + entries.AddRange(RequestCancelledEvents.Select(ToPair)); + entries.AddRange(SlotFilledEvents.Select(ToPair)); + entries.AddRange(SlotFreedEvents.Select(ToPair)); + + entries.Sort(new StringUtcComparer()); + + return entries.Select(ToLine).ToArray(); + } + + private StringBlockNumberPair ToPair(StorageRequest r) + { + return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.Request.BlockNumber); + } + + private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) + { + return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + } + + private StringBlockNumberPair ToPair(RequestCancelledEventDTO r) + { + return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + } + + private StringBlockNumberPair ToPair(SlotFilledEventDTO r) + { + return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + } + + private StringBlockNumberPair ToPair(SlotFreedEventDTO r) + { + return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + } + + private string ToLine(StringBlockNumberPair pair) + { + return $"[{pair.Number}] {pair.Str}"; + } + + public class StringBlockNumberPair + { + public StringBlockNumberPair(string str, ulong number) + { + Str = str; + Number = number; + } + + public string Str { get; } + public ulong Number { get; } + } + + public class StringUtcComparer : IComparer + { + public int Compare(StringBlockNumberPair? x, StringBlockNumberPair? y) + { + if (x == null && y == null) return 0; + if (x == null) return 1; + if (y == null) return -1; + return x.Number.CompareTo(y.Number); + } + } } } diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index ed885d13..c180648f 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -22,6 +22,9 @@ namespace TestNetRewarder [Uniform("market-insights", "mi", "MARKETINSIGHTS", false, "Semi-colon separated integers. Each represents a multiple of intervals, for which a market insights average will be generated.")] public string MarketInsights { get; set; } = "1;96"; + [Uniform("events-overview", "eo", "EVENTSOVERVIEW", false, "When greater than zero, chain event summary will be generated. (default 1)")] + public int CreateChainEventsOverview { get; set; } = 1; + public string LogPath { get diff --git a/Tools/TestNetRewarder/HistoricState.cs b/Tools/TestNetRewarder/HistoricState.cs index 53fd1bae..316a80ec 100644 --- a/Tools/TestNetRewarder/HistoricState.cs +++ b/Tools/TestNetRewarder/HistoricState.cs @@ -1,6 +1,7 @@ using CodexContractsPlugin; using CodexContractsPlugin.Marketplace; using GethPlugin; +using Newtonsoft.Json; namespace TestNetRewarder { @@ -19,6 +20,17 @@ namespace TestNetRewarder { foreach (var r in storageRequests) r.Update(contracts); } + + public void CleanUpOldRequests() + { + storageRequests.RemoveAll(r => + r.State == RequestState.Cancelled || + r.State == RequestState.Finished || + r.State == RequestState.Failed + ); + + foreach (var r in storageRequests) r.IsNew = false; + } } public class StorageRequest @@ -27,17 +39,26 @@ namespace TestNetRewarder { Request = request; Hosts = Array.Empty(); + IsNew = true; } public Request Request { get; } public EthAddress[] Hosts { get; private set; } public RequestState State { get; private set; } + public bool IsNew { get; set; } + + [JsonIgnore] public bool RecentlyStarted { get; private set; } + + [JsonIgnore] public bool RecentlyFinished { get; private set; } + [JsonIgnore] + public bool RecentlyChanged { get; private set; } + public void Update(ICodexContracts contracts) { - Hosts = GetHosts(contracts); + var newHosts = GetHosts(contracts); var newState = contracts.GetRequestState(Request); @@ -49,7 +70,25 @@ namespace TestNetRewarder State == RequestState.Started && newState == RequestState.Finished; + RecentlyChanged = + IsNew || + State != newState || + HostsChanged(newHosts); + State = newState; + Hosts = newHosts; + } + + private bool HostsChanged(EthAddress[] newHosts) + { + if (newHosts.Length != Hosts.Length) return true; + + foreach (var newHost in newHosts) + { + if (!Hosts.Contains(newHost)) return true; + } + + return false; } private EthAddress[] GetHosts(ICodexContracts contracts) diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 85303302..a293495e 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -65,29 +65,36 @@ namespace TestNetRewarder } var marketAverages = GetMarketAverages(chainState); + var eventsOverview = GenerateEventsOverview(chainState); log.Log($"Found {outgoingRewards.Count} rewards to send. Found {marketAverages.Length} market averages."); if (outgoingRewards.Any()) { - if (!await SendRewardsCommand(outgoingRewards, marketAverages)) + if (!await SendRewardsCommand(outgoingRewards, marketAverages, eventsOverview)) { log.Error("Failed to send reward command."); } } } + private string[] GenerateEventsOverview(ChainState chainState) + { + return chainState.GenerateOverview(); + } + private MarketAverage[] GetMarketAverages(ChainState chainState) { return marketTracker.ProcessChainState(chainState); } - private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages) + private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) { var cmd = new GiveRewardsCommand { Rewards = outgoingRewards.ToArray(), - Averages = marketAverages.ToArray() + Averages = marketAverages.ToArray(), + EventsOverview = eventsOverview }; log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); From 69666d3fee389332022d2821dbd943253d647f31 Mon Sep 17 00:00:00 2001 From: benbierens Date: Tue, 9 Apr 2024 09:30:45 +0200 Subject: [PATCH 009/142] Setting up future-containers --- Framework/KubernetesWorkflow/K8sController.cs | 6 +++++- .../KubernetesWorkflow/StartupWorkflow.cs | 21 ++++++++++++++----- .../Types/FutureContainers.cs | 20 ++++++++++++++++++ ProjectPlugins/CodexPlugin/CodexStarter.cs | 1 + 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 Framework/KubernetesWorkflow/Types/FutureContainers.cs diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index b14267d3..80b15d16 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -43,6 +43,11 @@ namespace KubernetesWorkflow return new StartResult(cluster, containerRecipes, deployment, internalService, externalService); } + public void WaitUntilOnline(RunningContainer container) + { + WaitUntilDeploymentOnline(container.Recipe.Name); + } + public PodInfo GetPodInfo(RunningDeployment deployment) { var pod = GetPodForDeployment(deployment); @@ -372,7 +377,6 @@ namespace KubernetesWorkflow }; client.Run(c => c.CreateNamespacedDeployment(deploymentSpec, K8sNamespace)); - WaitUntilDeploymentOnline(deploymentSpec.Metadata.Name); var name = deploymentSpec.Metadata.Name; return new RunningDeployment(name, podLabel); diff --git a/Framework/KubernetesWorkflow/StartupWorkflow.cs b/Framework/KubernetesWorkflow/StartupWorkflow.cs index 4d1a2f67..b8f7d779 100644 --- a/Framework/KubernetesWorkflow/StartupWorkflow.cs +++ b/Framework/KubernetesWorkflow/StartupWorkflow.cs @@ -9,8 +9,8 @@ namespace KubernetesWorkflow public interface IStartupWorkflow { IKnownLocations GetAvailableLocations(); - RunningContainers Start(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); - RunningContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); + FutureContainers Start(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); + FutureContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); PodInfo GetPodInfo(RunningContainer container); PodInfo GetPodInfo(RunningContainers containers); CrashWatcher CreateCrashWatcher(RunningContainer container); @@ -45,12 +45,12 @@ namespace KubernetesWorkflow return locationProvider.GetAvailableLocations(); } - public RunningContainers Start(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig) + public FutureContainers Start(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig) { return Start(numberOfContainers, KnownLocations.UnspecifiedLocation, recipeFactory, startupConfig); } - public RunningContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig) + public FutureContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig) { return K8s(controller => { @@ -67,7 +67,18 @@ namespace KubernetesWorkflow { componentFactory.Update(controller); } - return rc; + return new FutureContainers(rc, this); + }); + } + + public void WaitUntilOnline(RunningContainers rc) + { + K8s(controller => + { + foreach (var c in rc.Containers) + { + controller.WaitUntilOnline(c); + } }); } diff --git a/Framework/KubernetesWorkflow/Types/FutureContainers.cs b/Framework/KubernetesWorkflow/Types/FutureContainers.cs new file mode 100644 index 00000000..262eac44 --- /dev/null +++ b/Framework/KubernetesWorkflow/Types/FutureContainers.cs @@ -0,0 +1,20 @@ +namespace KubernetesWorkflow.Types +{ + public class FutureContainers + { + private readonly RunningContainers runningContainers; + private readonly StartupWorkflow workflow; + + public FutureContainers(RunningContainers runningContainers, StartupWorkflow workflow) + { + this.runningContainers = runningContainers; + this.workflow = workflow; + } + + public RunningContainers WaitForOnline() + { + workflow.WaitUntilOnline(runningContainers); + return runningContainers; + } + } +} diff --git a/ProjectPlugins/CodexPlugin/CodexStarter.cs b/ProjectPlugins/CodexPlugin/CodexStarter.cs index 05db6bdf..2a8f1bbd 100644 --- a/ProjectPlugins/CodexPlugin/CodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/CodexStarter.cs @@ -93,6 +93,7 @@ namespace CodexPlugin var workflow = pluginTools.CreateWorkflow(); result.Add(workflow.Start(1, location, recipe, startupConfig)); } + return result.ToArray(); } From 0182ce134fe22de33da22ed1c2d40a3a4e9a29fe Mon Sep 17 00:00:00 2001 From: benbierens Date: Tue, 9 Apr 2024 10:23:07 +0200 Subject: [PATCH 010/142] Applies future container to existing plugins --- .../CodexContractsStarter.cs | 2 +- .../CodexDiscordBotPlugin.cs | 4 +-- ProjectPlugins/CodexPlugin/CodexStarter.cs | 8 ++++-- .../DeployAndRunPlugin/DeployAndRunPlugin.cs | 2 +- ProjectPlugins/GethPlugin/GethStarter.cs | 2 +- .../MetricsPlugin/PrometheusStarter.cs | 2 +- .../CodexTests/BasicTests/MarketplaceTests.cs | 28 +++++++++---------- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index a6f6641e..0137a173 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -24,7 +24,7 @@ namespace CodexContractsPlugin var startupConfig = CreateStartupConfig(gethNode); startupConfig.NameOverride = "codex-contracts"; - var containers = workflow.Start(1, new CodexContractsContainerRecipe(), startupConfig); + var containers = workflow.Start(1, new CodexContractsContainerRecipe(), startupConfig).WaitForOnline(); if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure."); var container = containers.Containers[0]; diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index cad50c12..fd082cfa 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -46,14 +46,14 @@ namespace CodexDiscordBotPlugin var startupConfig = new StartupConfig(); startupConfig.NameOverride = config.Name; startupConfig.Add(config); - return workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig); + return workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig).WaitForOnline(); } private RunningContainers StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config) { var startupConfig = new StartupConfig(); startupConfig.Add(config); - return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig); + return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig).WaitForOnline(); } } } diff --git a/ProjectPlugins/CodexPlugin/CodexStarter.cs b/ProjectPlugins/CodexPlugin/CodexStarter.cs index 2a8f1bbd..6fcc4bd3 100644 --- a/ProjectPlugins/CodexPlugin/CodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/CodexStarter.cs @@ -87,14 +87,16 @@ namespace CodexPlugin private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, ILocation location) { - var result = new List(); + var futureContainers = new List(); for (var i = 0; i < numberOfNodes; i++) { var workflow = pluginTools.CreateWorkflow(); - result.Add(workflow.Start(1, location, recipe, startupConfig)); + futureContainers.Add(workflow.Start(1, location, recipe, startupConfig)); } - return result.ToArray(); + return futureContainers + .Select(f => f.WaitForOnline()) + .ToArray(); } private PodInfo GetPodInfo(RunningContainers rc) diff --git a/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs b/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs index 070b9fea..c290c0d5 100644 --- a/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs +++ b/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs @@ -30,7 +30,7 @@ namespace DeployAndRunPlugin startupConfig.Add(config); var location = workflow.GetAvailableLocations().Get("fixed-s-4vcpu-16gb-amd-yz8rd"); - var containers = workflow.Start(1, location, new DeployAndRunContainerRecipe(), startupConfig); + var containers = workflow.Start(1, location, new DeployAndRunContainerRecipe(), startupConfig).WaitForOnline(); return containers.Containers.Single(); } } diff --git a/ProjectPlugins/GethPlugin/GethStarter.cs b/ProjectPlugins/GethPlugin/GethStarter.cs index c5ad801f..edfba4e8 100644 --- a/ProjectPlugins/GethPlugin/GethStarter.cs +++ b/ProjectPlugins/GethPlugin/GethStarter.cs @@ -21,7 +21,7 @@ namespace GethPlugin startupConfig.NameOverride = gethStartupConfig.NameOverride; var workflow = tools.CreateWorkflow(); - var containers = workflow.Start(1, new GethContainerRecipe(), startupConfig); + var containers = workflow.Start(1, new GethContainerRecipe(), startupConfig).WaitForOnline(); if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure."); var container = containers.Containers[0]; diff --git a/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs b/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs index 53f784e4..f63f97d5 100644 --- a/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs +++ b/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs @@ -25,7 +25,7 @@ namespace MetricsPlugin startupConfig.Add(new PrometheusStartupConfig(GeneratePrometheusConfig(targets))); var workflow = tools.CreateWorkflow(); - var runningContainers = workflow.Start(1, recipe, startupConfig); + var runningContainers = workflow.Start(1, recipe, startupConfig).WaitForOnline(); if (runningContainers.Containers.Length != 1) throw new InvalidOperationException("Expected only 1 Prometheus container to be created."); Log("Metrics server started."); diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 7474696e..630ff803 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -19,22 +19,22 @@ namespace CodexTests.BasicTests var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); var contracts = Ci.StartCodexContracts(geth); + + var numberOfHosts = 5; + var hosts = AddCodex(numberOfHosts, s => s + .WithName("Host") + .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) + { + ContractClock = CodexLogLevel.Trace, + }) + .WithStorageQuota(11.GB()) + .EnableMarketplace(geth, contracts, m => m + .WithInitial(10.Eth(), hostInitialBalance) + .AsStorageNode() + .AsValidator())); - var numberOfHosts = 3; - for (var i = 0; i < numberOfHosts; i++) + foreach (var host in hosts) { - var host = AddCodex(s => s - .WithName("Host") - .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) - { - ContractClock = CodexLogLevel.Trace, - }) - .WithStorageQuota(11.GB()) - .EnableMarketplace(geth, contracts, m => m - .WithInitial(10.Eth(), hostInitialBalance) - .AsStorageNode() - .AsValidator())); - AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance)); var availability = new StorageAvailability( From b7ab2f994ed67288e3f48baadf827ffcbd8010cc Mon Sep 17 00:00:00 2001 From: benbierens Date: Tue, 9 Apr 2024 10:57:02 +0200 Subject: [PATCH 011/142] Fixes market test --- Tests/CodexTests/BasicTests/MarketplaceTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 630ff803..d5befe0d 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -33,9 +33,10 @@ namespace CodexTests.BasicTests .AsStorageNode() .AsValidator())); + var expectedHostBalance = (numberOfHosts * hostInitialBalance.Amount).TestTokens(); foreach (var host in hosts) { - AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance)); + AssertBalance(contracts, host, Is.EqualTo(expectedHostBalance)); var availability = new StorageAvailability( totalSpace: 10.GB(), From 0224f177331ff5558cea2d6988578dbd989dd864 Mon Sep 17 00:00:00 2001 From: benbierens Date: Tue, 9 Apr 2024 13:24:30 +0200 Subject: [PATCH 012/142] fixes serialization of request state --- ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index 4279347a..c539af68 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -5,6 +5,8 @@ using Nethereum.ABI; using Nethereum.Hex.HexTypes; using Nethereum.Util; using NethereumWorkflow; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Utils; namespace CodexContractsPlugin @@ -28,6 +30,7 @@ namespace CodexContractsPlugin SlotFreedEventDTO[] GetSlotFreedEvents(BlockInterval blockRange); } + [JsonConverter(typeof(StringEnumConverter))] public enum RequestState { New, From 4a0885cf2d77b6ff7b21422f0009177a495f56e6 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 11 Apr 2024 07:46:00 +0200 Subject: [PATCH 013/142] Check for marketplace contract up-to-date --- .../ContractsContainerInfoExtractor.cs | 16 +++- .../Marketplace/Marketplace.cs | 80 ++++++++++++++++--- Tools/TestNetRewarder/ChainState.cs | 3 +- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ContractsContainerInfoExtractor.cs b/ProjectPlugins/CodexContractsPlugin/ContractsContainerInfoExtractor.cs index 94cf4e6c..752192ca 100644 --- a/ProjectPlugins/CodexContractsPlugin/ContractsContainerInfoExtractor.cs +++ b/ProjectPlugins/CodexContractsPlugin/ContractsContainerInfoExtractor.cs @@ -1,4 +1,5 @@ -using KubernetesWorkflow; +using CodexContractsPlugin.Marketplace; +using KubernetesWorkflow; using KubernetesWorkflow.Types; using Logging; using Newtonsoft.Json; @@ -53,7 +54,18 @@ namespace CodexContractsPlugin var artifact = JObject.Parse(json); var abi = artifact["abi"]; - return abi!.ToString(Formatting.None); + var byteCode = artifact["bytecode"]; + var abiResult = abi!.ToString(Formatting.None); + var byteCodeResult = byteCode!.ToString(Formatting.None); + + if (byteCodeResult + .ToLowerInvariant() + .Replace("\"", "") != MarketplaceDeploymentBase.BYTECODE.ToLowerInvariant()) + { + throw new Exception("BYTECODE in CodexContractsPlugin does not match BYTECODE deployed by container. Update Marketplace.cs generated code?"); + } + + return abiResult; } private static string Retry(Func fetch) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs index 507334ab..a9ae4baf 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs @@ -7,6 +7,25 @@ using System.Numerics; #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 MarketplaceDeployment : MarketplaceDeploymentBase + { + public MarketplaceDeployment() : base(BYTECODE) { } + public MarketplaceDeployment(string byteCode) : base(byteCode) { } + } + + public class MarketplaceDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "0x60c06040523480156200001157600080fd5b5060405162003c1038038062003c10833981016040819052620000349162000487565b60208301518051608052816101004311620000965760405162461bcd60e51b815260206004820152601960248201527f496e73756666696369656e7420626c6f636b206865696768740000000000000060448201526064015b60405180910390fd5b81516000908155602083015160015560408301516002805460ff191660ff9092169190911790556060830151839190600390620000d4908262000627565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff9091161115620001535760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b606483600001516060015160ff161115620001b15760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b82516060810151602090910151606491620001cc91620006f3565b60ff1611156200021f5760405162461bcd60e51b815260206004820152601d60248201527f4d6178696d756d20736c617368696e672065786365656473203130302500000060448201526064016200008d565b82518051600c805460208085015160408087015160609788015160ff9081166401000000000260ff60201b1961ffff90931662010000029290921664ffffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600d90815591810151600e5593840151600f80549190931660ff19919091161790915592820151869391929190601090620002c7908262000627565b50505090505050505062000725565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715620003115762000311620002d6565b60405290565b604080519081016001600160401b0381118282101715620003115762000311620002d6565b604051601f8201601f191681016001600160401b0381118282101715620003675762000367620002d6565b604052919050565b805160ff811681146200038157600080fd5b919050565b6000608082840312156200039957600080fd5b620003a3620002ec565b90508151815260208083015181830152620003c1604084016200036f565b604083015260608301516001600160401b0380821115620003e157600080fd5b818501915085601f830112620003f657600080fd5b8151818111156200040b576200040b620002d6565b6200041f601f8201601f191685016200033c565b915080825286848285010111156200043657600080fd5b60005b818110156200045657838101850151838201860152840162000439565b5060008482840101525080606085015250505092915050565b80516001600160a01b03811681146200038157600080fd5b6000806000606084860312156200049d57600080fd5b83516001600160401b0380821115620004b557600080fd5b9085019081870360a0811215620004cb57600080fd5b620004d562000317565b6080821215620004e457600080fd5b620004ee620002ec565b9150620004fb846200036f565b82526200050b602085016200036f565b6020830152604084015161ffff811681146200052657600080fd5b604083015262000539606085016200036f565b6060830152908152608083015190828211156200055557600080fd5b620005638983860162000386565b6020820152809650505050506200057d602085016200046f565b91506200058d604085016200046f565b90509250925092565b600181811c90821680620005ab57607f821691505b602082108103620005cc57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000622576000816000526020600020601f850160051c81016020861015620005fd5750805b601f850160051c820191505b818110156200061e5782815560010162000609565b5050505b505050565b81516001600160401b03811115620006435762000643620002d6565b6200065b8162000654845462000596565b84620005d2565b602080601f8311600181146200069357600084156200067a5750858301515b600019600386901b1c1916600185901b1785556200061e565b600085815260208120601f198616915b82811015620006c457888601518255948401946001909101908401620006a3565b5085821015620006e35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60ff81811683821602908116908181146200071e57634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516134a26200076e6000396000818161039c01528181610b8801528181611cc10152818161204601526121b20152600081816125dc015261277b01526134a26000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c80639777b72c116100d8578063be5cdc481161008c578063f752196b11610066578063f752196b1461035a578063fb1e61ca1461037a578063fc0c546a1461039a57600080fd5b8063be5cdc4814610314578063c0cc4add14610334578063e8aa0a071461034757600080fd5b8063a3a0807e116100bd578063a3a0807e146102be578063a6af384b146102e1578063b396dc79146102f457600080fd5b80639777b72c146102a3578063a29c29a4146102ab57600080fd5b806359cc89ed1161012f5780636b00c8cf116101145780636b00c8cf146102375780636e2b54ee1461027b57806379502c551461028e57600080fd5b806359cc89ed1461020f5780635da738351461022257600080fd5b806308695fcd1161016057806308695fcd146101c2578063458d2bf1146101d75780634641dce6146101ea57600080fd5b806302fa8e651461017c57806305b90773146101a2575b600080fd5b61018f61018a3660046129b1565b6103c0565b6040519081526020015b60405180910390f35b6101b56101b03660046129b1565b610437565b60405161019991906129e0565b6101d56101d03660046129fa565b610529565b005b61018f6101e53660046129b1565b61067f565b6101fd6101f83660046129b1565b610698565b60405160ff9091168152602001610199565b6101d561021d366004612a2f565b6106ab565b61022a6109af565b6040516101999190612a66565b6102636102453660046129b1565b6000908152601360205260409020600501546001600160a01b031690565b6040516001600160a01b039091168152602001610199565b6101d56102893660046129b1565b6109d6565b610296610c09565b6040516101999190612af0565b61022a610d7a565b6101d56102b93660046129b1565b610d99565b6102d16102cc3660046129b1565b610f6a565b6040519015158152602001610199565b6101d56102ef366004612b72565b610f9f565b6103076103023660046129b1565b6111c5565b6040516101999190612c7b565b6103276103223660046129b1565b6113ae565b6040516101999190612cad565b6102d16103423660046129b1565b61147c565b6101d5610355366004612cc1565b61148f565b61018f6103683660046129b1565b60009081526007602052604090205490565b61038d6103883660046129b1565b6115cc565b6040516101999190612cef565b7f0000000000000000000000000000000000000000000000000000000000000000610263565b600081815260126020526040812060040154816103dc84610437565b905060008160048111156103f2576103f26129ca565b148061040f5750600181600481111561040d5761040d6129ca565b145b1561041b575092915050565b61042f8261042a600142612d18565b611783565b949350505050565b60008181526011602052604081205482906001600160a01b03166104945760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064015b60405180910390fd5b600083815260126020526040812090815460ff1660048111156104b9576104b96129ca565b1480156104d657506000848152601160205260409020600a015442115b156104e5576002925050610523565b6001815460ff1660048111156104fd576104fd6129ca565b14801561050d5750806004015442115b1561051c576003925050610523565b5460ff1691505b50919050565b6001610534836113ae565b6005811115610545576105456129ca565b146105925760405162461bcd60e51b815260206004820152601960248201527f536c6f74206e6f7420616363657074696e672070726f6f667300000000000000604482015260640161048b565b61059c828261179b565b6000828152601360209081526040808320600181015484526011909252909120600c5461ffff62010000909104166105e08560009081526007602052604090205490565b6105ea9190612d41565b60000361067957600c54600682015460009160649161061491640100000000900460ff1690612d55565b61061e9190612d6c565b9050808360040160008282546106349190612d18565b9091555050600c54600086815260076020526040902054610100820460ff169162010000900461ffff16906106699190612d6c565b1061067757610677856119c8565b505b50505050565b60006106928261068d611b6e565b611b79565b92915050565b6000610692826106a6611b6e565b611b8d565b60008381526011602052604090205483906001600160a01b03166107035760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b6000848152601160205260409020600181015467ffffffffffffffff16841061076e5760405162461bcd60e51b815260206004820152600c60248201527f496e76616c696420736c6f740000000000000000000000000000000000000000604482015260640161048b565b60408051602080820188905281830187905282518083038401815260609092019092528051910120600090600081815260136020526040812060018101899055600381018890559192506107c1836113ae565b60058111156107d2576107d26129ca565b1461081f5760405162461bcd60e51b815260206004820152601060248201527f536c6f74206973206e6f74206672656500000000000000000000000000000000604482015260640161048b565b60048301546000838152600560209081526040808320429055600690915290205561084a828661148f565b60058101805473ffffffffffffffffffffffffffffffffffffffff191633179055805460ff1916600190811782554260028301556000888152601260205260408120808301805491939290916108a1908490612d80565b909155506108b190508842611bf9565b8160020160008282546108c49190612d18565b909155505060068401546108d83382611c79565b80601460000160008282546108ed9190612d80565b9091555050600483018190556005830154610911906001600160a01b031685611d81565b887ff530852268993f91008f1a1e0b09b5c813acd4188481f1fa83c33c7182e814b48960405161094391815260200190565b60405180910390a26001808601549083015467ffffffffffffffff90911690036109a457815460ff1916600117825542600383015560405189907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b505050505050505050565b336000908152600b602052604090206060906109d1906109ce90611da3565b90565b905090565b6000818152601160205260409020600a8101544211610a375760405162461bcd60e51b815260206004820152601960248201527f52657175657374206e6f74207965742074696d6564206f757400000000000000604482015260640161048b565b80546001600160a01b03163314610a905760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e74206164647265737300000000000000000000604482015260640161048b565b600082815260126020526040812090815460ff166004811115610ab557610ab56129ca565b14610b025760405162461bcd60e51b815260206004820152600d60248201527f496e76616c696420737461746500000000000000000000000000000000000000604482015260640161048b565b805460ff191660021781558154610b22906001600160a01b031684611db0565b60405183907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600281015460158054829190600090610b67908490612d80565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610bd9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bfd9190612d93565b61067957610679612db5565b610c606040805160c0810182526000918101828152606082018390526080820183905260a0820192909252908190815260408051608081018252600080825260208281018290529282015260608082015291015290565b6040805160c081018252600c805460ff8082168486019081526101008304821660608087019190915261ffff62010000850416608080880191909152640100000000909404831660a087015290855285519283018652600d80548452600e54602085810191909152600f54909316968401969096526010805495969495928701949291840191610cef90612dcb565b80601f0160208091040260200160405190810160405280929190818152602001828054610d1b90612dcb565b8015610d685780601f10610d3d57610100808354040283529160200191610d68565b820191906000526020600020905b815481529060010190602001808311610d4b57829003601f168201915b50505050508152505081525050905090565b336000908152600a602052604090206060906109d1906109ce90611da3565b806000808281526013602052604090205460ff166005811115610dbe57610dbe6129ca565b03610dfa5760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b604482015260640161048b565b600082815260136020526040902060058101546001600160a01b03163314610e645760405162461bcd60e51b815260206004820152601960248201527f536c6f742066696c6c6564206279206f7468657220686f737400000000000000604482015260640161048b565b6000610e6f846113ae565b90506004816005811115610e8557610e856129ca565b03610ed25760405162461bcd60e51b815260206004820152600c60248201527f416c726561647920706169640000000000000000000000000000000000000000604482015260640161048b565b6002816005811115610ee657610ee66129ca565b03610efe57610ef9826001015485611dd2565b610679565b6005816005811115610f1257610f126129ca565b03610f2557610ef98260010154856120bf565b6003816005811115610f3957610f396129ca565b03610f4857610ef9338561222b565b6001816005811115610f5c57610f5c6129ca565b0361067957610679846119c8565b6000806000610f8084610f7b611b6e565b61224d565b909250905081801561042f575060025460ff9081169116109392505050565b33610fad6020830183612e17565b6001600160a01b0316146110035760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e74206164647265737300000000000000000000604482015260640161048b565b600061101661101183612fa9565b612330565b6000818152601160205260409020549091506001600160a01b03161561107e5760405162461bcd60e51b815260206004820152601660248201527f5265717565737420616c72656164792065786973747300000000000000000000604482015260640161048b565b600081815260116020526040902082906110988282613206565b50600090506110ab606084013542612d80565b905082610120013581116111015760405162461bcd60e51b815260206004820152601960248201527f5265717565737420656e64206265666f72652065787069727900000000000000604482015260640161048b565b600082815260126020908152604090912060040182905561112e9061112890850185612e17565b83612360565b600061114161113c85612fa9565b612382565b600084815260126020526040812060020182905560148054929350839290919061116c908490612d80565b9091555061117c90503382611c79565b7f5fdb86c365a247a4d97dcbcc5c3abde9d6e3e2de26273f3fda8eef5073b9a96c83856020018661012001356040516111b7939291906132fe565b60405180910390a150505050565b6111cd6128e9565b816000808281526013602052604090205460ff1660058111156111f2576111f26129ca565b0361122e5760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b604482015260640161048b565b60008381526013602052604090206112446128e9565b600180830154600090815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186529583015467ffffffffffffffff9081168752600284015487860152600384015487870152600484015460608801526005840154608088015260068401549187019190915260078301541660c08601529182019390935281518083018352600884018054929493850192829082906112f090612dcb565b80601f016020809104026020016040519081016040528092919081815260200182805461131c90612dcb565b80156113695780601f1061133e57610100808354040283529160200191611369565b820191906000526020600020905b81548152906001019060200180831161134c57829003601f168201915b505050918352505060019190910154602091820152908252600a83015482820152600b9092015460409091015290825260039092015491810191909152915050919050565b6000818152601360205260408120600181015482036113d05750600092915050565b60006113df8260010154610437565b90506004825460ff1660058111156113f9576113f96129ca565b03611408575060049392505050565b600281600481111561141c5761141c6129ca565b0361142b575060059392505050565b600381600481111561143f5761143f6129ca565b0361144e575060029392505050565b6004816004811115611462576114626129ca565b03611471575060039392505050565b505460ff1692915050565b60006106928261148a611b6e565b6123a7565b6000828152601360209081526040808320600101548084526011909252909120546001600160a01b03166114f75760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b60008381526013602090815260408083206001810154845260118352818420825160038082526080820190945291949093909290820160608036833701905050905061154a6115458761067f565b6123da565b8160008151811061155d5761155d61337a565b60209081029190910101526009820154611576906123eb565b816001815181106115895761158961337a565b6020026020010181815250508260030154816002815181106115ad576115ad61337a565b6020026020010181815250506115c48686836123f7565b505050505050565b6115d4612909565b60008281526011602052604090205482906001600160a01b031661162c5760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b600083815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e081018652600184015467ffffffffffffffff90811682526002850154828701526003850154828801526004850154606083015260058501546080830152600685015492820192909252600784015490911660c082015292810192909252825180840184526008820180549394929392850192829082906116d790612dcb565b80601f016020809104026020016040519081016040528092919081815260200182805461170390612dcb565b80156117505780601f1061172557610100808354040283529160200191611750565b820191906000526020600020905b81548152906001019060200180831161173357829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b82015481525050915050919050565b60008183106117925781611794565b825b9392505050565b60006117a6826125c2565b90504281106117f75760405162461bcd60e51b815260206004820152601860248201527f506572696f6420686173206e6f7420656e646564207965740000000000000000604482015260640161048b565b6001546118049082612d80565b42106118525760405162461bcd60e51b815260206004820152601460248201527f56616c69646174696f6e2074696d6564206f7574000000000000000000000000604482015260640161048b565b600083815260086020908152604080832085845290915290205460ff16156118bc5760405162461bcd60e51b815260206004820181905260248201527f50726f6f6620776173207375626d69747465642c206e6f74206d697373696e67604482015260640161048b565b6118c683836123a7565b6119125760405162461bcd60e51b815260206004820152601660248201527f50726f6f6620776173206e6f7420726571756972656400000000000000000000604482015260640161048b565b600083815260096020908152604080832085845290915290205460ff161561197c5760405162461bcd60e51b815260206004820152601f60248201527f50726f6f6620616c7265616479206d61726b6564206173206d697373696e6700604482015260640161048b565b60008381526009602090815260408083208584528252808320805460ff19166001908117909155868452600790925282208054919290916119be908490612d80565b9091555050505050565b60008181526013602090815260408083206001810154808552601290935292206005830154611a00906001600160a01b03168561222b565b6003808401546000868152601360205260408120805460ff1916815560018082018390556002820183905593810182905560048101829055600501805473ffffffffffffffffffffffffffffffffffffffff191690558383018054929392909190611a6c908490612d18565b909155505060405181815283907f1d31c9f8dea6e179f6a050db117595feea8937029ea51f5168a4780be7e8f5529060200160405180910390a2600085815260076020526040812055600083815260116020526040812060018085015490820154919291611ae4919067ffffffffffffffff16612d18565b600783015490915067ffffffffffffffff1681118015611b1957506001845460ff166004811115611b1757611b176129ca565b145b15611b6557835460ff19166004178455611b34600142612d18565b600485015560405185907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a25b50505050505050565b60006109d1426125d5565b6000611794611b888484611b8d565b612601565b600080611b9c61010043612d41565b90506000610100611bae856043612d55565b611bb89190612d41565b90506000611bc861010087612d41565b9050600061010082611bda8587612d80565b611be49190612d80565b611bee9190612d41565b979650505050505050565b6000828152601160205260408120600a8101548310611c5a5760405162461bcd60e51b815260206004820152601760248201527f5374617274206e6f74206265666f726520657870697279000000000000000000604482015260640161048b565b6005810154600a820154611c6f908590612d18565b61042f9190612d55565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015611d0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d309190612d93565b611d7c5760405162461bcd60e51b815260206004820152600f60248201527f5472616e73666572206661696c65640000000000000000000000000000000000604482015260640161048b565b505050565b6001600160a01b0382166000908152600b60205260409020611d7c908261265b565b6060600061179483612667565b6001600160a01b0382166000908152600a60205260409020611d7c90826126c3565b60008281526011602052604090205482906001600160a01b0316611e2a5760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b60008381526012602090815260408083206011909252909120815460ff191660031782558054611e63906001600160a01b031686611db0565b60008481526013602052604090206005810154611e89906001600160a01b03168661222b565b6004808201546000888152601160209081526040808320815160a0808201845282546001600160a01b03168252835160e081018552600184015467ffffffffffffffff908116825260028501548288015260038501548287015298840154606082015260058401546080820152600684015491810191909152600783015490971660c088015292830195909552805180820182526008860180549496611fe6959093850192919082908290611f3d90612dcb565b80601f0160208091040260200160405190810160405280929190818152602001828054611f6990612dcb565b8015611fb65780601f10611f8b57610100808354040283529160200191611fb6565b820191906000526020600020905b815481529060010190602001808311611f9957829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b820154815250506126cf565b611ff09190612d80565b905080601460010160008282546120079190612d80565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561208f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b39190612d93565b611b6557611b65612db5565b60008281526011602052604090205482906001600160a01b03166121175760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b6000828152601360205260409020600581015461213d906001600160a01b03168461222b565b60008160040154612152868460020154611bf9565b61215c9190612d80565b905080601460010160008282546121739190612d80565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156121fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061221f9190612d93565b61067757610677612db5565b6001600160a01b0382166000908152600b60205260409020611d7c90826126c3565b600080600061225b856113ae565b60008681526005602052604081205491925090612277906125d5565b9050600182600581111561228d5761228d6129ca565b1415806122a1575061229f85826126eb565b155b156122b457600080935093505050612329565b6122be8686611b8d565b925060006122cb84612601565b600254909150600090610100906122e59060ff1682613390565b60008a8152600660205260409020546123029161ffff1690612d55565b61230c9190612d6c565b905080158061232257506123208183612d41565b155b9550505050505b9250929050565b6000816040516020016123439190612cef565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600a60205260409020611d7c908261265b565b600061238d826126cf565b602083015151610692919067ffffffffffffffff16612d55565b60008060006123b6858561224d565b90925090508180156123d1575060025460ff90811690821610155b95945050505050565b600060ff1982168161042f826126f5565b600080611794836126f5565b60008381526008602052604081209061240e611b6e565b815260208101919091526040016000205460ff161561246f5760405162461bcd60e51b815260206004820152601760248201527f50726f6f6620616c7265616479207375626d6974746564000000000000000000604482015260640161048b565b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d916124b99186918691016133ab565b602060405180830381865afa1580156124d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fa9190612d93565b6125465760405162461bcd60e51b815260206004820152600d60248201527f496e76616c69642070726f6f6600000000000000000000000000000000000000604482015260640161048b565b600083815260086020526040812060019161255f611b6e565b815260200190815260200160002060006101000a81548160ff0219169083151502179055507f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d836040516125b591815260200190565b60405180910390a1505050565b60006106926125d083612767565b612774565b60006106927f000000000000000000000000000000000000000000000000000000000000000083612d6c565b60008060ff8316612613600143612d18565b61261d9190612d18565b409050600081900361263157612631612db5565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b600061179483836127a0565b6060816000018054806020026020016040519081016040528092919081815260200182805480156126b757602002820191906000526020600020905b8154815260200190600101908083116126a3575b50505050509050919050565b600061179483836127ef565b6020810151608081015160409091015160009161069291612d55565b6000818311611794565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b602081101561052357600891821c91612735908290612d55565b83901b7fff0000000000000000000000000000000000000000000000000000000000000016919091179060010161271b565b6000610692826001612d80565b60006106927f000000000000000000000000000000000000000000000000000000000000000083612d55565b60008181526001830160205260408120546127e757508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610692565b506000610692565b600081815260018301602052604081205480156128d8576000612813600183612d18565b855490915060009061282790600190612d18565b905081811461288c5760008660000182815481106128475761284761337a565b906000526020600020015490508087600001848154811061286a5761286a61337a565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061289d5761289d613456565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610692565b6000915050610692565b5092915050565b60405180604001604052806128fc612909565b8152602001600081525090565b6040518060a0016040528060006001600160a01b031681526020016129786040518060e00160405280600067ffffffffffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001600067ffffffffffffffff1681525090565b815260200161299d604051806040016040528060608152602001600080191681525090565b815260006020820181905260409091015290565b6000602082840312156129c357600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106129f4576129f46129ca565b91905290565b60008060408385031215612a0d57600080fd5b50508035926020909101359150565b6000610100828403121561052357600080fd5b60008060006101408486031215612a4557600080fd5b8335925060208401359150612a5d8560408601612a1c565b90509250925092565b6020808252825182820181905260009190848201906040850190845b81811015612a9e57835183529284019291840191600101612a82565b50909695505050505050565b6000815180845260005b81811015612ad057602081850181015186830182015201612ab4565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000825160ff815116602084015260ff602082015116604084015261ffff604082015116606084015260ff606082015116608084015250602083015160a080840152805160c0840152602081015160e084015260ff60408201511661010084015260608101519050608061012084015261042f610140840182612aaa565b600060208284031215612b8457600080fd5b813567ffffffffffffffff811115612b9b57600080fd5b8201610160818503121561179457600080fd5b6000815160408452612bc36040850182612aaa565b602093840151949093019390935250919050565b60006101606001600160a01b038351168452602083015167ffffffffffffffff808251166020870152602082015160408701526040820151606087015260608201516080870152608082015160a087015260a082015160c08701528060c08301511660e08701525050604083015181610100860152612c5882860182612bae565b915050606083015161012085015260808301516101408501528091505092915050565b602081526000825160406020840152612c976060840182612bd7565b9050602084015160408401528091505092915050565b60208101600683106129f4576129f46129ca565b6000806101208385031215612cd557600080fd5b82359150612ce68460208501612a1c565b90509250929050565b6020815260006117946020830184612bd7565b634e487b7160e01b600052601160045260246000fd5b8181038181111561069257610692612d02565b634e487b7160e01b600052601260045260246000fd5b600082612d5057612d50612d2b565b500690565b808202811582820484141761069257610692612d02565b600082612d7b57612d7b612d2b565b500490565b8082018082111561069257610692612d02565b600060208284031215612da557600080fd5b8151801515811461179457600080fd5b634e487b7160e01b600052600160045260246000fd5b600181811c90821680612ddf57607f821691505b60208210810361052357634e487b7160e01b600052602260045260246000fd5b6001600160a01b0381168114612e1457600080fd5b50565b600060208284031215612e2957600080fd5b813561179481612dff565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b60405290565b60405160a0810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b60405160e0810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b604051601f8201601f1916810167ffffffffffffffff81118282101715612ee257612ee2612e34565b604052919050565b67ffffffffffffffff81168114612e1457600080fd5b600060408284031215612f1257600080fd5b612f1a612e4a565b9050813567ffffffffffffffff80821115612f3457600080fd5b818401915084601f830112612f4857600080fd5b8135602082821115612f5c57612f5c612e34565b612f6e601f8301601f19168201612eb9565b92508183528681838601011115612f8457600080fd5b8181850182850137600081838501015282855280860135818601525050505092915050565b6000813603610160811215612fbd57600080fd5b612fc5612e73565b8335612fd081612dff565b815260e0601f1983011215612fe457600080fd5b612fec612e96565b91506020840135612ffc81612eea565b8083525060408401356020830152606084013560408301526080840135606083015260a0840135608083015260c084013560a083015260e084013561304081612eea565b60c083015260208101919091526101008301359067ffffffffffffffff82111561306957600080fd5b61307536838601612f00565b604082015261012084013560608201526101409093013560808401525090919050565b6000813561069281612eea565b60008235603e198336030181126130bb57600080fd5b9190910192915050565b601f821115611d7c576000816000526020600020601f850160051c810160208610156130ee5750805b601f850160051c820191505b818110156115c4578281556001016130fa565b8135601e1983360301811261312157600080fd5b8201803567ffffffffffffffff81111561313a57600080fd5b6020813603818401131561314d57600080fd5b6131618261315b8654612dcb565b866130c5565b6000601f831160018114613197576000841561317f57508482018301355b600019600386901b1c1916600185901b1786556131f4565b600086815260209020601f19851690835b828110156131c95787850186013582559385019360019091019085016131a8565b50858210156131e85760001960f88760031b161c198585890101351681555b505060018460011b0186555b50508085013560018501555050505050565b813561321181612dff565b6001600160a01b03811673ffffffffffffffffffffffffffffffffffffffff1983541617825550602082013561324681612eea565b60018201805467ffffffffffffffff191667ffffffffffffffff83161790555060408201356002820155606082013560038201556080820135600482015560a0820135600582015560c082013560068201556132ca6132a760e08401613098565b6007830167ffffffffffffffff821667ffffffffffffffff198254161781555050565b6132e46132db6101008401846130a5565b6008830161310d565b610120820135600a820155610140820135600b8201555050565b8381526101208101833561331181612eea565b67ffffffffffffffff8082166020850152602086013560408501526040860135606085015260608601356080850152608086013560a085015260a086013560c085015260c0860135915061336482612eea565b1660e08301526101009091019190915292915050565b634e487b7160e01b600052603260045260246000fd5b61ffff8281168282160390808211156128e2576128e2612d02565b823581526020808401359082015260006101208281016133db604085016040880180358252602090810135910152565b6133f5608085016080880180358252602090810135910152565b61340f60c0850160c0880180358252602090810135910152565b61010084019190915283519081905261014083019060209081860160005b828110156134495781518552938301939083019060010161342d565b5092979650505050505050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212201d12cd1bfef3b2ec5ee1f81d1ea21b12b2bb5ae8b67fb3ebaaeca5a194c2a0b564736f6c63430008170033"; + public MarketplaceDeploymentBase() : base(BYTECODE) { } + public MarketplaceDeploymentBase(string byteCode) : base(byteCode) { } + [Parameter("tuple", "configuration", 1)] + public virtual MarketplaceConfig Configuration { get; set; } + [Parameter("address", "token_", 2)] + public virtual string Token { get; set; } + [Parameter("address", "verifier", 3)] + public virtual string Verifier { get; set; } + } + public partial class ConfigFunction : ConfigFunctionBase { } [Function("config", typeof(ConfigOutputDTO))] @@ -24,8 +43,8 @@ namespace CodexContractsPlugin.Marketplace 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; } + [Parameter("tuple", "proof", 3)] + public virtual Groth16Proof Proof { get; set; } } public partial class FreeSlotFunction : FreeSlotFunctionBase { } @@ -170,8 +189,8 @@ namespace CodexContractsPlugin.Marketplace { [Parameter("bytes32", "id", 1)] public virtual byte[] Id { get; set; } - [Parameter("bytes", "proof", 2)] - public virtual byte[] Proof { get; set; } + [Parameter("tuple", "proof", 2)] + public virtual Groth16Proof Proof { get; set; } } public partial class TokenFunction : TokenFunctionBase { } @@ -207,8 +226,6 @@ namespace CodexContractsPlugin.Marketplace { [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 { } @@ -278,10 +295,8 @@ namespace CodexContractsPlugin.Marketplace [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; } + [Parameter("tuple", "", 1)] + public virtual MarketplaceConfig ReturnValue1 { get; set; } } @@ -446,6 +461,8 @@ namespace CodexContractsPlugin.Marketplace public virtual BigInteger Timeout { get; set; } [Parameter("uint8", "downtime", 3)] public virtual byte Downtime { get; set; } + [Parameter("string", "zkeyHash", 4)] + public virtual string ZkeyHash { get; set; } } public partial class MarketplaceConfig : MarketplaceConfigBase { } @@ -478,6 +495,48 @@ namespace CodexContractsPlugin.Marketplace public virtual ulong MaxSlotLoss { get; set; } } + public partial class G1Point : G1PointBase { } + + public class G1PointBase + { + [Parameter("uint256", "x", 1)] + public virtual BigInteger X { get; set; } + [Parameter("uint256", "y", 2)] + public virtual BigInteger Y { get; set; } + } + + public partial class Fp2Element : Fp2ElementBase { } + + public class Fp2ElementBase + { + [Parameter("uint256", "real", 1)] + public virtual BigInteger Real { get; set; } + [Parameter("uint256", "imag", 2)] + public virtual BigInteger Imag { get; set; } + } + + public partial class G2Point : G2PointBase { } + + public class G2PointBase + { + [Parameter("tuple", "x", 1)] + public virtual Fp2Element X { get; set; } + [Parameter("tuple", "y", 2)] + public virtual Fp2Element Y { get; set; } + } + + public partial class Groth16Proof : Groth16ProofBase { } + + public class Groth16ProofBase + { + [Parameter("tuple", "a", 1)] + public virtual G1Point A { get; set; } + [Parameter("tuple", "b", 2)] + public virtual G2Point B { get; set; } + [Parameter("tuple", "c", 3)] + public virtual G1Point C { get; set; } + } + public partial class Content : ContentBase { } public class ContentBase @@ -514,4 +573,5 @@ namespace CodexContractsPlugin.Marketplace 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. diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index ce7f8503..9c93f87d 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -11,6 +11,8 @@ namespace TestNetRewarder public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange) { + this.historicState = historicState; + NewRequests = contracts.GetStorageRequests(blockRange); historicState.CleanUpOldRequests(); historicState.ProcessNewRequests(NewRequests); @@ -23,7 +25,6 @@ namespace TestNetRewarder RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); - this.historicState = historicState; } public Request[] NewRequests { get; } From 1dbb749732c89a5f1b754a97f7e72eca2fdcad9e Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 11 Apr 2024 08:50:03 +0200 Subject: [PATCH 014/142] Removes ChangedEvents field from chain-state, not necessary --- Tools/TestNetRewarder/ChainState.cs | 22 +++++++++++----------- Tools/TestNetRewarder/HistoricState.cs | 24 ------------------------ 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index 9c93f87d..b290e786 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -20,7 +20,6 @@ namespace TestNetRewarder StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); - ChangedRequests = historicState.StorageRequests.Where(r => r.RecentlyChanged).ToArray(); RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); @@ -31,7 +30,6 @@ namespace TestNetRewarder public StorageRequest[] AllRequests => historicState.StorageRequests; public StorageRequest[] StartedRequests { get; private set; } public StorageRequest[] FinishedRequests { get; private set; } - public StorageRequest[] ChangedRequests { get; private set; } public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; } public RequestCancelledEventDTO[] RequestCancelledEvents { get; } public SlotFilledEventDTO[] SlotFilledEvents { get; } @@ -41,7 +39,7 @@ namespace TestNetRewarder { var entries = new List(); - entries.AddRange(ChangedRequests.Select(ToPair)); + entries.AddRange(NewRequests.Select(ToPair)); entries.AddRange(RequestFulfilledEvents.Select(ToPair)); entries.AddRange(RequestCancelledEvents.Select(ToPair)); entries.AddRange(SlotFilledEvents.Select(ToPair)); @@ -52,44 +50,46 @@ namespace TestNetRewarder return entries.Select(ToLine).ToArray(); } - private StringBlockNumberPair ToPair(StorageRequest r) + private StringBlockNumberPair ToPair(Request r) { - return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.Request.BlockNumber); + return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.BlockNumber); } private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) { - return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.BlockNumber); } private StringBlockNumberPair ToPair(RequestCancelledEventDTO r) { - return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("Cancelled", JsonConvert.SerializeObject(r), r.BlockNumber); } private StringBlockNumberPair ToPair(SlotFilledEventDTO r) { - return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("SlotFilled", JsonConvert.SerializeObject(r), r.BlockNumber); } private StringBlockNumberPair ToPair(SlotFreedEventDTO r) { - return new StringBlockNumberPair(JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("SlotFreed", JsonConvert.SerializeObject(r), r.BlockNumber); } private string ToLine(StringBlockNumberPair pair) { - return $"[{pair.Number}] {pair.Str}"; + return $"[{pair.Number}]({pair.Name}) {pair.Str}"; } public class StringBlockNumberPair { - public StringBlockNumberPair(string str, ulong number) + public StringBlockNumberPair(string name, string str, ulong number) { + Name = name; Str = str; Number = number; } + public string Name { get; } public string Str { get; } public ulong Number { get; } } diff --git a/Tools/TestNetRewarder/HistoricState.cs b/Tools/TestNetRewarder/HistoricState.cs index 316a80ec..36d335df 100644 --- a/Tools/TestNetRewarder/HistoricState.cs +++ b/Tools/TestNetRewarder/HistoricState.cs @@ -28,8 +28,6 @@ namespace TestNetRewarder r.State == RequestState.Finished || r.State == RequestState.Failed ); - - foreach (var r in storageRequests) r.IsNew = false; } } @@ -39,13 +37,11 @@ namespace TestNetRewarder { Request = request; Hosts = Array.Empty(); - IsNew = true; } public Request Request { get; } public EthAddress[] Hosts { get; private set; } public RequestState State { get; private set; } - public bool IsNew { get; set; } [JsonIgnore] public bool RecentlyStarted { get; private set; } @@ -53,9 +49,6 @@ namespace TestNetRewarder [JsonIgnore] public bool RecentlyFinished { get; private set; } - [JsonIgnore] - public bool RecentlyChanged { get; private set; } - public void Update(ICodexContracts contracts) { var newHosts = GetHosts(contracts); @@ -70,27 +63,10 @@ namespace TestNetRewarder State == RequestState.Started && newState == RequestState.Finished; - RecentlyChanged = - IsNew || - State != newState || - HostsChanged(newHosts); - State = newState; Hosts = newHosts; } - private bool HostsChanged(EthAddress[] newHosts) - { - if (newHosts.Length != Hosts.Length) return true; - - foreach (var newHost in newHosts) - { - if (!Hosts.Contains(newHost)) return true; - } - - return false; - } - private EthAddress[] GetHosts(ICodexContracts contracts) { var result = new List(); From ca7258ef286d492407f43535b6ae725b976118fb Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 11 Apr 2024 13:14:21 +0200 Subject: [PATCH 015/142] async dispatches sending of event overview to discord channel --- Tools/BiblioTech/Rewards/RoleDriver.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 2afff6ec..303f0699 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -45,10 +45,17 @@ namespace BiblioTech.Rewards private async Task ProcessChainEvents(string[] eventsOverview) { if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; - foreach (var e in eventsOverview) + await Task.Run(async () => { - await eventsChannel.SendMessageAsync(e); - } + foreach (var e in eventsOverview) + { + if (!string.IsNullOrEmpty(e)) + { + await eventsChannel.SendMessageAsync(e); + await Task.Delay(3000); + } + } + }); } private async Task> LoadAllUsers(SocketGuild guild) From 5ed78da30bf1778292c77ebf745b52bd1db49617 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 11 Apr 2024 17:28:12 +0200 Subject: [PATCH 016/142] Fixes possible crash in market averages --- Tools/TestNetRewarder/MarketTracker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index 47957c8e..e22c141b 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -42,6 +42,7 @@ namespace TestNetRewarder private ChainState[] SelectStates(int numberOfIntervals) { if (numberOfIntervals < 1) return Array.Empty(); + if (numberOfIntervals > buffer.Count) return Array.Empty(); return buffer.TakeLast(numberOfIntervals).ToArray(); } From 2cf5a26934f36daf43485476707872cc29e38b6a Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 12 Apr 2024 08:35:20 +0200 Subject: [PATCH 017/142] Sends updates to discord bot when no rewards are found. --- Tools/TestNetRewarder/BotClient.cs | 2 +- Tools/TestNetRewarder/Processor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/TestNetRewarder/BotClient.cs b/Tools/TestNetRewarder/BotClient.cs index c112af48..426c5011 100644 --- a/Tools/TestNetRewarder/BotClient.cs +++ b/Tools/TestNetRewarder/BotClient.cs @@ -26,7 +26,7 @@ namespace TestNetRewarder public async Task SendRewards(GiveRewardsCommand command) { - if (command == null || command.Rewards == null || !command.Rewards.Any()) return false; + if (command == null) return false; var result = await HttpPostJson(command); log.Log("Reward response: " + result); return result == "OK"; diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index a293495e..3c8a6ecc 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -69,7 +69,7 @@ namespace TestNetRewarder log.Log($"Found {outgoingRewards.Count} rewards to send. Found {marketAverages.Length} market averages."); - if (outgoingRewards.Any()) + if (outgoingRewards.Any() || marketAverages.Any() || eventsOverview.Any()) { if (!await SendRewardsCommand(outgoingRewards, marketAverages, eventsOverview)) { From ae25b58610e091ad324dd54e187f8fc842f943c6 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 12 Apr 2024 09:12:51 +0200 Subject: [PATCH 018/142] Fixes exception in market averages --- Framework/DiscordRewards/GiveRewardsCommand.cs | 2 +- Tools/BiblioTech/Commands/MarketCommand.cs | 3 ++- Tools/TestNetRewarder/MarketTracker.cs | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/GiveRewardsCommand.cs index 96176334..48dabcca 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/GiveRewardsCommand.cs @@ -16,7 +16,7 @@ public class MarketAverage { public int NumberOfFinished { get; set; } - public TimeSpan TimeRange { get; set; } + public int TimeRangeSeconds { get; set; } public float Price { get; set; } public float Size { get; set; } public float Duration { get; set; } diff --git a/Tools/BiblioTech/Commands/MarketCommand.cs b/Tools/BiblioTech/Commands/MarketCommand.cs index 92891811..0df58d5d 100644 --- a/Tools/BiblioTech/Commands/MarketCommand.cs +++ b/Tools/BiblioTech/Commands/MarketCommand.cs @@ -31,7 +31,8 @@ namespace BiblioTech.Commands private string[] GetInsight(MarketAverage avg) { - var headerLine = $"[Last {Time.FormatDuration(avg.TimeRange)}] ({avg.NumberOfFinished} Contracts finished)"; + var timeRange = TimeSpan.FromSeconds(avg.TimeRangeSeconds); + var headerLine = $"[Last {Time.FormatDuration(timeRange)}] ({avg.NumberOfFinished} Contracts finished)"; if (avg.NumberOfFinished == 0) { diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index e22c141b..17e5e82d 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -53,7 +53,7 @@ namespace TestNetRewarder return new MarketAverage { NumberOfFinished = CountNumberOfFinishedRequests(states), - TimeRange = GetTotalTimeRange(states), + TimeRangeSeconds = GetTotalTimeRange(states), Price = Average(states, s => s.Request.Ask.Reward), Duration = Average(states, s => s.Request.Ask.Duration), Size = Average(states, s => GetTotalSize(s.Request.Ask)), @@ -93,12 +93,13 @@ namespace TestNetRewarder } } + if (count < 1.0f) return 0.0f; return sum / count; } - private TimeSpan GetTotalTimeRange(ChainState[] states) + private int GetTotalTimeRange(ChainState[] states) { - return Program.Config.Interval * states.Length; + return Convert.ToInt32((Program.Config.Interval * states.Length).TotalSeconds); } private int CountNumberOfFinishedRequests(ChainState[] states) From 24cf6c70b8159449e5c8b285ef365d0306cd4770 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 13 Apr 2024 08:57:46 +0200 Subject: [PATCH 019/142] Improve event logging --- .../Marketplace/Customizations.cs | 11 ++-- Tools/BiblioTech/Rewards/RewardController.cs | 5 +- Tools/BiblioTech/Rewards/RoleDriver.cs | 11 +++- Tools/TestNetRewarder/BotClient.cs | 4 +- Tools/TestNetRewarder/ChainState.cs | 57 +++++++++++++++---- 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 8e8edb2c..68427b60 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -1,5 +1,6 @@ #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. using GethPlugin; +using NethereumWorkflow.BlockUtils; using Newtonsoft.Json; namespace CodexContractsPlugin.Marketplace @@ -7,7 +8,7 @@ namespace CodexContractsPlugin.Marketplace public partial class Request : RequestBase { [JsonIgnore] - public ulong BlockNumber { get; set; } + public BlockTimeEntry Block { get; set; } public byte[] RequestId { get; set; } public EthAddress ClientAddress { get { return new EthAddress(Client); } } @@ -16,26 +17,26 @@ namespace CodexContractsPlugin.Marketplace public partial class RequestFulfilledEventDTO { [JsonIgnore] - public ulong BlockNumber { get; set; } + public BlockTimeEntry Block { get; set; } } public partial class RequestCancelledEventDTO { [JsonIgnore] - public ulong BlockNumber { get; set; } + public BlockTimeEntry Block { get; set; } } public partial class SlotFilledEventDTO { [JsonIgnore] - public ulong BlockNumber { get; set; } + public BlockTimeEntry Block { get; set; } public EthAddress Host { get; set; } } public partial class SlotFreedEventDTO { [JsonIgnore] - public ulong BlockNumber { get; set; } + public BlockTimeEntry Block { get; set; } } } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 6bf7b9f3..99f73ee5 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -23,7 +23,10 @@ namespace BiblioTech.Rewards { try { - Program.Averages = cmd.Averages; + if (cmd.Averages != null && cmd.Averages.Any()) + { + Program.Averages = cmd.Averages; + } await Program.RoleDriver.GiveRewards(cmd); } catch (Exception ex) diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 303f0699..6695af32 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -24,6 +24,16 @@ namespace BiblioTech.Rewards { Program.Log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'"); + if (rewards.Rewards.Any()) + { + await ProcessRewards(rewards); + } + + await ProcessChainEvents(rewards.EventsOverview); + } + + private async Task ProcessRewards(GiveRewardsCommand rewards) + { var guild = GetGuild(); // We load all role and user information first, // so we don't ask the server for the same info multiple times. @@ -33,7 +43,6 @@ namespace BiblioTech.Rewards rewardsChannel); await context.ProcessGiveRewardsCommand(LookUpUsers(rewards)); - await ProcessChainEvents(rewards.EventsOverview); } private SocketTextChannel? GetChannel(string name) diff --git a/Tools/TestNetRewarder/BotClient.cs b/Tools/TestNetRewarder/BotClient.cs index 426c5011..3ee1fb99 100644 --- a/Tools/TestNetRewarder/BotClient.cs +++ b/Tools/TestNetRewarder/BotClient.cs @@ -1,7 +1,5 @@ -using CodexContractsPlugin.Marketplace; -using DiscordRewards; +using DiscordRewards; using Logging; -using Newtonsoft.Json; using System.Net.Http.Json; namespace TestNetRewarder diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index b290e786..3f114493 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -1,5 +1,6 @@ using CodexContractsPlugin; using CodexContractsPlugin.Marketplace; +using NethereumWorkflow.BlockUtils; using Newtonsoft.Json; using Utils; @@ -8,6 +9,29 @@ namespace TestNetRewarder public class ChainState { private readonly HistoricState historicState; + private readonly string[] colorIcons = new[] + { + "🔴", + "🟠", + "🟡", + "🟢", + "🔵", + "🟣", + "🟤", + "⚫", + "⚪", + "🟥", + "🟧", + "🟨", + "🟩", + "🟦", + "🟪", + "🟫", + "⬛", + "⬜", + "🔶", + "🔷" + }; public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange) { @@ -52,46 +76,59 @@ namespace TestNetRewarder private StringBlockNumberPair ToPair(Request r) { - return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) { - return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } private StringBlockNumberPair ToPair(RequestCancelledEventDTO r) { - return new StringBlockNumberPair("Cancelled", JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("Cancelled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } private StringBlockNumberPair ToPair(SlotFilledEventDTO r) { - return new StringBlockNumberPair("SlotFilled", JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("SlotFilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } private StringBlockNumberPair ToPair(SlotFreedEventDTO r) { - return new StringBlockNumberPair("SlotFreed", JsonConvert.SerializeObject(r), r.BlockNumber); + return new StringBlockNumberPair("SlotFreed", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } private string ToLine(StringBlockNumberPair pair) { - return $"[{pair.Number}]({pair.Name}) {pair.Str}"; + var nl = Environment.NewLine; + var colorIcon = GetColorIcon(pair.RequestId); + return $"{colorIcon} {pair.Block} ({pair.Name}){nl}" + + $"```json{nl}" + + $"{pair.Str}{nl}" + + $"```"; + } + + private string GetColorIcon(byte[] requestId) + { + var index = requestId[0] % colorIcons.Length; + return colorIcons[index]; } public class StringBlockNumberPair { - public StringBlockNumberPair(string name, string str, ulong number) + public StringBlockNumberPair(string name, string str, BlockTimeEntry block, byte[] requestId) { Name = name; Str = str; - Number = number; + Block = block; + RequestId = requestId; } public string Name { get; } public string Str { get; } - public ulong Number { get; } + public BlockTimeEntry Block { get; } + public byte[] RequestId { get; } } public class StringUtcComparer : IComparer @@ -101,7 +138,7 @@ namespace TestNetRewarder if (x == null && y == null) return 0; if (x == null) return 1; if (y == null) return -1; - return x.Number.CompareTo(y.Number); + return x.Block.BlockNumber.CompareTo(y.Block.BlockNumber); } } } From 581cc80d5d849cd0b6e2660773ec857028df9312 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 13 Apr 2024 09:19:20 +0200 Subject: [PATCH 020/142] Implements getting blockTimeEntries --- .../BlockUtils/BlockTimeFinder.cs | 7 +++++++ .../NethereumWorkflow/NethereumInteraction.cs | 7 +++++++ .../CodexContractsPlugin/CodexContractsAccess.cs | 16 +++++++++++----- ProjectPlugins/GethPlugin/GethNode.cs | 7 +++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs index e4c73d50..b1d84e99 100644 --- a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs +++ b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs @@ -18,6 +18,13 @@ namespace NethereumWorkflow.BlockUtils bounds = new BlockchainBounds(cache, web3); } + public BlockTimeEntry Get(ulong blockNumber) + { + var b = cache.Get(blockNumber); + if (b != null) return b; + return GetBlock(blockNumber); + } + public ulong? GetHighestBlockNumberBefore(DateTime moment) { bounds.Initialize(); diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 197cf2d0..f965c35a 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -121,5 +121,12 @@ namespace NethereumWorkflow to: toBlock.Value ); } + + public BlockTimeEntry GetBlockForNumber(ulong number) + { + var wrapper = new Web3Wrapper(web3, log); + var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log); + return blockTimeFinder.Get(number); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index c539af68..188d0548 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -5,6 +5,7 @@ using Nethereum.ABI; using Nethereum.Hex.HexTypes; using Nethereum.Util; using NethereumWorkflow; +using NethereumWorkflow.BlockUtils; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Utils; @@ -89,7 +90,7 @@ namespace CodexContractsPlugin { var requestEvent = i.GetRequest(Deployment.MarketplaceAddress, e.Event.RequestId); var result = requestEvent.ReturnValue1; - result.BlockNumber = e.Log.BlockNumber.ToUlong(); + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); result.RequestId = e.Event.RequestId; return result; }) @@ -102,7 +103,7 @@ namespace CodexContractsPlugin return events.Select(e => { var result = e.Event; - result.BlockNumber = e.Log.BlockNumber.ToUlong(); + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); return result; }).ToArray(); } @@ -113,7 +114,7 @@ namespace CodexContractsPlugin return events.Select(e => { var result = e.Event; - result.BlockNumber = e.Log.BlockNumber.ToUlong(); + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); return result; }).ToArray(); } @@ -124,7 +125,7 @@ namespace CodexContractsPlugin return events.Select(e => { var result = e.Event; - result.BlockNumber = e.Log.BlockNumber.ToUlong(); + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); result.Host = GetEthAddressFromTransaction(e.Log.TransactionHash); return result; }).ToArray(); @@ -136,7 +137,7 @@ namespace CodexContractsPlugin return events.Select(e => { var result = e.Event; - result.BlockNumber = e.Log.BlockNumber.ToUlong(); + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); return result; }).ToArray(); } @@ -169,6 +170,11 @@ namespace CodexContractsPlugin return gethNode.Call(Deployment.MarketplaceAddress, func); } + private BlockTimeEntry GetBlock(ulong number) + { + return gethNode.GetBlockForNumber(number); + } + private EthAddress GetEthAddressFromTransaction(string transactionHash) { var transaction = gethNode.GetTransaction(transactionHash); diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index bfa3d0f9..08d07366 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -5,6 +5,7 @@ using Nethereum.ABI.FunctionEncoding.Attributes; using Nethereum.Contracts; using Nethereum.RPC.Eth.DTOs; using NethereumWorkflow; +using NethereumWorkflow.BlockUtils; using Utils; namespace GethPlugin @@ -27,6 +28,7 @@ namespace GethPlugin List> GetEvents(string address, BlockInterval blockRange) where TEvent : IEventDTO, new(); List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); + BlockTimeEntry GetBlockForNumber(ulong number); } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -160,6 +162,11 @@ namespace GethPlugin return StartInteraction().ConvertTimeRangeToBlockRange(timeRange); } + public BlockTimeEntry GetBlockForNumber(ulong number) + { + return StartInteraction().GetBlockForNumber(number); + } + protected abstract NethereumInteraction StartInteraction(); } } From 899d775873970ad93141098b32e1c6d5a7bfb038 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 13 Apr 2024 11:15:39 +0200 Subject: [PATCH 021/142] Adds finished requests --- Tools/TestNetRewarder/ChainState.cs | 6 ++++++ Tools/TestNetRewarder/Processor.cs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index 3f114493..c3ce8a24 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -68,6 +68,7 @@ namespace TestNetRewarder entries.AddRange(RequestCancelledEvents.Select(ToPair)); entries.AddRange(SlotFilledEvents.Select(ToPair)); entries.AddRange(SlotFreedEvents.Select(ToPair)); + entries.AddRange(FinishedRequests.Select(ToPair)); entries.Sort(new StringUtcComparer()); @@ -79,6 +80,11 @@ namespace TestNetRewarder return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.Block, r.RequestId); } + public StringBlockNumberPair ToPair(StorageRequest r) + { + return new StringBlockNumberPair("FinishedRequest", JsonConvert.SerializeObject(r), r.Request.Block, r.Request.RequestId); + } + private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) { return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 3c8a6ecc..c18247b4 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -67,7 +67,9 @@ namespace TestNetRewarder var marketAverages = GetMarketAverages(chainState); var eventsOverview = GenerateEventsOverview(chainState); - log.Log($"Found {outgoingRewards.Count} rewards to send. Found {marketAverages.Length} market averages."); + log.Log($"Found {outgoingRewards.Count} rewards. " + + $"Found {marketAverages.Length} market averages. " + + $"Found {eventsOverview.Length} events."); if (outgoingRewards.Any() || marketAverages.Any() || eventsOverview.Any()) { From 7db9360ba411da2b24a56d0c3957d24d340b333e Mon Sep 17 00:00:00 2001 From: gmega Date: Sat, 13 Apr 2024 16:27:47 +0300 Subject: [PATCH 022/142] add network/file scalability test --- .../ScalabilityTests/ScalabilityTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs new file mode 100644 index 00000000..c597d49e --- /dev/null +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -0,0 +1,43 @@ +using CodexPlugin; +using NUnit.Framework; +using Utils; + +namespace CodexTests.ScalabilityTests; + +[TestFixture] +public class ScalabilityTests : CodexDistTest +{ + private const string PatchedImage = "codexstorage/nim-codex:sha-9aeac06-dist-tests"; + private const string MasterImage = "codexstorage/nim-codex:sha-5380912-dist-tests"; + + [Test] + [Combinatorial] + public void ShouldMaintainFileInNetwork( + [Values(10, 20, 40, 80, 100)] int numberOfNodes, + [Values(100, 1000, 5000, 10000)] int fileSizeInMb, + [Values(true, false)] bool usePatchedImage + ) + { + CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; + + var bootstrap = AddCodex(); + var nodes = AddCodex(numberOfNodes - 1, + s => s.WithBootstrapNode(bootstrap)).ToList(); + + var uploader = nodes.PickOneRandom(); + var downloader = nodes.PickOneRandom(); + + var testFile = GenerateTestFile(fileSizeInMb.MB()); + var contentId = uploader.UploadFile(testFile); + var downloadedFile = downloader.DownloadContent(contentId); + + downloadedFile!.AssertIsEqual(testFile); + + uploader.Stop(true); + + var otherDownloader = nodes.PickOneRandom(); + downloadedFile = otherDownloader.DownloadContent(contentId); + + downloadedFile!.AssertIsEqual(testFile); + } +} \ No newline at end of file From 80261959e74139abcc988b866a58bca5ce3befef Mon Sep 17 00:00:00 2001 From: gmega Date: Sat, 13 Apr 2024 16:36:16 +0300 Subject: [PATCH 023/142] set log level to info --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index c597d49e..b767f39b 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -19,10 +19,10 @@ public class ScalabilityTests : CodexDistTest ) { CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; - + var bootstrap = AddCodex(); var nodes = AddCodex(numberOfNodes - 1, - s => s.WithBootstrapNode(bootstrap)).ToList(); + s => s.WithBootstrapNode(bootstrap).WithLogLevel(CodexLogLevel.Info)).ToList(); var uploader = nodes.PickOneRandom(); var downloader = nodes.PickOneRandom(); From e3b16fd7420a890930827fe421aea86f30a730e0 Mon Sep 17 00:00:00 2001 From: gmega Date: Sat, 13 Apr 2024 17:09:17 +0300 Subject: [PATCH 024/142] add ability to stop single containers --- Framework/KubernetesWorkflow/K8sController.cs | 2 +- Framework/KubernetesWorkflow/K8sHooks.cs | 8 +++---- .../KubernetesWorkflow/StartupWorkflow.cs | 20 ++++++++-------- .../Types/FutureContainers.cs | 12 +++++----- .../Types/RunningContainer.cs | 2 +- .../{RunningContainers.cs => RunningPod.cs} | 13 ++++------ .../CodexDiscordBotPlugin.cs | 8 +++---- .../CoreInterfaceExtensions.cs | 4 ++-- ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- ProjectPlugins/CodexPlugin/CodexAccess.cs | 6 ++--- ProjectPlugins/CodexPlugin/CodexDeployment.cs | 14 +++++------ ProjectPlugins/CodexPlugin/CodexNode.cs | 20 +++++++++------- .../CodexPlugin/CodexNodeFactory.cs | 2 +- ProjectPlugins/CodexPlugin/CodexNodeGroup.cs | 17 +++++++++---- ProjectPlugins/CodexPlugin/CodexPlugin.cs | 4 ++-- ProjectPlugins/CodexPlugin/CodexStarter.cs | 24 ++++++++++++------- .../CodexPlugin/CoreInterfaceExtensions.cs | 4 ++-- ProjectPlugins/GethPlugin/GethDeployment.cs | 8 +++---- .../MetricsPlugin/CoreInterfaceExtensions.cs | 12 +++++----- ProjectPlugins/MetricsPlugin/MetricsPlugin.cs | 8 +++---- .../MetricsPlugin/PrometheusStarter.cs | 6 ++--- .../ElasticSearchLogDownloader.cs | 4 ++-- Tests/CodexContinuousTests/SingleTestRun.cs | 10 ++++---- Tests/CodexContinuousTests/StartupChecker.cs | 8 +++---- .../BasicTests/ContinuousSubstitute.cs | 2 +- Tests/DistTestCore/TestLifecycle.cs | 6 ++--- Tools/CodexNetDeployer/Deployer.cs | 6 ++--- Tools/CodexNetDeployer/K8sHook.cs | 4 ++-- 28 files changed, 125 insertions(+), 111 deletions(-) rename Framework/KubernetesWorkflow/Types/{RunningContainers.cs => RunningPod.cs} (61%) diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 80b15d16..a17ad823 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -705,7 +705,7 @@ namespace KubernetesWorkflow private string GetPodName(RunningContainer container) { - return GetPodForDeployment(container.RunningContainers.StartResult.Deployment).Metadata.Name; + return GetPodForDeployment(container.RunningPod.StartResult.Deployment).Metadata.Name; } private V1Pod GetPodForDeployment(RunningDeployment deployment) diff --git a/Framework/KubernetesWorkflow/K8sHooks.cs b/Framework/KubernetesWorkflow/K8sHooks.cs index 74bb93b4..4dad7401 100644 --- a/Framework/KubernetesWorkflow/K8sHooks.cs +++ b/Framework/KubernetesWorkflow/K8sHooks.cs @@ -5,18 +5,18 @@ namespace KubernetesWorkflow { public interface IK8sHooks { - void OnContainersStarted(RunningContainers runningContainers); - void OnContainersStopped(RunningContainers runningContainers); + void OnContainersStarted(RunningPod runningPod); + void OnContainersStopped(RunningPod runningPod); void OnContainerRecipeCreated(ContainerRecipe recipe); } public class DoNothingK8sHooks : IK8sHooks { - public void OnContainersStarted(RunningContainers runningContainers) + public void OnContainersStarted(RunningPod runningPod) { } - public void OnContainersStopped(RunningContainers runningContainers) + public void OnContainersStopped(RunningPod runningPod) { } diff --git a/Framework/KubernetesWorkflow/StartupWorkflow.cs b/Framework/KubernetesWorkflow/StartupWorkflow.cs index b8f7d779..cda61489 100644 --- a/Framework/KubernetesWorkflow/StartupWorkflow.cs +++ b/Framework/KubernetesWorkflow/StartupWorkflow.cs @@ -12,9 +12,9 @@ namespace KubernetesWorkflow FutureContainers Start(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); FutureContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); PodInfo GetPodInfo(RunningContainer container); - PodInfo GetPodInfo(RunningContainers containers); + PodInfo GetPodInfo(RunningPod pod); CrashWatcher CreateCrashWatcher(RunningContainer container); - void Stop(RunningContainers containers, bool waitTillStopped); + void Stop(RunningPod pod, bool waitTillStopped); void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null); string ExecuteCommand(RunningContainer container, string command, params string[] args); void DeleteNamespace(); @@ -60,7 +60,7 @@ namespace KubernetesWorkflow var startResult = controller.BringOnline(recipes, location); var containers = CreateContainers(startResult, recipes, startupConfig); - var rc = new RunningContainers(startupConfig, startResult, containers); + var rc = new RunningPod(startupConfig, startResult, containers); cluster.Configuration.Hooks.OnContainersStarted(rc); if (startResult.ExternalService != null) @@ -71,7 +71,7 @@ namespace KubernetesWorkflow }); } - public void WaitUntilOnline(RunningContainers rc) + public void WaitUntilOnline(RunningPod rc) { K8s(controller => { @@ -84,12 +84,12 @@ namespace KubernetesWorkflow public PodInfo GetPodInfo(RunningContainer container) { - return K8s(c => c.GetPodInfo(container.RunningContainers.StartResult.Deployment)); + return K8s(c => c.GetPodInfo(container.RunningPod.StartResult.Deployment)); } - public PodInfo GetPodInfo(RunningContainers containers) + public PodInfo GetPodInfo(RunningPod pod) { - return K8s(c => c.GetPodInfo(containers.StartResult.Deployment)); + return K8s(c => c.GetPodInfo(pod.StartResult.Deployment)); } public CrashWatcher CreateCrashWatcher(RunningContainer container) @@ -97,12 +97,12 @@ namespace KubernetesWorkflow return K8s(c => c.CreateCrashWatcher(container)); } - public void Stop(RunningContainers runningContainers, bool waitTillStopped) + public void Stop(RunningPod runningPod, bool waitTillStopped) { K8s(controller => { - controller.Stop(runningContainers.StartResult, waitTillStopped); - cluster.Configuration.Hooks.OnContainersStopped(runningContainers); + controller.Stop(runningPod.StartResult, waitTillStopped); + cluster.Configuration.Hooks.OnContainersStopped(runningPod); }); } diff --git a/Framework/KubernetesWorkflow/Types/FutureContainers.cs b/Framework/KubernetesWorkflow/Types/FutureContainers.cs index 262eac44..296be534 100644 --- a/Framework/KubernetesWorkflow/Types/FutureContainers.cs +++ b/Framework/KubernetesWorkflow/Types/FutureContainers.cs @@ -2,19 +2,19 @@ { public class FutureContainers { - private readonly RunningContainers runningContainers; + private readonly RunningPod runningPod; private readonly StartupWorkflow workflow; - public FutureContainers(RunningContainers runningContainers, StartupWorkflow workflow) + public FutureContainers(RunningPod runningPod, StartupWorkflow workflow) { - this.runningContainers = runningContainers; + this.runningPod = runningPod; this.workflow = workflow; } - public RunningContainers WaitForOnline() + public RunningPod WaitForOnline() { - workflow.WaitUntilOnline(runningContainers); - return runningContainers; + workflow.WaitUntilOnline(runningPod); + return runningPod; } } } diff --git a/Framework/KubernetesWorkflow/Types/RunningContainer.cs b/Framework/KubernetesWorkflow/Types/RunningContainer.cs index f391e457..b0fbc02e 100644 --- a/Framework/KubernetesWorkflow/Types/RunningContainer.cs +++ b/Framework/KubernetesWorkflow/Types/RunningContainer.cs @@ -19,7 +19,7 @@ namespace KubernetesWorkflow.Types public ContainerAddress[] Addresses { get; } [JsonIgnore] - public RunningContainers RunningContainers { get; internal set; } = null!; + public RunningPod RunningPod { get; internal set; } = null!; public Address GetAddress(ILog log, string portTag) { diff --git a/Framework/KubernetesWorkflow/Types/RunningContainers.cs b/Framework/KubernetesWorkflow/Types/RunningPod.cs similarity index 61% rename from Framework/KubernetesWorkflow/Types/RunningContainers.cs rename to Framework/KubernetesWorkflow/Types/RunningPod.cs index 9a6e5f3a..7f1a24d7 100644 --- a/Framework/KubernetesWorkflow/Types/RunningContainers.cs +++ b/Framework/KubernetesWorkflow/Types/RunningPod.cs @@ -2,15 +2,15 @@ namespace KubernetesWorkflow.Types { - public class RunningContainers + public class RunningPod { - public RunningContainers(StartupConfig startupConfig, StartResult startResult, RunningContainer[] containers) + public RunningPod(StartupConfig startupConfig, StartResult startResult, RunningContainer[] containers) { StartupConfig = startupConfig; StartResult = startResult; Containers = containers; - foreach (var c in containers) c.RunningContainers = this; + foreach (var c in containers) c.RunningPod = this; } public StartupConfig StartupConfig { get; } @@ -31,12 +31,7 @@ namespace KubernetesWorkflow.Types public static class RunningContainersExtensions { - public static RunningContainer[] Containers(this RunningContainers[] runningContainers) - { - return runningContainers.SelectMany(c => c.Containers).ToArray(); - } - - public static string Describe(this RunningContainers[] runningContainers) + public static string Describe(this RunningPod[] runningContainers) { return string.Join(",", runningContainers.Select(c => c.Describe())); } diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index fd082cfa..f347805a 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -29,19 +29,19 @@ namespace CodexDiscordBotPlugin { } - public RunningContainers Deploy(DiscordBotStartupConfig config) + public RunningPod Deploy(DiscordBotStartupConfig config) { var workflow = tools.CreateWorkflow(); return StartContainer(workflow, config); } - public RunningContainers DeployRewarder(RewarderBotStartupConfig config) + public RunningPod DeployRewarder(RewarderBotStartupConfig config) { var workflow = tools.CreateWorkflow(); return StartRewarderContainer(workflow, config); } - private RunningContainers StartContainer(IStartupWorkflow workflow, DiscordBotStartupConfig config) + private RunningPod StartContainer(IStartupWorkflow workflow, DiscordBotStartupConfig config) { var startupConfig = new StartupConfig(); startupConfig.NameOverride = config.Name; @@ -49,7 +49,7 @@ namespace CodexDiscordBotPlugin return workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig).WaitForOnline(); } - private RunningContainers StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config) + private RunningPod StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config) { var startupConfig = new StartupConfig(); startupConfig.Add(config); diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/CodexDiscordBotPlugin/CoreInterfaceExtensions.cs index c17cec15..c91d7118 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CoreInterfaceExtensions.cs @@ -5,12 +5,12 @@ namespace CodexDiscordBotPlugin { public static class CoreInterfaceExtensions { - public static RunningContainers DeployCodexDiscordBot(this CoreInterface ci, DiscordBotStartupConfig config) + public static RunningPod DeployCodexDiscordBot(this CoreInterface ci, DiscordBotStartupConfig config) { return Plugin(ci).Deploy(config); } - public static RunningContainers DeployRewarderBot(this CoreInterface ci, RewarderBotStartupConfig config) + public static RunningPod DeployRewarderBot(this CoreInterface ci, RewarderBotStartupConfig config) { return Plugin(ci).DeployRewarder(config); } diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 212fa193..5cf3dc96 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -38,7 +38,7 @@ namespace CodexPlugin if (string.IsNullOrEmpty(OpenApiYamlHash)) throw new Exception("OpenAPI yaml hash was not inserted by pre-build trigger."); } - public void CheckCompatibility(RunningContainers[] containers) + public void CheckCompatibility(RunningPod[] containers) { if (checkPassed) return; diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 697ff2cd..e35a9f9d 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -13,7 +13,7 @@ namespace CodexPlugin private readonly Mapper mapper = new Mapper(); private bool hasContainerCrashed; - public CodexAccess(IPluginTools tools, RunningContainer container, CrashWatcher crashWatcher) + public CodexAccess(IPluginTools tools, RunningPod container, CrashWatcher crashWatcher) { this.tools = tools; Container = container; @@ -23,7 +23,7 @@ namespace CodexPlugin CrashWatcher.Start(this); } - public RunningContainer Container { get; } + public RunningPod Container { get; } public CrashWatcher CrashWatcher { get; } public DebugInfo GetDebugInfo() @@ -136,7 +136,7 @@ namespace CodexPlugin private Address GetAddress() { - return Container.GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag); + return Container.Containers.Single().GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag); } private void CheckContainerCrashed(HttpClient client) diff --git a/ProjectPlugins/CodexPlugin/CodexDeployment.cs b/ProjectPlugins/CodexPlugin/CodexDeployment.cs index 9f74dd31..7cbf4506 100644 --- a/ProjectPlugins/CodexPlugin/CodexDeployment.cs +++ b/ProjectPlugins/CodexPlugin/CodexDeployment.cs @@ -7,8 +7,8 @@ namespace CodexPlugin public class CodexDeployment { public CodexDeployment(CodexInstance[] codexInstances, GethDeployment gethDeployment, - CodexContractsDeployment codexContractsDeployment, RunningContainers? prometheusContainer, - RunningContainers? discordBotContainer, DeploymentMetadata metadata, + CodexContractsDeployment codexContractsDeployment, RunningPod? prometheusContainer, + RunningPod? discordBotContainer, DeploymentMetadata metadata, String id) { Id = id; @@ -24,20 +24,20 @@ namespace CodexPlugin public CodexInstance[] CodexInstances { get; } public GethDeployment GethDeployment { get; } public CodexContractsDeployment CodexContractsDeployment { get; } - public RunningContainers? PrometheusContainer { get; } - public RunningContainers? DiscordBotContainer { get; } + public RunningPod? PrometheusContainer { get; } + public RunningPod? DiscordBotContainer { get; } public DeploymentMetadata Metadata { get; } } public class CodexInstance { - public CodexInstance(RunningContainers containers, DebugInfo info) + public CodexInstance(RunningPod pod, DebugInfo info) { - Containers = containers; + Pod = pod; Info = info; } - public RunningContainers Containers { get; } + public RunningPod Pod { get; } public DebugInfo Info { get; } } diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index e1e475ce..54299c3c 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -44,7 +44,9 @@ namespace CodexPlugin transferSpeeds = new TransferSpeeds(); } - public RunningContainer Container { get { return CodexAccess.Container; } } + public RunningPod Pod { get { return CodexAccess.Container; } } + + public RunningContainer Container { get { return Pod.Containers.Single(); } } public CodexAccess CodexAccess { get; } public CrashWatcher CrashWatcher { get => CodexAccess.CrashWatcher; } public CodexNodeGroup Group { get; } @@ -56,7 +58,7 @@ namespace CodexPlugin { get { - return new MetricsScrapeTarget(CodexAccess.Container, CodexContainerRecipe.MetricsPortTag); + return new MetricsScrapeTarget(CodexAccess.Container.Containers.First(), CodexContainerRecipe.MetricsPortTag); } } @@ -142,11 +144,13 @@ namespace CodexPlugin public void Stop(bool waitTillStopped) { - if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " + - "individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " + - "available for codex-nodes in groups of 1."); - - Group.BringOffline(waitTillStopped); + Group.Stop(this, waitTillStopped); + CrashWatcher.Stop(); + // if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " + + // "individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " + + // "available for codex-nodes in groups of 1."); + // + // Group.BringOffline(waitTillStopped); } public void EnsureOnlineGetVersionResponse() @@ -171,7 +175,7 @@ namespace CodexPlugin // The peer we want to connect is in a different pod. // We must replace the default IP with the pod IP in the multiAddress. var workflow = tools.CreateWorkflow(); - var podInfo = workflow.GetPodInfo(peer.Container); + var podInfo = workflow.GetPodInfo(peer.Pod); return peerInfo.Addrs.Select(a => a .Replace("0.0.0.0", podInfo.Ip)) diff --git a/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs b/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs index 05b80b28..18483de6 100644 --- a/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs +++ b/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs @@ -35,7 +35,7 @@ namespace CodexPlugin private EthAddress? GetEthAddress(CodexAccess access) { - var ethAccount = access.Container.Recipe.Additionals.Get(); + var ethAccount = access.Container.Containers.Single().Recipe.Additionals.Get(); if (ethAccount == null) return null; return ethAccount.EthAddress; } diff --git a/ProjectPlugins/CodexPlugin/CodexNodeGroup.cs b/ProjectPlugins/CodexPlugin/CodexNodeGroup.cs index 1f7ed4eb..47bf35d9 100644 --- a/ProjectPlugins/CodexPlugin/CodexNodeGroup.cs +++ b/ProjectPlugins/CodexPlugin/CodexNodeGroup.cs @@ -15,11 +15,11 @@ namespace CodexPlugin { private readonly CodexStarter starter; - public CodexNodeGroup(CodexStarter starter, IPluginTools tools, RunningContainers[] containers, ICodexNodeFactory codexNodeFactory) + public CodexNodeGroup(CodexStarter starter, IPluginTools tools, RunningPod[] containers, ICodexNodeFactory codexNodeFactory) { this.starter = starter; Containers = containers; - Nodes = containers.Containers().Select(c => CreateOnlineCodexNode(c, tools, codexNodeFactory)).ToArray(); + Nodes = containers.Select(c => CreateOnlineCodexNode(c, tools, codexNodeFactory)).ToArray(); Version = new DebugInfoVersion(); } @@ -39,7 +39,14 @@ namespace CodexPlugin Containers = null!; } - public RunningContainers[] Containers { get; private set; } + public void Stop(CodexNode node, bool waitTillStopped) + { + starter.Stop(node.Pod, waitTillStopped); + Nodes = Nodes.Where(n => n != node).ToArray(); + Containers = Containers.Where(c => c != node.Pod).ToArray(); + } + + public RunningPod[] Containers { get; private set; } public CodexNode[] Nodes { get; private set; } public DebugInfoVersion Version { get; private set; } public IMetricsScrapeTarget[] ScrapeTargets => Nodes.Select(n => n.MetricsScrapeTarget).ToArray(); @@ -74,9 +81,9 @@ namespace CodexPlugin Version = first; } - private CodexNode CreateOnlineCodexNode(RunningContainer c, IPluginTools tools, ICodexNodeFactory factory) + private CodexNode CreateOnlineCodexNode(RunningPod c, IPluginTools tools, ICodexNodeFactory factory) { - var watcher = factory.CreateCrashWatcher(c); + var watcher = factory.CreateCrashWatcher(c.Containers.Single()); var access = new CodexAccess(tools, c, watcher); return factory.CreateOnlineCodexNode(access, this); } diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index 9b8586e8..7b722ed8 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -32,13 +32,13 @@ namespace CodexPlugin { } - public RunningContainers[] DeployCodexNodes(int numberOfNodes, Action setup) + public RunningPod[] DeployCodexNodes(int numberOfNodes, Action setup) { var codexSetup = GetSetup(numberOfNodes, setup); return codexStarter.BringOnline(codexSetup); } - public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningContainers[] containers) + public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningPod[] containers) { containers = containers.Select(c => SerializeGate.Gate(c)).ToArray(); return codexStarter.WrapCodexContainers(coreInterface, containers); diff --git a/ProjectPlugins/CodexPlugin/CodexStarter.cs b/ProjectPlugins/CodexPlugin/CodexStarter.cs index 6fcc4bd3..dec13d38 100644 --- a/ProjectPlugins/CodexPlugin/CodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/CodexStarter.cs @@ -19,7 +19,7 @@ namespace CodexPlugin apiChecker = new ApiChecker(pluginTools); } - public RunningContainers[] BringOnline(CodexSetup codexSetup) + public RunningPod[] BringOnline(CodexSetup codexSetup) { LogSeparator(); Log($"Starting {codexSetup.Describe()}..."); @@ -34,14 +34,14 @@ namespace CodexPlugin { var podInfo = GetPodInfo(rc); var podInfos = string.Join(", ", rc.Containers.Select(c => $"Container: '{c.Name}' runs at '{podInfo.K8SNodeName}'={podInfo.Ip}")); - Log($"Started {codexSetup.NumberOfNodes} nodes of image '{containers.Containers().First().Recipe.Image}'. ({podInfos})"); + Log($"Started {codexSetup.NumberOfNodes} nodes of image '{containers.First().Containers.First().Recipe.Image}'. ({podInfos})"); } LogSeparator(); return containers; } - public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningContainers[] containers) + public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningPod[] containers) { var codexNodeFactory = new CodexNodeFactory(pluginTools); @@ -65,6 +65,14 @@ namespace CodexPlugin Log("Stopped."); } + public void Stop(RunningPod pod, bool waitTillStopped) + { + Log($"Stopping node..."); + var workflow = pluginTools.CreateWorkflow(); + workflow.Stop(pod, waitTillStopped); + Log("Stopped."); + } + public string GetCodexId() { if (versionResponse != null) return versionResponse.Version; @@ -85,7 +93,7 @@ namespace CodexPlugin return startupConfig; } - private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, ILocation location) + private RunningPod[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, ILocation location) { var futureContainers = new List(); for (var i = 0; i < numberOfNodes; i++) @@ -99,13 +107,13 @@ namespace CodexPlugin .ToArray(); } - private PodInfo GetPodInfo(RunningContainers rc) + private PodInfo GetPodInfo(RunningPod rc) { var workflow = pluginTools.CreateWorkflow(); return workflow.GetPodInfo(rc); } - private CodexNodeGroup CreateCodexGroup(CoreInterface coreInterface, RunningContainers[] runningContainers, CodexNodeFactory codexNodeFactory) + private CodexNodeGroup CreateCodexGroup(CoreInterface coreInterface, RunningPod[] runningContainers, CodexNodeFactory codexNodeFactory) { var group = new CodexNodeGroup(this, pluginTools, runningContainers, codexNodeFactory); @@ -122,10 +130,10 @@ namespace CodexPlugin return group; } - private void CodexNodesNotOnline(CoreInterface coreInterface, RunningContainers[] runningContainers) + private void CodexNodesNotOnline(CoreInterface coreInterface, RunningPod[] runningContainers) { Log("Codex nodes failed to start"); - foreach (var container in runningContainers.Containers()) coreInterface.DownloadLog(container); + foreach (var container in runningContainers.First().Containers) coreInterface.DownloadLog(container); } private void LogSeparator() diff --git a/ProjectPlugins/CodexPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/CodexPlugin/CoreInterfaceExtensions.cs index 529b7fd8..5f6065df 100644 --- a/ProjectPlugins/CodexPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/CodexPlugin/CoreInterfaceExtensions.cs @@ -5,12 +5,12 @@ namespace CodexPlugin { public static class CoreInterfaceExtensions { - public static RunningContainers[] DeployCodexNodes(this CoreInterface ci, int number, Action setup) + public static RunningPod[] DeployCodexNodes(this CoreInterface ci, int number, Action setup) { return Plugin(ci).DeployCodexNodes(number, setup); } - public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, RunningContainers[] containers) + public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, RunningPod[] containers) { return Plugin(ci).WrapCodexContainers(ci, containers); } diff --git a/ProjectPlugins/GethPlugin/GethDeployment.cs b/ProjectPlugins/GethPlugin/GethDeployment.cs index 8d7f0dd5..e463ce42 100644 --- a/ProjectPlugins/GethPlugin/GethDeployment.cs +++ b/ProjectPlugins/GethPlugin/GethDeployment.cs @@ -7,9 +7,9 @@ namespace GethPlugin { public class GethDeployment : IHasContainer { - public GethDeployment(RunningContainers containers, Port discoveryPort, Port httpPort, Port wsPort, GethAccount account, string pubKey) + public GethDeployment(RunningPod pod, Port discoveryPort, Port httpPort, Port wsPort, GethAccount account, string pubKey) { - Containers = containers; + Pod = pod; DiscoveryPort = discoveryPort; HttpPort = httpPort; WsPort = wsPort; @@ -17,9 +17,9 @@ namespace GethPlugin PubKey = pubKey; } - public RunningContainers Containers { get; } + public RunningPod Pod { get; } [JsonIgnore] - public RunningContainer Container { get { return Containers.Containers.Single(); } } + public RunningContainer Container { get { return Pod.Containers.Single(); } } public Port DiscoveryPort { get; } public Port HttpPort { get; } public Port WsPort { get; } diff --git a/ProjectPlugins/MetricsPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/MetricsPlugin/CoreInterfaceExtensions.cs index 83920825..6d5859da 100644 --- a/ProjectPlugins/MetricsPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/MetricsPlugin/CoreInterfaceExtensions.cs @@ -6,24 +6,24 @@ namespace MetricsPlugin { public static class CoreInterfaceExtensions { - public static RunningContainers DeployMetricsCollector(this CoreInterface ci, params IHasMetricsScrapeTarget[] scrapeTargets) + public static RunningPod DeployMetricsCollector(this CoreInterface ci, params IHasMetricsScrapeTarget[] scrapeTargets) { return Plugin(ci).DeployMetricsCollector(scrapeTargets.Select(t => t.MetricsScrapeTarget).ToArray()); } - public static RunningContainers DeployMetricsCollector(this CoreInterface ci, params IMetricsScrapeTarget[] scrapeTargets) + public static RunningPod DeployMetricsCollector(this CoreInterface ci, params IMetricsScrapeTarget[] scrapeTargets) { return Plugin(ci).DeployMetricsCollector(scrapeTargets); } - public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningContainers metricsContainer, IHasMetricsScrapeTarget scrapeTarget) + public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningPod metricsPod, IHasMetricsScrapeTarget scrapeTarget) { - return ci.WrapMetricsCollector(metricsContainer, scrapeTarget.MetricsScrapeTarget); + return ci.WrapMetricsCollector(metricsPod, scrapeTarget.MetricsScrapeTarget); } - public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningContainers metricsContainer, IMetricsScrapeTarget scrapeTarget) + public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningPod metricsPod, IMetricsScrapeTarget scrapeTarget) { - return Plugin(ci).WrapMetricsCollectorDeployment(metricsContainer, scrapeTarget); + return Plugin(ci).WrapMetricsCollectorDeployment(metricsPod, scrapeTarget); } public static IMetricsAccess[] GetMetricsFor(this CoreInterface ci, params IHasManyMetricScrapeTargets[] manyScrapeTargets) diff --git a/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs b/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs index b2c2b8ea..bc7d926b 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs @@ -31,15 +31,15 @@ namespace MetricsPlugin { } - public RunningContainers DeployMetricsCollector(IMetricsScrapeTarget[] scrapeTargets) + public RunningPod DeployMetricsCollector(IMetricsScrapeTarget[] scrapeTargets) { return starter.CollectMetricsFor(scrapeTargets); } - public IMetricsAccess WrapMetricsCollectorDeployment(RunningContainers runningContainer, IMetricsScrapeTarget target) + public IMetricsAccess WrapMetricsCollectorDeployment(RunningPod runningPod, IMetricsScrapeTarget target) { - runningContainer = SerializeGate.Gate(runningContainer); - return starter.CreateAccessForTarget(runningContainer, target); + runningPod = SerializeGate.Gate(runningPod); + return starter.CreateAccessForTarget(runningPod, target); } public LogFile? DownloadAllMetrics(IMetricsAccess metricsAccess, string targetName) diff --git a/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs b/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs index f63f97d5..159947a2 100644 --- a/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs +++ b/ProjectPlugins/MetricsPlugin/PrometheusStarter.cs @@ -16,7 +16,7 @@ namespace MetricsPlugin this.tools = tools; } - public RunningContainers CollectMetricsFor(IMetricsScrapeTarget[] targets) + public RunningPod CollectMetricsFor(IMetricsScrapeTarget[] targets) { if (!targets.Any()) throw new ArgumentException(nameof(targets) + " must not be empty."); @@ -32,9 +32,9 @@ namespace MetricsPlugin return runningContainers; } - public MetricsAccess CreateAccessForTarget(RunningContainers metricsContainer, IMetricsScrapeTarget target) + public MetricsAccess CreateAccessForTarget(RunningPod metricsPod, IMetricsScrapeTarget target) { - var metricsQuery = new MetricsQuery(tools, metricsContainer.Containers.Single()); + var metricsQuery = new MetricsQuery(tools, metricsPod.Containers.Single()); return new MetricsAccess(metricsQuery, target); } diff --git a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs index 80203af3..10c909ca 100644 --- a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs +++ b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs @@ -49,8 +49,8 @@ namespace ContinuousTests var start = startUtc.ToString("o"); var end = endUtc.ToString("o"); - var containerName = container.RunningContainers.StartResult.Deployment.Name; - var namespaceName = container.RunningContainers.StartResult.Cluster.Configuration.KubernetesNamespace; + var containerName = container.RunningPod.StartResult.Deployment.Name; + var namespaceName = container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace; //container_name : codex3-5 - deploymentName as stored in pod // pod_namespace : codex - continuous - nolimits - tests - 1 diff --git a/Tests/CodexContinuousTests/SingleTestRun.cs b/Tests/CodexContinuousTests/SingleTestRun.cs index 7c6c5a6f..38bc5021 100644 --- a/Tests/CodexContinuousTests/SingleTestRun.cs +++ b/Tests/CodexContinuousTests/SingleTestRun.cs @@ -125,8 +125,8 @@ namespace ContinuousTests foreach (var node in nodes) { var container = node.Container; - var deploymentName = container.RunningContainers.StartResult.Deployment.Name; - var namespaceName = container.RunningContainers.StartResult.Cluster.Configuration.KubernetesNamespace; + var deploymentName = container.RunningPod.StartResult.Deployment.Name; + var namespaceName = container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace; var openingLine = $"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().Id}"; elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(), node.Container, effectiveStart, @@ -295,13 +295,13 @@ namespace ContinuousTests return entryPoint.CreateInterface().WrapCodexContainers(containers).ToArray(); } - private RunningContainers[] SelectRandomContainers() + private RunningPod[] SelectRandomContainers() { var number = handle.Test.RequiredNumberOfNodes; - var containers = config.CodexDeployment.CodexInstances.Select(i => i.Containers).ToList(); + var containers = config.CodexDeployment.CodexInstances.Select(i => i.Pod).ToList(); if (number == -1) return containers.ToArray(); - var result = new RunningContainers[number]; + var result = new RunningPod[number]; for (var i = 0; i < number; i++) { result[i] = containers.PickOneRandom(); diff --git a/Tests/CodexContinuousTests/StartupChecker.cs b/Tests/CodexContinuousTests/StartupChecker.cs index d3fb456c..57c383c8 100644 --- a/Tests/CodexContinuousTests/StartupChecker.cs +++ b/Tests/CodexContinuousTests/StartupChecker.cs @@ -43,13 +43,13 @@ namespace ContinuousTests var workflow = entryPoint.Tools.CreateWorkflow(); foreach (var instance in deployment.CodexInstances) { - foreach (var container in instance.Containers.Containers) + foreach (var container in instance.Pod.Containers) { var podInfo = workflow.GetPodInfo(container); log.Log($"Codex environment variables for '{container.Name}':"); log.Log( - $"Namespace: {container.RunningContainers.StartResult.Cluster.Configuration.KubernetesNamespace} - " + - $"Pod name: {podInfo.Name} - Deployment name: {instance.Containers.StartResult.Deployment.Name}"); + $"Namespace: {container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace} - " + + $"Pod name: {podInfo.Name} - Deployment name: {instance.Pod.StartResult.Deployment.Name}"); var codexVars = container.Recipe.EnvVars; foreach (var vars in codexVars) log.Log(vars.ToString()); log.Log(""); @@ -92,7 +92,7 @@ namespace ContinuousTests private void CheckCodexNodes(BaseLog log, Configuration config) { var nodes = entryPoint.CreateInterface() - .WrapCodexContainers(config.CodexDeployment.CodexInstances.Select(i => i.Containers).ToArray()); + .WrapCodexContainers(config.CodexDeployment.CodexInstances.Select(i => i.Pod).ToArray()); var pass = true; foreach (var n in nodes) { diff --git a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs b/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs index 299c4b94..2898be20 100644 --- a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs +++ b/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs @@ -132,7 +132,7 @@ namespace CodexTests.BasicTests private const string BytesStoredMetric = "codexRepostoreBytesUsed"; - private void PerformTest(ICodexNode primary, ICodexNode secondary, RunningContainers rc) + private void PerformTest(ICodexNode primary, ICodexNode secondary, RunningPod rc) { ScopedTestFiles(() => { diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 50cb5fb2..1421fe59 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -13,7 +13,7 @@ namespace DistTestCore private const string TestsType = "dist-tests"; private readonly EntryPoint entryPoint; private readonly Dictionary metadata; - private readonly List runningContainers = new(); + private readonly List runningContainers = new(); private readonly string deployId; public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace, string deployId) @@ -65,12 +65,12 @@ namespace DistTestCore return DateTime.UtcNow - TestStart; } - public void OnContainersStarted(RunningContainers rc) + public void OnContainersStarted(RunningPod rc) { runningContainers.Add(rc); } - public void OnContainersStopped(RunningContainers rc) + public void OnContainersStopped(RunningPod rc) { runningContainers.Remove(rc); } diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 440d092a..04171083 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -122,7 +122,7 @@ namespace CodexNetDeployer }); } - private RunningContainers? DeployDiscordBot(CoreInterface ci, GethDeployment gethDeployment, + private RunningPod? DeployDiscordBot(CoreInterface ci, GethDeployment gethDeployment, CodexContractsDeployment contractsDeployment) { if (!config.DeployDiscordBot) return null; @@ -155,7 +155,7 @@ namespace CodexNetDeployer return rc; } - private RunningContainers? StartMetricsService(CoreInterface ci, List startResults) + private RunningPod? StartMetricsService(CoreInterface ci, List startResults) { if (!config.MetricsScraper || !startResults.Any()) return null; @@ -180,7 +180,7 @@ namespace CodexNetDeployer private CodexInstance CreateCodexInstance(ICodexNode node) { - return new CodexInstance(node.Container.RunningContainers, node.GetDebugInfo()); + return new CodexInstance(node.Container.RunningPod, node.GetDebugInfo()); } private string? GetKubeConfig(string kubeConfigFile) diff --git a/Tools/CodexNetDeployer/K8sHook.cs b/Tools/CodexNetDeployer/K8sHook.cs index b4c0c16a..b8c78b7c 100644 --- a/Tools/CodexNetDeployer/K8sHook.cs +++ b/Tools/CodexNetDeployer/K8sHook.cs @@ -18,11 +18,11 @@ namespace CodexNetDeployer this.metadata = metadata; } - public void OnContainersStarted(RunningContainers rc) + public void OnContainersStarted(RunningPod rc) { } - public void OnContainersStopped(RunningContainers rc) + public void OnContainersStopped(RunningPod rc) { } From 5ffe34bb83e85dcc6df23fbb03080b502c97ab59 Mon Sep 17 00:00:00 2001 From: gmega Date: Sat, 13 Apr 2024 17:20:23 +0300 Subject: [PATCH 025/142] stop crash watcher before stopping pod --- ProjectPlugins/CodexPlugin/CodexNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 54299c3c..38b8d418 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -144,8 +144,8 @@ namespace CodexPlugin public void Stop(bool waitTillStopped) { - Group.Stop(this, waitTillStopped); CrashWatcher.Stop(); + Group.Stop(this, waitTillStopped); // if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " + // "individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " + // "available for codex-nodes in groups of 1."); From a2e4869403d38ae8195b8c29df85b320ec5cad7b Mon Sep 17 00:00:00 2001 From: gmega Date: Sat, 13 Apr 2024 17:33:21 +0300 Subject: [PATCH 026/142] use long timeouts --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index b767f39b..b66345a3 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -1,4 +1,5 @@ using CodexPlugin; +using DistTestCore; using NUnit.Framework; using Utils; @@ -12,6 +13,7 @@ public class ScalabilityTests : CodexDistTest [Test] [Combinatorial] + [UseLongTimeouts] public void ShouldMaintainFileInNetwork( [Values(10, 20, 40, 80, 100)] int numberOfNodes, [Values(100, 1000, 5000, 10000)] int fileSizeInMb, From d847c4f3ec757d70c94fc1fadbfe2111a84350e7 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 14 Apr 2024 08:56:22 +0200 Subject: [PATCH 027/142] Adds names to kube wait functions --- Framework/KubernetesWorkflow/K8sController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index a17ad823..d81b87c4 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -868,7 +868,7 @@ namespace KubernetesWorkflow private void WaitUntilNamespaceCreated() { - WaitUntil(() => IsNamespaceOnline(K8sNamespace)); + WaitUntil(() => IsNamespaceOnline(K8sNamespace), nameof(WaitUntilNamespaceCreated)); } private void WaitUntilDeploymentOnline(string deploymentName) @@ -877,7 +877,7 @@ namespace KubernetesWorkflow { var deployment = client.Run(c => c.ReadNamespacedDeployment(deploymentName, K8sNamespace)); return deployment?.Status.AvailableReplicas != null && deployment.Status.AvailableReplicas > 0; - }); + }, nameof(WaitUntilDeploymentOnline)); } private void WaitUntilDeploymentOffline(string deploymentName) @@ -887,7 +887,7 @@ namespace KubernetesWorkflow var deployments = client.Run(c => c.ListNamespacedDeployment(K8sNamespace)); var deployment = deployments.Items.SingleOrDefault(d => d.Metadata.Name == deploymentName); return deployment == null || deployment.Status.AvailableReplicas == 0; - }); + }, nameof(WaitUntilDeploymentOffline)); } private void WaitUntilPodsForDeploymentAreOffline(RunningDeployment deployment) @@ -896,10 +896,10 @@ namespace KubernetesWorkflow { var pods = FindPodsByLabel(deployment.PodLabel); return !pods.Any(); - }); + }, nameof(WaitUntilPodsForDeploymentAreOffline)); } - private void WaitUntil(Func predicate) + private void WaitUntil(Func predicate, string msg) { var sw = Stopwatch.Begin(log, true); try @@ -908,7 +908,7 @@ namespace KubernetesWorkflow } finally { - sw.End("", 1); + sw.End(msg, 1); } } From 015d8da21d8487e7eeabdff124458edcc919cb7c Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 14 Apr 2024 09:17:25 +0200 Subject: [PATCH 028/142] Better logging for Time.WaitUntil. --- Framework/KubernetesWorkflow/K8sController.cs | 2 +- Framework/Utils/Time.cs | 13 ++++++++----- .../CodexContractsPlugin/CodexContractsStarter.cs | 8 ++++---- Tests/CodexTests/BasicTests/ContinuousSubstitute.cs | 2 +- Tests/CodexTests/BasicTests/NetworkIsolationTest.cs | 4 ++-- Tests/DistTestCore/Helpers/AssertHelpers.cs | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index d81b87c4..71430a7e 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -904,7 +904,7 @@ namespace KubernetesWorkflow var sw = Stopwatch.Begin(log, true); try { - Time.WaitUntil(predicate, cluster.K8sOperationTimeout(), cluster.K8sOperationRetryDelay()); + Time.WaitUntil(predicate, cluster.K8sOperationTimeout(), cluster.K8sOperationRetryDelay(), msg); } finally { diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index 82a836e6..e54ed184 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -57,24 +57,27 @@ return result; } - public static void WaitUntil(Func predicate) + public static void WaitUntil(Func predicate, string msg) { - WaitUntil(predicate, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(1)); + WaitUntil(predicate, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(1), msg); } - public static void WaitUntil(Func predicate, TimeSpan timeout, TimeSpan retryDelay) + public static void WaitUntil(Func predicate, TimeSpan timeout, TimeSpan retryDelay, string msg) { var start = DateTime.UtcNow; + var tries = 1; var state = predicate(); while (!state) { - if (DateTime.UtcNow - start > timeout) + var duration = DateTime.UtcNow - start; + if (duration > timeout) { - throw new TimeoutException("Operation timed out."); + throw new TimeoutException($"Operation timed out after {tries} tries over (total) {FormatDuration(duration)}. '{msg}'"); } Sleep(retryDelay); state = predicate(); + tries++; } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index 0137a173..d2b5c3f0 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -59,7 +59,7 @@ namespace CodexContractsPlugin var logHandler = new ContractsReadyLogHandler(tools.GetLog()); workflow.DownloadContainerLog(container, logHandler, 100); return logHandler.Found; - }); + }, nameof(DeployContract)); Log("Contracts deployed. Extracting addresses..."); var extractor = new ContractsContainerInfoExtractor(tools.GetLog(), workflow, container); @@ -71,7 +71,7 @@ namespace CodexContractsPlugin Log("Extract completed. Checking sync..."); - Time.WaitUntil(() => interaction.IsSynced(marketplaceAddress, abi)); + Time.WaitUntil(() => interaction.IsSynced(marketplaceAddress, abi), nameof(DeployContract)); Log("Synced. Codex SmartContracts deployed."); @@ -83,9 +83,9 @@ namespace CodexContractsPlugin tools.GetLog().Log(msg); } - private void WaitUntil(Func predicate) + private void WaitUntil(Func predicate, string msg) { - Time.WaitUntil(predicate, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(2)); + Time.WaitUntil(predicate, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(2), msg); } private StartupConfig CreateStartupConfig(IGethNode gethNode) diff --git a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs b/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs index 2898be20..e135d2f4 100644 --- a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs +++ b/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs @@ -154,7 +154,7 @@ namespace CodexTests.BasicTests var newBytes = Convert.ToInt64(afterBytesStored.Values.Last().Value - beforeBytesStored.Values.Last().Value); return high > newBytes && newBytes > low; - }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2)); + }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2), nameof(ContinuousSubstitute)); FileUtils.TrackedFile? downloadedFile = null; LogBytesPerMillisecond(() => downloadedFile = secondary.DownloadContent(contentId)); diff --git a/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs b/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs index ccacdba4..26045504 100644 --- a/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs +++ b/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs @@ -19,7 +19,7 @@ namespace CodexTests.BasicTests { node = Ci.StartCodexNode(); - Time.WaitUntil(() => node == null, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(5)); + Time.WaitUntil(() => node == null, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(5), nameof(SetUpANodeAndWait)); } [Test] @@ -27,7 +27,7 @@ namespace CodexTests.BasicTests { var myNode = Ci.StartCodexNode(); - Time.WaitUntil(() => node != null, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); + Time.WaitUntil(() => node != null, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5), nameof(ForeignNodeConnects)); try { diff --git a/Tests/DistTestCore/Helpers/AssertHelpers.cs b/Tests/DistTestCore/Helpers/AssertHelpers.cs index efd0749c..c0f995d3 100644 --- a/Tests/DistTestCore/Helpers/AssertHelpers.cs +++ b/Tests/DistTestCore/Helpers/AssertHelpers.cs @@ -14,7 +14,7 @@ namespace DistTestCore.Helpers Time.WaitUntil(() => { var c = constraint.Resolve(); return c.ApplyTo(actual()).IsSuccess; - }); + }, "RetryAssert: " + message); } catch (TimeoutException) { From 86074dab6a8ab72a7efde5054b9f84f3df222d4e Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 14 Apr 2024 09:29:13 +0200 Subject: [PATCH 029/142] Bump k8s operation timeout for long timeset --- Framework/Core/TimeSet.cs | 10 +++++----- Tests/DistTestCore/Configuration.cs | 2 +- Tools/CodexNetDeployer/Deployer.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Framework/Core/TimeSet.cs b/Framework/Core/TimeSet.cs index 3acd8524..0b8ba94d 100644 --- a/Framework/Core/TimeSet.cs +++ b/Framework/Core/TimeSet.cs @@ -5,7 +5,7 @@ TimeSpan HttpCallTimeout(); int HttpMaxNumberOfRetries(); TimeSpan HttpCallRetryDelay(); - TimeSpan WaitForK8sServiceDelay(); + TimeSpan K8sOperationRetryDelay(); TimeSpan K8sOperationTimeout(); } @@ -26,7 +26,7 @@ return TimeSpan.FromSeconds(1); } - public TimeSpan WaitForK8sServiceDelay() + public TimeSpan K8sOperationRetryDelay() { return TimeSpan.FromSeconds(10); } @@ -54,14 +54,14 @@ return TimeSpan.FromSeconds(2); } - public TimeSpan WaitForK8sServiceDelay() + public TimeSpan K8sOperationRetryDelay() { - return TimeSpan.FromSeconds(10); + return TimeSpan.FromSeconds(30); } public TimeSpan K8sOperationTimeout() { - return TimeSpan.FromMinutes(15); + return TimeSpan.FromHours(1); } } } diff --git a/Tests/DistTestCore/Configuration.cs b/Tests/DistTestCore/Configuration.cs index b1a94da5..39ae73ac 100644 --- a/Tests/DistTestCore/Configuration.cs +++ b/Tests/DistTestCore/Configuration.cs @@ -36,7 +36,7 @@ namespace DistTestCore var config = new KubernetesWorkflow.Configuration( kubeConfigFile: kubeConfigFile, operationTimeout: timeSet.K8sOperationTimeout(), - retryDelay: timeSet.WaitForK8sServiceDelay(), + retryDelay: timeSet.K8sOperationRetryDelay(), kubernetesNamespace: k8sNamespace ); diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 04171083..adea96d9 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -270,7 +270,7 @@ namespace CodexNetDeployer return TimeSpan.FromMinutes(10); } - public TimeSpan WaitForK8sServiceDelay() + public TimeSpan K8sOperationRetryDelay() { return TimeSpan.FromSeconds(30); } From fb1090681635b32aae9d54114a00548c7254fa39 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 14 Apr 2024 10:43:06 +0200 Subject: [PATCH 030/142] removes 20node value --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index b66345a3..364e582e 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -15,7 +15,7 @@ public class ScalabilityTests : CodexDistTest [Combinatorial] [UseLongTimeouts] public void ShouldMaintainFileInNetwork( - [Values(10, 20, 40, 80, 100)] int numberOfNodes, + [Values(10, 40, 80, 100)] int numberOfNodes, [Values(100, 1000, 5000, 10000)] int fileSizeInMb, [Values(true, false)] bool usePatchedImage ) From 3683044bf7dd87227923f9c76391b021c0d8ef66 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 14 Apr 2024 11:17:59 +0200 Subject: [PATCH 031/142] Dont download container logs --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 364e582e..4ce41bfb 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -14,6 +14,7 @@ public class ScalabilityTests : CodexDistTest [Test] [Combinatorial] [UseLongTimeouts] + [DontDownloadLogsOnFailure] public void ShouldMaintainFileInNetwork( [Values(10, 40, 80, 100)] int numberOfNodes, [Values(100, 1000, 5000, 10000)] int fileSizeInMb, From 23ebd4166b633eecb8614c4d06ee30c21ee7250c Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 15 Apr 2024 07:36:12 +0200 Subject: [PATCH 032/142] Disables downloading logs --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 2 +- Tests/DistTestCore/Configuration.cs | 3 +++ Tests/DistTestCore/DistTest.cs | 5 +++-- ...ogsOnFailureAttribute.cs => DontDownloadLogsAttribute.cs} | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) rename Tests/DistTestCore/{DontDownloadLogsOnFailureAttribute.cs => DontDownloadLogsAttribute.cs} (67%) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 4ce41bfb..79fa1451 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -14,7 +14,7 @@ public class ScalabilityTests : CodexDistTest [Test] [Combinatorial] [UseLongTimeouts] - [DontDownloadLogsOnFailure] + [DontDownloadLogs] public void ShouldMaintainFileInNetwork( [Values(10, 40, 80, 100)] int numberOfNodes, [Values(100, 1000, 5000, 10000)] int fileSizeInMb, diff --git a/Tests/DistTestCore/Configuration.cs b/Tests/DistTestCore/Configuration.cs index 39ae73ac..3fa34b5e 100644 --- a/Tests/DistTestCore/Configuration.cs +++ b/Tests/DistTestCore/Configuration.cs @@ -24,6 +24,9 @@ namespace DistTestCore this.dataFilesPath = dataFilesPath; } + /// + /// Does not override [DontDownloadLogs] attribute. + /// public bool AlwaysDownloadContainerLogs { get; set; } public KubernetesWorkflow.Configuration GetK8sConfiguration(ITimeSet timeSet, string k8sNamespace) diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 76f9d5bc..cfc31585 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -267,10 +267,11 @@ namespace DistTestCore private bool ShouldDownloadAllLogs(TestStatus testStatus) { + if (!IsDownloadingLogsEnabled()) return false; if (configuration.AlwaysDownloadContainerLogs) return true; if (testStatus == TestStatus.Failed) { - return IsDownloadingLogsEnabled(); + return true; } return false; @@ -289,7 +290,7 @@ namespace DistTestCore private bool IsDownloadingLogsEnabled() { var testProperties = TestContext.CurrentContext.Test.Properties; - return !testProperties.ContainsKey(DontDownloadLogsOnFailureAttribute.DontDownloadKey); + return !testProperties.ContainsKey(DontDownloadLogsAttribute.DontDownloadKey); } } diff --git a/Tests/DistTestCore/DontDownloadLogsOnFailureAttribute.cs b/Tests/DistTestCore/DontDownloadLogsAttribute.cs similarity index 67% rename from Tests/DistTestCore/DontDownloadLogsOnFailureAttribute.cs rename to Tests/DistTestCore/DontDownloadLogsAttribute.cs index 800b35b8..13baab8f 100644 --- a/Tests/DistTestCore/DontDownloadLogsOnFailureAttribute.cs +++ b/Tests/DistTestCore/DontDownloadLogsAttribute.cs @@ -3,11 +3,11 @@ namespace DistTestCore { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class DontDownloadLogsOnFailureAttribute : PropertyAttribute + public class DontDownloadLogsAttribute : PropertyAttribute { public const string DontDownloadKey = "DontDownloadLogs"; - public DontDownloadLogsOnFailureAttribute() + public DontDownloadLogsAttribute() : base(DontDownloadKey) { } From 700fc0ea40297da1f49d369f646d3f1c31c806e3 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 15 Apr 2024 07:57:13 +0200 Subject: [PATCH 033/142] Sets quota for codex nodes. Sets loglevel for bootstrap node. --- Framework/Utils/NumberSource.cs | 9 +++++++-- .../CodexTests/ScalabilityTests/ScalabilityTests.cs | 13 +++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Framework/Utils/NumberSource.cs b/Framework/Utils/NumberSource.cs index 2c7266fe..69d51899 100644 --- a/Framework/Utils/NumberSource.cs +++ b/Framework/Utils/NumberSource.cs @@ -2,6 +2,7 @@ { public class NumberSource { + private readonly object @lock = new object(); private int number; public NumberSource(int start) @@ -11,8 +12,12 @@ public int GetNextNumber() { - var n = number; - number++; + var n = -1; + lock (@lock) + { + n = number; + number++; + } return n; } } diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 79fa1451..3fa7e213 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -23,9 +23,14 @@ public class ScalabilityTests : CodexDistTest { CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; - var bootstrap = AddCodex(); - var nodes = AddCodex(numberOfNodes - 1, - s => s.WithBootstrapNode(bootstrap).WithLogLevel(CodexLogLevel.Info)).ToList(); + var logLevel = CodexLogLevel.Info; + + var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); + var nodes = AddCodex(numberOfNodes - 1, s => s + .WithBootstrapNode(bootstrap) + .WithLogLevel(logLevel) + .WithStorageQuota((fileSizeInMb + 50).MB()) + ).ToList(); var uploader = nodes.PickOneRandom(); var downloader = nodes.PickOneRandom(); @@ -43,4 +48,4 @@ public class ScalabilityTests : CodexDistTest downloadedFile!.AssertIsEqual(testFile); } -} \ No newline at end of file +} From 570b174a00020585c295b68efd8e6c88ddb51f2f Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 15 Apr 2024 08:12:57 +0200 Subject: [PATCH 034/142] Adds everyone-test-a-file test --- .../ScalabilityTests/ScalabilityTests.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 3fa7e213..0940e34a 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -1,5 +1,6 @@ using CodexPlugin; using DistTestCore; +using FileUtils; using NUnit.Framework; using Utils; @@ -11,6 +12,10 @@ public class ScalabilityTests : CodexDistTest private const string PatchedImage = "codexstorage/nim-codex:sha-9aeac06-dist-tests"; private const string MasterImage = "codexstorage/nim-codex:sha-5380912-dist-tests"; + /// + /// We upload a file to node A, then download it with B. + /// Then we stop node A, and download again with node C. + /// [Test] [Combinatorial] [UseLongTimeouts] @@ -48,4 +53,76 @@ public class ScalabilityTests : CodexDistTest downloadedFile!.AssertIsEqual(testFile); } + + /// + /// We upload a file to each node, to put a more wide-spread load on the network. + /// Then we run the same test as ShouldMaintainFileInNetwork. + /// + [Test] + [Combinatorial] + [UseLongTimeouts] + [DontDownloadLogs] + public void EveryoneGetsAFile( + [Values(10, 40, 80, 100)] int numberOfNodes, + [Values(100, 1000)] int fileSizeInMb, + [Values(true, false)] bool usePatchedImage + ) + { + CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; + + var logLevel = CodexLogLevel.Info; + + var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); + var nodes = AddCodex(numberOfNodes - 1, s => s + .WithBootstrapNode(bootstrap) + .WithLogLevel(logLevel) + .WithStorageQuota((fileSizeInMb + 50).MB()) + ).ToList(); + + var pairTasks = nodes.Select(n => + { + return Task.Run(() => + { + var file = GenerateTestFile(fileSizeInMb.MB()); + var cid = n.UploadFile(file); + return new NodeFilePair(n, file, cid); + }); + }); + + var pairs = pairTasks.Select(t => Time.Wait(t)).ToList(); + + RunDoubleDownloadTest( + pairs.PickOneRandom(), + pairs.PickOneRandom(), + pairs.PickOneRandom() + ); + } + + private void RunDoubleDownloadTest(NodeFilePair source, NodeFilePair dl1, NodeFilePair dl2) + { + var expectedFile = source.File; + var cid = source.Cid; + + var file1 = dl1.Node.DownloadContent(cid); + file1!.AssertIsEqual(expectedFile); + + source.Node.Stop(true); + + var file2 = dl2.Node.DownloadContent(cid); + file2!.AssertIsEqual(expectedFile); + } + + public class NodeFilePair + { + public NodeFilePair(ICodexNode node, TrackedFile file, ContentId cid) + { + Node = node; + File = file; + Cid = cid; + } + + public ICodexNode Node { get; } + public TrackedFile File { get; } + public ContentId Cid { get; } + } } From 630dc2814ae0e2ce1d121b17fbd2aaa8fa65297d Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 15 Apr 2024 08:13:43 +0200 Subject: [PATCH 035/142] disable new test --- Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 0940e34a..598f0bf2 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -58,6 +58,7 @@ public class ScalabilityTests : CodexDistTest /// We upload a file to each node, to put a more wide-spread load on the network. /// Then we run the same test as ShouldMaintainFileInNetwork. /// + [Ignore("Make ShouldMaintainFileInNetwork pass reliably first.")] [Test] [Combinatorial] [UseLongTimeouts] From eed989cbf52398a0c3de1b0f6a5f5f7a1ae740ea Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 15 Apr 2024 11:37:14 +0200 Subject: [PATCH 036/142] Handle errors during log download --- Tests/DistTestCore/DistTest.cs | 2 +- Tests/DistTestCore/TestLifecycle.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index cfc31585..b6c0ba96 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -98,7 +98,7 @@ namespace DistTestCore } catch (Exception ex) { - fixtureLog.Error("Cleanup failed: " + ex.Message); + fixtureLog.Error("Cleanup failed: " + ex); GlobalTestFailure.HasFailed = true; } } diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 1421fe59..542ca27c 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -93,13 +93,20 @@ namespace DistTestCore public void DownloadAllLogs() { - foreach (var rc in runningContainers) + try { - foreach (var c in rc.Containers) + foreach (var rc in runningContainers) { - CoreInterface.DownloadLog(c); + foreach (var c in rc.Containers) + { + CoreInterface.DownloadLog(c); + } } } + catch (Exception ex) + { + Log.Error("Exception during log download: " + ex); + } } } } From c856f404e3fe8753e70c94a965c2419b4d523f96 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 19 Apr 2024 11:40:32 +0200 Subject: [PATCH 037/142] Adds test to show from which hosts blocks are downloaded. --- Framework/Core/DownloadedLog.cs | 14 ++++ .../MultiPeerDownloadTests.cs | 72 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs diff --git a/Framework/Core/DownloadedLog.cs b/Framework/Core/DownloadedLog.cs index 923bcbc8..fa4d5597 100644 --- a/Framework/Core/DownloadedLog.cs +++ b/Framework/Core/DownloadedLog.cs @@ -4,6 +4,7 @@ namespace Core { public interface IDownloadedLog { + void IterateLines(Action action); string[] GetLinesContaining(string expectedString); string[] FindLinesThatContain(params string[] tags); void DeleteFile(); @@ -18,6 +19,19 @@ namespace Core this.logFile = logFile; } + public void IterateLines(Action action) + { + using var file = File.OpenRead(logFile.FullFilename); + using var streamReader = new StreamReader(file); + + var line = streamReader.ReadLine(); + while (line != null) + { + action(line); + line = streamReader.ReadLine(); + } + } + public string[] GetLinesContaining(string expectedString) { using var file = File.OpenRead(logFile.FullFilename); diff --git a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs new file mode 100644 index 00000000..2ff5103d --- /dev/null +++ b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs @@ -0,0 +1,72 @@ +using NUnit.Framework; +using Utils; + +namespace CodexTests.ScalabilityTests +{ + [TestFixture] + public class MultiPeerDownloadTests : AutoBootstrapDistTest + { + [Test] + public void MultiPeerDownload() + { + var hosts = AddCodex(5, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); + var file = GenerateTestFile(100.MB()); + var cid = hosts[0].UploadFile(file); + + var uploadLog = Ci.DownloadLog(hosts[0]); + var blockCids = uploadLog + .FindLinesThatContain("Putting block into network store") + .Select(s => + { + var start = s.IndexOf("cid=") + 4; + var end = s.IndexOf(" count="); + var len = end - start; + return s.Substring(start, len); + }) + .ToArray(); + + // Each host has the file. + foreach (var h in hosts) h.DownloadContent(cid); + + var client = AddCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); + var resultFile = client.DownloadContent(cid); + resultFile!.AssertIsEqual(file); + + var downloadLog = Ci.DownloadLog(client); + var blocksPerHost = new Dictionary(); + var seenBlocks = new List(); + var host = string.Empty; + downloadLog.IterateLines(line => + { + if (line.Contains("peer=") && line.Contains(" len=")) + { + var start = line.IndexOf("peer=") + 5; + var end = line.IndexOf(" len="); + var len = end - start; + host = line.Substring(start, len); + } + else if (!string.IsNullOrEmpty(host) && line.Contains("Storing block with key")) + { + var start = line.IndexOf("cid=") + 4; + var end = line.IndexOf(" count="); + var len = end - start; + var blockCid = line.Substring(start, len); + + if (!seenBlocks.Contains(blockCid)) + { + seenBlocks.Add(blockCid); + if (!blocksPerHost.ContainsKey(host)) blocksPerHost.Add(host, 1); + else blocksPerHost[host]++; + } + } + }); + + Log("Total number of blocks in dataset: " + blockCids.Length); + Log("Blocks fetched per host:"); + foreach (var pair in blocksPerHost) + { + Log($"Host: {pair.Key} = {pair.Value}"); + } + } + } +} From c4c3f61a23488b9773b697c14ffb5b92048f80fa Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 19 Apr 2024 11:52:39 +0200 Subject: [PATCH 038/142] parameterizes tests --- ProjectPlugins/CodexPlugin/CodexNode.cs | 2 +- .../ScalabilityTests/MultiPeerDownloadTests.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 38b8d418..36fb1dc0 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -73,7 +73,7 @@ namespace CodexPlugin public string GetName() { - return CodexAccess.Container.Name; + return Container.Name; } public DebugInfo GetDebugInfo() diff --git a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs index 2ff5103d..3c4e4822 100644 --- a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs +++ b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs @@ -7,10 +7,14 @@ namespace CodexTests.ScalabilityTests public class MultiPeerDownloadTests : AutoBootstrapDistTest { [Test] - public void MultiPeerDownload() + [Combinatorial] + public void MultiPeerDownload( + [Values(5, 10, 20)] int numberOfHosts, + [Values(100, 1000)] int fileSize + ) { - var hosts = AddCodex(5, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); - var file = GenerateTestFile(100.MB()); + var hosts = AddCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); + var file = GenerateTestFile(fileSize.MB()); var cid = hosts[0].UploadFile(file); var uploadLog = Ci.DownloadLog(hosts[0]); From 7ec99347515d532034efacdf58d2381fda5d16ff Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 22 Apr 2024 11:17:47 +0200 Subject: [PATCH 039/142] Update to multipeer download test --- .../MultiPeerDownloadTests.cs | 68 +++++++++++++++---- Tests/DistTestCore/DistTest.cs | 19 ++++-- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs index 3c4e4822..f4c0a5f0 100644 --- a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs +++ b/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using DistTestCore; +using NUnit.Framework; using Utils; namespace CodexTests.ScalabilityTests @@ -7,6 +8,8 @@ namespace CodexTests.ScalabilityTests public class MultiPeerDownloadTests : AutoBootstrapDistTest { [Test] + [DontDownloadLogs] + [UseLongTimeouts] [Combinatorial] public void MultiPeerDownload( [Values(5, 10, 20)] int numberOfHosts, @@ -16,8 +19,10 @@ namespace CodexTests.ScalabilityTests var hosts = AddCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); var file = GenerateTestFile(fileSize.MB()); var cid = hosts[0].UploadFile(file); + var tailOfManifestCid = cid.Id.Substring(cid.Id.Length - 6); var uploadLog = Ci.DownloadLog(hosts[0]); + var expectedNumberOfBlocks = RoundUp(fileSize.MB().SizeInBytes, 64.KB().SizeInBytes) + 1; // +1 for manifest block. var blockCids = uploadLog .FindLinesThatContain("Putting block into network store") .Select(s => @@ -29,7 +34,8 @@ namespace CodexTests.ScalabilityTests }) .ToArray(); - // Each host has the file. + Assert.That(blockCids.Length, Is.EqualTo(expectedNumberOfBlocks)); + foreach (var h in hosts) h.DownloadContent(cid); var client = AddCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); @@ -37,9 +43,8 @@ namespace CodexTests.ScalabilityTests resultFile!.AssertIsEqual(file); var downloadLog = Ci.DownloadLog(client); - var blocksPerHost = new Dictionary(); - var seenBlocks = new List(); var host = string.Empty; + var blockCidHostMap = new Dictionary(); downloadLog.IterateLines(line => { if (line.Contains("peer=") && line.Contains(" len=")) @@ -56,21 +61,60 @@ namespace CodexTests.ScalabilityTests var len = end - start; var blockCid = line.Substring(start, len); - if (!seenBlocks.Contains(blockCid)) - { - seenBlocks.Add(blockCid); - if (!blocksPerHost.ContainsKey(host)) blocksPerHost.Add(host, 1); - else blocksPerHost[host]++; - } + blockCidHostMap.Add(blockCid, host); + host = string.Empty; } }); - Log("Total number of blocks in dataset: " + blockCids.Length); + var totalFetched = blockCidHostMap.Count(p => !string.IsNullOrEmpty(p.Value)); + //PrintFullMap(blockCidHostMap); + PrintOverview(blockCidHostMap); + + Log("Expected number of blocks: " + expectedNumberOfBlocks); + Log("Total number of block CIDs found in dataset + manifest block: " + blockCids.Length); + Log("Total blocks fetched by hosts: " + totalFetched); + Assert.That(totalFetched, Is.EqualTo(expectedNumberOfBlocks)); + } + + private void PrintOverview(Dictionary blockCidHostMap) + { + var overview = new Dictionary(); + foreach (var pair in blockCidHostMap) + { + if (!overview.ContainsKey(pair.Value)) overview.Add(pair.Value, 1); + else overview[pair.Value]++; + } + Log("Blocks fetched per host:"); - foreach (var pair in blocksPerHost) + foreach (var pair in overview) { Log($"Host: {pair.Key} = {pair.Value}"); } } + + private void PrintFullMap(Dictionary blockCidHostMap) + { + Log("Per block, host it was fetched from:"); + foreach (var pair in blockCidHostMap) + { + if (string.IsNullOrEmpty(pair.Value)) + { + Log($"block: {pair.Key} = Not seen"); + } + else + { + Log($"block: {pair.Key} = '{pair.Value}'"); + } + } + } + + private long RoundUp(long filesize, long blockSize) + { + double f = filesize; + double b = blockSize; + + var result = Math.Ceiling(f / b); + return Convert.ToInt64(result); + } } } diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index b6c0ba96..ed99fe94 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -236,9 +236,19 @@ namespace DistTestCore } private bool ShouldUseLongTimeouts() + { + return CurrentTestMethodHasAttribute(); + } + + private bool HasDontDownloadAttribute() + { + return CurrentTestMethodHasAttribute(); + } + + private bool CurrentTestMethodHasAttribute() where T : PropertyAttribute { // Don't be fooled! TestContext.CurrentTest.Test allows you easy access to the attributes of the current test. - // But this doesn't work for tests making use of [TestCase]. So instead, we use reflection here to figure out + // But this doesn't work for tests making use of [TestCase] or [Combinatorial]. So instead, we use reflection here to figure out // if the attribute is present. var currentTest = TestContext.CurrentContext.Test; var className = currentTest.ClassName; @@ -247,7 +257,7 @@ namespace DistTestCore var testClasses = testAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray(); var testMethods = testClasses.SelectMany(c => c.GetMethods()).Where(m => m.Name == methodName).ToArray(); - return testMethods.Any(m => m.GetCustomAttribute() != null); + return testMethods.Any(m => m.GetCustomAttribute() != null); } private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) @@ -267,8 +277,8 @@ namespace DistTestCore private bool ShouldDownloadAllLogs(TestStatus testStatus) { - if (!IsDownloadingLogsEnabled()) return false; if (configuration.AlwaysDownloadContainerLogs) return true; + if (!IsDownloadingLogsEnabled()) return false; if (testStatus == TestStatus.Failed) { return true; @@ -289,8 +299,7 @@ namespace DistTestCore private bool IsDownloadingLogsEnabled() { - var testProperties = TestContext.CurrentContext.Test.Properties; - return !testProperties.ContainsKey(DontDownloadLogsAttribute.DontDownloadKey); + return !HasDontDownloadAttribute(); } } From 58f7f9384af4ef757c93276be941da354cc821d3 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 23 Apr 2024 09:10:10 +0200 Subject: [PATCH 040/142] Test for disc speeds --- Framework/Utils/Formatter.cs | 6 +- .../ScalabilityTests/ClusterSpeedTests.cs | 83 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs diff --git a/Framework/Utils/Formatter.cs b/Framework/Utils/Formatter.cs index 1ea15503..7422c84e 100644 --- a/Framework/Utils/Formatter.cs +++ b/Framework/Utils/Formatter.cs @@ -1,4 +1,6 @@ -namespace Utils +using System.Globalization; + +namespace Utils { public static class Formatter { @@ -10,7 +12,7 @@ var sizeOrder = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); var digit = Math.Round(bytes / Math.Pow(1024, sizeOrder), 1); - return digit.ToString() + sizeSuffixes[sizeOrder]; + return digit.ToString(CultureInfo.InvariantCulture) + sizeSuffixes[sizeOrder]; } } } diff --git a/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs b/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs new file mode 100644 index 00000000..d8b27b10 --- /dev/null +++ b/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs @@ -0,0 +1,83 @@ +using DistTestCore; +using Logging; +using NUnit.Framework; +using Utils; + +namespace CodexTests.ScalabilityTests +{ + [TestFixture] + public class ClusterDiscSpeedTests : DistTest + { + private readonly Random random = new Random(); + + [Test] + [Combinatorial] + public void DiscSpeedTest( + [Values(1, 10, 100, 1024, 1024 * 10, 1024 * 100, 1024 * 1024)] int bufferSizeKb + ) + { + long targetSize = (long)(1024 * 1024 * 1024) * 2; + long bufferSizeBytes = ((long)bufferSizeKb) * 1024; + + var filename = nameof(DiscSpeedTest); + + Thread.Sleep(1000); + if (File.Exists(filename)) File.Delete(filename); + Thread.Sleep(1000); + var writeSpeed = PerformWrite(targetSize, bufferSizeBytes, filename); + Thread.Sleep(1000); + var readSpeed = PerformRead(targetSize, bufferSizeBytes, filename); + + Log($"Write speed: {writeSpeed} per second."); + Log($"Read speed: {writeSpeed} per second."); + } + + private ByteSize PerformWrite(long targetSize, long bufferSizeBytes, string filename) + { + long bytesWritten = 0; + var buffer = new byte[bufferSizeBytes]; + random.NextBytes(buffer); + + var sw = Stopwatch.Begin(GetTestLog()); + using (var stream = File.OpenWrite(filename)) + { + while (bytesWritten < targetSize) + { + long remaining = targetSize - bytesWritten; + long toWrite = Math.Min(bufferSizeBytes, remaining); + + stream.Write(buffer, 0, Convert.ToInt32(toWrite)); + bytesWritten += toWrite; + } + } + var duration = sw.End("WriteTime"); + double totalSeconds = duration.TotalSeconds; + double totalBytes = bytesWritten; + double bytesPerSecond = totalBytes / totalSeconds; + return new ByteSize(Convert.ToInt64(bytesPerSecond)); + } + + private ByteSize PerformRead(long targetSize, long bufferSizeBytes, string filename) + { + long bytesRead = 0; + var buffer = new byte[bufferSizeBytes]; + var sw = Stopwatch.Begin(GetTestLog()); + using (var stream = File.OpenRead(filename)) + { + while (bytesRead < targetSize) + { + long remaining = targetSize - bytesRead; + long toRead = Math.Min(bufferSizeBytes, remaining); + + var r = stream.Read(buffer, 0, Convert.ToInt32(toRead)); + bytesRead += r; + } + } + var duration = sw.End("ReadTime"); + double totalSeconds = duration.TotalSeconds; + double totalBytes = bytesRead; + double bytesPerSecond = totalBytes / totalSeconds; + return new ByteSize(Convert.ToInt64(bytesPerSecond)); + } + } +} From 6545f3469f54b72d05aeb3f1a8aad3ef23355270 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 23 Apr 2024 13:16:11 +0200 Subject: [PATCH 041/142] dumb mistake by me --- Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs b/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs index d8b27b10..0e3fdafd 100644 --- a/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs @@ -21,15 +21,15 @@ namespace CodexTests.ScalabilityTests var filename = nameof(DiscSpeedTest); - Thread.Sleep(1000); + Thread.Sleep(2000); if (File.Exists(filename)) File.Delete(filename); - Thread.Sleep(1000); + Thread.Sleep(2000); var writeSpeed = PerformWrite(targetSize, bufferSizeBytes, filename); - Thread.Sleep(1000); + Thread.Sleep(2000); var readSpeed = PerformRead(targetSize, bufferSizeBytes, filename); Log($"Write speed: {writeSpeed} per second."); - Log($"Read speed: {writeSpeed} per second."); + Log($"Read speed: {readSpeed} per second."); } private ByteSize PerformWrite(long targetSize, long bufferSizeBytes, string filename) From a6379d02f1f124ccff2a99787123a45d3f87196d Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 24 Apr 2024 09:41:05 +0200 Subject: [PATCH 042/142] Adds test for upload/download large file with single node --- .../OneClientLargeFileTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs diff --git a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs new file mode 100644 index 00000000..76c8fdff --- /dev/null +++ b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs @@ -0,0 +1,36 @@ +using CodexPlugin; +using DistTestCore; +using NUnit.Framework; +using Utils; + +namespace CodexTests.ScalabilityTests +{ + [TestFixture] + public class OneClientLargeFileTests : CodexDistTest + { + [Test] + [Combinatorial] + [UseLongTimeouts] + public void OneClientLargeFile([Values( + 256, + 512, + 1024, // GB + 2048, + 4096, + 8192, + 16384, + 32768, + 65536, + 131072 + )] int sizeMb) + { + var testFile = GenerateTestFile(sizeMb.MB()); + + var node = AddCodex(s => s.WithLogLevel(CodexLogLevel.Warn)); + var contentId = node.UploadFile(testFile); + var downloadedFile = node.DownloadContent(contentId); + + testFile.AssertIsEqual(downloadedFile); + } + } +} From 50b7e2300db3180ac0307d5d67a3043a815ed4bd Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Apr 2024 09:25:00 +0200 Subject: [PATCH 043/142] sets quota --- Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs index 76c8fdff..75dccbe3 100644 --- a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs +++ b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs @@ -26,7 +26,10 @@ namespace CodexTests.ScalabilityTests { var testFile = GenerateTestFile(sizeMb.MB()); - var node = AddCodex(s => s.WithLogLevel(CodexLogLevel.Warn)); + var node = AddCodex(s => s + .WithLogLevel(CodexLogLevel.Warn) + .WithStorageQuota((sizeMb + 10).MB()) + ); var contentId = node.UploadFile(testFile); var downloadedFile = node.DownloadContent(contentId); From 0c700ded9d18a4fbf3e6842eae216e5b52798fa8 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Apr 2024 10:25:12 +0200 Subject: [PATCH 044/142] Test for detecting common log lines --- Tests/CodexTests/BasicTests/LogHelperTests.cs | 61 +++++++++++++++++++ Tests/CodexTests/BasicTests/OneClientTests.cs | 7 +-- 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 Tests/CodexTests/BasicTests/LogHelperTests.cs diff --git a/Tests/CodexTests/BasicTests/LogHelperTests.cs b/Tests/CodexTests/BasicTests/LogHelperTests.cs new file mode 100644 index 00000000..d0b623a2 --- /dev/null +++ b/Tests/CodexTests/BasicTests/LogHelperTests.cs @@ -0,0 +1,61 @@ +using CodexPlugin; +using NUnit.Framework; +using Utils; + +namespace CodexTests.BasicTests +{ + [TestFixture] + public class LogHelperTests : CodexDistTest + { + [Test] + public void FindMostCommonLogMessages() + { + var node = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); + + + node.UploadFile(GenerateTestFile(1.GB())); + + + var map = GetLogMap(node).OrderByDescending(p => p.Value); + foreach (var entry in map) + { + Log($"'{entry.Key}' = {entry.Value}"); + } + } + + private Dictionary GetLogMap(ICodexNode node) + { + var log = Ci.DownloadLog(node); + var map = new Dictionary(); + log.IterateLines(line => + { + if (string.IsNullOrEmpty(line) || + !line.Contains(" ") || + !line.Contains("=") || + line.Length < 34 || + line[33] != ' ' + ) return; + + // "INF 2024-04-14 10:40:50.042+00:00 Creating a private key and saving it tid=1 count=2" + var start = 34; + var msg = line.Substring(start); + + // "Creating a private key and saving it tid=1 count=2" + var firstEqualSign = msg.IndexOf("="); + msg = msg.Substring(0, firstEqualSign); + + // "Creating a private key and saving it tid" + var lastSpace = msg.LastIndexOf(" "); + msg = msg.Substring(0, lastSpace); + + // "Creating a private key and saving it " + msg = msg.Trim(); + + // "Creating a private key and saving it" + if (map.ContainsKey(msg)) map[msg] += 1; + else map.Add(msg, 1); + }); + return map; + } + } +} diff --git a/Tests/CodexTests/BasicTests/OneClientTests.cs b/Tests/CodexTests/BasicTests/OneClientTests.cs index d8045862..9d4f5a58 100644 --- a/Tests/CodexTests/BasicTests/OneClientTests.cs +++ b/Tests/CodexTests/BasicTests/OneClientTests.cs @@ -1,5 +1,4 @@ using CodexPlugin; -using DistTestCore; using NUnit.Framework; using Utils; @@ -11,7 +10,7 @@ namespace CodexTests.BasicTests [Test] public void OneClientTest() { - var primary = Ci.StartCodexNode(); + var primary = AddCodex(); PerformOneClientTest(primary); } @@ -19,11 +18,11 @@ namespace CodexTests.BasicTests [Test] public void RestartTest() { - var primary = Ci.StartCodexNode(); + var primary = AddCodex(); primary.Stop(waitTillStopped: true); - primary = Ci.StartCodexNode(); + primary = AddCodex(); PerformOneClientTest(primary); } From 15d7e6483e406183dbf0b1ec2ead1740bee8414b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Apr 2024 10:57:19 +0200 Subject: [PATCH 045/142] Allows parsing longs only from a certain timestamp --- Tests/CodexTests/BasicTests/LogHelperTests.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Tests/CodexTests/BasicTests/LogHelperTests.cs b/Tests/CodexTests/BasicTests/LogHelperTests.cs index d0b623a2..2e978012 100644 --- a/Tests/CodexTests/BasicTests/LogHelperTests.cs +++ b/Tests/CodexTests/BasicTests/LogHelperTests.cs @@ -5,25 +5,32 @@ using Utils; namespace CodexTests.BasicTests { [TestFixture] - public class LogHelperTests : CodexDistTest + public class LogHelperTests : AutoBootstrapDistTest { [Test] public void FindMostCommonLogMessages() { - var node = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); + var uploader = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); + var downloader = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); - node.UploadFile(GenerateTestFile(1.GB())); + var cid = uploader.UploadFile(GenerateTestFile(1.MB())); + + Thread.Sleep(1000); + var logStartUtc = DateTime.UtcNow; + Thread.Sleep(1000); + + downloader.DownloadContent(cid); - var map = GetLogMap(node).OrderByDescending(p => p.Value); + var map = GetLogMap(uploader, logStartUtc).OrderByDescending(p => p.Value); foreach (var entry in map) { Log($"'{entry.Key}' = {entry.Value}"); } } - private Dictionary GetLogMap(ICodexNode node) + private Dictionary GetLogMap(ICodexNode node, DateTime? startUtc = null) { var log = Ci.DownloadLog(node); var map = new Dictionary(); @@ -35,6 +42,13 @@ namespace CodexTests.BasicTests line.Length < 34 || line[33] != ' ' ) return; + + if (startUtc.HasValue) + { + var timestampLine = line.Substring(4, 23); + var timestamp = DateTime.Parse(timestampLine); + if (timestamp < startUtc) return; + } // "INF 2024-04-14 10:40:50.042+00:00 Creating a private key and saving it tid=1 count=2" var start = 34; From 9a6866cf8e9b0b5a0fd064f5526a8e3a37172ffe Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Apr 2024 15:04:02 +0200 Subject: [PATCH 046/142] clean up logs in Codex --- Tests/CodexTests/BasicTests/LogHelperTests.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/CodexTests/BasicTests/LogHelperTests.cs b/Tests/CodexTests/BasicTests/LogHelperTests.cs index 2e978012..33c509cd 100644 --- a/Tests/CodexTests/BasicTests/LogHelperTests.cs +++ b/Tests/CodexTests/BasicTests/LogHelperTests.cs @@ -10,11 +10,11 @@ namespace CodexTests.BasicTests [Test] public void FindMostCommonLogMessages() { - var uploader = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); - var downloader = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace)); + var uploader = AddCodex(s => s.WithName("uploader").WithLogLevel(CodexLogLevel.Trace)); + var downloader = AddCodex(s => s.WithName("downloader").WithLogLevel(CodexLogLevel.Trace)); - var cid = uploader.UploadFile(GenerateTestFile(1.MB())); + var cid = uploader.UploadFile(GenerateTestFile(100.MB())); Thread.Sleep(1000); var logStartUtc = DateTime.UtcNow; @@ -23,10 +23,14 @@ namespace CodexTests.BasicTests downloader.DownloadContent(cid); - var map = GetLogMap(uploader, logStartUtc).OrderByDescending(p => p.Value); + var map = GetLogMap(downloader, logStartUtc).OrderByDescending(p => p.Value); + Log("Downloader - Receive"); foreach (var entry in map) { - Log($"'{entry.Key}' = {entry.Value}"); + if (entry.Value > 9) + { + Log($"'{entry.Key}' = {entry.Value}"); + } } } From 52cec0e9f3c7b059922bab3d9f8ce870669e6d56 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 26 Apr 2024 10:43:36 +0200 Subject: [PATCH 047/142] bump codex image --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 7203aa3d..15863dca 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1524803-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-3041f5f-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; From a20e9a30cf47ebfb68ee87df165f225a86985457 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 26 Apr 2024 11:10:14 +0200 Subject: [PATCH 048/142] bumps to image with proof support --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 15863dca..673557e1 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-3041f5f-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-bb0829e-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; From ce7ba47a32cdf126926f1e814e2853aaab56e434 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 27 Apr 2024 11:33:13 +0200 Subject: [PATCH 049/142] trying to detect upload slowdown related to number of blocks in node --- .../OneClientLargeFileTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs index 75dccbe3..a8693c90 100644 --- a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs +++ b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs @@ -1,5 +1,6 @@ using CodexPlugin; using DistTestCore; +using Logging; using NUnit.Framework; using Utils; @@ -35,5 +36,36 @@ namespace CodexTests.ScalabilityTests testFile.AssertIsEqual(downloadedFile); } + + [Test] + public void ManyFiles() + { + // I suspect that the upload speed is linked to the total + // number of blocks already in the node. I suspect the + // metadata store to be the cause of any slow-down. + // Using this test to detect and quantify the numbers. + + var node = AddCodex(s => s + .WithLogLevel(CodexLogLevel.Trace) + .WithStorageQuota(20.GB()) + ); + + var times = new List(); + for (var i = 0; i < 100; i++) + { + Thread.Sleep(1000); + var file = GenerateTestFile(100.MB()); + times.Add(Stopwatch.Measure(GetTestLog(), "Upload_" + i, () => + { + node.UploadFile(file); + })); + } + + Log("Upload times:"); + foreach (var t in times) + { + Log(Time.FormatDuration(t)); + } + } } } From 84a68521b02abbc01a2bffc1b9e14c8d99cb64a0 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 27 Apr 2024 14:20:31 +0200 Subject: [PATCH 050/142] sets up parser for codex log lines --- ProjectPlugins/CodexPlugin/CodexLogLine.cs | 105 ++++++++++++++++++ Tests/CodexTests/BasicTests/LogHelperTests.cs | 32 +----- 2 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 ProjectPlugins/CodexPlugin/CodexLogLine.cs diff --git a/ProjectPlugins/CodexPlugin/CodexLogLine.cs b/ProjectPlugins/CodexPlugin/CodexLogLine.cs new file mode 100644 index 00000000..d2758145 --- /dev/null +++ b/ProjectPlugins/CodexPlugin/CodexLogLine.cs @@ -0,0 +1,105 @@ +using System.Globalization; + +namespace CodexPlugin +{ + public class CodexLogLine + { + public static CodexLogLine? Parse(string line) + { + try + { + if (string.IsNullOrEmpty(line) || + line.Length < 34 || + line[3] != ' ' || + line[33] != ' ') return null; + + line = line.Replace(Environment.NewLine, string.Empty); + + var level = line.Substring(0, 3); + var dtLine = line.Substring(4, 23); + + var firstEqualSign = line.IndexOf('='); + var msgStart = 34; + var msgEnd = line.Substring(0, firstEqualSign).LastIndexOf(' '); + var msg = line.Substring(msgStart, msgEnd - msgStart).Trim(); + var attrsLine = line.Substring(msgEnd); + + var attrs = SplitAttrs(attrsLine); + + var format = "yyyy-MM-dd HH:mm:ss.fff"; + var dt = DateTime.ParseExact(dtLine, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToUniversalTime(); + + return new CodexLogLine() + { + LogLevel = level, + TimestampUtc = dt, + Message = msg, + Attributes = attrs + }; + } + catch + { + return null; + } + } + + public string LogLevel { get; set; } = string.Empty; + public DateTime TimestampUtc { get; set; } + public string Message { get; set; } = string.Empty; + public Dictionary Attributes { get; private set; } = new Dictionary(); + + /// + /// After too much time spent cursing at regexes, here's what I got: + /// Parses input string into 'key=value' pair, considerate of quoted (") values. + /// + private static Dictionary SplitAttrs(string input) + { + input += " "; + var result = new Dictionary(); + + var key = string.Empty; + var value = string.Empty; + var mode = 1; + var inQuote = false; + + foreach (var c in input) + { + if (mode == 1) + { + if (c == '=') mode = 2; + else if (c == ' ') + { + if (string.IsNullOrEmpty(key)) continue; + else + { + result.Add(key, string.Empty); + key = string.Empty; + value = string.Empty; + } + } + else key += c; + } + else if (mode == 2) + { + if (c == ' ' && !inQuote) + { + result.Add(key, value); + key = string.Empty; + value = string.Empty; + mode = 1; + } + else if (c == '\"') + { + inQuote = !inQuote; + } + else + { + value += c; + } + } + } + + return result; + } + } +} diff --git a/Tests/CodexTests/BasicTests/LogHelperTests.cs b/Tests/CodexTests/BasicTests/LogHelperTests.cs index 33c509cd..8b6ba5e6 100644 --- a/Tests/CodexTests/BasicTests/LogHelperTests.cs +++ b/Tests/CodexTests/BasicTests/LogHelperTests.cs @@ -40,38 +40,16 @@ namespace CodexTests.BasicTests var map = new Dictionary(); log.IterateLines(line => { - if (string.IsNullOrEmpty(line) || - !line.Contains(" ") || - !line.Contains("=") || - line.Length < 34 || - line[33] != ' ' - ) return; + var log = CodexLogLine.Parse(line); + if (log == null) return; if (startUtc.HasValue) { - var timestampLine = line.Substring(4, 23); - var timestamp = DateTime.Parse(timestampLine); - if (timestamp < startUtc) return; + if (log.TimestampUtc < startUtc) return; } - // "INF 2024-04-14 10:40:50.042+00:00 Creating a private key and saving it tid=1 count=2" - var start = 34; - var msg = line.Substring(start); - - // "Creating a private key and saving it tid=1 count=2" - var firstEqualSign = msg.IndexOf("="); - msg = msg.Substring(0, firstEqualSign); - - // "Creating a private key and saving it tid" - var lastSpace = msg.LastIndexOf(" "); - msg = msg.Substring(0, lastSpace); - - // "Creating a private key and saving it " - msg = msg.Trim(); - - // "Creating a private key and saving it" - if (map.ContainsKey(msg)) map[msg] += 1; - else map.Add(msg, 1); + if (map.ContainsKey(log.Message)) map[log.Message] += 1; + else map.Add(log.Message, 1); }); return map; } From f38f7861a0b0ee801e99b2f624e816c7af5887e3 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 27 Apr 2024 14:33:04 +0200 Subject: [PATCH 051/142] Log comparing test --- .../OneClientLargeFileTests.cs | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs index a8693c90..3e87e1de 100644 --- a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs +++ b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs @@ -50,15 +50,39 @@ namespace CodexTests.ScalabilityTests .WithStorageQuota(20.GB()) ); + var startUtc = DateTime.UtcNow; + var endUtc = DateTime.UtcNow; + + var fastMap = new Dictionary(); + var slowMap = new Dictionary(); + var times = new List(); for (var i = 0; i < 100; i++) { Thread.Sleep(1000); var file = GenerateTestFile(100.MB()); - times.Add(Stopwatch.Measure(GetTestLog(), "Upload_" + i, () => + startUtc = DateTime.UtcNow; + var duration = Stopwatch.Measure(GetTestLog(), "Upload_" + i, () => { node.UploadFile(file); - })); + }); + times.Add(duration); + endUtc = DateTime.UtcNow; + + // We collect the log of the node during the upload. + // We count the line occurances. + // If the upload was fast, add it to the fast-map. + // If it was slow, add it to the slow-map. + // After the test, we can compare and hopefully see what the node was doing during the slow uploads + // that it wasn't doing during the fast ones. + if (duration.TotalSeconds < 12) + { + AddToLogMap(fastMap, node, startUtc, endUtc); + } + else if (duration.TotalSeconds > 25) + { + AddToLogMap(slowMap, node, startUtc, endUtc); + } } Log("Upload times:"); @@ -66,6 +90,37 @@ namespace CodexTests.ScalabilityTests { Log(Time.FormatDuration(t)); } + Log("Fast map:"); + foreach (var entry in fastMap.OrderByDescending(p => p.Value)) + { + if (entry.Value > 9) + { + Log($"'{entry.Key}' = {entry.Value}"); + } + } + Log("Slow map:"); + foreach (var entry in slowMap.OrderByDescending(p => p.Value)) + { + if (entry.Value > 9) + { + Log($"'{entry.Key}' = {entry.Value}"); + } + } + } + + private void AddToLogMap(Dictionary map, ICodexNode node, DateTime startUtc, DateTime endUtc) + { + var log = Ci.DownloadLog(node, 1000000); + log.IterateLines(line => + { + var log = CodexLogLine.Parse(line); + if (log == null) return; + if (log.TimestampUtc < startUtc) return; + if (log.TimestampUtc > endUtc) return; + + if (map.ContainsKey(log.Message)) map[log.Message] += 1; + else map.Add(log.Message, 1); + }); } } } From 2902f6baabb36b666a80576d89425aba057c2903 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 1 May 2024 10:05:46 +0200 Subject: [PATCH 052/142] latest master image of Codex --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 673557e1..85900295 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-bb0829e-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-c58d4d7-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; From dd36929a81f288dffd9997cfec07aed27ad1ee24 Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 1 May 2024 13:51:46 +0200 Subject: [PATCH 053/142] Sets scalability tests to current codex image --- .../ScalabilityTests/ScalabilityTests.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 598f0bf2..668d26e7 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -9,9 +9,6 @@ namespace CodexTests.ScalabilityTests; [TestFixture] public class ScalabilityTests : CodexDistTest { - private const string PatchedImage = "codexstorage/nim-codex:sha-9aeac06-dist-tests"; - private const string MasterImage = "codexstorage/nim-codex:sha-5380912-dist-tests"; - /// /// We upload a file to node A, then download it with B. /// Then we stop node A, and download again with node C. @@ -22,12 +19,9 @@ public class ScalabilityTests : CodexDistTest [DontDownloadLogs] public void ShouldMaintainFileInNetwork( [Values(10, 40, 80, 100)] int numberOfNodes, - [Values(100, 1000, 5000, 10000)] int fileSizeInMb, - [Values(true, false)] bool usePatchedImage + [Values(100, 1000, 5000, 10000)] int fileSizeInMb ) { - CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; - var logLevel = CodexLogLevel.Info; var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); @@ -58,19 +52,15 @@ public class ScalabilityTests : CodexDistTest /// We upload a file to each node, to put a more wide-spread load on the network. /// Then we run the same test as ShouldMaintainFileInNetwork. /// - [Ignore("Make ShouldMaintainFileInNetwork pass reliably first.")] [Test] [Combinatorial] [UseLongTimeouts] [DontDownloadLogs] public void EveryoneGetsAFile( [Values(10, 40, 80, 100)] int numberOfNodes, - [Values(100, 1000)] int fileSizeInMb, - [Values(true, false)] bool usePatchedImage + [Values(100, 1000, 5000, 10000)] int fileSizeInMb ) { - CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage; - var logLevel = CodexLogLevel.Info; var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); From e187bfc9413d5212baa6115a0c6e73b30388cb81 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 2 May 2024 08:41:20 +0200 Subject: [PATCH 054/142] Changes time.retry to fixed timelength instead of fixed number of retries --- Framework/Core/Http.cs | 2 +- Framework/Core/TimeSet.cs | 35 +++++++++++++---- Framework/KubernetesWorkflow/K8sController.cs | 2 +- Framework/Utils/Time.cs | 38 ++++++++++--------- .../CodexTests/BasicTests/MarketplaceTests.cs | 2 +- Tools/CodexNetDeployer/Deployer.cs | 4 +- 6 files changed, 53 insertions(+), 30 deletions(-) diff --git a/Framework/Core/Http.cs b/Framework/Core/Http.cs index 0bd3117a..49b3b097 100644 --- a/Framework/Core/Http.cs +++ b/Framework/Core/Http.cs @@ -58,7 +58,7 @@ namespace Core { lock (httpLock) { - return Time.Retry(operation, timeSet.HttpMaxNumberOfRetries(), timeSet.HttpCallRetryDelay(), description); + return Time.Retry(operation, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), description); } } diff --git a/Framework/Core/TimeSet.cs b/Framework/Core/TimeSet.cs index 0b8ba94d..0e29c31d 100644 --- a/Framework/Core/TimeSet.cs +++ b/Framework/Core/TimeSet.cs @@ -2,10 +2,31 @@ { public interface ITimeSet { + /// + /// Timeout for a single HTTP call. + /// TimeSpan HttpCallTimeout(); - int HttpMaxNumberOfRetries(); + + /// + /// Maximum total time to attempt to make a successful HTTP call to a service. + /// When HTTP calls time out during this timespan, retries will be made. + /// + TimeSpan HttpRetryTimeout(); + + /// + /// After a failed HTTP call, wait this long before trying again. + /// TimeSpan HttpCallRetryDelay(); + + /// + /// After a failed K8s operation, wait this long before trying again. + /// TimeSpan K8sOperationRetryDelay(); + + /// + /// Maximum total time to attempt to perform a successful k8s operation. + /// If k8s operations fail during this timespan, retries will be made. + /// TimeSpan K8sOperationTimeout(); } @@ -16,9 +37,9 @@ return TimeSpan.FromMinutes(3); } - public int HttpMaxNumberOfRetries() + public TimeSpan HttpRetryTimeout() { - return 3; + return TimeSpan.FromMinutes(10); } public TimeSpan HttpCallRetryDelay() @@ -41,17 +62,17 @@ { public TimeSpan HttpCallTimeout() { - return TimeSpan.FromHours(2); + return TimeSpan.FromMinutes(30); } - public int HttpMaxNumberOfRetries() + public TimeSpan HttpRetryTimeout() { - return 1; + return TimeSpan.FromHours(2.2); } public TimeSpan HttpCallRetryDelay() { - return TimeSpan.FromSeconds(2); + return TimeSpan.FromSeconds(20); } public TimeSpan K8sOperationRetryDelay() diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 71430a7e..0f50d874 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -712,7 +712,7 @@ namespace KubernetesWorkflow { return Time.Retry(() => GetPodForDeplomentInternal(deployment), // We will wait up to 1 minute, k8s might be moving pods around. - maxRetries: 6, + maxTimeout: TimeSpan.FromMinutes(1), retryTime: TimeSpan.FromSeconds(10), description: "Find pod by label for deployment."); } diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index e54ed184..f0f76043 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -83,35 +83,36 @@ public static void Retry(Action action, string description) { - Retry(action, 1, description); + Retry(action, TimeSpan.FromSeconds(30), description); } public static T Retry(Func action, string description) { - return Retry(action, 1, description); + return Retry(action, TimeSpan.FromSeconds(30), description); } - public static void Retry(Action action, int maxRetries, string description) + public static void Retry(Action action, TimeSpan maxTimeout, string description) { - Retry(action, maxRetries, TimeSpan.FromSeconds(5), description); + Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description); } - public static T Retry(Func action, int maxRetries, string description) + public static T Retry(Func action, TimeSpan maxTimeout, string description) { - return Retry(action, maxRetries, TimeSpan.FromSeconds(5), description); + return Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description); } - public static void Retry(Action action, int maxRetries, TimeSpan retryTime, string description) + public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { var start = DateTime.UtcNow; - var retries = 0; + var tries = 1; var exceptions = new List(); + while (true) { - if (retries > maxRetries) + var duration = DateTime.UtcNow - start; + if (duration > maxTimeout) { - var duration = DateTime.UtcNow - start; - throw new TimeoutException($"Retry '{description}' timed out after {maxRetries} tries over {Time.FormatDuration(duration)}.", new AggregateException(exceptions)); + throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.", new AggregateException(exceptions)); } try @@ -122,24 +123,25 @@ catch (Exception ex) { exceptions.Add(ex); - retries++; + tries++; } Sleep(retryTime); } } - public static T Retry(Func action, int maxRetries, TimeSpan retryTime, string description) + public static T Retry(Func action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { var start = DateTime.UtcNow; - var retries = 0; + var tries = 1; var exceptions = new List(); + while (true) { - if (retries > maxRetries) + var duration = DateTime.UtcNow - start; + if (duration > maxTimeout) { - var duration = DateTime.UtcNow - start; - throw new TimeoutException($"Retry '{description}' timed out after {maxRetries} tries over {Time.FormatDuration(duration)}.", new AggregateException(exceptions)); + throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.", new AggregateException(exceptions)); } try @@ -149,7 +151,7 @@ catch (Exception ex) { exceptions.Add(ex); - retries++; + tries++; } Sleep(retryTime); diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index d5befe0d..f81d7bd9 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -95,7 +95,7 @@ namespace CodexTests.BasicTests Log($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(); - }, Convert.ToInt32(purchase.Duration.TotalSeconds / 5) + 10, TimeSpan.FromSeconds(5), "Checking SlotFilled events"); + }, purchase.Expiry + TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5), "Checking SlotFilled events"); } private void AssertStorageRequest(Request request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer) diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index adea96d9..9537096c 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -255,9 +255,9 @@ namespace CodexNetDeployer return TimeSpan.FromSeconds(2); } - public int HttpMaxNumberOfRetries() + public TimeSpan HttpRetryTimeout() { - return 2; + return TimeSpan.FromSeconds(30); } public TimeSpan HttpCallTimeout() From 39ff757b39536800f815bdc4780e0af033e23c82 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 6 May 2024 16:12:26 +0200 Subject: [PATCH 055/142] Update + instructions for codex contracts --- .../Marketplace/Marketplace.cs | 20 ++++++++++++++++++- .../Marketplace/README.md | 13 ++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs index a9ae4baf..feb9518d 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs @@ -15,7 +15,7 @@ namespace CodexContractsPlugin.Marketplace public class MarketplaceDeploymentBase : ContractDeploymentMessage { - public static string BYTECODE = "0x60c06040523480156200001157600080fd5b5060405162003c1038038062003c10833981016040819052620000349162000487565b60208301518051608052816101004311620000965760405162461bcd60e51b815260206004820152601960248201527f496e73756666696369656e7420626c6f636b206865696768740000000000000060448201526064015b60405180910390fd5b81516000908155602083015160015560408301516002805460ff191660ff9092169190911790556060830151839190600390620000d4908262000627565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff9091161115620001535760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b606483600001516060015160ff161115620001b15760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b82516060810151602090910151606491620001cc91620006f3565b60ff1611156200021f5760405162461bcd60e51b815260206004820152601d60248201527f4d6178696d756d20736c617368696e672065786365656473203130302500000060448201526064016200008d565b82518051600c805460208085015160408087015160609788015160ff9081166401000000000260ff60201b1961ffff90931662010000029290921664ffffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600d90815591810151600e5593840151600f80549190931660ff19919091161790915592820151869391929190601090620002c7908262000627565b50505090505050505062000725565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715620003115762000311620002d6565b60405290565b604080519081016001600160401b0381118282101715620003115762000311620002d6565b604051601f8201601f191681016001600160401b0381118282101715620003675762000367620002d6565b604052919050565b805160ff811681146200038157600080fd5b919050565b6000608082840312156200039957600080fd5b620003a3620002ec565b90508151815260208083015181830152620003c1604084016200036f565b604083015260608301516001600160401b0380821115620003e157600080fd5b818501915085601f830112620003f657600080fd5b8151818111156200040b576200040b620002d6565b6200041f601f8201601f191685016200033c565b915080825286848285010111156200043657600080fd5b60005b818110156200045657838101850151838201860152840162000439565b5060008482840101525080606085015250505092915050565b80516001600160a01b03811681146200038157600080fd5b6000806000606084860312156200049d57600080fd5b83516001600160401b0380821115620004b557600080fd5b9085019081870360a0811215620004cb57600080fd5b620004d562000317565b6080821215620004e457600080fd5b620004ee620002ec565b9150620004fb846200036f565b82526200050b602085016200036f565b6020830152604084015161ffff811681146200052657600080fd5b604083015262000539606085016200036f565b6060830152908152608083015190828211156200055557600080fd5b620005638983860162000386565b6020820152809650505050506200057d602085016200046f565b91506200058d604085016200046f565b90509250925092565b600181811c90821680620005ab57607f821691505b602082108103620005cc57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000622576000816000526020600020601f850160051c81016020861015620005fd5750805b601f850160051c820191505b818110156200061e5782815560010162000609565b5050505b505050565b81516001600160401b03811115620006435762000643620002d6565b6200065b8162000654845462000596565b84620005d2565b602080601f8311600181146200069357600084156200067a5750858301515b600019600386901b1c1916600185901b1785556200061e565b600085815260208120601f198616915b82811015620006c457888601518255948401946001909101908401620006a3565b5085821015620006e35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60ff81811683821602908116908181146200071e57634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516134a26200076e6000396000818161039c01528181610b8801528181611cc10152818161204601526121b20152600081816125dc015261277b01526134a26000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c80639777b72c116100d8578063be5cdc481161008c578063f752196b11610066578063f752196b1461035a578063fb1e61ca1461037a578063fc0c546a1461039a57600080fd5b8063be5cdc4814610314578063c0cc4add14610334578063e8aa0a071461034757600080fd5b8063a3a0807e116100bd578063a3a0807e146102be578063a6af384b146102e1578063b396dc79146102f457600080fd5b80639777b72c146102a3578063a29c29a4146102ab57600080fd5b806359cc89ed1161012f5780636b00c8cf116101145780636b00c8cf146102375780636e2b54ee1461027b57806379502c551461028e57600080fd5b806359cc89ed1461020f5780635da738351461022257600080fd5b806308695fcd1161016057806308695fcd146101c2578063458d2bf1146101d75780634641dce6146101ea57600080fd5b806302fa8e651461017c57806305b90773146101a2575b600080fd5b61018f61018a3660046129b1565b6103c0565b6040519081526020015b60405180910390f35b6101b56101b03660046129b1565b610437565b60405161019991906129e0565b6101d56101d03660046129fa565b610529565b005b61018f6101e53660046129b1565b61067f565b6101fd6101f83660046129b1565b610698565b60405160ff9091168152602001610199565b6101d561021d366004612a2f565b6106ab565b61022a6109af565b6040516101999190612a66565b6102636102453660046129b1565b6000908152601360205260409020600501546001600160a01b031690565b6040516001600160a01b039091168152602001610199565b6101d56102893660046129b1565b6109d6565b610296610c09565b6040516101999190612af0565b61022a610d7a565b6101d56102b93660046129b1565b610d99565b6102d16102cc3660046129b1565b610f6a565b6040519015158152602001610199565b6101d56102ef366004612b72565b610f9f565b6103076103023660046129b1565b6111c5565b6040516101999190612c7b565b6103276103223660046129b1565b6113ae565b6040516101999190612cad565b6102d16103423660046129b1565b61147c565b6101d5610355366004612cc1565b61148f565b61018f6103683660046129b1565b60009081526007602052604090205490565b61038d6103883660046129b1565b6115cc565b6040516101999190612cef565b7f0000000000000000000000000000000000000000000000000000000000000000610263565b600081815260126020526040812060040154816103dc84610437565b905060008160048111156103f2576103f26129ca565b148061040f5750600181600481111561040d5761040d6129ca565b145b1561041b575092915050565b61042f8261042a600142612d18565b611783565b949350505050565b60008181526011602052604081205482906001600160a01b03166104945760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064015b60405180910390fd5b600083815260126020526040812090815460ff1660048111156104b9576104b96129ca565b1480156104d657506000848152601160205260409020600a015442115b156104e5576002925050610523565b6001815460ff1660048111156104fd576104fd6129ca565b14801561050d5750806004015442115b1561051c576003925050610523565b5460ff1691505b50919050565b6001610534836113ae565b6005811115610545576105456129ca565b146105925760405162461bcd60e51b815260206004820152601960248201527f536c6f74206e6f7420616363657074696e672070726f6f667300000000000000604482015260640161048b565b61059c828261179b565b6000828152601360209081526040808320600181015484526011909252909120600c5461ffff62010000909104166105e08560009081526007602052604090205490565b6105ea9190612d41565b60000361067957600c54600682015460009160649161061491640100000000900460ff1690612d55565b61061e9190612d6c565b9050808360040160008282546106349190612d18565b9091555050600c54600086815260076020526040902054610100820460ff169162010000900461ffff16906106699190612d6c565b1061067757610677856119c8565b505b50505050565b60006106928261068d611b6e565b611b79565b92915050565b6000610692826106a6611b6e565b611b8d565b60008381526011602052604090205483906001600160a01b03166107035760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b6000848152601160205260409020600181015467ffffffffffffffff16841061076e5760405162461bcd60e51b815260206004820152600c60248201527f496e76616c696420736c6f740000000000000000000000000000000000000000604482015260640161048b565b60408051602080820188905281830187905282518083038401815260609092019092528051910120600090600081815260136020526040812060018101899055600381018890559192506107c1836113ae565b60058111156107d2576107d26129ca565b1461081f5760405162461bcd60e51b815260206004820152601060248201527f536c6f74206973206e6f74206672656500000000000000000000000000000000604482015260640161048b565b60048301546000838152600560209081526040808320429055600690915290205561084a828661148f565b60058101805473ffffffffffffffffffffffffffffffffffffffff191633179055805460ff1916600190811782554260028301556000888152601260205260408120808301805491939290916108a1908490612d80565b909155506108b190508842611bf9565b8160020160008282546108c49190612d18565b909155505060068401546108d83382611c79565b80601460000160008282546108ed9190612d80565b9091555050600483018190556005830154610911906001600160a01b031685611d81565b887ff530852268993f91008f1a1e0b09b5c813acd4188481f1fa83c33c7182e814b48960405161094391815260200190565b60405180910390a26001808601549083015467ffffffffffffffff90911690036109a457815460ff1916600117825542600383015560405189907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b505050505050505050565b336000908152600b602052604090206060906109d1906109ce90611da3565b90565b905090565b6000818152601160205260409020600a8101544211610a375760405162461bcd60e51b815260206004820152601960248201527f52657175657374206e6f74207965742074696d6564206f757400000000000000604482015260640161048b565b80546001600160a01b03163314610a905760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e74206164647265737300000000000000000000604482015260640161048b565b600082815260126020526040812090815460ff166004811115610ab557610ab56129ca565b14610b025760405162461bcd60e51b815260206004820152600d60248201527f496e76616c696420737461746500000000000000000000000000000000000000604482015260640161048b565b805460ff191660021781558154610b22906001600160a01b031684611db0565b60405183907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600281015460158054829190600090610b67908490612d80565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610bd9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bfd9190612d93565b61067957610679612db5565b610c606040805160c0810182526000918101828152606082018390526080820183905260a0820192909252908190815260408051608081018252600080825260208281018290529282015260608082015291015290565b6040805160c081018252600c805460ff8082168486019081526101008304821660608087019190915261ffff62010000850416608080880191909152640100000000909404831660a087015290855285519283018652600d80548452600e54602085810191909152600f54909316968401969096526010805495969495928701949291840191610cef90612dcb565b80601f0160208091040260200160405190810160405280929190818152602001828054610d1b90612dcb565b8015610d685780601f10610d3d57610100808354040283529160200191610d68565b820191906000526020600020905b815481529060010190602001808311610d4b57829003601f168201915b50505050508152505081525050905090565b336000908152600a602052604090206060906109d1906109ce90611da3565b806000808281526013602052604090205460ff166005811115610dbe57610dbe6129ca565b03610dfa5760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b604482015260640161048b565b600082815260136020526040902060058101546001600160a01b03163314610e645760405162461bcd60e51b815260206004820152601960248201527f536c6f742066696c6c6564206279206f7468657220686f737400000000000000604482015260640161048b565b6000610e6f846113ae565b90506004816005811115610e8557610e856129ca565b03610ed25760405162461bcd60e51b815260206004820152600c60248201527f416c726561647920706169640000000000000000000000000000000000000000604482015260640161048b565b6002816005811115610ee657610ee66129ca565b03610efe57610ef9826001015485611dd2565b610679565b6005816005811115610f1257610f126129ca565b03610f2557610ef98260010154856120bf565b6003816005811115610f3957610f396129ca565b03610f4857610ef9338561222b565b6001816005811115610f5c57610f5c6129ca565b0361067957610679846119c8565b6000806000610f8084610f7b611b6e565b61224d565b909250905081801561042f575060025460ff9081169116109392505050565b33610fad6020830183612e17565b6001600160a01b0316146110035760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e74206164647265737300000000000000000000604482015260640161048b565b600061101661101183612fa9565b612330565b6000818152601160205260409020549091506001600160a01b03161561107e5760405162461bcd60e51b815260206004820152601660248201527f5265717565737420616c72656164792065786973747300000000000000000000604482015260640161048b565b600081815260116020526040902082906110988282613206565b50600090506110ab606084013542612d80565b905082610120013581116111015760405162461bcd60e51b815260206004820152601960248201527f5265717565737420656e64206265666f72652065787069727900000000000000604482015260640161048b565b600082815260126020908152604090912060040182905561112e9061112890850185612e17565b83612360565b600061114161113c85612fa9565b612382565b600084815260126020526040812060020182905560148054929350839290919061116c908490612d80565b9091555061117c90503382611c79565b7f5fdb86c365a247a4d97dcbcc5c3abde9d6e3e2de26273f3fda8eef5073b9a96c83856020018661012001356040516111b7939291906132fe565b60405180910390a150505050565b6111cd6128e9565b816000808281526013602052604090205460ff1660058111156111f2576111f26129ca565b0361122e5760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b604482015260640161048b565b60008381526013602052604090206112446128e9565b600180830154600090815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186529583015467ffffffffffffffff9081168752600284015487860152600384015487870152600484015460608801526005840154608088015260068401549187019190915260078301541660c08601529182019390935281518083018352600884018054929493850192829082906112f090612dcb565b80601f016020809104026020016040519081016040528092919081815260200182805461131c90612dcb565b80156113695780601f1061133e57610100808354040283529160200191611369565b820191906000526020600020905b81548152906001019060200180831161134c57829003601f168201915b505050918352505060019190910154602091820152908252600a83015482820152600b9092015460409091015290825260039092015491810191909152915050919050565b6000818152601360205260408120600181015482036113d05750600092915050565b60006113df8260010154610437565b90506004825460ff1660058111156113f9576113f96129ca565b03611408575060049392505050565b600281600481111561141c5761141c6129ca565b0361142b575060059392505050565b600381600481111561143f5761143f6129ca565b0361144e575060029392505050565b6004816004811115611462576114626129ca565b03611471575060039392505050565b505460ff1692915050565b60006106928261148a611b6e565b6123a7565b6000828152601360209081526040808320600101548084526011909252909120546001600160a01b03166114f75760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b60008381526013602090815260408083206001810154845260118352818420825160038082526080820190945291949093909290820160608036833701905050905061154a6115458761067f565b6123da565b8160008151811061155d5761155d61337a565b60209081029190910101526009820154611576906123eb565b816001815181106115895761158961337a565b6020026020010181815250508260030154816002815181106115ad576115ad61337a565b6020026020010181815250506115c48686836123f7565b505050505050565b6115d4612909565b60008281526011602052604090205482906001600160a01b031661162c5760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b600083815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e081018652600184015467ffffffffffffffff90811682526002850154828701526003850154828801526004850154606083015260058501546080830152600685015492820192909252600784015490911660c082015292810192909252825180840184526008820180549394929392850192829082906116d790612dcb565b80601f016020809104026020016040519081016040528092919081815260200182805461170390612dcb565b80156117505780601f1061172557610100808354040283529160200191611750565b820191906000526020600020905b81548152906001019060200180831161173357829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b82015481525050915050919050565b60008183106117925781611794565b825b9392505050565b60006117a6826125c2565b90504281106117f75760405162461bcd60e51b815260206004820152601860248201527f506572696f6420686173206e6f7420656e646564207965740000000000000000604482015260640161048b565b6001546118049082612d80565b42106118525760405162461bcd60e51b815260206004820152601460248201527f56616c69646174696f6e2074696d6564206f7574000000000000000000000000604482015260640161048b565b600083815260086020908152604080832085845290915290205460ff16156118bc5760405162461bcd60e51b815260206004820181905260248201527f50726f6f6620776173207375626d69747465642c206e6f74206d697373696e67604482015260640161048b565b6118c683836123a7565b6119125760405162461bcd60e51b815260206004820152601660248201527f50726f6f6620776173206e6f7420726571756972656400000000000000000000604482015260640161048b565b600083815260096020908152604080832085845290915290205460ff161561197c5760405162461bcd60e51b815260206004820152601f60248201527f50726f6f6620616c7265616479206d61726b6564206173206d697373696e6700604482015260640161048b565b60008381526009602090815260408083208584528252808320805460ff19166001908117909155868452600790925282208054919290916119be908490612d80565b9091555050505050565b60008181526013602090815260408083206001810154808552601290935292206005830154611a00906001600160a01b03168561222b565b6003808401546000868152601360205260408120805460ff1916815560018082018390556002820183905593810182905560048101829055600501805473ffffffffffffffffffffffffffffffffffffffff191690558383018054929392909190611a6c908490612d18565b909155505060405181815283907f1d31c9f8dea6e179f6a050db117595feea8937029ea51f5168a4780be7e8f5529060200160405180910390a2600085815260076020526040812055600083815260116020526040812060018085015490820154919291611ae4919067ffffffffffffffff16612d18565b600783015490915067ffffffffffffffff1681118015611b1957506001845460ff166004811115611b1757611b176129ca565b145b15611b6557835460ff19166004178455611b34600142612d18565b600485015560405185907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a25b50505050505050565b60006109d1426125d5565b6000611794611b888484611b8d565b612601565b600080611b9c61010043612d41565b90506000610100611bae856043612d55565b611bb89190612d41565b90506000611bc861010087612d41565b9050600061010082611bda8587612d80565b611be49190612d80565b611bee9190612d41565b979650505050505050565b6000828152601160205260408120600a8101548310611c5a5760405162461bcd60e51b815260206004820152601760248201527f5374617274206e6f74206265666f726520657870697279000000000000000000604482015260640161048b565b6005810154600a820154611c6f908590612d18565b61042f9190612d55565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015611d0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d309190612d93565b611d7c5760405162461bcd60e51b815260206004820152600f60248201527f5472616e73666572206661696c65640000000000000000000000000000000000604482015260640161048b565b505050565b6001600160a01b0382166000908152600b60205260409020611d7c908261265b565b6060600061179483612667565b6001600160a01b0382166000908152600a60205260409020611d7c90826126c3565b60008281526011602052604090205482906001600160a01b0316611e2a5760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b60008381526012602090815260408083206011909252909120815460ff191660031782558054611e63906001600160a01b031686611db0565b60008481526013602052604090206005810154611e89906001600160a01b03168661222b565b6004808201546000888152601160209081526040808320815160a0808201845282546001600160a01b03168252835160e081018552600184015467ffffffffffffffff908116825260028501548288015260038501548287015298840154606082015260058401546080820152600684015491810191909152600783015490971660c088015292830195909552805180820182526008860180549496611fe6959093850192919082908290611f3d90612dcb565b80601f0160208091040260200160405190810160405280929190818152602001828054611f6990612dcb565b8015611fb65780601f10611f8b57610100808354040283529160200191611fb6565b820191906000526020600020905b815481529060010190602001808311611f9957829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b820154815250506126cf565b611ff09190612d80565b905080601460010160008282546120079190612d80565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561208f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b39190612d93565b611b6557611b65612db5565b60008281526011602052604090205482906001600160a01b03166121175760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b604482015260640161048b565b6000828152601360205260409020600581015461213d906001600160a01b03168461222b565b60008160040154612152868460020154611bf9565b61215c9190612d80565b905080601460010160008282546121739190612d80565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156121fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061221f9190612d93565b61067757610677612db5565b6001600160a01b0382166000908152600b60205260409020611d7c90826126c3565b600080600061225b856113ae565b60008681526005602052604081205491925090612277906125d5565b9050600182600581111561228d5761228d6129ca565b1415806122a1575061229f85826126eb565b155b156122b457600080935093505050612329565b6122be8686611b8d565b925060006122cb84612601565b600254909150600090610100906122e59060ff1682613390565b60008a8152600660205260409020546123029161ffff1690612d55565b61230c9190612d6c565b905080158061232257506123208183612d41565b155b9550505050505b9250929050565b6000816040516020016123439190612cef565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600a60205260409020611d7c908261265b565b600061238d826126cf565b602083015151610692919067ffffffffffffffff16612d55565b60008060006123b6858561224d565b90925090508180156123d1575060025460ff90811690821610155b95945050505050565b600060ff1982168161042f826126f5565b600080611794836126f5565b60008381526008602052604081209061240e611b6e565b815260208101919091526040016000205460ff161561246f5760405162461bcd60e51b815260206004820152601760248201527f50726f6f6620616c7265616479207375626d6974746564000000000000000000604482015260640161048b565b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d916124b99186918691016133ab565b602060405180830381865afa1580156124d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fa9190612d93565b6125465760405162461bcd60e51b815260206004820152600d60248201527f496e76616c69642070726f6f6600000000000000000000000000000000000000604482015260640161048b565b600083815260086020526040812060019161255f611b6e565b815260200190815260200160002060006101000a81548160ff0219169083151502179055507f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d836040516125b591815260200190565b60405180910390a1505050565b60006106926125d083612767565b612774565b60006106927f000000000000000000000000000000000000000000000000000000000000000083612d6c565b60008060ff8316612613600143612d18565b61261d9190612d18565b409050600081900361263157612631612db5565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b600061179483836127a0565b6060816000018054806020026020016040519081016040528092919081815260200182805480156126b757602002820191906000526020600020905b8154815260200190600101908083116126a3575b50505050509050919050565b600061179483836127ef565b6020810151608081015160409091015160009161069291612d55565b6000818311611794565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b602081101561052357600891821c91612735908290612d55565b83901b7fff0000000000000000000000000000000000000000000000000000000000000016919091179060010161271b565b6000610692826001612d80565b60006106927f000000000000000000000000000000000000000000000000000000000000000083612d55565b60008181526001830160205260408120546127e757508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610692565b506000610692565b600081815260018301602052604081205480156128d8576000612813600183612d18565b855490915060009061282790600190612d18565b905081811461288c5760008660000182815481106128475761284761337a565b906000526020600020015490508087600001848154811061286a5761286a61337a565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061289d5761289d613456565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610692565b6000915050610692565b5092915050565b60405180604001604052806128fc612909565b8152602001600081525090565b6040518060a0016040528060006001600160a01b031681526020016129786040518060e00160405280600067ffffffffffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001600067ffffffffffffffff1681525090565b815260200161299d604051806040016040528060608152602001600080191681525090565b815260006020820181905260409091015290565b6000602082840312156129c357600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106129f4576129f46129ca565b91905290565b60008060408385031215612a0d57600080fd5b50508035926020909101359150565b6000610100828403121561052357600080fd5b60008060006101408486031215612a4557600080fd5b8335925060208401359150612a5d8560408601612a1c565b90509250925092565b6020808252825182820181905260009190848201906040850190845b81811015612a9e57835183529284019291840191600101612a82565b50909695505050505050565b6000815180845260005b81811015612ad057602081850181015186830182015201612ab4565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000825160ff815116602084015260ff602082015116604084015261ffff604082015116606084015260ff606082015116608084015250602083015160a080840152805160c0840152602081015160e084015260ff60408201511661010084015260608101519050608061012084015261042f610140840182612aaa565b600060208284031215612b8457600080fd5b813567ffffffffffffffff811115612b9b57600080fd5b8201610160818503121561179457600080fd5b6000815160408452612bc36040850182612aaa565b602093840151949093019390935250919050565b60006101606001600160a01b038351168452602083015167ffffffffffffffff808251166020870152602082015160408701526040820151606087015260608201516080870152608082015160a087015260a082015160c08701528060c08301511660e08701525050604083015181610100860152612c5882860182612bae565b915050606083015161012085015260808301516101408501528091505092915050565b602081526000825160406020840152612c976060840182612bd7565b9050602084015160408401528091505092915050565b60208101600683106129f4576129f46129ca565b6000806101208385031215612cd557600080fd5b82359150612ce68460208501612a1c565b90509250929050565b6020815260006117946020830184612bd7565b634e487b7160e01b600052601160045260246000fd5b8181038181111561069257610692612d02565b634e487b7160e01b600052601260045260246000fd5b600082612d5057612d50612d2b565b500690565b808202811582820484141761069257610692612d02565b600082612d7b57612d7b612d2b565b500490565b8082018082111561069257610692612d02565b600060208284031215612da557600080fd5b8151801515811461179457600080fd5b634e487b7160e01b600052600160045260246000fd5b600181811c90821680612ddf57607f821691505b60208210810361052357634e487b7160e01b600052602260045260246000fd5b6001600160a01b0381168114612e1457600080fd5b50565b600060208284031215612e2957600080fd5b813561179481612dff565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b60405290565b60405160a0810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b60405160e0810167ffffffffffffffff81118282101715612e6d57612e6d612e34565b604051601f8201601f1916810167ffffffffffffffff81118282101715612ee257612ee2612e34565b604052919050565b67ffffffffffffffff81168114612e1457600080fd5b600060408284031215612f1257600080fd5b612f1a612e4a565b9050813567ffffffffffffffff80821115612f3457600080fd5b818401915084601f830112612f4857600080fd5b8135602082821115612f5c57612f5c612e34565b612f6e601f8301601f19168201612eb9565b92508183528681838601011115612f8457600080fd5b8181850182850137600081838501015282855280860135818601525050505092915050565b6000813603610160811215612fbd57600080fd5b612fc5612e73565b8335612fd081612dff565b815260e0601f1983011215612fe457600080fd5b612fec612e96565b91506020840135612ffc81612eea565b8083525060408401356020830152606084013560408301526080840135606083015260a0840135608083015260c084013560a083015260e084013561304081612eea565b60c083015260208101919091526101008301359067ffffffffffffffff82111561306957600080fd5b61307536838601612f00565b604082015261012084013560608201526101409093013560808401525090919050565b6000813561069281612eea565b60008235603e198336030181126130bb57600080fd5b9190910192915050565b601f821115611d7c576000816000526020600020601f850160051c810160208610156130ee5750805b601f850160051c820191505b818110156115c4578281556001016130fa565b8135601e1983360301811261312157600080fd5b8201803567ffffffffffffffff81111561313a57600080fd5b6020813603818401131561314d57600080fd5b6131618261315b8654612dcb565b866130c5565b6000601f831160018114613197576000841561317f57508482018301355b600019600386901b1c1916600185901b1786556131f4565b600086815260209020601f19851690835b828110156131c95787850186013582559385019360019091019085016131a8565b50858210156131e85760001960f88760031b161c198585890101351681555b505060018460011b0186555b50508085013560018501555050505050565b813561321181612dff565b6001600160a01b03811673ffffffffffffffffffffffffffffffffffffffff1983541617825550602082013561324681612eea565b60018201805467ffffffffffffffff191667ffffffffffffffff83161790555060408201356002820155606082013560038201556080820135600482015560a0820135600582015560c082013560068201556132ca6132a760e08401613098565b6007830167ffffffffffffffff821667ffffffffffffffff198254161781555050565b6132e46132db6101008401846130a5565b6008830161310d565b610120820135600a820155610140820135600b8201555050565b8381526101208101833561331181612eea565b67ffffffffffffffff8082166020850152602086013560408501526040860135606085015260608601356080850152608086013560a085015260a086013560c085015260c0860135915061336482612eea565b1660e08301526101009091019190915292915050565b634e487b7160e01b600052603260045260246000fd5b61ffff8281168282160390808211156128e2576128e2612d02565b823581526020808401359082015260006101208281016133db604085016040880180358252602090810135910152565b6133f5608085016080880180358252602090810135910152565b61340f60c0850160c0880180358252602090810135910152565b61010084019190915283519081905261014083019060209081860160005b828110156134495781518552938301939083019060010161342d565b5092979650505050505050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212201d12cd1bfef3b2ec5ee1f81d1ea21b12b2bb5ae8b67fb3ebaaeca5a194c2a0b564736f6c63430008170033"; + public static string BYTECODE = "0x60c06040523480156200001157600080fd5b5060405162003c9b38038062003c9b833981016040819052620000349162000487565b60208301518051608052816101004311620000965760405162461bcd60e51b815260206004820152601960248201527f496e73756666696369656e7420626c6f636b206865696768740000000000000060448201526064015b60405180910390fd5b81516000908155602083015160015560408301516002805460ff191660ff9092169190911790556060830151839190600390620000d4908262000627565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff9091161115620001535760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b606483600001516060015160ff161115620001b15760405162461bcd60e51b815260206004820152601560248201527f4d757374206265206c657373207468616e20313030000000000000000000000060448201526064016200008d565b82516060810151602090910151606491620001cc91620006f3565b60ff1611156200021f5760405162461bcd60e51b815260206004820152601d60248201527f4d6178696d756d20736c617368696e672065786365656473203130302500000060448201526064016200008d565b82518051600c805460208085015160408087015160609788015160ff9081166401000000000260ff60201b1961ffff90931662010000029290921664ffffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600d90815591810151600e5593840151600f80549190931660ff19919091161790915592820151869391929190601090620002c7908262000627565b50505090505050505062000725565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715620003115762000311620002d6565b60405290565b604080519081016001600160401b0381118282101715620003115762000311620002d6565b604051601f8201601f191681016001600160401b0381118282101715620003675762000367620002d6565b604052919050565b805160ff811681146200038157600080fd5b919050565b6000608082840312156200039957600080fd5b620003a3620002ec565b90508151815260208083015181830152620003c1604084016200036f565b604083015260608301516001600160401b0380821115620003e157600080fd5b818501915085601f830112620003f657600080fd5b8151818111156200040b576200040b620002d6565b6200041f601f8201601f191685016200033c565b915080825286848285010111156200043657600080fd5b60005b818110156200045657838101850151838201860152840162000439565b5060008482840101525080606085015250505092915050565b80516001600160a01b03811681146200038157600080fd5b6000806000606084860312156200049d57600080fd5b83516001600160401b0380821115620004b557600080fd5b9085019081870360a0811215620004cb57600080fd5b620004d562000317565b6080821215620004e457600080fd5b620004ee620002ec565b9150620004fb846200036f565b82526200050b602085016200036f565b6020830152604084015161ffff811681146200052657600080fd5b604083015262000539606085016200036f565b6060830152908152608083015190828211156200055557600080fd5b620005638983860162000386565b6020820152809650505050506200057d602085016200046f565b91506200058d604085016200046f565b90509250925092565b600181811c90821680620005ab57607f821691505b602082108103620005cc57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000622576000816000526020600020601f850160051c81016020861015620005fd5750805b601f850160051c820191505b818110156200061e5782815560010162000609565b5050505b505050565b81516001600160401b03811115620006435762000643620002d6565b6200065b8162000654845462000596565b84620005d2565b602080601f8311600181146200069357600084156200067a5750858301515b600019600386901b1c1916600185901b1785556200061e565b600085815260208120601f198616915b82811015620006c457888601518255948401946001909101908401620006a3565b5085821015620006e35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60ff81811683821602908116908181146200071e57634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a05161352d6200076e600039600081816103ca01528181610bc001528181611d59015281816120de015261224a0152600081816126670152612806015261352d6000f3fe608060405234801561001057600080fd5b50600436106101825760003560e01c80639777b72c116100d8578063be5cdc481161008c578063f752196b11610066578063f752196b14610388578063fb1e61ca146103a8578063fc0c546a146103c857600080fd5b8063be5cdc4814610342578063c0cc4add14610362578063e8aa0a071461037557600080fd5b8063a3a0807e116100bd578063a3a0807e146102ec578063a6af384b1461030f578063b396dc791461032257600080fd5b80639777b72c146102d1578063a29c29a4146102d957600080fd5b80634641dce61161013a5780636b00c8cf116101145780636b00c8cf146102655780636e2b54ee146102a957806379502c55146102bc57600080fd5b80634641dce61461021857806359cc89ed1461023d5780635da738351461025057600080fd5b806308695fcd1161016b57806308695fcd146101cd578063329b5a0b146101e2578063458d2bf11461020557600080fd5b806302fa8e651461018757806305b90773146101ad575b600080fd5b61019a610195366004612a3c565b6103ee565b6040519081526020015b60405180910390f35b6101c06101bb366004612a3c565b610465565b6040516101a49190612a6b565b6101e06101db366004612a85565b610557565b005b61019a6101f0366004612a3c565b60009081526012602052604090206005015490565b61019a610213366004612a3c565b6106ad565b61022b610226366004612a3c565b6106c6565b60405160ff90911681526020016101a4565b6101e061024b366004612aba565b6106d9565b6102586109dd565b6040516101a49190612af1565b610291610273366004612a3c565b6000908152601360205260409020600501546001600160a01b031690565b6040516001600160a01b0390911681526020016101a4565b6101e06102b7366004612a3c565b610a04565b6102c4610c41565b6040516101a49190612b7b565b610258610db2565b6101e06102e7366004612a3c565b610dd1565b6102ff6102fa366004612a3c565b610fa2565b60405190151581526020016101a4565b6101e061031d366004612bfd565b610fd7565b610335610330366004612a3c565b611240565b6040516101a49190612d06565b610355610350366004612a3c565b611429565b6040516101a49190612d38565b6102ff610370366004612a3c565b6114f7565b6101e0610383366004612d4c565b61150a565b61019a610396366004612a3c565b60009081526007602052604090205490565b6103bb6103b6366004612a3c565b611647565b6040516101a49190612d7a565b7f0000000000000000000000000000000000000000000000000000000000000000610291565b6000818152601260205260408120600401548161040a84610465565b9050600081600481111561042057610420612a55565b148061043d5750600181600481111561043b5761043b612a55565b145b15610449575092915050565b61045d82610458600142612da3565b6117fe565b949350505050565b60008181526011602052604081205482906001600160a01b03166104c25760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064015b60405180910390fd5b600083815260126020526040812090815460ff1660048111156104e7576104e7612a55565b148015610504575060008481526012602052604090206005015442115b15610513576002925050610551565b6001815460ff16600481111561052b5761052b612a55565b14801561053b5750806004015442115b1561054a576003925050610551565b5460ff1691505b50919050565b600161056283611429565b600581111561057357610573612a55565b146105c05760405162461bcd60e51b815260206004820152601960248201527f536c6f74206e6f7420616363657074696e672070726f6f66730000000000000060448201526064016104b9565b6105ca8282611816565b6000828152601360209081526040808320600181015484526011909252909120600c5461ffff620100009091041661060e8560009081526007602052604090205490565b6106189190612dcc565b6000036106a757600c54600682015460009160649161064291640100000000900460ff1690612de0565b61064c9190612df7565b9050808360040160008282546106629190612da3565b9091555050600c54600086815260076020526040902054610100820460ff169162010000900461ffff16906106979190612df7565b106106a5576106a585611a43565b505b50505050565b60006106c0826106bb611be9565b611bf4565b92915050565b60006106c0826106d4611be9565b611c08565b60008381526011602052604090205483906001600160a01b03166107315760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064016104b9565b6000848152601160205260409020600181015467ffffffffffffffff16841061079c5760405162461bcd60e51b815260206004820152600c60248201527f496e76616c696420736c6f74000000000000000000000000000000000000000060448201526064016104b9565b60408051602080820188905281830187905282518083038401815260609092019092528051910120600090600081815260136020526040812060018101899055600381018890559192506107ef83611429565b600581111561080057610800612a55565b1461084d5760405162461bcd60e51b815260206004820152601060248201527f536c6f74206973206e6f7420667265650000000000000000000000000000000060448201526064016104b9565b600483015460008381526005602090815260408083204290556006909152902055610878828661150a565b60058101805473ffffffffffffffffffffffffffffffffffffffff191633179055805460ff1916600190811782554260028301556000888152601260205260408120808301805491939290916108cf908490612e0b565b909155506108df90508842611c74565b8160020160008282546108f29190612da3565b909155505060068401546109063382611d11565b806014600001600082825461091b9190612e0b565b909155505060048301819055600583015461093f906001600160a01b031685611e19565b887ff530852268993f91008f1a1e0b09b5c813acd4188481f1fa83c33c7182e814b48960405161097191815260200190565b60405180910390a26001808601549083015467ffffffffffffffff90911690036109d257815460ff1916600117825542600383015560405189907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b505050505050505050565b336000908152600b602052604090206060906109ff906109fc90611e3b565b90565b905090565b60008181526011602090815260408083206012909252909120600501544211610a6f5760405162461bcd60e51b815260206004820152601960248201527f52657175657374206e6f74207965742074696d6564206f75740000000000000060448201526064016104b9565b80546001600160a01b03163314610ac85760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e7420616464726573730000000000000000000060448201526064016104b9565b600082815260126020526040812090815460ff166004811115610aed57610aed612a55565b14610b3a5760405162461bcd60e51b815260206004820152600d60248201527f496e76616c69642073746174650000000000000000000000000000000000000060448201526064016104b9565b805460ff191660021781558154610b5a906001600160a01b031684611e48565b60405183907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600281015460158054829190600090610b9f908490612e0b565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610c11573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c359190612e1e565b6106a7576106a7612e40565b610c986040805160c0810182526000918101828152606082018390526080820183905260a0820192909252908190815260408051608081018252600080825260208281018290529282015260608082015291015290565b6040805160c081018252600c805460ff8082168486019081526101008304821660608087019190915261ffff62010000850416608080880191909152640100000000909404831660a087015290855285519283018652600d80548452600e54602085810191909152600f54909316968401969096526010805495969495928701949291840191610d2790612e56565b80601f0160208091040260200160405190810160405280929190818152602001828054610d5390612e56565b8015610da05780601f10610d7557610100808354040283529160200191610da0565b820191906000526020600020905b815481529060010190602001808311610d8357829003601f168201915b50505050508152505081525050905090565b336000908152600a602052604090206060906109ff906109fc90611e3b565b806000808281526013602052604090205460ff166005811115610df657610df6612a55565b03610e325760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b60448201526064016104b9565b600082815260136020526040902060058101546001600160a01b03163314610e9c5760405162461bcd60e51b815260206004820152601960248201527f536c6f742066696c6c6564206279206f7468657220686f73740000000000000060448201526064016104b9565b6000610ea784611429565b90506004816005811115610ebd57610ebd612a55565b03610f0a5760405162461bcd60e51b815260206004820152600c60248201527f416c72656164792070616964000000000000000000000000000000000000000060448201526064016104b9565b6002816005811115610f1e57610f1e612a55565b03610f3657610f31826001015485611e6a565b6106a7565b6005816005811115610f4a57610f4a612a55565b03610f5d57610f31826001015485612157565b6003816005811115610f7157610f71612a55565b03610f8057610f3133856122c3565b6001816005811115610f9457610f94612a55565b036106a7576106a784611a43565b6000806000610fb884610fb3611be9565b6122e5565b909250905081801561045d575060025460ff9081169116109392505050565b33610fe56020830183612ea2565b6001600160a01b03161461103b5760405162461bcd60e51b815260206004820152601660248201527f496e76616c696420636c69656e7420616464726573730000000000000000000060448201526064016104b9565b600061104e61104983613034565b6123c8565b6000818152601160205260409020549091506001600160a01b0316156110b65760405162461bcd60e51b815260206004820152601660248201527f5265717565737420616c7265616479206578697374730000000000000000000060448201526064016104b9565b60008261012001351180156110d357506060820135610120830135105b61111f5760405162461bcd60e51b815260206004820152601360248201527f457870697279206e6f7420696e2072616e67650000000000000000000000000060448201526064016104b9565b600081815260116020526040902082906111398282613291565b5061114a9050606083013542612e0b565b60008281526012602052604090206004015561116b61012083013542612e0b565b6000828152601260209081526040909120600501919091556111999061119390840184612ea2565b826123f8565b60006111ac6111a784613034565b61241a565b60008381526012602052604081206002018290556014805492935083929091906111d7908490612e0b565b909155506111e790503382611d11565b7f5fdb86c365a247a4d97dcbcc5c3abde9d6e3e2de26273f3fda8eef5073b9a96c8284602001601260008681526020019081526020016000206005015460405161123393929190613389565b60405180910390a1505050565b611248612974565b816000808281526013602052604090205460ff16600581111561126d5761126d612a55565b036112a95760405162461bcd60e51b815260206004820152600c60248201526b536c6f74206973206672656560a01b60448201526064016104b9565b60008381526013602052604090206112bf612974565b600180830154600090815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186529583015467ffffffffffffffff9081168752600284015487860152600384015487870152600484015460608801526005840154608088015260068401549187019190915260078301541660c086015291820193909352815180830183526008840180549294938501928290829061136b90612e56565b80601f016020809104026020016040519081016040528092919081815260200182805461139790612e56565b80156113e45780601f106113b9576101008083540402835291602001916113e4565b820191906000526020600020905b8154815290600101906020018083116113c757829003601f168201915b505050918352505060019190910154602091820152908252600a83015482820152600b9092015460409091015290825260039092015491810191909152915050919050565b60008181526013602052604081206001810154820361144b5750600092915050565b600061145a8260010154610465565b90506004825460ff16600581111561147457611474612a55565b03611483575060049392505050565b600281600481111561149757611497612a55565b036114a6575060059392505050565b60038160048111156114ba576114ba612a55565b036114c9575060029392505050565b60048160048111156114dd576114dd612a55565b036114ec575060039392505050565b505460ff1692915050565b60006106c082611505611be9565b61243f565b6000828152601360209081526040808320600101548084526011909252909120546001600160a01b03166115725760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064016104b9565b6000838152601360209081526040808320600181015484526011835281842082516003808252608082019094529194909390929082016060803683370190505090506115c56115c0876106ad565b612472565b816000815181106115d8576115d8613405565b602090810291909101015260098201546115f190612483565b8160018151811061160457611604613405565b60200260200101818152505082600301548160028151811061162857611628613405565b60200260200101818152505061163f86868361248f565b505050505050565b61164f612994565b60008281526011602052604090205482906001600160a01b03166116a75760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064016104b9565b600083815260116020908152604091829020825160a0808201855282546001600160a01b03168252845160e081018652600184015467ffffffffffffffff90811682526002850154828701526003850154828801526004850154606083015260058501546080830152600685015492820192909252600784015490911660c0820152928101929092528251808401845260088201805493949293928501928290829061175290612e56565b80601f016020809104026020016040519081016040528092919081815260200182805461177e90612e56565b80156117cb5780601f106117a0576101008083540402835291602001916117cb565b820191906000526020600020905b8154815290600101906020018083116117ae57829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b82015481525050915050919050565b600081831061180d578161180f565b825b9392505050565b60006118218261264d565b90504281106118725760405162461bcd60e51b815260206004820152601860248201527f506572696f6420686173206e6f7420656e64656420796574000000000000000060448201526064016104b9565b60015461187f9082612e0b565b42106118cd5760405162461bcd60e51b815260206004820152601460248201527f56616c69646174696f6e2074696d6564206f757400000000000000000000000060448201526064016104b9565b600083815260086020908152604080832085845290915290205460ff16156119375760405162461bcd60e51b815260206004820181905260248201527f50726f6f6620776173207375626d69747465642c206e6f74206d697373696e6760448201526064016104b9565b611941838361243f565b61198d5760405162461bcd60e51b815260206004820152601660248201527f50726f6f6620776173206e6f742072657175697265640000000000000000000060448201526064016104b9565b600083815260096020908152604080832085845290915290205460ff16156119f75760405162461bcd60e51b815260206004820152601f60248201527f50726f6f6620616c7265616479206d61726b6564206173206d697373696e670060448201526064016104b9565b60008381526009602090815260408083208584528252808320805460ff1916600190811790915586845260079092528220805491929091611a39908490612e0b565b9091555050505050565b60008181526013602090815260408083206001810154808552601290935292206005830154611a7b906001600160a01b0316856122c3565b6003808401546000868152601360205260408120805460ff1916815560018082018390556002820183905593810182905560048101829055600501805473ffffffffffffffffffffffffffffffffffffffff191690558383018054929392909190611ae7908490612da3565b909155505060405181815283907f1d31c9f8dea6e179f6a050db117595feea8937029ea51f5168a4780be7e8f5529060200160405180910390a2600085815260076020526040812055600083815260116020526040812060018085015490820154919291611b5f919067ffffffffffffffff16612da3565b600783015490915067ffffffffffffffff1681118015611b9457506001845460ff166004811115611b9257611b92612a55565b145b15611be057835460ff19166004178455611baf600142612da3565b600485015560405185907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a25b50505050505050565b60006109ff42612660565b600061180f611c038484611c08565b61268c565b600080611c1761010043612dcc565b90506000610100611c29856043612de0565b611c339190612dcc565b90506000611c4361010087612dcc565b9050600061010082611c558587612e0b565b611c5f9190612e0b565b611c699190612dcc565b979650505050505050565b600082815260116020908152604080832060129092528220600501548310611cde5760405162461bcd60e51b815260206004820152601760248201527f5374617274206e6f74206265666f72652065787069727900000000000000000060448201526064016104b9565b600581015483611cfd8660009081526012602052604090206005015490565b611d079190612da3565b61045d9190612de0565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015611da4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dc89190612e1e565b611e145760405162461bcd60e51b815260206004820152600f60248201527f5472616e73666572206661696c6564000000000000000000000000000000000060448201526064016104b9565b505050565b6001600160a01b0382166000908152600b60205260409020611e1490826126e6565b6060600061180f836126f2565b6001600160a01b0382166000908152600a60205260409020611e14908261274e565b60008281526011602052604090205482906001600160a01b0316611ec25760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064016104b9565b60008381526012602090815260408083206011909252909120815460ff191660031782558054611efb906001600160a01b031686611e48565b60008481526013602052604090206005810154611f21906001600160a01b0316866122c3565b6004808201546000888152601160209081526040808320815160a0808201845282546001600160a01b03168252835160e081018552600184015467ffffffffffffffff908116825260028501548288015260038501548287015298840154606082015260058401546080820152600684015491810191909152600783015490971660c08801529283019590955280518082018252600886018054949661207e959093850192919082908290611fd590612e56565b80601f016020809104026020016040519081016040528092919081815260200182805461200190612e56565b801561204e5780601f106120235761010080835404028352916020019161204e565b820191906000526020600020905b81548152906001019060200180831161203157829003601f168201915b505050505081526020016001820154815250508152602001600a8201548152602001600b8201548152505061275a565b6120889190612e0b565b9050806014600101600082825461209f9190612e0b565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612127573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061214b9190612e1e565b611be057611be0612e40565b60008281526011602052604090205482906001600160a01b03166121af5760405162461bcd60e51b815260206004820152600f60248201526e155b9adb9bdddb881c995c5d595cdd608a1b60448201526064016104b9565b600082815260136020526040902060058101546121d5906001600160a01b0316846122c3565b600081600401546121ea868460020154611c74565b6121f49190612e0b565b9050806014600101600082825461220b9190612e0b565b9091555050815460ff191660049081178355600583015460405163a9059cbb60e01b81526001600160a01b0391821692810192909252602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612293573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122b79190612e1e565b6106a5576106a5612e40565b6001600160a01b0382166000908152600b60205260409020611e14908261274e565b60008060006122f385611429565b6000868152600560205260408120549192509061230f90612660565b9050600182600581111561232557612325612a55565b14158061233957506123378582612776565b155b1561234c576000809350935050506123c1565b6123568686611c08565b925060006123638461268c565b6002549091506000906101009061237d9060ff168261341b565b60008a81526006602052604090205461239a9161ffff1690612de0565b6123a49190612df7565b90508015806123ba57506123b88183612dcc565b155b9550505050505b9250929050565b6000816040516020016123db9190612d7a565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600a60205260409020611e1490826126e6565b60006124258261275a565b6020830151516106c0919067ffffffffffffffff16612de0565b600080600061244e85856122e5565b9092509050818015612469575060025460ff90811690821610155b95945050505050565b600060ff1982168161045d82612780565b60008061180f83612780565b6000838152600860205260408120906124a6611be9565b815260208101919091526040016000205460ff16156125075760405162461bcd60e51b815260206004820152601760248201527f50726f6f6620616c7265616479207375626d697474656400000000000000000060448201526064016104b9565b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d91612551918691869101613436565b602060405180830381865afa15801561256e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125929190612e1e565b6125de5760405162461bcd60e51b815260206004820152600d60248201527f496e76616c69642070726f6f660000000000000000000000000000000000000060448201526064016104b9565b60008381526008602052604081206001916125f7611be9565b815260200190815260200160002060006101000a81548160ff0219169083151502179055507f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d8360405161123391815260200190565b60006106c061265b836127f2565b6127ff565b60006106c07f000000000000000000000000000000000000000000000000000000000000000083612df7565b60008060ff831661269e600143612da3565b6126a89190612da3565b40905060008190036126bc576126bc612e40565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b600061180f838361282b565b60608160000180548060200260200160405190810160405280929190818152602001828054801561274257602002820191906000526020600020905b81548152602001906001019080831161272e575b50505050509050919050565b600061180f838361287a565b602081015160808101516040909101516000916106c091612de0565b600081831161180f565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b602081101561055157600891821c916127c0908290612de0565b83901b7fff000000000000000000000000000000000000000000000000000000000000001691909117906001016127a6565b60006106c0826001612e0b565b60006106c07f000000000000000000000000000000000000000000000000000000000000000083612de0565b6000818152600183016020526040812054612872575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556106c0565b5060006106c0565b6000818152600183016020526040812054801561296357600061289e600183612da3565b85549091506000906128b290600190612da3565b90508181146129175760008660000182815481106128d2576128d2613405565b90600052602060002001549050808760000184815481106128f5576128f5613405565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612928576129286134e1565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506106c0565b60009150506106c0565b5092915050565b6040518060400160405280612987612994565b8152602001600081525090565b6040518060a0016040528060006001600160a01b03168152602001612a036040518060e00160405280600067ffffffffffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001600067ffffffffffffffff1681525090565b8152602001612a28604051806040016040528060608152602001600080191681525090565b815260006020820181905260409091015290565b600060208284031215612a4e57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b6020810160058310612a7f57612a7f612a55565b91905290565b60008060408385031215612a9857600080fd5b50508035926020909101359150565b6000610100828403121561055157600080fd5b60008060006101408486031215612ad057600080fd5b8335925060208401359150612ae88560408601612aa7565b90509250925092565b6020808252825182820181905260009190848201906040850190845b81811015612b2957835183529284019291840191600101612b0d565b50909695505050505050565b6000815180845260005b81811015612b5b57602081850181015186830182015201612b3f565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000825160ff815116602084015260ff602082015116604084015261ffff604082015116606084015260ff606082015116608084015250602083015160a080840152805160c0840152602081015160e084015260ff60408201511661010084015260608101519050608061012084015261045d610140840182612b35565b600060208284031215612c0f57600080fd5b813567ffffffffffffffff811115612c2657600080fd5b8201610160818503121561180f57600080fd5b6000815160408452612c4e6040850182612b35565b602093840151949093019390935250919050565b60006101606001600160a01b038351168452602083015167ffffffffffffffff808251166020870152602082015160408701526040820151606087015260608201516080870152608082015160a087015260a082015160c08701528060c08301511660e08701525050604083015181610100860152612ce382860182612c39565b915050606083015161012085015260808301516101408501528091505092915050565b602081526000825160406020840152612d226060840182612c62565b9050602084015160408401528091505092915050565b6020810160068310612a7f57612a7f612a55565b6000806101208385031215612d6057600080fd5b82359150612d718460208501612aa7565b90509250929050565b60208152600061180f6020830184612c62565b634e487b7160e01b600052601160045260246000fd5b818103818111156106c0576106c0612d8d565b634e487b7160e01b600052601260045260246000fd5b600082612ddb57612ddb612db6565b500690565b80820281158282048414176106c0576106c0612d8d565b600082612e0657612e06612db6565b500490565b808201808211156106c0576106c0612d8d565b600060208284031215612e3057600080fd5b8151801515811461180f57600080fd5b634e487b7160e01b600052600160045260246000fd5b600181811c90821680612e6a57607f821691505b60208210810361055157634e487b7160e01b600052602260045260246000fd5b6001600160a01b0381168114612e9f57600080fd5b50565b600060208284031215612eb457600080fd5b813561180f81612e8a565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612ef857612ef8612ebf565b60405290565b60405160a0810167ffffffffffffffff81118282101715612ef857612ef8612ebf565b60405160e0810167ffffffffffffffff81118282101715612ef857612ef8612ebf565b604051601f8201601f1916810167ffffffffffffffff81118282101715612f6d57612f6d612ebf565b604052919050565b67ffffffffffffffff81168114612e9f57600080fd5b600060408284031215612f9d57600080fd5b612fa5612ed5565b9050813567ffffffffffffffff80821115612fbf57600080fd5b818401915084601f830112612fd357600080fd5b8135602082821115612fe757612fe7612ebf565b612ff9601f8301601f19168201612f44565b9250818352868183860101111561300f57600080fd5b8181850182850137600081838501015282855280860135818601525050505092915050565b600081360361016081121561304857600080fd5b613050612efe565b833561305b81612e8a565b815260e0601f198301121561306f57600080fd5b613077612f21565b9150602084013561308781612f75565b8083525060408401356020830152606084013560408301526080840135606083015260a0840135608083015260c084013560a083015260e08401356130cb81612f75565b60c083015260208101919091526101008301359067ffffffffffffffff8211156130f457600080fd5b61310036838601612f8b565b604082015261012084013560608201526101409093013560808401525090919050565b600081356106c081612f75565b60008235603e1983360301811261314657600080fd5b9190910192915050565b601f821115611e14576000816000526020600020601f850160051c810160208610156131795750805b601f850160051c820191505b8181101561163f57828155600101613185565b8135601e198336030181126131ac57600080fd5b8201803567ffffffffffffffff8111156131c557600080fd5b602081360381840113156131d857600080fd5b6131ec826131e68654612e56565b86613150565b6000601f831160018114613222576000841561320a57508482018301355b600019600386901b1c1916600185901b17865561327f565b600086815260209020601f19851690835b82811015613254578785018601358255938501936001909101908501613233565b50858210156132735760001960f88760031b161c198585890101351681555b505060018460011b0186555b50508085013560018501555050505050565b813561329c81612e8a565b6001600160a01b03811673ffffffffffffffffffffffffffffffffffffffff198354161782555060208201356132d181612f75565b60018201805467ffffffffffffffff191667ffffffffffffffff83161790555060408201356002820155606082013560038201556080820135600482015560a0820135600582015560c0820135600682015561335561333260e08401613123565b6007830167ffffffffffffffff821667ffffffffffffffff198254161781555050565b61336f613366610100840184613130565b60088301613198565b610120820135600a820155610140820135600b8201555050565b8381526101208101833561339c81612f75565b67ffffffffffffffff8082166020850152602086013560408501526040860135606085015260608601356080850152608086013560a085015260a086013560c085015260c086013591506133ef82612f75565b1660e08301526101009091019190915292915050565b634e487b7160e01b600052603260045260246000fd5b61ffff82811682821603908082111561296d5761296d612d8d565b82358152602080840135908201526000610120828101613466604085016040880180358252602090810135910152565b613480608085016080880180358252602090810135910152565b61349a60c0850160c0880180358252602090810135910152565b61010084019190915283519081905261014083019060209081860160005b828110156134d4578151855293830193908301906001016134b8565b5092979650505050505050565b634e487b7160e01b600052603160045260246000fdfea264697066735822122018af34de337780d47ca891540a9390f1b4ef028567fed32e08410582d7c9120564736f6c63430008170033"; public MarketplaceDeploymentBase() : base(BYTECODE) { } public MarketplaceDeploymentBase(string byteCode) : base(byteCode) { } [Parameter("tuple", "configuration", 1)] @@ -155,6 +155,15 @@ namespace CodexContractsPlugin.Marketplace public virtual byte[] RequestId { get; set; } } + public partial class RequestExpiryFunction : RequestExpiryFunctionBase { } + + [Function("requestExpiry", "uint256")] + public class RequestExpiryFunctionBase : FunctionMessage + { + [Parameter("bytes32", "requestId", 1)] + public virtual byte[] RequestId { get; set; } + } + public partial class RequestStateFunction : RequestStateFunctionBase { } [Function("requestState", "uint8")] @@ -395,6 +404,15 @@ namespace CodexContractsPlugin.Marketplace public virtual BigInteger ReturnValue1 { get; set; } } + public partial class RequestExpiryOutputDTO : RequestExpiryOutputDTOBase { } + + [FunctionOutput] + public class RequestExpiryOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + public partial class RequestStateOutputDTO : RequestStateOutputDTOBase { } [FunctionOutput] diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/README.md b/ProjectPlugins/CodexContractsPlugin/Marketplace/README.md index bfb23ce1..2b8ddff5 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/README.md +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/README.md @@ -1 +1,14 @@ This code was generated using the Nethereum code generator, here: http://playground.nethereum.com + +1. Go to site -> Abi Code Gen. +1. Contract name = "Marketplace". +1. In container, get "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json". +1. Save only ABI section as new JSON. (top-level is a json array.) +1. From original JSON get byte code. +1. Put ABI JSON and byte code into site. +1. Generate. +1. From site generated code, copy `public partial class MarketplaceDeployment` and everything after it. (be considerate of namespace brackets!) +1. In Marketplace/Marketplace.cs, replace content of 'namespace CodexContractsPlugin.Marketplace'. + + + From 725dfc23a62a4246f4d43a0a948676f58e1fdaff Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 7 May 2024 09:49:00 +0200 Subject: [PATCH 056/142] Cleanup of codex tests --- .../BasicTests/ContinuousSubstitute.cs | 298 ------------------ .../ScalabilityTests/ScalabilityTests.cs | 1 + .../DiscordBotTests.cs | 3 +- .../LogHelperTests.cs | 7 +- .../NetworkIsolationTest.cs | 2 +- 5 files changed, 7 insertions(+), 304 deletions(-) delete mode 100644 Tests/CodexTests/BasicTests/ContinuousSubstitute.cs rename Tests/CodexTests/{BasicTests => UtilityTests}/DiscordBotTests.cs (98%) rename Tests/CodexTests/{BasicTests => UtilityTests}/LogHelperTests.cs (94%) rename Tests/CodexTests/{BasicTests => UtilityTests}/NetworkIsolationTest.cs (97%) diff --git a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs b/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs deleted file mode 100644 index e135d2f4..00000000 --- a/Tests/CodexTests/BasicTests/ContinuousSubstitute.cs +++ /dev/null @@ -1,298 +0,0 @@ -using CodexContractsPlugin; -using CodexPlugin; -using GethPlugin; -using KubernetesWorkflow.Types; -using Logging; -using MetricsPlugin; -using NUnit.Framework; -using Utils; - -namespace CodexTests.BasicTests -{ - [Ignore("Used for debugging continuous tests")] - [TestFixture] - public class ContinuousSubstitute : AutoBootstrapDistTest - { - [Test] - public void ContinuousTestSubstitute() - { - var geth = Ci.StartGethNode(s => s.IsMiner().WithName("geth")); - var contract = Ci.StartCodexContracts(geth); - - var group = AddCodex(5, o => o - .EnableMetrics() - .EnableMarketplace(geth, contract, s => s - .WithInitial(10.Eth(), 100000.TestTokens()) - .AsStorageNode() - .AsValidator()) - .WithBlockTTL(TimeSpan.FromMinutes(5)) - .WithBlockMaintenanceInterval(TimeSpan.FromSeconds(10)) - .WithBlockMaintenanceNumber(100) - .WithStorageQuota(1.GB())); - - var nodes = group.Cast().ToArray(); - - var rc = Ci.DeployMetricsCollector(nodes); - - var availability = new StorageAvailability( - totalSpace: 500.MB(), - maxDuration: TimeSpan.FromMinutes(5), - minPriceForTotalSpace: 500.TestTokens(), - maxCollateral: 1024.TestTokens() - ); - - foreach (var node in nodes) - { - node.Marketplace.MakeStorageAvailable(availability); - } - - var endTime = DateTime.UtcNow + TimeSpan.FromHours(10); - while (DateTime.UtcNow < endTime) - { - var allNodes = nodes.ToList(); - var primary = allNodes.PickOneRandom(); - var secondary = allNodes.PickOneRandom(); - - Log("Run Test"); - PerformTest(primary, secondary, rc); - - Thread.Sleep(TimeSpan.FromSeconds(5)); - } - } - - private void LogBytesPerMillisecond(Action action) - { - var sw = Stopwatch.Begin(GetTestLog()); - action(); - var duration = sw.End(); - double totalMs = duration.TotalMilliseconds; - double totalBytes = fileSize.SizeInBytes; - - var bytesPerMs = totalBytes / totalMs; - Log($"Bytes per millisecond: {bytesPerMs}"); - } - - [Test] - public void PeerTest() - { - var group = AddCodex(5, o => o - //.EnableMetrics() - //.EnableMarketplace(100000.TestTokens(), 0.Eth(), isValidator: true) - .WithBlockTTL(TimeSpan.FromMinutes(2)) - .WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2)) - .WithBlockMaintenanceNumber(10000) - .WithBlockTTL(TimeSpan.FromMinutes(2)) - .WithStorageQuota(1.GB())); - - var nodes = group.Cast().ToArray(); - - var checkTime = DateTime.UtcNow + TimeSpan.FromMinutes(1); - var endTime = DateTime.UtcNow + TimeSpan.FromHours(10); - while (DateTime.UtcNow < endTime) - { - //CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes()); - //CheckRoutingTables(GetAllOnlineCodexNodes()); - - var node = nodes.ToList().PickOneRandom(); - var file = GenerateTestFile(50.MB()); - node.UploadFile(file); - - Thread.Sleep(20000); - } - } - - private void CheckRoutingTables(IEnumerable nodes) - { - var all = nodes.ToArray(); - var allIds = all.Select(n => n.GetDebugInfo().Table.LocalNode.NodeId).ToArray(); - - var errors = all.Select(n => AreAllPresent(n, allIds)).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - - if (errors.Any()) - { - Assert.Fail(string.Join(Environment.NewLine, errors)); - } - } - - private string AreAllPresent(ICodexNode n, string[] allIds) - { - var info = n.GetDebugInfo(); - var known = info.Table.Nodes.Select(n => n.NodeId).ToArray(); - var expected = allIds.Where(i => i != info.Table.LocalNode.NodeId).ToArray(); - - if (!expected.All(ex => known.Contains(ex))) - { - return $"Not all of '{string.Join(",", expected)}' were present in routing table: '{string.Join(",", known)}'"; - } - - return string.Empty; - } - - private ByteSize fileSize = 80.MB(); - - private const string BytesStoredMetric = "codexRepostoreBytesUsed"; - - private void PerformTest(ICodexNode primary, ICodexNode secondary, RunningPod rc) - { - ScopedTestFiles(() => - { - var testFile = GenerateTestFile(fileSize); - - var metrics = Ci.WrapMetricsCollector(rc, primary); - var beforeBytesStored = metrics.GetMetric(BytesStoredMetric); - - ContentId contentId = null!; - LogBytesPerMillisecond(() => contentId = primary.UploadFile(testFile)); - - var low = fileSize.SizeInBytes; - var high = low * 1.2; - Log("looking for: " + low + " < " + high); - - Time.WaitUntil(() => - { - var afterBytesStored = metrics.GetMetric(BytesStoredMetric); - var newBytes = Convert.ToInt64(afterBytesStored.Values.Last().Value - beforeBytesStored.Values.Last().Value); - - return high > newBytes && newBytes > low; - }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2), nameof(ContinuousSubstitute)); - - FileUtils.TrackedFile? downloadedFile = null; - LogBytesPerMillisecond(() => downloadedFile = secondary.DownloadContent(contentId)); - - testFile.AssertIsEqual(downloadedFile); - }); - } - - [Test] - public void HoldMyBeerTest() - { - var blockExpirationTime = TimeSpan.FromMinutes(3); - var group = AddCodex(3, o => o - .EnableMetrics() - .WithBlockTTL(blockExpirationTime) - .WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2)) - .WithBlockMaintenanceNumber(10000) - .WithStorageQuota(2000.MB())); - - var nodes = group.Cast().ToArray(); - - var endTime = DateTime.UtcNow + TimeSpan.FromHours(24); - - var filesize = 80.MB(); - double codexDefaultBlockSize = 31 * 64 * 33; - var numberOfBlocks = Convert.ToInt64(Math.Ceiling(filesize.SizeInBytes / codexDefaultBlockSize)); - var sizeInBytes = filesize.SizeInBytes; - Assert.That(numberOfBlocks, Is.EqualTo(1282)); - - var startTime = DateTime.UtcNow; - var successfulUploads = 0; - var successfulDownloads = 0; - - while (DateTime.UtcNow < endTime) - { - foreach (var node in nodes) - { - try - { - Thread.Sleep(TimeSpan.FromSeconds(5)); - - ScopedTestFiles(() => - { - var uploadStartTime = DateTime.UtcNow; - var file = GenerateTestFile(filesize); - var cid = node.UploadFile(file); - - var cidTag = cid.Id.Substring(cid.Id.Length - 6); - Measure("upload-log-asserts", () => - { - var uploadLog = Ci.DownloadLog(node, tailLines: 50000); - - var storeLines = uploadLog.FindLinesThatContain("Stored data", "topics=\"codex node\""); - uploadLog.DeleteFile(); - - var storeLine = GetLineForCidTag(storeLines, cidTag); - AssertStoreLineContains(storeLine, numberOfBlocks, sizeInBytes); - }); - successfulUploads++; - - var uploadTimeTaken = DateTime.UtcNow - uploadStartTime; - if (uploadTimeTaken >= blockExpirationTime.Subtract(TimeSpan.FromSeconds(10))) - { - Assert.Fail("Upload took too long. Blocks already expired."); - } - - var dl = node.DownloadContent(cid); - file.AssertIsEqual(dl); - - Measure("download-log-asserts", () => - { - var downloadLog = Ci.DownloadLog(node, tailLines: 50000); - - var sentLines = downloadLog.FindLinesThatContain("Sent bytes", "topics=\"codex restapi\""); - downloadLog.DeleteFile(); - - var sentLine = GetLineForCidTag(sentLines, cidTag); - AssertSentLineContains(sentLine, sizeInBytes); - }); - successfulDownloads++; - }); - } - catch - { - var testDuration = DateTime.UtcNow - startTime; - Log("Test failed. Delaying shut-down by 30 seconds to collect metrics."); - Log($"Test failed after {Time.FormatDuration(testDuration)} and {successfulUploads} successful uploads and {successfulDownloads} successful downloads"); - Thread.Sleep(TimeSpan.FromSeconds(30)); - throw; - } - } - - Thread.Sleep(TimeSpan.FromSeconds(5)); - } - } - - private void AssertSentLineContains(string sentLine, long sizeInBytes) - { - var tag = "bytes="; - var token = sentLine.Substring(sentLine.IndexOf(tag) + tag.Length); - var bytes = Convert.ToInt64(token); - Assert.AreEqual(sizeInBytes, bytes, $"Sent bytes: Number of bytes incorrect. Line: '{sentLine}'"); - } - - private void AssertStoreLineContains(string storeLine, long numberOfBlocks, long sizeInBytes) - { - var tokens = storeLine.Split(" "); - - var blocksToken = GetToken(tokens, "blocks="); - var sizeToken = GetToken(tokens, "size="); - if (blocksToken == null) Assert.Fail("blockToken not found in " + storeLine); - if (sizeToken == null) Assert.Fail("sizeToken not found in " + storeLine); - - var blocks = Convert.ToInt64(blocksToken); - var size = Convert.ToInt64(sizeToken?.Replace("'NByte", "")); - - var lineLog = $" Line: '{storeLine}'"; - Assert.AreEqual(numberOfBlocks, blocks, "Stored data: Number of blocks incorrect." + lineLog); - Assert.AreEqual(sizeInBytes, size, "Stored data: Number of blocks incorrect." + lineLog); - } - - private string GetLineForCidTag(string[] lines, string cidTag) - { - var result = lines.SingleOrDefault(l => l.Contains(cidTag)); - if (result == null) - { - Assert.Fail($"Failed to find '{cidTag}' in lines: '{string.Join(",", lines)}'"); - throw new Exception(); - } - - return result; - } - - private string? GetToken(string[] tokens, string tag) - { - var token = tokens.SingleOrDefault(t => t.StartsWith(tag)); - if (token == null) return null; - return token.Substring(tag.Length); - } - } -} diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs index 668d26e7..9a224f7d 100644 --- a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs @@ -52,6 +52,7 @@ public class ScalabilityTests : CodexDistTest /// We upload a file to each node, to put a more wide-spread load on the network. /// Then we run the same test as ShouldMaintainFileInNetwork. /// + [Ignore("Fix ShouldMaintainFileInNetwork for all values first")] [Test] [Combinatorial] [UseLongTimeouts] diff --git a/Tests/CodexTests/BasicTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs similarity index 98% rename from Tests/CodexTests/BasicTests/DiscordBotTests.cs rename to Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 64309234..ab197751 100644 --- a/Tests/CodexTests/BasicTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -5,12 +5,13 @@ using GethPlugin; using NUnit.Framework; using Utils; -namespace CodexTests.BasicTests +namespace CodexTests.UtilityTests { [TestFixture] public class DiscordBotTests : AutoBootstrapDistTest { [Test] + [Ignore("Used for debugging bots")] public void BotRewardTest() { var myAccount = EthAccount.GenerateNew(); diff --git a/Tests/CodexTests/BasicTests/LogHelperTests.cs b/Tests/CodexTests/UtilityTests/LogHelperTests.cs similarity index 94% rename from Tests/CodexTests/BasicTests/LogHelperTests.cs rename to Tests/CodexTests/UtilityTests/LogHelperTests.cs index 8b6ba5e6..f8561f85 100644 --- a/Tests/CodexTests/BasicTests/LogHelperTests.cs +++ b/Tests/CodexTests/UtilityTests/LogHelperTests.cs @@ -2,18 +2,18 @@ using NUnit.Framework; using Utils; -namespace CodexTests.BasicTests +namespace CodexTests.UtilityTests { [TestFixture] public class LogHelperTests : AutoBootstrapDistTest { [Test] + [Ignore("Used to find the most common log messages.")] public void FindMostCommonLogMessages() { var uploader = AddCodex(s => s.WithName("uploader").WithLogLevel(CodexLogLevel.Trace)); var downloader = AddCodex(s => s.WithName("downloader").WithLogLevel(CodexLogLevel.Trace)); - var cid = uploader.UploadFile(GenerateTestFile(100.MB())); Thread.Sleep(1000); @@ -22,7 +22,6 @@ namespace CodexTests.BasicTests downloader.DownloadContent(cid); - var map = GetLogMap(downloader, logStartUtc).OrderByDescending(p => p.Value); Log("Downloader - Receive"); foreach (var entry in map) @@ -47,7 +46,7 @@ namespace CodexTests.BasicTests { if (log.TimestampUtc < startUtc) return; } - + if (map.ContainsKey(log.Message)) map[log.Message] += 1; else map.Add(log.Message, 1); }); diff --git a/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs b/Tests/CodexTests/UtilityTests/NetworkIsolationTest.cs similarity index 97% rename from Tests/CodexTests/BasicTests/NetworkIsolationTest.cs rename to Tests/CodexTests/UtilityTests/NetworkIsolationTest.cs index 26045504..c100143c 100644 --- a/Tests/CodexTests/BasicTests/NetworkIsolationTest.cs +++ b/Tests/CodexTests/UtilityTests/NetworkIsolationTest.cs @@ -3,7 +3,7 @@ using DistTestCore; using NUnit.Framework; using Utils; -namespace CodexTests.BasicTests +namespace CodexTests.UtilityTests { // Warning! // This is a test to check network-isolation in the test-infrastructure. From 8ea4c4ee3727ed023ac59e9ae47d4058c089e1b3 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 7 May 2024 10:15:02 +0200 Subject: [PATCH 057/142] Clean up codex long tests --- .../CodexPlugin/CodexContainerRecipe.cs | 3 +- .../BasicTests/DownloadTests.cs | 45 ++++--- .../BasicTests/TestInfraTests.cs | 8 +- .../CodexLongTests/BasicTests/UploadTests.cs | 42 +++--- .../MultiPeerDownloadTests.cs | 0 .../ScalabilityTests/ScalabilityTests.cs | 0 .../OneClientLargeFileTests.cs | 126 ------------------ .../ClusterSpeedTests.cs | 1 + 8 files changed, 57 insertions(+), 168 deletions(-) rename Tests/{CodexTests => CodexLongTests}/ScalabilityTests/MultiPeerDownloadTests.cs (100%) rename Tests/{CodexTests => CodexLongTests}/ScalabilityTests/ScalabilityTests.cs (100%) delete mode 100644 Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs rename Tests/CodexTests/{ScalabilityTests => UtilityTests}/ClusterSpeedTests.cs (97%) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 85900295..e8187740 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,8 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-c58d4d7-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-ea6d681-dist-tests"; + public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; diff --git a/Tests/CodexLongTests/BasicTests/DownloadTests.cs b/Tests/CodexLongTests/BasicTests/DownloadTests.cs index 533934df..fbfc20b0 100644 --- a/Tests/CodexLongTests/BasicTests/DownloadTests.cs +++ b/Tests/CodexLongTests/BasicTests/DownloadTests.cs @@ -1,3 +1,4 @@ +using CodexPlugin; using CodexTests; using DistTestCore; using FileUtils; @@ -7,35 +8,45 @@ using Utils; namespace CodexLongTests.BasicTests { [TestFixture] - public class DownloadTests : CodexDistTest + public class DownloadTests : AutoBootstrapDistTest { - [TestCase(3, 500)] - [TestCase(5, 100)] - [TestCase(10, 256)] + [Test] + [Combinatorial] [UseLongTimeouts] - public void ParallelDownload(int numberOfNodes, int filesizeMb) + public void ParallelDownload( + [Values(1, 3, 5)] int numberOfFiles, + [Values(10, 50, 100)] int filesizeMb) { - var group = AddCodex(numberOfNodes); var host = AddCodex(); + var client = AddCodex(); - foreach (var node in group) + var testfiles = new List(); + var contentIds = new List(); + var downloadedFiles = new List(); + + for (int i = 0; i < numberOfFiles; i++) { - host.ConnectToPeer(node); + testfiles.Add(GenerateTestFile(filesizeMb.MB())); + contentIds.Add(new ContentId()); + downloadedFiles.Add(null); } - var testFile = GenerateTestFile(filesizeMb.MB()); - var contentId = host.UploadFile(testFile); - var list = new List>(); - - foreach (var node in group) + for (int i = 0; i < numberOfFiles; i++) { - list.Add(Task.Run(() => { return node.DownloadContent(contentId); })); + contentIds[i] = host.UploadFile(testfiles[i]); } - Task.WaitAll(list.ToArray()); - foreach (var task in list) + var downloadTasks = new List(); + for (int i = 0; i < numberOfFiles; i++) { - testFile.AssertIsEqual(task.Result); + downloadTasks.Add(Task.Run(() => { downloadedFiles[i] = client.DownloadContent(contentIds[i]); })); + } + + Task.WaitAll(downloadTasks.ToArray()); + + for (int i = 0; i < numberOfFiles; i++) + { + testfiles[i].AssertIsEqual(downloadedFiles[i]); } } } diff --git a/Tests/CodexLongTests/BasicTests/TestInfraTests.cs b/Tests/CodexLongTests/BasicTests/TestInfraTests.cs index 5720457b..51d41b70 100644 --- a/Tests/CodexLongTests/BasicTests/TestInfraTests.cs +++ b/Tests/CodexLongTests/BasicTests/TestInfraTests.cs @@ -6,7 +6,9 @@ namespace CodexLongTests.BasicTests { public class TestInfraTests : CodexDistTest { - [Test, UseLongTimeouts] + [Test] + [UseLongTimeouts] + [Ignore("Not supported atm")] public void TestInfraShouldHave1000AddressSpacesPerPod() { var group = AddCodex(1000, s => s.EnableMetrics()); @@ -17,7 +19,9 @@ namespace CodexLongTests.BasicTests "Not all created nodes provided a unique id."); } - [Test, UseLongTimeouts] + [Test] + [UseLongTimeouts] + [Ignore("Not supported atm")] public void TestInfraSupportsManyConcurrentPods() { for (var i = 0; i < 20; i++) diff --git a/Tests/CodexLongTests/BasicTests/UploadTests.cs b/Tests/CodexLongTests/BasicTests/UploadTests.cs index 57e02965..31718aa0 100644 --- a/Tests/CodexLongTests/BasicTests/UploadTests.cs +++ b/Tests/CodexLongTests/BasicTests/UploadTests.cs @@ -8,41 +8,39 @@ using Utils; namespace CodexLongTests.BasicTests { [TestFixture] - public class UploadTests : CodexDistTest + public class UploadTests : AutoBootstrapDistTest { - [TestCase(3, 50)] - [TestCase(5, 75)] - [TestCase(10, 25)] + [Test] + [Combinatorial] [UseLongTimeouts] - public void ParallelUpload(int numberOfNodes, int filesizeMb) + public void ParallelUpload( + [Values(1, 3, 5)] int numberOfFiles, + [Values(10, 50, 100)] int filesizeMb) { - var group = AddCodex(numberOfNodes); var host = AddCodex(); - - foreach (var node in group) - { - host.ConnectToPeer(node); - } + var client = AddCodex(); var testfiles = new List(); - var contentIds = new List>(); + var contentIds = new List(); - for (int i = 0; i < group.Count(); i++) + for (int i = 0; i < numberOfFiles; i++) { testfiles.Add(GenerateTestFile(filesizeMb.MB())); - var n = i; - contentIds.Add(Task.Run(() => { return host.UploadFile(testfiles[n]); })); + contentIds.Add(new ContentId()); } - var downloads = new List>(); - for (int i = 0; i < group.Count(); i++) + + var uploadTasks = new List(); + for (int i = 0; i < numberOfFiles; i++) { - var n = i; - downloads.Add(Task.Run(() => { return group[n].DownloadContent(contentIds[n].Result); })); + uploadTasks.Add(Task.Run(() => { contentIds[i] = host.UploadFile(testfiles[i]); })); } - Task.WaitAll(downloads.ToArray()); - for (int i = 0; i < group.Count(); i++) + + Task.WaitAll(uploadTasks.ToArray()); + + for (int i = 0; i < numberOfFiles; i++) { - testfiles[i].AssertIsEqual(downloads[i].Result); + var downloaded = client.DownloadContent(contentIds[i]); + testfiles[i].AssertIsEqual(downloaded); } } } diff --git a/Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs b/Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs similarity index 100% rename from Tests/CodexTests/ScalabilityTests/MultiPeerDownloadTests.cs rename to Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs diff --git a/Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs similarity index 100% rename from Tests/CodexTests/ScalabilityTests/ScalabilityTests.cs rename to Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs diff --git a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs b/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs deleted file mode 100644 index 3e87e1de..00000000 --- a/Tests/CodexTests/ScalabilityTests/OneClientLargeFileTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -using CodexPlugin; -using DistTestCore; -using Logging; -using NUnit.Framework; -using Utils; - -namespace CodexTests.ScalabilityTests -{ - [TestFixture] - public class OneClientLargeFileTests : CodexDistTest - { - [Test] - [Combinatorial] - [UseLongTimeouts] - public void OneClientLargeFile([Values( - 256, - 512, - 1024, // GB - 2048, - 4096, - 8192, - 16384, - 32768, - 65536, - 131072 - )] int sizeMb) - { - var testFile = GenerateTestFile(sizeMb.MB()); - - var node = AddCodex(s => s - .WithLogLevel(CodexLogLevel.Warn) - .WithStorageQuota((sizeMb + 10).MB()) - ); - var contentId = node.UploadFile(testFile); - var downloadedFile = node.DownloadContent(contentId); - - testFile.AssertIsEqual(downloadedFile); - } - - [Test] - public void ManyFiles() - { - // I suspect that the upload speed is linked to the total - // number of blocks already in the node. I suspect the - // metadata store to be the cause of any slow-down. - // Using this test to detect and quantify the numbers. - - var node = AddCodex(s => s - .WithLogLevel(CodexLogLevel.Trace) - .WithStorageQuota(20.GB()) - ); - - var startUtc = DateTime.UtcNow; - var endUtc = DateTime.UtcNow; - - var fastMap = new Dictionary(); - var slowMap = new Dictionary(); - - var times = new List(); - for (var i = 0; i < 100; i++) - { - Thread.Sleep(1000); - var file = GenerateTestFile(100.MB()); - startUtc = DateTime.UtcNow; - var duration = Stopwatch.Measure(GetTestLog(), "Upload_" + i, () => - { - node.UploadFile(file); - }); - times.Add(duration); - endUtc = DateTime.UtcNow; - - // We collect the log of the node during the upload. - // We count the line occurances. - // If the upload was fast, add it to the fast-map. - // If it was slow, add it to the slow-map. - // After the test, we can compare and hopefully see what the node was doing during the slow uploads - // that it wasn't doing during the fast ones. - if (duration.TotalSeconds < 12) - { - AddToLogMap(fastMap, node, startUtc, endUtc); - } - else if (duration.TotalSeconds > 25) - { - AddToLogMap(slowMap, node, startUtc, endUtc); - } - } - - Log("Upload times:"); - foreach (var t in times) - { - Log(Time.FormatDuration(t)); - } - Log("Fast map:"); - foreach (var entry in fastMap.OrderByDescending(p => p.Value)) - { - if (entry.Value > 9) - { - Log($"'{entry.Key}' = {entry.Value}"); - } - } - Log("Slow map:"); - foreach (var entry in slowMap.OrderByDescending(p => p.Value)) - { - if (entry.Value > 9) - { - Log($"'{entry.Key}' = {entry.Value}"); - } - } - } - - private void AddToLogMap(Dictionary map, ICodexNode node, DateTime startUtc, DateTime endUtc) - { - var log = Ci.DownloadLog(node, 1000000); - log.IterateLines(line => - { - var log = CodexLogLine.Parse(line); - if (log == null) return; - if (log.TimestampUtc < startUtc) return; - if (log.TimestampUtc > endUtc) return; - - if (map.ContainsKey(log.Message)) map[log.Message] += 1; - else map.Add(log.Message, 1); - }); - } - } -} diff --git a/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs similarity index 97% rename from Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs rename to Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs index 0e3fdafd..9e647eaf 100644 --- a/Tests/CodexTests/ScalabilityTests/ClusterSpeedTests.cs +++ b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs @@ -12,6 +12,7 @@ namespace CodexTests.ScalabilityTests [Test] [Combinatorial] + [Ignore("Used to measure disc io speeds in cluster.")] public void DiscSpeedTest( [Values(1, 10, 100, 1024, 1024 * 10, 1024 * 100, 1024 * 1024)] int bufferSizeKb ) From 266c661958b199ac6a6f4af08212d4d0b4f73009 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 7 May 2024 10:21:05 +0200 Subject: [PATCH 058/142] Updates codex API --- ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- ProjectPlugins/CodexPlugin/Mapper.cs | 2 +- ProjectPlugins/CodexPlugin/openapi.yaml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 5cf3dc96..f3e4e10a 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -9,7 +9,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "5A-B0-2A-AC-42-B1-A2-49-6F-9D-4E-D8-56-40-10-A6-67-F4-0D-2A-9F-E0-84-5C-EB-B8-2D-4F-D8-56-79-6C"; + private const string OpenApiYamlHash = "0F-C8-02-1E-2C-2C-15-F6-91-6A-01-31-11-49-95-06-79-26-25-BF-27-3C-A8-2E-5F-7F-34-FD-C0-57-A0-9A"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 9db2a84c..053f15e5 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -56,7 +56,7 @@ namespace CodexPlugin ProofProbability = ToDecInt(purchase.ProofProbability), Reward = ToDecInt(purchase.PricePerSlotPerSecond), Collateral = ToDecInt(purchase.RequiredCollateral), - Expiry = ToDecInt(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + purchase.Expiry.TotalSeconds), + Expiry = ToDecInt(purchase.Expiry.TotalSeconds), Nodes = Convert.ToInt32(purchase.MinRequiredNumberOfNodes), Tolerance = Convert.ToInt32(purchase.NodeFailureTolerance) }; diff --git a/ProjectPlugins/CodexPlugin/openapi.yaml b/ProjectPlugins/CodexPlugin/openapi.yaml index cf9b6b6c..49c75e64 100644 --- a/ProjectPlugins/CodexPlugin/openapi.yaml +++ b/ProjectPlugins/CodexPlugin/openapi.yaml @@ -213,8 +213,7 @@ components: description: Number as decimal string that represents how much collateral is asked from hosts that wants to fill a slots expiry: type: string - description: Number as decimal string that represents expiry time of the request (in unix timestamp) - + description: Number as decimal string that represents expiry threshold in seconds from when the Request is submitted. When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided. The number of seconds can not be higher then the Request's duration itself. StorageAsk: type: object required: From 0ec43a93250ab275153199560a1fd32f3c3a300a Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 7 May 2024 11:04:32 +0200 Subject: [PATCH 059/142] Shuts up json-serialization codex logging --- Framework/KubernetesWorkflow/Types/RunningPod.cs | 2 +- ProjectPlugins/CodexPlugin/CodexSetup.cs | 1 + ProjectPlugins/CodexPlugin/CodexStartupConfig.cs | 9 ++++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Framework/KubernetesWorkflow/Types/RunningPod.cs b/Framework/KubernetesWorkflow/Types/RunningPod.cs index 7f1a24d7..4d03cf36 100644 --- a/Framework/KubernetesWorkflow/Types/RunningPod.cs +++ b/Framework/KubernetesWorkflow/Types/RunningPod.cs @@ -20,7 +20,7 @@ namespace KubernetesWorkflow.Types [JsonIgnore] public string Name { - get { return $"{Containers.Length}x '{Containers.First().Name}'"; } + get { return $"'{string.Join("&", Containers.Select(c => c.Name).ToArray())}'"; } } public string Describe() diff --git a/ProjectPlugins/CodexPlugin/CodexSetup.cs b/ProjectPlugins/CodexPlugin/CodexSetup.cs index 6f9d7679..2367cedc 100644 --- a/ProjectPlugins/CodexPlugin/CodexSetup.cs +++ b/ProjectPlugins/CodexPlugin/CodexSetup.cs @@ -52,6 +52,7 @@ namespace CodexPlugin public CodexLogLevel Libp2p { get; set; } public CodexLogLevel ContractClock { get; set; } = CodexLogLevel.Warn; public CodexLogLevel? BlockExchange { get; } + public CodexLogLevel JsonSerialize { get; set; } = CodexLogLevel.Warn; } public class CodexSetup : CodexStartupConfig, ICodexSetup diff --git a/ProjectPlugins/CodexPlugin/CodexStartupConfig.cs b/ProjectPlugins/CodexPlugin/CodexStartupConfig.cs index dbed45ab..8e7ead12 100644 --- a/ProjectPlugins/CodexPlugin/CodexStartupConfig.cs +++ b/ProjectPlugins/CodexPlugin/CodexStartupConfig.cs @@ -73,11 +73,18 @@ namespace CodexPlugin "contracts", "clock" }; + var jsonSerializeTopics = new[] + { + "serde", + "json", + "serialization" + }; level = $"{level};" + $"{CustomTopics.DiscV5.ToString()!.ToLowerInvariant()}:{string.Join(",", discV5Topics)};" + $"{CustomTopics.Libp2p.ToString()!.ToLowerInvariant()}:{string.Join(",", libp2pTopics)};" + - $"{CustomTopics.ContractClock.ToString().ToLowerInvariant()}:{string.Join(",", contractClockTopics)}"; + $"{CustomTopics.ContractClock.ToString().ToLowerInvariant()}:{string.Join(",", contractClockTopics)};" + + $"{CustomTopics.JsonSerialize.ToString().ToLowerInvariant()}:{string.Join(",", jsonSerializeTopics)}"; if (CustomTopics.BlockExchange != null) { From c01f9dbb2164666de7b62e70300baf4673b05584 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 9 May 2024 08:57:19 +0200 Subject: [PATCH 060/142] Fixes crash in blocktimefinder. --- Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs index b1d84e99..a8e67f4d 100644 --- a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs +++ b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs @@ -20,6 +20,7 @@ namespace NethereumWorkflow.BlockUtils public BlockTimeEntry Get(ulong blockNumber) { + bounds.Initialize(); var b = cache.Get(blockNumber); if (b != null) return b; return GetBlock(blockNumber); From c6a757d6fba60c749d475486d2fd93dfc5293bfc Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 9 May 2024 09:32:48 +0200 Subject: [PATCH 061/142] Rename AddCodex. Better retry logging --- Framework/Utils/Time.cs | 22 +++++++++++++++---- .../BasicTests/DownloadTests.cs | 4 ++-- .../BasicTests/LargeFileTests.cs | 2 +- .../BasicTests/TestInfraTests.cs | 4 ++-- .../CodexLongTests/BasicTests/UploadTests.cs | 4 ++-- .../LongFullyConnectedDownloadTests.cs | 2 +- .../MultiPeerDownloadTests.cs | 4 ++-- .../ScalabilityTests/ScalabilityTests.cs | 8 +++---- Tests/CodexTests/AutoBootstrapDistTest.cs | 2 +- Tests/CodexTests/BasicTests/ExampleTests.cs | 6 ++--- .../CodexTests/BasicTests/MarketplaceTests.cs | 4 ++-- Tests/CodexTests/BasicTests/OneClientTests.cs | 6 ++--- .../CodexTests/BasicTests/ThreeClientTest.cs | 4 ++-- Tests/CodexTests/BasicTests/TwoClientTests.cs | 4 ++-- Tests/CodexTests/CodexDistTest.cs | 14 ++++++------ .../FullyConnectedDownloadTests.cs | 6 ++--- .../LayeredDiscoveryTests.cs | 22 +++++++++---------- .../PeerDiscoveryTests/PeerDiscoveryTests.cs | 8 +++---- .../UtilityTests/DiscordBotTests.cs | 4 ++-- .../CodexTests/UtilityTests/LogHelperTests.cs | 4 ++-- 20 files changed, 74 insertions(+), 60 deletions(-) diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index f0f76043..f1a109d9 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -1,4 +1,6 @@ -namespace Utils +using System.Diagnostics; + +namespace Utils { public static class Time { @@ -105,16 +107,18 @@ { var start = DateTime.UtcNow; var tries = 1; - var exceptions = new List(); + var tryInfo = new List<(Exception, TimeSpan)>(); while (true) { var duration = DateTime.UtcNow - start; if (duration > maxTimeout) { - throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.", new AggregateException(exceptions)); + var info = FormatTryInfos(tryInfo); + throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.{Environment.NewLine}{info}"); } + var sw = Stopwatch.StartNew(); try { action(); @@ -122,7 +126,7 @@ } catch (Exception ex) { - exceptions.Add(ex); + tryInfo.Add((ex, sw.Elapsed)); tries++; } @@ -130,6 +134,16 @@ } } + private static string FormatTryInfos(List<(Exception, TimeSpan)> tryInfo) + { + return string.Join(Environment.NewLine, tryInfo.Select(FormatTryInfo).ToArray()); + } + + private static string FormatTryInfo((Exception, TimeSpan) info, int index) + { + return $"Attempt {index} took {FormatDuration(info.Item2)} and failed with exception {info.Item1}."; + } + public static T Retry(Func action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { var start = DateTime.UtcNow; diff --git a/Tests/CodexLongTests/BasicTests/DownloadTests.cs b/Tests/CodexLongTests/BasicTests/DownloadTests.cs index fbfc20b0..0a95a643 100644 --- a/Tests/CodexLongTests/BasicTests/DownloadTests.cs +++ b/Tests/CodexLongTests/BasicTests/DownloadTests.cs @@ -17,8 +17,8 @@ namespace CodexLongTests.BasicTests [Values(1, 3, 5)] int numberOfFiles, [Values(10, 50, 100)] int filesizeMb) { - var host = AddCodex(); - var client = AddCodex(); + var host = StartCodex(); + var client = StartCodex(); var testfiles = new List(); var contentIds = new List(); diff --git a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs index 1a6e21cc..0470e35a 100644 --- a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs +++ b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs @@ -48,7 +48,7 @@ namespace CodexLongTests.BasicTests var expectedFile = GenerateTestFile(sizeMB); - var node = AddCodex(s => s.WithStorageQuota((size + 10).MB())); + var node = StartCodex(s => s.WithStorageQuota((size + 10).MB())); var uploadStart = DateTime.UtcNow; var cid = node.UploadFile(expectedFile); diff --git a/Tests/CodexLongTests/BasicTests/TestInfraTests.cs b/Tests/CodexLongTests/BasicTests/TestInfraTests.cs index 51d41b70..7dcf02b0 100644 --- a/Tests/CodexLongTests/BasicTests/TestInfraTests.cs +++ b/Tests/CodexLongTests/BasicTests/TestInfraTests.cs @@ -11,7 +11,7 @@ namespace CodexLongTests.BasicTests [Ignore("Not supported atm")] public void TestInfraShouldHave1000AddressSpacesPerPod() { - var group = AddCodex(1000, s => s.EnableMetrics()); + var group = StartCodex(1000, s => s.EnableMetrics()); var nodeIds = group.Select(n => n.GetDebugInfo().Id).ToArray(); @@ -26,7 +26,7 @@ namespace CodexLongTests.BasicTests { for (var i = 0; i < 20; i++) { - var n = AddCodex(); + var n = StartCodex(); Assert.That(!string.IsNullOrEmpty(n.GetDebugInfo().Id)); } diff --git a/Tests/CodexLongTests/BasicTests/UploadTests.cs b/Tests/CodexLongTests/BasicTests/UploadTests.cs index 31718aa0..361c3a73 100644 --- a/Tests/CodexLongTests/BasicTests/UploadTests.cs +++ b/Tests/CodexLongTests/BasicTests/UploadTests.cs @@ -17,8 +17,8 @@ namespace CodexLongTests.BasicTests [Values(1, 3, 5)] int numberOfFiles, [Values(10, 50, 100)] int filesizeMb) { - var host = AddCodex(); - var client = AddCodex(); + var host = StartCodex(); + var client = StartCodex(); var testfiles = new List(); var contentIds = new List(); diff --git a/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs b/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs index 8f57e38a..15fc9f34 100644 --- a/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs +++ b/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs @@ -15,7 +15,7 @@ namespace CodexLongTests.DownloadConnectivityTests [Values(10, 15, 20)] int numberOfNodes, [Values(10, 100)] int sizeMBs) { - for (var i = 0; i < numberOfNodes; i++) AddCodex(); + for (var i = 0; i < numberOfNodes; i++) StartCodex(); CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB()); } diff --git a/Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs b/Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs index f4c0a5f0..b1640b6d 100644 --- a/Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/MultiPeerDownloadTests.cs @@ -16,7 +16,7 @@ namespace CodexTests.ScalabilityTests [Values(100, 1000)] int fileSize ) { - var hosts = AddCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); + var hosts = StartCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); var file = GenerateTestFile(fileSize.MB()); var cid = hosts[0].UploadFile(file); var tailOfManifestCid = cid.Id.Substring(cid.Id.Length - 6); @@ -38,7 +38,7 @@ namespace CodexTests.ScalabilityTests foreach (var h in hosts) h.DownloadContent(cid); - var client = AddCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); + var client = StartCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace)); var resultFile = client.DownloadContent(cid); resultFile!.AssertIsEqual(file); diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 9a224f7d..b87be971 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -24,8 +24,8 @@ public class ScalabilityTests : CodexDistTest { var logLevel = CodexLogLevel.Info; - var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); - var nodes = AddCodex(numberOfNodes - 1, s => s + var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); + var nodes = StartCodex(numberOfNodes - 1, s => s .WithBootstrapNode(bootstrap) .WithLogLevel(logLevel) .WithStorageQuota((fileSizeInMb + 50).MB()) @@ -64,8 +64,8 @@ public class ScalabilityTests : CodexDistTest { var logLevel = CodexLogLevel.Info; - var bootstrap = AddCodex(s => s.WithLogLevel(logLevel)); - var nodes = AddCodex(numberOfNodes - 1, s => s + var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); + var nodes = StartCodex(numberOfNodes - 1, s => s .WithBootstrapNode(bootstrap) .WithLogLevel(logLevel) .WithStorageQuota((fileSizeInMb + 50).MB()) diff --git a/Tests/CodexTests/AutoBootstrapDistTest.cs b/Tests/CodexTests/AutoBootstrapDistTest.cs index 5c70496e..fa103208 100644 --- a/Tests/CodexTests/AutoBootstrapDistTest.cs +++ b/Tests/CodexTests/AutoBootstrapDistTest.cs @@ -8,7 +8,7 @@ namespace CodexTests [SetUp] public void SetUpBootstrapNode() { - BootstrapNode = AddCodex(s => s.WithName("BOOTSTRAP")); + BootstrapNode = StartCodex(s => s.WithName("BOOTSTRAP")); } [TearDown] diff --git a/Tests/CodexTests/BasicTests/ExampleTests.cs b/Tests/CodexTests/BasicTests/ExampleTests.cs index e7453b5e..03562981 100644 --- a/Tests/CodexTests/BasicTests/ExampleTests.cs +++ b/Tests/CodexTests/BasicTests/ExampleTests.cs @@ -13,7 +13,7 @@ namespace CodexTests.BasicTests [Test] public void CodexLogExample() { - var primary = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Warn, CodexLogLevel.Warn))); + var primary = StartCodex(s => s.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Warn, CodexLogLevel.Warn))); var cid = primary.UploadFile(GenerateTestFile(5.MB())); @@ -28,8 +28,8 @@ namespace CodexTests.BasicTests [Test] public void TwoMetricsExample() { - var group = AddCodex(2, s => s.EnableMetrics()); - var group2 = AddCodex(2, s => s.EnableMetrics()); + var group = StartCodex(2, s => s.EnableMetrics()); + var group2 = StartCodex(2, s => s.EnableMetrics()); var primary = group[0]; var secondary = group[1]; diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index f81d7bd9..6904e56e 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -21,7 +21,7 @@ namespace CodexTests.BasicTests var contracts = Ci.StartCodexContracts(geth); var numberOfHosts = 5; - var hosts = AddCodex(numberOfHosts, s => s + var hosts = StartCodex(numberOfHosts, s => s .WithName("Host") .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) { @@ -49,7 +49,7 @@ namespace CodexTests.BasicTests var testFile = GenerateTestFile(fileSize); - var client = AddCodex(s => s + var client = StartCodex(s => s .WithName("Client") .EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), clientInitialBalance))); diff --git a/Tests/CodexTests/BasicTests/OneClientTests.cs b/Tests/CodexTests/BasicTests/OneClientTests.cs index 9d4f5a58..11e81256 100644 --- a/Tests/CodexTests/BasicTests/OneClientTests.cs +++ b/Tests/CodexTests/BasicTests/OneClientTests.cs @@ -10,7 +10,7 @@ namespace CodexTests.BasicTests [Test] public void OneClientTest() { - var primary = AddCodex(); + var primary = StartCodex(); PerformOneClientTest(primary); } @@ -18,11 +18,11 @@ namespace CodexTests.BasicTests [Test] public void RestartTest() { - var primary = AddCodex(); + var primary = StartCodex(); primary.Stop(waitTillStopped: true); - primary = AddCodex(); + primary = StartCodex(); PerformOneClientTest(primary); } diff --git a/Tests/CodexTests/BasicTests/ThreeClientTest.cs b/Tests/CodexTests/BasicTests/ThreeClientTest.cs index bd9cdfb4..2285aa95 100644 --- a/Tests/CodexTests/BasicTests/ThreeClientTest.cs +++ b/Tests/CodexTests/BasicTests/ThreeClientTest.cs @@ -9,8 +9,8 @@ namespace CodexTests.BasicTests [Test] public void ThreeClient() { - var primary = AddCodex(); - var secondary = AddCodex(); + var primary = StartCodex(); + var secondary = StartCodex(); var testFile = GenerateTestFile(10.MB()); diff --git a/Tests/CodexTests/BasicTests/TwoClientTests.cs b/Tests/CodexTests/BasicTests/TwoClientTests.cs index 546fbda3..ab15ff25 100644 --- a/Tests/CodexTests/BasicTests/TwoClientTests.cs +++ b/Tests/CodexTests/BasicTests/TwoClientTests.cs @@ -10,8 +10,8 @@ namespace CodexTests.BasicTests [Test] public void TwoClientTest() { - var uploader = AddCodex(s => s.WithName("Uploader")); - var downloader = AddCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader)); + var uploader = StartCodex(s => s.WithName("Uploader")); + var downloader = StartCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader)); PerformTwoClientTest(uploader, downloader); } diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 0578c27a..7058b8e1 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -39,22 +39,22 @@ namespace CodexTests onlineCodexNodes.Remove(lifecycle); } - public ICodexNode AddCodex() + public ICodexNode StartCodex() { - return AddCodex(s => { }); + return StartCodex(s => { }); } - public ICodexNode AddCodex(Action setup) + public ICodexNode StartCodex(Action setup) { - return AddCodex(1, setup)[0]; + return StartCodex(1, setup)[0]; } - public ICodexNodeGroup AddCodex(int numberOfNodes) + public ICodexNodeGroup StartCodex(int numberOfNodes) { - return AddCodex(numberOfNodes, s => { }); + return StartCodex(numberOfNodes, s => { }); } - public ICodexNodeGroup AddCodex(int numberOfNodes, Action setup) + public ICodexNodeGroup StartCodex(int numberOfNodes, Action setup) { var group = Ci.StartCodexNodes(numberOfNodes, s => { diff --git a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs index 7f11171f..5b4f1a93 100644 --- a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs +++ b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs @@ -11,7 +11,7 @@ namespace CodexTests.DownloadConnectivityTests [Test] public void MetricsDoesNotInterfereWithPeerDownload() { - AddCodex(2, s => s.EnableMetrics()); + StartCodex(2, s => s.EnableMetrics()); AssertAllNodesConnected(); } @@ -21,7 +21,7 @@ namespace CodexTests.DownloadConnectivityTests { var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); - AddCodex(2, s => s.EnableMarketplace(geth, contracts, m => m + StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TestTokens()))); AssertAllNodesConnected(); @@ -33,7 +33,7 @@ namespace CodexTests.DownloadConnectivityTests [Values(2, 5)] int numberOfNodes, [Values(1, 10)] int sizeMBs) { - AddCodex(numberOfNodes); + StartCodex(numberOfNodes); AssertAllNodesConnected(sizeMBs); } diff --git a/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs index 00c0cda9..9c884fe1 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs @@ -8,10 +8,10 @@ namespace CodexTests.PeerDiscoveryTests [Test] public void TwoLayersTest() { - var root = AddCodex(); - var l1Source = AddCodex(s => s.WithBootstrapNode(root)); - var l1Node = AddCodex(s => s.WithBootstrapNode(root)); - var l2Target = AddCodex(s => s.WithBootstrapNode(l1Node)); + var root = StartCodex(); + var l1Source = StartCodex(s => s.WithBootstrapNode(root)); + var l1Node = StartCodex(s => s.WithBootstrapNode(root)); + var l2Target = StartCodex(s => s.WithBootstrapNode(l1Node)); AssertAllNodesConnected(); } @@ -19,11 +19,11 @@ namespace CodexTests.PeerDiscoveryTests [Test] public void ThreeLayersTest() { - var root = AddCodex(); - var l1Source = AddCodex(s => s.WithBootstrapNode(root)); - var l1Node = AddCodex(s => s.WithBootstrapNode(root)); - var l2Node = AddCodex(s => s.WithBootstrapNode(l1Node)); - var l3Target = AddCodex(s => s.WithBootstrapNode(l2Node)); + var root = StartCodex(); + var l1Source = StartCodex(s => s.WithBootstrapNode(root)); + var l1Node = StartCodex(s => s.WithBootstrapNode(root)); + var l2Node = StartCodex(s => s.WithBootstrapNode(l1Node)); + var l3Target = StartCodex(s => s.WithBootstrapNode(l2Node)); AssertAllNodesConnected(); } @@ -33,10 +33,10 @@ namespace CodexTests.PeerDiscoveryTests [TestCase(10)] public void NodeChainTest(int chainLength) { - var node = AddCodex(); + var node = StartCodex(); for (var i = 1; i < chainLength; i++) { - node = AddCodex(s => s.WithBootstrapNode(node)); + node = StartCodex(s => s.WithBootstrapNode(node)); } AssertAllNodesConnected(); diff --git a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 1e0faa4b..82c53fb0 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -12,7 +12,7 @@ namespace CodexTests.PeerDiscoveryTests public void CanReportUnknownPeerId() { var unknownId = "16Uiu2HAkv2CHWpff3dj5iuVNERAp8AGKGNgpGjPexJZHSqUstfsK"; - var node = AddCodex(); + var node = StartCodex(); var result = node.GetDebugPeer(unknownId); Assert.That(result.IsPeerFound, Is.False); @@ -21,7 +21,7 @@ namespace CodexTests.PeerDiscoveryTests [Test] public void MetricsDoesNotInterfereWithPeerDiscovery() { - AddCodex(2, s => s.EnableMetrics()); + StartCodex(2, s => s.EnableMetrics()); AssertAllNodesConnected(); } @@ -31,7 +31,7 @@ namespace CodexTests.PeerDiscoveryTests { var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); - AddCodex(2, s => s.EnableMarketplace(geth, contracts, m => m + StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TestTokens()))); AssertAllNodesConnected(); @@ -42,7 +42,7 @@ namespace CodexTests.PeerDiscoveryTests [TestCase(10)] public void VariableNodes(int number) { - AddCodex(number); + StartCodex(number); AssertAllNodesConnected(); } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index ab197751..d6425256 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -58,7 +58,7 @@ namespace CodexTests.UtilityTests for (var i = 0; i < numberOfHosts; i++) { - var seller = AddCodex(s => s + var seller = StartCodex(s => s .WithName("Seller") .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) { @@ -82,7 +82,7 @@ namespace CodexTests.UtilityTests var testFile = GenerateTestFile(fileSize); - var buyer = AddCodex(s => s + var buyer = StartCodex(s => s .WithName("Buyer") .EnableMarketplace(geth, contracts, m => m .WithAccount(myAccount) diff --git a/Tests/CodexTests/UtilityTests/LogHelperTests.cs b/Tests/CodexTests/UtilityTests/LogHelperTests.cs index f8561f85..acc73d34 100644 --- a/Tests/CodexTests/UtilityTests/LogHelperTests.cs +++ b/Tests/CodexTests/UtilityTests/LogHelperTests.cs @@ -11,8 +11,8 @@ namespace CodexTests.UtilityTests [Ignore("Used to find the most common log messages.")] public void FindMostCommonLogMessages() { - var uploader = AddCodex(s => s.WithName("uploader").WithLogLevel(CodexLogLevel.Trace)); - var downloader = AddCodex(s => s.WithName("downloader").WithLogLevel(CodexLogLevel.Trace)); + var uploader = StartCodex(s => s.WithName("uploader").WithLogLevel(CodexLogLevel.Trace)); + var downloader = StartCodex(s => s.WithName("downloader").WithLogLevel(CodexLogLevel.Trace)); var cid = uploader.UploadFile(GenerateTestFile(100.MB())); From 6995cbfb23fb63d971484ff065b5d7344a32ad37 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 9 May 2024 09:38:04 +0200 Subject: [PATCH 062/142] hacky way to download container logs while retries are failing. --- Framework/Utils/Time.cs | 7 +++++++ Tests/CodexLongTests/BasicTests/LargeFileTests.cs | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index f1a109d9..85cfb1a6 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -144,6 +144,12 @@ namespace Utils return $"Attempt {index} took {FormatDuration(info.Item2)} and failed with exception {info.Item1}."; } + private static Action failedCallback = i => { }; + public static void SetRetryFailedCallback(Action onRetryFailed) + { + failedCallback = onRetryFailed; + } + public static T Retry(Func action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { var start = DateTime.UtcNow; @@ -165,6 +171,7 @@ namespace Utils catch (Exception ex) { exceptions.Add(ex); + failedCallback(tries); tries++; } diff --git a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs index 0470e35a..d182ed8b 100644 --- a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs +++ b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs @@ -50,6 +50,8 @@ namespace CodexLongTests.BasicTests var node = StartCodex(s => s.WithStorageQuota((size + 10).MB())); + Time.SetRetryFailedCallback(i => OnFailed(i, node)); + var uploadStart = DateTime.UtcNow; var cid = node.UploadFile(expectedFile); var downloadStart = DateTime.UtcNow; @@ -60,6 +62,17 @@ namespace CodexLongTests.BasicTests AssertTimeConstraint(uploadStart, downloadStart, downloadFinished, size); } + private void OnFailed(int tries, ICodexNode node) + { + if (tries < 5) return; + + if (tries % 10 == 0) + { + Log($"After try {tries}, downloading node log."); + Ci.DownloadLog(node); + } + } + private void AssertTimeConstraint(DateTime uploadStart, DateTime downloadStart, DateTime downloadFinished, long size) { float sizeInMB = size; From d8d7605ce56d6181290ea999c1e48dffbc79f17a Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 10 May 2024 10:08:45 +0200 Subject: [PATCH 063/142] Increases marketplace graceperiod timespan. --- ProjectPlugins/CodexPlugin/MarketplaceAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index c0d7d180..4f8ca1e2 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -83,7 +83,7 @@ namespace CodexPlugin { private readonly ILog log; private readonly CodexAccess codexAccess; - private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(10); + private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); private DateTime? contractStartUtc; public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) From 64ae7c8efe3eedda0b71af2a80fa30f420694af7 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 15 May 2024 10:34:40 +0200 Subject: [PATCH 064/142] Latest image of Codex --- Framework/FileUtils/TrackedFile.cs | 2 +- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/FileUtils/TrackedFile.cs b/Framework/FileUtils/TrackedFile.cs index 694408b9..9ff45d7a 100644 --- a/Framework/FileUtils/TrackedFile.cs +++ b/Framework/FileUtils/TrackedFile.cs @@ -65,7 +65,7 @@ namespace FileUtils if (readExpected == 0 && readActual == 0) { - log.Log($"OK: '{Describe()}' is equal to '{actual.Describe()}'."); + log.Log($"OK: {Describe()} is equal to {actual.Describe()}."); return; } diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index e8187740..7bc308ac 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-ea6d681-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-267266a-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; From 9717591224cec828ffd9116fe4d93d305dad1246 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 11:54:31 +0200 Subject: [PATCH 065/142] Updates bot config to use ids instead of names --- Framework/ArgsUniform/ArgsUniform.cs | 8 +++----- Tools/BiblioTech/Configuration.cs | 20 ++++++++++---------- Tools/BiblioTech/Program.cs | 2 ++ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Framework/ArgsUniform/ArgsUniform.cs b/Framework/ArgsUniform/ArgsUniform.cs index d987aa71..e8f0277c 100644 --- a/Framework/ArgsUniform/ArgsUniform.cs +++ b/Framework/ArgsUniform/ArgsUniform.cs @@ -42,7 +42,7 @@ namespace ArgsUniform { printAppInfo(); PrintHelp(); - throw new Exception(); + Environment.Exit(0); } var result = Activator.CreateInstance(); @@ -55,9 +55,7 @@ namespace ArgsUniform { if (!UniformAssign(result, attr, uniformProperty) && attr.Required) { - { - missingRequired.Add(uniformProperty); - } + missingRequired.Add(uniformProperty); } } } @@ -75,7 +73,7 @@ namespace ArgsUniform } PrintHelp(); - throw new ArgumentException("Unable to assemble all required arguments"); + Environment.Exit(1); } if (printResult) diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 37e1cc33..73e143e8 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -7,23 +7,23 @@ namespace BiblioTech [Uniform("token", "t", "TOKEN", true, "Discord Application Token")] public string ApplicationToken { get; set; } = string.Empty; - [Uniform("server-name", "sn", "SERVERNAME", true, "Name of the Discord server")] - public string ServerName { get; set; } = string.Empty; + [Uniform("server-id", "sn", "SERVERID", true, "ID of the Discord server")] + public ulong ServerId { get; set; } [Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")] public string DataPath { get; set; } = "datapath"; - [Uniform("admin-role", "a", "ADMINROLE", true, "Name of the Discord server admin role")] - public string AdminRoleName { get; set; } = string.Empty; + [Uniform("admin-role-id", "a", "ADMINROLEID", true, "ID of the Discord server admin role")] + public ulong AdminRoleId { get; set; } - [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("admin-channel-id", "ac", "ADMINCHANNELID", true, "ID of the Discord server channel where admin commands are allowed.")] + public ulong AdminChannelId{ get; set; } - [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("rewards-channel-id", "rc", "REWARDSCHANNELID", false, "ID of the Discord server channel where participation rewards will be announced.")] + public ulong RewardsChannelId { get; set; } - [Uniform("chain-events-channel-name", "cc", "CHAINEVENTSCHANNELNAME", false, "Name of the Discord server channel where chain events will be posted.")] - public string ChainEventsChannelName { get; set; } = ""; + [Uniform("chain-events-channel-id", "cc", "CHAINEVENTSCHANNELID", false, "ID of the Discord server channel where chain events will be posted.")] + public ulong ChainEventsChannelID { get; set; } [Uniform("reward-api-port", "rp", "REWARDAPIPORT", false, "TCP listen port for the reward API.")] public int RewardApiPort { get; set; } = 31080; diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index a604812a..f289d9b8 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -21,6 +21,8 @@ namespace BiblioTech public static Task Main(string[] args) { + Log = new ConsoleLog(); + var uniformArgs = new ArgsUniform(PrintHelp, args); Config = uniformArgs.Parse(); From b3771cce3245dc9571f43d33650d7473d752e79e Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 12:14:03 +0200 Subject: [PATCH 066/142] Extract assigner from argsUniform --- Framework/ArgsUniform/ArgsUniform.cs | 134 +------------------------ Framework/ArgsUniform/Assigner.cs | 142 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 130 deletions(-) create mode 100644 Framework/ArgsUniform/Assigner.cs diff --git a/Framework/ArgsUniform/ArgsUniform.cs b/Framework/ArgsUniform/ArgsUniform.cs index e8f0277c..8804c25f 100644 --- a/Framework/ArgsUniform/ArgsUniform.cs +++ b/Framework/ArgsUniform/ArgsUniform.cs @@ -4,9 +4,8 @@ namespace ArgsUniform { public class ArgsUniform { + private readonly Assigner assigner; private readonly Action printAppInfo; - private readonly object? defaultsProvider; - private readonly IEnv.IEnv env; private readonly string[] args; private const int cliStart = 8; private const int shortStart = 38; @@ -31,9 +30,9 @@ namespace ArgsUniform public ArgsUniform(Action printAppInfo, object defaultsProvider, IEnv.IEnv env, params string[] args) { this.printAppInfo = printAppInfo; - this.defaultsProvider = defaultsProvider; - this.env = env; this.args = args; + + assigner = new Assigner(env, args, defaultsProvider); } public T Parse(bool printResult = false) @@ -53,7 +52,7 @@ namespace ArgsUniform var attr = uniformProperty.GetCustomAttribute(); if (attr != null) { - if (!UniformAssign(result, attr, uniformProperty) && attr.Required) + if (!assigner.UniformAssign(result, attr, uniformProperty) && attr.Required) { missingRequired.Add(uniformProperty); } @@ -124,130 +123,5 @@ namespace ArgsUniform Console.CursorLeft = descStart; Console.Write(desc + Environment.NewLine); } - - private object GetDefaultValue(Type t) - { - if (t.IsValueType) return Activator.CreateInstance(t)!; - return null!; - } - - private bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty) - { - if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true; - if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true; - if (AssignFromDefaultsIfAble(result, uniformProperty)) return true; - return false; - } - - private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty) - { - var currentValue = uniformProperty.GetValue(result); - var isEmptryString = (currentValue as string) == string.Empty; - if (currentValue != GetDefaultValue(uniformProperty.PropertyType) && !isEmptryString) return true; - - if (defaultsProvider == null) return false; - - var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name); - if (defaultProperty == null) return false; - - var value = defaultProperty.GetValue(defaultsProvider); - if (value != null) - { - return Assign(result, uniformProperty, value); - } - return false; - } - - private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty) - { - var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty); - if (!string.IsNullOrEmpty(e)) - { - return Assign(result, uniformProperty, e); - } - return false; - } - - private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty) - { - var fromArg = GetFromArgs(attr.Arg); - if (fromArg != null) - { - return Assign(result, uniformProperty, fromArg); - } - var fromShort = GetFromArgs(attr.ArgShort); - if (fromShort != null) - { - return Assign(result, uniformProperty, fromShort); - } - return false; - } - - private bool Assign(T result, PropertyInfo uniformProperty, object value) - { - if (uniformProperty.PropertyType == value.GetType()) - { - uniformProperty.SetValue(result, value); - return true; - } - else - { - if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int)) - { - uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType)); - return true; - } - else - { - if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value); - if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value); - if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value); - - throw new NotSupportedException(); - } - } - } - - private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value) - { - var s = value.ToString(); - if (Enum.TryParse(uniformProperty.PropertyType, s, out var e)) - { - uniformProperty.SetValue(result, e); - return true; - } - return false; - } - - private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value) - { - if (int.TryParse(value.ToString(), out int i)) - { - uniformProperty.SetValue(result, i); - return true; - } - return false; - } - - private static bool AssignBool(T result, PropertyInfo uniformProperty, object value) - { - var s = value.ToString(); - if (s == "1" || (s != null && s.ToLowerInvariant() == "true")) - { - uniformProperty.SetValue(result, true); - } - return true; - } - - private string? GetFromArgs(string key) - { - var argKey = $"--{key}="; - var arg = args.FirstOrDefault(a => a.StartsWith(argKey)); - if (arg != null) - { - return arg.Substring(argKey.Length); - } - return null; - } } } diff --git a/Framework/ArgsUniform/Assigner.cs b/Framework/ArgsUniform/Assigner.cs new file mode 100644 index 00000000..b25c68bf --- /dev/null +++ b/Framework/ArgsUniform/Assigner.cs @@ -0,0 +1,142 @@ +using System.Reflection; + +namespace ArgsUniform +{ + public class Assigner + { + private readonly IEnv.IEnv env; + private readonly string[] args; + private readonly object? defaultsProvider; + + public Assigner(IEnv.IEnv env, string[] args, object? defaultsProvider) + { + this.env = env; + this.args = args; + this.defaultsProvider = defaultsProvider; + } + + public bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty) + { + if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true; + if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true; + if (AssignFromDefaultsIfAble(result, uniformProperty)) return true; + return false; + } + + private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty) + { + var currentValue = uniformProperty.GetValue(result); + var isEmptryString = (currentValue as string) == string.Empty; + if (currentValue != GetDefaultValueForType(uniformProperty.PropertyType) && !isEmptryString) return true; + if (defaultsProvider == null) return false; + + var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name); + if (defaultProperty == null) return false; + + var value = defaultProperty.GetValue(defaultsProvider); + if (value != null) + { + return Assign(result, uniformProperty, value); + } + return false; + } + + private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty) + { + var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty); + if (!string.IsNullOrEmpty(e)) + { + return Assign(result, uniformProperty, e); + } + return false; + } + + private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty) + { + var fromArg = GetFromArgs(attr.Arg); + if (fromArg != null) + { + return Assign(result, uniformProperty, fromArg); + } + var fromShort = GetFromArgs(attr.ArgShort); + if (fromShort != null) + { + return Assign(result, uniformProperty, fromShort); + } + return false; + } + + private bool Assign(T result, PropertyInfo uniformProperty, object value) + { + if (uniformProperty.PropertyType == value.GetType()) + { + uniformProperty.SetValue(result, value); + return true; + } + else + { + if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int)) + { + uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType)); + return true; + } + else + { + if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value); + if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value); + if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value); + + throw new NotSupportedException(); + } + } + } + + private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value) + { + var s = value.ToString(); + if (Enum.TryParse(uniformProperty.PropertyType, s, out var e)) + { + uniformProperty.SetValue(result, e); + return true; + } + return false; + } + + private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value) + { + if (int.TryParse(value.ToString(), out int i)) + { + uniformProperty.SetValue(result, i); + return true; + } + return false; + } + + private static bool AssignBool(T result, PropertyInfo uniformProperty, object value) + { + var s = value.ToString(); + if (s == "1" || (s != null && s.ToLowerInvariant() == "true")) + { + uniformProperty.SetValue(result, true); + } + return true; + } + + private string? GetFromArgs(string key) + { + var argKey = $"--{key}="; + var arg = args.FirstOrDefault(a => a.StartsWith(argKey)); + if (arg != null) + { + return arg.Substring(argKey.Length); + } + return null; + } + + private static object GetDefaultValueForType(Type t) + { + if (t.IsValueType) return Activator.CreateInstance(t)!; + return null!; + } + } +} From 7d9dcb263d044faa237404f59067d094e6b4e50c Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 14:05:58 +0200 Subject: [PATCH 067/142] Prints default ArgsUniform values. Applies discord IDs instead of names. --- Framework/ArgsUniform/ArgsUniform.cs | 49 ++++++++++++++------------ Framework/ArgsUniform/Assigner.cs | 40 +++++++++++++++------ Tools/BiblioTech/AdminChecker.cs | 4 +-- Tools/BiblioTech/CommandHandler.cs | 2 +- Tools/BiblioTech/Configuration.cs | 10 +++--- Tools/BiblioTech/Rewards/RoleDriver.cs | 16 ++++----- 6 files changed, 71 insertions(+), 50 deletions(-) diff --git a/Framework/ArgsUniform/ArgsUniform.cs b/Framework/ArgsUniform/ArgsUniform.cs index 8804c25f..68bf8f27 100644 --- a/Framework/ArgsUniform/ArgsUniform.cs +++ b/Framework/ArgsUniform/ArgsUniform.cs @@ -61,7 +61,7 @@ namespace ArgsUniform if (missingRequired.Any()) { - PrintResults(result, uniformProperties); + PrintResults(printResult,result, uniformProperties); Print(""); foreach (var missing in missingRequired) { @@ -75,34 +75,36 @@ namespace ArgsUniform Environment.Exit(1); } - if (printResult) - { - PrintResults(result, uniformProperties); - } + PrintResults(printResult, result, uniformProperties); return result; } - private void PrintResults(T result, PropertyInfo[] uniformProperties) - { - Print(""); - foreach (var p in uniformProperties) - { - Print($"\t{p.Name} = {p.GetValue(result)}"); - } - Print(""); - } - public void PrintHelp() { Print(""); - PrintAligned("CLI option:", "(short)", "Environment variable:", "Description"); - var attrs = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).Select(p => p.GetCustomAttribute()).Where(a => a != null).ToArray(); - foreach (var attr in attrs) + PrintAligned("CLI option:", "(short)", "Environment variable:", "Description", "(default)"); + var props = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray(); + foreach (var prop in props) { - var a = attr!; - var optional = !a.Required ? " *" : ""; - PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional); + var a = prop.GetCustomAttribute(); + if (a != null) + { + var optional = !a.Required ? " (optional)" : ""; + var def = assigner.DescribeDefaultFor(prop); + PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional, $"({def})"); + } + } + Print(""); + } + + private void PrintResults(bool printResult, T result, PropertyInfo[] uniformProperties) + { + if (!printResult) return; + Print(""); + foreach (var p in uniformProperties) + { + Print($"\t{p.Name} = {p.GetValue(result)}"); } Print(""); } @@ -112,7 +114,7 @@ namespace ArgsUniform Console.WriteLine(msg); } - private void PrintAligned(string cli, string s, string env, string desc) + private void PrintAligned(string cli, string s, string env, string desc, string def) { Console.CursorLeft = cliStart; Console.Write(cli); @@ -121,7 +123,8 @@ namespace ArgsUniform Console.CursorLeft = envStart; Console.Write(env); Console.CursorLeft = descStart; - Console.Write(desc + Environment.NewLine); + Console.Write(desc + " "); + Console.Write(def + Environment.NewLine); } } } diff --git a/Framework/ArgsUniform/Assigner.cs b/Framework/ArgsUniform/Assigner.cs index b25c68bf..d1e2e81e 100644 --- a/Framework/ArgsUniform/Assigner.cs +++ b/Framework/ArgsUniform/Assigner.cs @@ -23,20 +23,38 @@ namespace ArgsUniform return false; } + public string DescribeDefaultFor(PropertyInfo property) + { + var obj = Activator.CreateInstance(); + var defaultValue = GetDefaultValue(obj, property); + if (defaultValue == null) return ""; + if (defaultValue is string str) + { + return "\"" + str + "\""; + } + return defaultValue.ToString() ?? string.Empty; + } + + private object? GetDefaultValue(T result, PropertyInfo uniformProperty) + { + // Get value from object's static initializer if it's there. + var currentValue = uniformProperty.GetValue(result); + if (currentValue != null) return currentValue; + + // Get value from defaults-provider object if it's there. + if (defaultsProvider == null) return null; + var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name); + if (defaultProperty == null) return null; + return defaultProperty.GetValue(defaultsProvider); + } + private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty) { - var currentValue = uniformProperty.GetValue(result); - var isEmptryString = (currentValue as string) == string.Empty; - if (currentValue != GetDefaultValueForType(uniformProperty.PropertyType) && !isEmptryString) return true; - if (defaultsProvider == null) return false; - - var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name); - if (defaultProperty == null) return false; - - var value = defaultProperty.GetValue(defaultsProvider); - if (value != null) + var defaultValue = GetDefaultValue(result, uniformProperty); + var isEmptryString = (defaultValue as string) == string.Empty; + if (defaultValue != null && defaultValue != GetDefaultValueForType(uniformProperty.PropertyType) && !isEmptryString) { - return Assign(result, uniformProperty, value); + return Assign(result, uniformProperty, defaultValue); } return false; } diff --git a/Tools/BiblioTech/AdminChecker.cs b/Tools/BiblioTech/AdminChecker.cs index e1c82cda..def2870b 100644 --- a/Tools/BiblioTech/AdminChecker.cs +++ b/Tools/BiblioTech/AdminChecker.cs @@ -24,7 +24,7 @@ namespace BiblioTech public bool IsAdminChannel(IChannel channel) { - return channel.Name == Program.Config.AdminChannelName; + return channel.Id == Program.Config.AdminChannelId; } public ISocketMessageChannel GetAdminChannel() @@ -45,7 +45,7 @@ namespace BiblioTech private void UpdateAdminIds() { lastUpdate = DateTime.UtcNow; - var adminRole = guild.Roles.Single(r => r.Name == Program.Config.AdminRoleName); + var adminRole = guild.Roles.Single(r => r.Id == Program.Config.AdminRoleId); adminIds = adminRole.Members.Select(m => m.Id).ToArray(); } } diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 79071773..c7263c5b 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -22,7 +22,7 @@ namespace BiblioTech private async Task Client_Ready() { - var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); + var guild = client.Guilds.Single(g => g.Id == Program.Config.ServerId); Program.AdminChecker.SetGuild(guild); Program.Log.Log($"Initializing for guild: '{guild.Name}'"); diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 73e143e8..9164cf7b 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -10,7 +10,7 @@ namespace BiblioTech [Uniform("server-id", "sn", "SERVERID", true, "ID of the Discord server")] public ulong ServerId { get; set; } - [Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")] + [Uniform("datapath", "dp", "DATAPATH", true, "Root path where all data files will be saved.")] public string DataPath { get; set; } = "datapath"; [Uniform("admin-role-id", "a", "ADMINROLEID", true, "ID of the Discord server admin role")] @@ -23,15 +23,15 @@ namespace BiblioTech public ulong RewardsChannelId { get; set; } [Uniform("chain-events-channel-id", "cc", "CHAINEVENTSCHANNELID", false, "ID of the Discord server channel where chain events will be posted.")] - public ulong ChainEventsChannelID { get; set; } + public ulong ChainEventsChannelId { get; set; } - [Uniform("reward-api-port", "rp", "REWARDAPIPORT", false, "TCP listen port for the reward API.")] + [Uniform("reward-api-port", "rp", "REWARDAPIPORT", true, "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.")] + [Uniform("send-eth", "se", "SENDETH", true, "Amount of Eth send by the mint command.")] public int SendEth { get; set; } = 10; - [Uniform("mint-tt", "mt", "MINTTT", false, "Amount of TestTokens minted by the mint command. Default: 1073741824")] + [Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TestTokens minted by the mint command.")] public int MintTT { get; set; } = 1073741824; public string EndpointsPath diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 6695af32..f0ea48f7 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -16,8 +16,8 @@ namespace BiblioTech.Rewards { this.client = client; - rewardsChannel = GetChannel(Program.Config.RewardsChannelName); - eventsChannel = GetChannel(Program.Config.ChainEventsChannelName); + rewardsChannel = GetChannel(Program.Config.RewardsChannelId); + eventsChannel = GetChannel(Program.Config.ChainEventsChannelId); } public async Task GiveRewards(GiveRewardsCommand rewards) @@ -45,10 +45,10 @@ namespace BiblioTech.Rewards await context.ProcessGiveRewardsCommand(LookUpUsers(rewards)); } - private SocketTextChannel? GetChannel(string name) + private SocketTextChannel? GetChannel(ulong id) { - if (string.IsNullOrEmpty(name)) return null; - return GetGuild().TextChannels.SingleOrDefault(c => c.Name == name); + if (id == 0) return null; + return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id); } private async Task ProcessChainEvents(string[] eventsOverview) @@ -147,11 +147,11 @@ namespace BiblioTech.Rewards private SocketGuild GetGuild() { - var guild = client.Guilds.SingleOrDefault(g => g.Name == Program.Config.ServerName); + var guild = client.Guilds.SingleOrDefault(g => g.Id == Program.Config.ServerId); 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))}]"); + throw new Exception($"Unable to find guild by id: '{Program.Config.ServerId}'. " + + $"Known guilds: [{string.Join(",", client.Guilds.Select(g => g.Name + " (" + g.Id + ")"))}]"); } return guild; } From 8847de116d91447f836d489debe419c717ff7944 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 14:07:14 +0200 Subject: [PATCH 068/142] cleanup rewarder bot config --- Tools/TestNetRewarder/Configuration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index c180648f..c88a0aa8 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -4,16 +4,16 @@ namespace TestNetRewarder { public class Configuration { - [Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")] + [Uniform("datapath", "dp", "DATAPATH", true, "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)")] + [Uniform("discordbot-port", "dp", "DISCORDBOTPORT", true, "port number of the discord bot reward API.")] public int DiscordPort { get; set; } = 31080; - [Uniform("interval-minutes", "im", "INTERVALMINUTES", false, "time in minutes between reward updates. (default 15)")] + [Uniform("interval-minutes", "im", "INTERVALMINUTES", true, "time in minutes between reward updates.")] public int IntervalMinutes { 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'.")] @@ -22,7 +22,7 @@ namespace TestNetRewarder [Uniform("market-insights", "mi", "MARKETINSIGHTS", false, "Semi-colon separated integers. Each represents a multiple of intervals, for which a market insights average will be generated.")] public string MarketInsights { get; set; } = "1;96"; - [Uniform("events-overview", "eo", "EVENTSOVERVIEW", false, "When greater than zero, chain event summary will be generated. (default 1)")] + [Uniform("events-overview", "eo", "EVENTSOVERVIEW", false, "When greater than zero, chain event summary will be generated.")] public int CreateChainEventsOverview { get; set; } = 1; public string LogPath From ad2181db0ba34185f70e905c872b51da95d7b9a5 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 14:58:42 +0200 Subject: [PATCH 069/142] Fixes invariant-culture in assigner. Adds support for ulong. --- Framework/ArgsUniform/Assigner.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Framework/ArgsUniform/Assigner.cs b/Framework/ArgsUniform/Assigner.cs index d1e2e81e..9083b0ef 100644 --- a/Framework/ArgsUniform/Assigner.cs +++ b/Framework/ArgsUniform/Assigner.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Globalization; +using System.Reflection; namespace ArgsUniform { @@ -103,8 +104,11 @@ namespace ArgsUniform if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value); if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value); if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value); + if (uniformProperty.PropertyType == typeof(ulong)) return AssignUlong(result, uniformProperty, value); - throw new NotSupportedException(); + throw new NotSupportedException( + $"Unsupported property type '${uniformProperty.PropertyType}' " + + $"for property '${uniformProperty.Name}'."); } } } @@ -122,7 +126,17 @@ namespace ArgsUniform private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value) { - if (int.TryParse(value.ToString(), out int i)) + if (int.TryParse(value.ToString(), CultureInfo.InvariantCulture, out int i)) + { + uniformProperty.SetValue(result, i); + return true; + } + return false; + } + + private bool AssignUlong(T? result, PropertyInfo uniformProperty, object value) + { + if (ulong.TryParse(value.ToString(), CultureInfo.InvariantCulture, out ulong i)) { uniformProperty.SetValue(result, i); return true; From 22cf82b99b37f68287d10be445978abc39614516 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 May 2024 16:00:19 +0200 Subject: [PATCH 070/142] Adds no-discord debug option to discord bot. --- .../UtilityTests/ClusterSpeedTests.cs | 6 +-- Tools/BiblioTech/Configuration.cs | 28 +++------- Tools/BiblioTech/LoggingRoleDriver.cs | 24 +++++++++ Tools/BiblioTech/Program.cs | 51 ++++++++++++------- 4 files changed, 65 insertions(+), 44 deletions(-) create mode 100644 Tools/BiblioTech/LoggingRoleDriver.cs diff --git a/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs index 9e647eaf..7800e132 100644 --- a/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs +++ b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs @@ -3,7 +3,7 @@ using Logging; using NUnit.Framework; using Utils; -namespace CodexTests.ScalabilityTests +namespace CodexTests.UtilityTests { [TestFixture] public class ClusterDiscSpeedTests : DistTest @@ -18,7 +18,7 @@ namespace CodexTests.ScalabilityTests ) { long targetSize = (long)(1024 * 1024 * 1024) * 2; - long bufferSizeBytes = ((long)bufferSizeKb) * 1024; + long bufferSizeBytes = (long)bufferSizeKb * 1024; var filename = nameof(DiscSpeedTest); @@ -28,7 +28,7 @@ namespace CodexTests.ScalabilityTests var writeSpeed = PerformWrite(targetSize, bufferSizeBytes, filename); Thread.Sleep(2000); var readSpeed = PerformRead(targetSize, bufferSizeBytes, filename); - + Log($"Write speed: {writeSpeed} per second."); Log($"Read speed: {readSpeed} per second."); } diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 9164cf7b..ffbf4c66 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -34,28 +34,12 @@ namespace BiblioTech [Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TestTokens minted by the mint command.")] public int MintTT { get; set; } = 1073741824; - public string EndpointsPath - { - get - { - return Path.Combine(DataPath, "endpoints"); - } - } + [Uniform("no-discord", "nd", "NODISCORD", false, "For debugging: Bypasses all Discord API calls.")] + public int NoDiscord { get; set; } = 0; - public string UserDataPath - { - get - { - return Path.Combine(DataPath, "users"); - } - } - - public string LogPath - { - get - { - return Path.Combine(DataPath, "logs"); - } - } + public string EndpointsPath => Path.Combine(DataPath, "endpoints"); + public string UserDataPath => Path.Combine(DataPath, "users"); + public string LogPath => Path.Combine(DataPath, "logs"); + public bool DebugNoDiscord => NoDiscord == 1; } } diff --git a/Tools/BiblioTech/LoggingRoleDriver.cs b/Tools/BiblioTech/LoggingRoleDriver.cs new file mode 100644 index 00000000..9275a7a5 --- /dev/null +++ b/Tools/BiblioTech/LoggingRoleDriver.cs @@ -0,0 +1,24 @@ +using BiblioTech.Rewards; +using DiscordRewards; +using Logging; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class LoggingRoleDriver : IDiscordRoleDriver + { + private readonly ILog log; + + public LoggingRoleDriver(ILog log) + { + this.log = log; + } + + public async Task GiveRewards(GiveRewardsCommand rewards) + { + await Task.CompletedTask; + + log.Log(JsonConvert.SerializeObject(rewards, Formatting.None)); + } + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index f289d9b8..6ec7dae9 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -41,25 +41,15 @@ namespace BiblioTech public async Task MainAsync(string[] args) { Log.Log("Starting Codex Discord Bot..."); - client = new DiscordSocketClient(); - client.Log += ClientLog; - - var notifyCommand = new NotifyCommand(); - var associateCommand = new UserAssociateCommand(notifyCommand); - var sprCommand = new SprCommand(); - var handler = new CommandHandler(client, - new GetBalanceCommand(associateCommand), - new MintCommand(associateCommand), - sprCommand, - associateCommand, - notifyCommand, - new AdminCommand(sprCommand), - new MarketCommand() - ); - - await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); - await client.StartAsync(); - AdminChecker = new AdminChecker(); + if (Config.DebugNoDiscord) + { + Log.Log("Debug option is set. Discord connection disabled!"); + RoleDriver = new LoggingRoleDriver(Log); + } + else + { + await StartDiscordBot(); + } var builder = WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel((context, options) => @@ -75,6 +65,29 @@ namespace BiblioTech await Task.Delay(-1); } + private async Task StartDiscordBot() + { + client = new DiscordSocketClient(); + client.Log += ClientLog; + + var notifyCommand = new NotifyCommand(); + var associateCommand = new UserAssociateCommand(notifyCommand); + var sprCommand = new SprCommand(); + var handler = new CommandHandler(client, + new GetBalanceCommand(associateCommand), + new MintCommand(associateCommand), + sprCommand, + associateCommand, + notifyCommand, + new AdminCommand(sprCommand), + new MarketCommand() + ); + + await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); + await client.StartAsync(); + AdminChecker = new AdminChecker(); + } + private static void PrintHelp() { Log.Log("BiblioTech - Codex Discord Bot"); From d6f7e225beac683813139a720f94ec74230b99a2 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 20 May 2024 16:18:01 +0200 Subject: [PATCH 071/142] successful downloading of bot log --- .../DiscordBotContainerRecipe.cs | 4 +- .../UtilityTests/DiscordBotTests.cs | 172 ++++++++++-------- 2 files changed, 104 insertions(+), 72 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs index 9a2e3fed..86ee1e25 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin public class DiscordBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-bibliotech"; - public override string Image => "codexstorage/codex-discordbot:sha-8c64352"; + public override string Image => "codexstorage/codex-discordbot:sha-22cf82b"; public static string RewardsPort = "bot_rewards_port"; @@ -33,6 +33,8 @@ namespace CodexDiscordBotPlugin AddEnvVar("CODEXCONTRACTS_TOKENADDRESS", gethInfo.TokenAddress); AddEnvVar("CODEXCONTRACTS_ABI", gethInfo.Abi); + AddEnvVar("NODISCORD", "1"); + AddInternalPortAndVar("REWARDAPIPORT", RewardsPort); if (!string.IsNullOrEmpty(config.DataPath)) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index d6425256..2223f92d 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -2,6 +2,7 @@ using CodexDiscordBotPlugin; using CodexPlugin; using GethPlugin; +using KubernetesWorkflow.Types; using NUnit.Framework; using Utils; @@ -10,86 +11,42 @@ namespace CodexTests.UtilityTests [TestFixture] public class DiscordBotTests : AutoBootstrapDistTest { + private readonly TestToken hostInitialBalance = 3000.TestTokens(); + private readonly TestToken clientInitialBalance = 1000000000.TestTokens(); + private readonly ByteSize fileSize = 11.MB(); + [Test] - [Ignore("Used for debugging bots")] public void BotRewardTest() { - var myAccount = EthAccount.GenerateNew(); - - var sellerInitialBalance = 234.TestTokens(); - var buyerInitialBalance = 100000.TestTokens(); - var fileSize = 11.MB(); + var clientAccount = EthAccount.GenerateNew(); var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); var contracts = Ci.StartCodexContracts(geth); + var gethInfo = CreateGethInfo(geth, contracts); - // 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, - intervalMinutes: "1", - historyStartUtc: GetTestRunTimeRange().From - TimeSpan.FromMinutes(3), - gethInfo: gethInfo, - dataPath: null - )); + var botContainer = StartDiscordBot(gethInfo); - var numberOfHosts = 3; + var hostAccount = EthAccount.GenerateNew(); + StartHosts(hostAccount, geth, contracts); - for (var i = 0; i < numberOfHosts; i++) - { - var seller = StartCodex(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())); + StartRewarderBot(gethInfo, botContainer); - var availability = new StorageAvailability( - totalSpace: 10.GB(), - maxDuration: TimeSpan.FromMinutes(30), - minPriceForTotalSpace: 1.TestTokens(), - maxCollateral: 20.TestTokens() - ); - seller.Marketplace.MakeStorageAvailable(availability); - } + var client = StartClient(geth, contracts, clientAccount); + var purchaseContract = ClientPurchasesStorage(client); + + //purchaseContract.WaitForStorageContractStarted(); + //purchaseContract.WaitForStorageContractFinished(); + Thread.Sleep(TimeSpan.FromMinutes(5)); + + var botLog = Ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); + var aaaa = 0; + } + + private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) + { var testFile = GenerateTestFile(fileSize); - - var buyer = StartCodex(s => s - .WithName("Buyer") - .EnableMarketplace(geth, contracts, m => m - .WithAccount(myAccount) - .WithInitial(10.Eth(), buyerInitialBalance))); - - var contentId = buyer.UploadFile(testFile); - + var contentId = client.UploadFile(testFile); var purchase = new StoragePurchaseRequest(contentId) { PricePerSlotPerSecond = 2.TestTokens(), @@ -101,11 +58,84 @@ namespace CodexTests.UtilityTests Expiry = TimeSpan.FromMinutes(5) }; - var purchaseContract = buyer.Marketplace.RequestStorage(purchase); + return client.Marketplace.RequestStorage(purchase); + } - purchaseContract.WaitForStorageContractStarted(); + private ICodexNode StartClient(IGethNode geth, ICodexContracts contracts, EthAccount clientAccount) + { + return StartCodex(s => s + .WithName("Client") + .EnableMarketplace(geth, contracts, m => m + .WithAccount(clientAccount) + .WithInitial(10.Eth(), clientInitialBalance))); + } - purchaseContract.WaitForStorageContractFinished(); + private void StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) + { + Ci.DeployRewarderBot(new RewarderBotStartupConfig( + discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, + discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, + intervalMinutes: "10", + historyStartUtc: DateTime.UtcNow, + gethInfo: gethInfo, + dataPath: null + )); + } + + private DiscordBotGethInfo CreateGethInfo(IGethNode geth, ICodexContracts contracts) + { + return 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 + ); + } + + private RunningContainer StartDiscordBot(DiscordBotGethInfo gethInfo) + { + 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 + )); + return bot.Containers.Single(); + } + + private void StartHosts(EthAccount hostAccount, IGethNode geth, ICodexContracts contracts) + { + var numberOfHosts = 5; + var hosts = StartCodex(numberOfHosts, s => s + .WithName("Host") + .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) + { + ContractClock = CodexLogLevel.Trace, + }) + .WithStorageQuota(11.GB()) + .EnableMarketplace(geth, contracts, m => m + .WithAccount(hostAccount) + .WithInitial(10.Eth(), hostInitialBalance) + .AsStorageNode() + .AsValidator())); + + var availability = new StorageAvailability( + totalSpace: 10.GB(), + maxDuration: TimeSpan.FromMinutes(30), + minPriceForTotalSpace: 1.TestTokens(), + maxCollateral: 20.TestTokens() + ); + + foreach (var host in hosts) + { + host.Marketplace.MakeStorageAvailable(availability); + } } } } From fa1b560a917a1e457a3f822ab4b232d8e1c2f0dc Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 21 May 2024 12:58:17 +0200 Subject: [PATCH 072/142] Adds waiting for debug-mode start message --- .../CodexDiscordBotPlugin.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index f347805a..b2bb5973 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -1,11 +1,13 @@ using Core; using KubernetesWorkflow; using KubernetesWorkflow.Types; +using Utils; namespace CodexDiscordBotPlugin { public class CodexDiscordBotPlugin : IProjectPlugin, IHasLogPrefix, IHasMetadata { + private const string ExpectedStartupMessage = "Debug option is set. Discord connection disabled!"; private readonly IPluginTools tools; public CodexDiscordBotPlugin(IPluginTools tools) @@ -46,7 +48,9 @@ namespace CodexDiscordBotPlugin var startupConfig = new StartupConfig(); startupConfig.NameOverride = config.Name; startupConfig.Add(config); - return workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig).WaitForOnline(); + var pod = workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig).WaitForOnline(); + WaitForStartupMessage(workflow, pod); + return pod; } private RunningPod StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config) @@ -55,5 +59,44 @@ namespace CodexDiscordBotPlugin startupConfig.Add(config); return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig).WaitForOnline(); } + + private void WaitForStartupMessage(IStartupWorkflow workflow, RunningPod pod) + { + var finder = new LogLineFinder(ExpectedStartupMessage, workflow); + Time.WaitUntil(() => + { + finder.FindLine(pod); + return finder.Found; + }, nameof(WaitForStartupMessage)); + } + + public class LogLineFinder : LogHandler + { + private readonly string message; + private readonly IStartupWorkflow workflow; + + public LogLineFinder(string message, IStartupWorkflow workflow) + { + this.message = message; + this.workflow = workflow; + } + + public void FindLine(RunningPod pod) + { + Found = false; + foreach (var c in pod.Containers) + { + workflow.DownloadContainerLog(c, this); + if (Found) return; + } + } + + public bool Found { get; private set; } + + protected override void ProcessLine(string line) + { + if (!Found && line.Contains(message)) Found = true; + } + } } } From e082f8a31c66bd46e2ac775591d2e868d70a89ca Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 21 May 2024 13:09:46 +0200 Subject: [PATCH 073/142] Changes Mint-TT bot parameter to BigInt type. --- Framework/ArgsUniform/Assigner.cs | 12 ++++++++++++ .../CodexContractsPlugin/TestTokenExtensions.cs | 9 ++++++++- Tools/BiblioTech/Commands/MintCommand.cs | 2 +- Tools/BiblioTech/Configuration.cs | 3 ++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Framework/ArgsUniform/Assigner.cs b/Framework/ArgsUniform/Assigner.cs index 9083b0ef..4db7a08c 100644 --- a/Framework/ArgsUniform/Assigner.cs +++ b/Framework/ArgsUniform/Assigner.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Numerics; using System.Reflection; namespace ArgsUniform @@ -105,6 +106,7 @@ namespace ArgsUniform if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value); if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value); if (uniformProperty.PropertyType == typeof(ulong)) return AssignUlong(result, uniformProperty, value); + if (uniformProperty.PropertyType == typeof(BigInteger)) return AssignBigInt(result, uniformProperty, value); throw new NotSupportedException( $"Unsupported property type '${uniformProperty.PropertyType}' " + @@ -144,6 +146,16 @@ namespace ArgsUniform return false; } + private bool AssignBigInt(T result, PropertyInfo uniformProperty, object value) + { + if (BigInteger.TryParse(value.ToString(), CultureInfo.InvariantCulture, out BigInteger i)) + { + uniformProperty.SetValue(result, i); + return true; + } + return false; + } + private static bool AssignBool(T result, PropertyInfo uniformProperty, object value) { var s = value.ToString(); diff --git a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs index a19abde4..eccc849d 100644 --- a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs @@ -1,4 +1,6 @@ -namespace CodexContractsPlugin +using System.Numerics; + +namespace CodexContractsPlugin { public class TestToken : IComparable { @@ -41,5 +43,10 @@ { return new TestToken(i); } + + public static TestToken TestTokens(this BigInteger i) + { + return new TestToken((decimal)i); + } } } diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index a017ee2f..d6d616f7 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -77,7 +77,7 @@ namespace BiblioTech.Commands private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr) { var testTokens = contracts.GetTestTokenBalance(addr); - return testTokens.Amount < Program.Config.MintTT; + return testTokens.Amount < Program.Config.MintTT.TestTokens().Amount; } private bool ShouldSendEth(IGethNode gethNode, EthAddress addr) diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 9164cf7b..b67689f3 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -1,4 +1,5 @@ using ArgsUniform; +using System.Numerics; namespace BiblioTech { @@ -32,7 +33,7 @@ namespace BiblioTech public int SendEth { get; set; } = 10; [Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TestTokens minted by the mint command.")] - public int MintTT { get; set; } = 1073741824; + public BigInteger MintTT { get; set; } = 1073741824; public string EndpointsPath { From a236544ee94e3babc6b2141c1dcd1910ed16b96f Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 21 May 2024 16:10:14 +0200 Subject: [PATCH 074/142] parameterizing the bot test --- Tests/CodexTests/CodexTests.csproj | 1 + .../UtilityTests/DiscordBotTests.cs | 120 ++++++++++++++++-- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/Tests/CodexTests/CodexTests.csproj b/Tests/CodexTests/CodexTests.csproj index 495caf6f..10d1a225 100644 --- a/Tests/CodexTests/CodexTests.csproj +++ b/Tests/CodexTests/CodexTests.csproj @@ -13,6 +13,7 @@ + diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 2223f92d..f781795f 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -1,8 +1,11 @@ using CodexContractsPlugin; using CodexDiscordBotPlugin; using CodexPlugin; +using Core; +using DiscordRewards; using GethPlugin; using KubernetesWorkflow.Types; +using Newtonsoft.Json; using NUnit.Framework; using Utils; @@ -11,9 +14,9 @@ namespace CodexTests.UtilityTests [TestFixture] public class DiscordBotTests : AutoBootstrapDistTest { - private readonly TestToken hostInitialBalance = 3000.TestTokens(); + private readonly RewardRepo repo = new RewardRepo(); + private readonly TestToken hostInitialBalance = 3000000.TestTokens(); private readonly TestToken clientInitialBalance = 1000000000.TestTokens(); - private readonly ByteSize fileSize = 11.MB(); [Test] public void BotRewardTest() @@ -35,23 +38,39 @@ namespace CodexTests.UtilityTests var purchaseContract = ClientPurchasesStorage(client); - //purchaseContract.WaitForStorageContractStarted(); - //purchaseContract.WaitForStorageContractFinished(); - Thread.Sleep(TimeSpan.FromMinutes(5)); + var apiCalls = new RewardApiCalls(Ci, botContainer); - var botLog = Ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); - var aaaa = 0; + Time.WaitUntil(() => apiCalls.Get().Length > 4, TimeSpan.FromMinutes(10), TimeSpan.FromSeconds(10), "Waiting for API calls"); + + var calls = apiCalls.Get(); + foreach (var call in calls) + { + var line = ""; + if (call.Averages.Any()) line += $"{call.Averages.Length} average. "; + if (call.EventsOverview.Any()) line += $"{call.EventsOverview.Length} events. "; + foreach (var r in call.Rewards) + { + var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); + var isClient = r.UserAddresses.Any(a => a == clientAccount.EthAddress.Address); + var isHost = r.UserAddresses.Any(a => a == hostAccount.EthAddress.Address); + if (isHost && isClient) throw new Exception("what?"); + var name = isClient ? "Client" : "Host"; + + line += name + " = " + reward.Message; + } + Log(line); + } } private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) { - var testFile = GenerateTestFile(fileSize); + var testFile = GenerateTestFile(GetMinFileSize()); var contentId = client.UploadFile(testFile); var purchase = new StoragePurchaseRequest(contentId) { PricePerSlotPerSecond = 2.TestTokens(), RequiredCollateral = 10.TestTokens(), - MinRequiredNumberOfNodes = 5, + MinRequiredNumberOfNodes = GetNumberOfRequiredHosts(), NodeFailureTolerance = 2, ProofProbability = 5, Duration = TimeSpan.FromMinutes(6), @@ -111,14 +130,13 @@ namespace CodexTests.UtilityTests private void StartHosts(EthAccount hostAccount, IGethNode geth, ICodexContracts contracts) { - var numberOfHosts = 5; - var hosts = StartCodex(numberOfHosts, s => s + var hosts = StartCodex(GetNumberOfLiveHosts(), s => s .WithName("Host") .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) { ContractClock = CodexLogLevel.Trace, }) - .WithStorageQuota(11.GB()) + .WithStorageQuota(GetFileSizePlus(50)) .EnableMarketplace(geth, contracts, m => m .WithAccount(hostAccount) .WithInitial(10.Eth(), hostInitialBalance) @@ -126,10 +144,10 @@ namespace CodexTests.UtilityTests .AsValidator())); var availability = new StorageAvailability( - totalSpace: 10.GB(), + totalSpace: GetFileSizePlus(5), maxDuration: TimeSpan.FromMinutes(30), minPriceForTotalSpace: 1.TestTokens(), - maxCollateral: 20.TestTokens() + maxCollateral: hostInitialBalance ); foreach (var host in hosts) @@ -137,5 +155,79 @@ namespace CodexTests.UtilityTests host.Marketplace.MakeStorageAvailable(availability); } } + + private int GetNumberOfLiveHosts() + { + return Convert.ToInt32(GetNumberOfRequiredHosts()) + 3; + } + + private ByteSize GetFileSizePlus(int plusMb) + { + return new ByteSize(GetMinFileSize().SizeInBytes + plusMb.MB().SizeInBytes); + } + + private ByteSize GetMinFileSize() + { + ulong minSlotSize = 0; + ulong minNumHosts = 0; + foreach (var r in repo.Rewards) + { + var s = Convert.ToUInt64(r.CheckConfig.MinSlotSize.SizeInBytes); + var h = r.CheckConfig.MinNumberOfHosts; + if (s > minSlotSize) minSlotSize = s; + if (h > minNumHosts) minNumHosts = h; + } + + var minFileSize = (minSlotSize * minNumHosts) + 1024; + return new ByteSize(Convert.ToInt64(minFileSize)); + } + + private uint GetNumberOfRequiredHosts() + { + return Convert.ToUInt32(repo.Rewards.Max(r => r.CheckConfig.MinNumberOfHosts)); + } + + public class RewardApiCalls + { + private readonly CoreInterface ci; + private readonly RunningContainer botContainer; + private readonly Dictionary commands = new Dictionary(); + + public RewardApiCalls(CoreInterface ci, RunningContainer botContainer) + { + this.ci = ci; + this.botContainer = botContainer; + } + + public void Update() + { + var botLog = ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); + + var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) AddToCache(line); + } + + public GiveRewardsCommand[] Get() + { + Update(); + return commands.Select(c => c.Value).ToArray(); + } + + private void AddToCache(string line) + { + try + { + var timestamp = line.Substring(0, 30); + if (commands.ContainsKey(timestamp)) return; + var json = line.Substring(31); + + var cmd = JsonConvert.DeserializeObject(json); + if (cmd != null) commands.Add(timestamp, cmd); + } + catch + { + } + } + } } } From f7bdafbdc5ad6c3c8d5404ff3a193b591459c709 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 22 May 2024 11:06:34 +0200 Subject: [PATCH 075/142] Improved representation of TestToken amounts --- .../CodexContractsAccess.cs | 4 +- .../ContractInteractions.cs | 6 +- .../TestTokenExtensions.cs | 76 +++++++++++++++---- ProjectPlugins/CodexPlugin/CodexSetup.cs | 4 +- ProjectPlugins/CodexPlugin/Mapper.cs | 9 +-- .../CodexPlugin/MarketplaceTypes.cs | 4 +- .../CodexTests/BasicTests/MarketplaceTests.cs | 14 ++-- .../FullyConnectedDownloadTests.cs | 2 +- .../PeerDiscoveryTests/PeerDiscoveryTests.cs | 2 +- .../UtilityTests/DiscordBotTests.cs | 12 +-- .../CodexContractsPlugin/TestTokenTests.cs | 46 +++++++++++ Tests/FrameworkTests/FrameworkTests.csproj | 1 + .../BiblioTech/Commands/GetBalanceCommand.cs | 2 +- Tools/BiblioTech/Commands/MintCommand.cs | 4 +- Tools/BiblioTech/Configuration.cs | 2 +- Tools/CodexNetDeployer/CodexNodeStarter.cs | 6 +- 16 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 Tests/FrameworkTests/CodexContractsPlugin/TestTokenTests.cs diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index 188d0548..197d4f99 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -67,7 +67,7 @@ namespace CodexContractsPlugin public string MintTestTokens(EthAddress ethAddress, TestToken testTokens) { - return StartInteraction().MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress); + return StartInteraction().MintTestTokens(ethAddress, testTokens.TstWei, Deployment.TokenAddress); } public TestToken GetTestTokenBalance(IHasEthAddress owner) @@ -78,7 +78,7 @@ namespace CodexContractsPlugin public TestToken GetTestTokenBalance(EthAddress ethAddress) { var balance = StartInteraction().GetBalance(Deployment.TokenAddress, ethAddress.Address); - return balance.TestTokens(); + return balance.TstWei(); } public Request[] GetStorageRequests(BlockInterval blockRange) diff --git a/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs b/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs index 93045db2..29840f97 100644 --- a/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs +++ b/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs @@ -44,7 +44,7 @@ namespace CodexContractsPlugin } } - public string MintTestTokens(EthAddress address, decimal amount, string tokenAddress) + public string MintTestTokens(EthAddress address, BigInteger amount, string tokenAddress) { log.Debug($"{amount} -> {address} (token: {tokenAddress})"); return MintTokens(address.Address, amount, tokenAddress); @@ -85,7 +85,7 @@ namespace CodexContractsPlugin } } - private string MintTokens(string account, decimal amount, string tokenAddress) + private string MintTokens(string account, BigInteger amount, string tokenAddress) { log.Debug($"({tokenAddress}) {amount} --> {account}"); if (string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for MintTestTokens"); @@ -93,7 +93,7 @@ namespace CodexContractsPlugin var function = new MintTokensFunction { Holder = account, - Amount = amount.ToBig() + Amount = amount }; return gethNode.SendTransaction(tokenAddress, function); diff --git a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs index eccc849d..e94032a7 100644 --- a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs @@ -4,49 +4,99 @@ namespace CodexContractsPlugin { public class TestToken : IComparable { - public TestToken(decimal amount) + public static BigInteger WeiFactor = new BigInteger(1000000000000000000); + + public TestToken(BigInteger tstWei) { - Amount = amount; + TstWei = tstWei; + Tst = tstWei / WeiFactor; } - public decimal Amount { get; } + public BigInteger TstWei { get; } + public BigInteger Tst { get; } public int CompareTo(TestToken? other) { - return Amount.CompareTo(other!.Amount); + return TstWei.CompareTo(other!.TstWei); } public override bool Equals(object? obj) { - return obj is TestToken token && Amount == token.Amount; + return obj is TestToken token && TstWei == token.TstWei; } public override int GetHashCode() { - return HashCode.Combine(Amount); + return HashCode.Combine(TstWei); } public override string ToString() { - return $"{Amount} TestTokens"; + var weiOnly = TstWei % WeiFactor; + + var tokens = new List(); + if (Tst > 0) tokens.Add($"{Tst} TST"); + if (weiOnly > 0) tokens.Add($"{weiOnly} TSTWEI"); + + return string.Join(" + ", tokens); + } + + public static TestToken operator +(TestToken a, TestToken b) + { + return new TestToken(a.TstWei + b.TstWei); + } + + public static bool operator <(TestToken a, TestToken b) + { + return a.TstWei < b.TstWei; + } + + public static bool operator >(TestToken a, TestToken b) + { + return a.TstWei > b.TstWei; + } + + public static bool operator ==(TestToken a, TestToken b) + { + return a.TstWei == b.TstWei; + } + + public static bool operator !=(TestToken a, TestToken b) + { + return a.TstWei != b.TstWei; } } - public static class TokensIntExtensions + public static class TestTokensExtensions { - public static TestToken TestTokens(this int i) + public static TestToken TstWei(this int i) { - return TestTokens(Convert.ToDecimal(i)); + return TstWei(Convert.ToDecimal(i)); } - public static TestToken TestTokens(this decimal i) + public static TestToken TstWei(this decimal i) + { + return new TestToken(new BigInteger(i)); + } + + public static TestToken TstWei(this BigInteger i) { return new TestToken(i); } - public static TestToken TestTokens(this BigInteger i) + public static TestToken Tst(this int i) { - return new TestToken((decimal)i); + return Tst(Convert.ToDecimal(i)); + } + + public static TestToken Tst(this decimal i) + { + return new TestToken(new BigInteger(i) * TestToken.WeiFactor); + } + + public static TestToken Tst(this BigInteger i) + { + return new TestToken(i * TestToken.WeiFactor); } } } diff --git a/ProjectPlugins/CodexPlugin/CodexSetup.cs b/ProjectPlugins/CodexPlugin/CodexSetup.cs index 2367cedc..04aaf427 100644 --- a/ProjectPlugins/CodexPlugin/CodexSetup.cs +++ b/ProjectPlugins/CodexPlugin/CodexSetup.cs @@ -168,7 +168,7 @@ namespace CodexPlugin public bool IsStorageNode { get; private set; } public bool IsValidator { get; private set; } public Ether InitialEth { get; private set; } = 0.Eth(); - public TestToken InitialTestTokens { get; private set; } = 0.TestTokens(); + public TestToken InitialTestTokens { get; private set; } = 0.Tst(); public EthAccount EthAccount { get; private set; } = EthAccount.GenerateNew(); public IMarketplaceSetup AsStorageNode() @@ -202,7 +202,7 @@ namespace CodexPlugin result += IsStorageNode ? "(storageNode)" : "()"; result += IsValidator ? "(validator)" : "() "; result += $"Address: '{EthAccount.EthAddress}' "; - result += $"InitialEth/TT({InitialEth.Eth}/{InitialTestTokens.Amount})"; + result += $"{InitialEth.Eth} / {InitialTestTokens}"; result += "] "; return result; } diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 053f15e5..61c95b4b 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -74,10 +74,10 @@ namespace CodexPlugin public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD read) { return new StorageAvailability( - totalSpace: new Utils.ByteSize(Convert.ToInt64(read.TotalSize)), + totalSpace: new ByteSize(Convert.ToInt64(read.TotalSize)), maxDuration: TimeSpan.FromSeconds(Convert.ToDouble(read.Duration)), - minPriceForTotalSpace: new TestToken(Convert.ToDecimal(read.MinPrice)), - maxCollateral: new TestToken(Convert.ToDecimal(read.MaxCollateral)) + minPriceForTotalSpace: new TestToken(BigInteger.Parse(read.MinPrice)), + maxCollateral: new TestToken(BigInteger.Parse(read.MaxCollateral)) ) { Id = read.Id @@ -165,8 +165,7 @@ namespace CodexPlugin private string ToDecInt(TestToken t) { - var i = new BigInteger(t.Amount); - return i.ToString("D"); + return t.TstWei.ToString("D"); } } } diff --git a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs index 5edc4560..caeda252 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs @@ -12,8 +12,8 @@ namespace CodexPlugin } public ContentId ContentId { get; set; } - public TestToken PricePerSlotPerSecond { get; set; } = 1.TestTokens(); - public TestToken RequiredCollateral { get; set; } = 1.TestTokens(); + public TestToken PricePerSlotPerSecond { get; set; } = 1.TstWei(); + public TestToken RequiredCollateral { get; set; } = 1.TstWei(); public uint MinRequiredNumberOfNodes { get; set; } public uint NodeFailureTolerance { get; set; } public int ProofProbability { get; set; } diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 6904e56e..6365d8b7 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -13,8 +13,8 @@ namespace CodexTests.BasicTests [Test] public void MarketplaceExample() { - var hostInitialBalance = 234.TestTokens(); - var clientInitialBalance = 100000.TestTokens(); + var hostInitialBalance = 234.TstWei(); + var clientInitialBalance = 100000.TstWei(); var fileSize = 10.MB(); var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); @@ -33,7 +33,7 @@ namespace CodexTests.BasicTests .AsStorageNode() .AsValidator())); - var expectedHostBalance = (numberOfHosts * hostInitialBalance.Amount).TestTokens(); + var expectedHostBalance = (numberOfHosts * hostInitialBalance.TstWei).TstWei(); foreach (var host in hosts) { AssertBalance(contracts, host, Is.EqualTo(expectedHostBalance)); @@ -41,8 +41,8 @@ namespace CodexTests.BasicTests var availability = new StorageAvailability( totalSpace: 10.GB(), maxDuration: TimeSpan.FromMinutes(30), - minPriceForTotalSpace: 1.TestTokens(), - maxCollateral: 20.TestTokens() + minPriceForTotalSpace: 1.TstWei(), + maxCollateral: 20.TstWei() ); host.Marketplace.MakeStorageAvailable(availability); } @@ -60,8 +60,8 @@ namespace CodexTests.BasicTests var purchase = new StoragePurchaseRequest(contentId) { - PricePerSlotPerSecond = 2.TestTokens(), - RequiredCollateral = 10.TestTokens(), + PricePerSlotPerSecond = 2.TstWei(), + RequiredCollateral = 10.TstWei(), MinRequiredNumberOfNodes = 5, NodeFailureTolerance = 2, ProofProbability = 5, diff --git a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs index 5b4f1a93..e40d9fbd 100644 --- a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs +++ b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs @@ -22,7 +22,7 @@ namespace CodexTests.DownloadConnectivityTests var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m - .WithInitial(10.Eth(), 1000.TestTokens()))); + .WithInitial(10.Eth(), 1000.TstWei()))); AssertAllNodesConnected(); } diff --git a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 82c53fb0..34da8d4a 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -32,7 +32,7 @@ namespace CodexTests.PeerDiscoveryTests var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m - .WithInitial(10.Eth(), 1000.TestTokens()))); + .WithInitial(10.Eth(), 1000.TstWei()))); AssertAllNodesConnected(); } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index d6425256..de3c5916 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -16,8 +16,8 @@ namespace CodexTests.UtilityTests { var myAccount = EthAccount.GenerateNew(); - var sellerInitialBalance = 234.TestTokens(); - var buyerInitialBalance = 100000.TestTokens(); + var sellerInitialBalance = 234.TstWei(); + var buyerInitialBalance = 100000.TstWei(); var fileSize = 11.MB(); var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); @@ -74,8 +74,8 @@ namespace CodexTests.UtilityTests var availability = new StorageAvailability( totalSpace: 10.GB(), maxDuration: TimeSpan.FromMinutes(30), - minPriceForTotalSpace: 1.TestTokens(), - maxCollateral: 20.TestTokens() + minPriceForTotalSpace: 1.TstWei(), + maxCollateral: 20.TstWei() ); seller.Marketplace.MakeStorageAvailable(availability); } @@ -92,8 +92,8 @@ namespace CodexTests.UtilityTests var purchase = new StoragePurchaseRequest(contentId) { - PricePerSlotPerSecond = 2.TestTokens(), - RequiredCollateral = 10.TestTokens(), + PricePerSlotPerSecond = 2.TstWei(), + RequiredCollateral = 10.TstWei(), MinRequiredNumberOfNodes = 5, NodeFailureTolerance = 2, ProofProbability = 5, diff --git a/Tests/FrameworkTests/CodexContractsPlugin/TestTokenTests.cs b/Tests/FrameworkTests/CodexContractsPlugin/TestTokenTests.cs new file mode 100644 index 00000000..b74ac86e --- /dev/null +++ b/Tests/FrameworkTests/CodexContractsPlugin/TestTokenTests.cs @@ -0,0 +1,46 @@ +using CodexContractsPlugin; +using NUnit.Framework; +using System.Numerics; + +namespace FrameworkTests.CodexContractsPlugin +{ + [TestFixture] + public class TestTokenTests + { + private const decimal factor = 1000000000000000000m; + + [Test] + public void RepresentsSmallAmount() + { + var t = 10.TstWei(); + + Assert.That(t.TstWei, Is.EqualTo(new BigInteger(10))); + Assert.That(t.Tst, Is.EqualTo(new BigInteger(0))); + Assert.That(t.ToString(), Is.EqualTo("10 TSTWEI")); + } + + [Test] + public void RepresentsLargeAmount() + { + var t = 10.Tst(); + + var expected = new BigInteger(10 * factor); + Assert.That(t.TstWei, Is.EqualTo(expected)); + Assert.That(t.Tst, Is.EqualTo(new BigInteger(10))); + Assert.That(t.ToString(), Is.EqualTo("10 TST")); + } + + [Test] + public void RepresentsLongAmount() + { + var a = 10.Tst(); + var b = 20.TstWei(); + var t = a + b; + + var expected = new BigInteger((10 * factor) + 20); + Assert.That(t.TstWei, Is.EqualTo(expected)); + Assert.That(t.Tst, Is.EqualTo(new BigInteger(10))); + Assert.That(t.ToString(), Is.EqualTo("10 TST + 20 TSTWEI")); + } + } +} diff --git a/Tests/FrameworkTests/FrameworkTests.csproj b/Tests/FrameworkTests/FrameworkTests.csproj index f22fe575..4bc09aa5 100644 --- a/Tests/FrameworkTests/FrameworkTests.csproj +++ b/Tests/FrameworkTests/FrameworkTests.csproj @@ -16,6 +16,7 @@ + diff --git a/Tools/BiblioTech/Commands/GetBalanceCommand.cs b/Tools/BiblioTech/Commands/GetBalanceCommand.cs index 3c983359..9454c79d 100644 --- a/Tools/BiblioTech/Commands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/Commands/GetBalanceCommand.cs @@ -32,7 +32,7 @@ namespace BiblioTech.Commands } var eth = 0.Eth(); - var testTokens = 0.TestTokens(); + var testTokens = 0.TstWei(); await Task.Run(() => { diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index d6d616f7..d3d24519 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -51,7 +51,7 @@ namespace BiblioTech.Commands { if (ShouldMintTestTokens(contracts, addr)) { - var tokens = Program.Config.MintTT.TestTokens(); + var tokens = Program.Config.MintTT.TstWei(); var transaction = contracts.MintTestTokens(addr, tokens); report.Add($"Minted {tokens} {FormatTransactionLink(transaction)}"); return new Transaction(tokens, transaction); @@ -77,7 +77,7 @@ namespace BiblioTech.Commands private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr) { var testTokens = contracts.GetTestTokenBalance(addr); - return testTokens.Amount < Program.Config.MintTT.TestTokens().Amount; + return testTokens < Program.Config.MintTT.TstWei(); } private bool ShouldSendEth(IGethNode gethNode, EthAddress addr) diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index b67689f3..296bf42f 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -32,7 +32,7 @@ namespace BiblioTech [Uniform("send-eth", "se", "SENDETH", true, "Amount of Eth send by the mint command.")] public int SendEth { get; set; } = 10; - [Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TestTokens minted by the mint command.")] + [Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TSTWEI minted by the mint command.")] public BigInteger MintTT { get; set; } = 1073741824; public string EndpointsPath diff --git a/Tools/CodexNetDeployer/CodexNodeStarter.cs b/Tools/CodexNetDeployer/CodexNodeStarter.cs index 97dd4fd8..81df4e0a 100644 --- a/Tools/CodexNetDeployer/CodexNodeStarter.cs +++ b/Tools/CodexNetDeployer/CodexNodeStarter.cs @@ -43,7 +43,7 @@ namespace CodexNetDeployer { s.EnableMarketplace(gethNode, contracts, m => { - m.WithInitial(100.Eth(), config.InitialTestTokens.TestTokens()); + m.WithInitial(100.Eth(), config.InitialTestTokens.TstWei()); if (validatorsLeft > 0) m.AsValidator(); if (config.ShouldMakeStorageAvailable) m.AsStorageNode(); }); @@ -71,8 +71,8 @@ namespace CodexNetDeployer var availability = new StorageAvailability( totalSpace: config.StorageSell!.Value.MB(), maxDuration: TimeSpan.FromSeconds(config.MaxDuration), - minPriceForTotalSpace: config.MinPrice.TestTokens(), - maxCollateral: config.MaxCollateral.TestTokens() + minPriceForTotalSpace: config.MinPrice.TstWei(), + maxCollateral: config.MaxCollateral.TstWei() ); var response = codexNode.Marketplace.MakeStorageAvailable(availability); From 6e9ea47b7d81ece3078a68eb19e27c350063bbf2 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 23 May 2024 11:15:14 +0200 Subject: [PATCH 076/142] Tracking contract state change moments in MarketplaceAccess --- Framework/Utils/Time.cs | 6 ++ .../CodexPlugin/MarketplaceAccess.cs | 60 +++++++++++++++---- .../CodexTests/BasicTests/MarketplaceTests.cs | 4 ++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index 85cfb1a6..10caaf47 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -20,6 +20,12 @@ namespace Utils task.Wait(); } + public static string FormatDuration(TimeSpan? d) + { + if (d == null) return "[NULL]"; + return FormatDuration(d.Value); + } + public static string FormatDuration(TimeSpan d) { var result = ""; diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index 4f8ca1e2..2b90af41 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -38,7 +38,9 @@ namespace CodexPlugin Log($"Storage requested successfully. PurchaseId: '{response}'."); - return new StoragePurchaseContract(log, codexAccess, response, purchase); + var contract = new StoragePurchaseContract(log, codexAccess, response, purchase); + contract.WaitForStorageContractSubmitted(); + return contract; } public string MakeStorageAvailable(StorageAvailability availability) @@ -54,7 +56,7 @@ namespace CodexPlugin private void Log(string msg) { - log.Log($"{codexAccess.Container.Name} {msg}"); + log.Log($"{codexAccess.Container.Containers.Single().Name} {msg}"); } } @@ -84,7 +86,10 @@ namespace CodexPlugin private readonly ILog log; private readonly CodexAccess codexAccess; private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); - private DateTime? contractStartUtc; + private readonly DateTime contractPendingUtc = DateTime.UtcNow; + private DateTime? contractSubmittedUtc = DateTime.UtcNow; + private DateTime? contractStartedUtc; + private DateTime? contractFinishedUtc; public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) { @@ -97,23 +102,38 @@ namespace CodexPlugin public string PurchaseId { get; } public StoragePurchaseRequest Purchase { get; } + public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc; + public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc; + public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc; + public TimeSpan? StartedToFinished => contractFinishedUtc - contractStartedUtc; + + public void WaitForStorageContractSubmitted() + { + WaitForStorageContractState(gracePeriod, "submitted", sleep: 200); + contractSubmittedUtc = DateTime.UtcNow; + LogSubmittedDuration(); + } + public void WaitForStorageContractStarted() { var timeout = Purchase.Expiry + gracePeriod; WaitForStorageContractState(timeout, "started"); - contractStartUtc = DateTime.UtcNow; + contractStartedUtc = DateTime.UtcNow; + LogStartedDuration(); } public void WaitForStorageContractFinished() { - if (!contractStartUtc.HasValue) + if (!contractStartedUtc.HasValue) { WaitForStorageContractStarted(); } - var currentContractTime = DateTime.UtcNow - contractStartUtc!.Value; + var currentContractTime = DateTime.UtcNow - contractStartedUtc!.Value; var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; WaitForStorageContractState(timeout, "finished"); + contractFinishedUtc = DateTime.UtcNow; + LogFinishedDuration(); } public StoragePurchase GetPurchaseStatus(string purchaseId) @@ -121,12 +141,12 @@ namespace CodexPlugin return codexAccess.GetPurchaseStatus(purchaseId); } - private void WaitForStorageContractState(TimeSpan timeout, string desiredState) + private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000) { var lastState = ""; var waitStart = DateTime.UtcNow; - log.Log($"Waiting for {Time.FormatDuration(timeout)} for contract '{PurchaseId}' to reach state '{desiredState}'."); + Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'."); while (lastState != desiredState) { var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId); @@ -137,7 +157,7 @@ namespace CodexPlugin log.Debug("Purchase status: " + statusJson); } - Thread.Sleep(1000); + Thread.Sleep(sleep); if (lastState == "errored") { @@ -149,7 +169,27 @@ namespace CodexPlugin FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}"); } } - log.Log($"Contract '{desiredState}'."); + } + + private void LogSubmittedDuration() + { + Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)}"); + } + + private void LogStartedDuration() + { + Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)}"); + } + + private void LogFinishedDuration() + { + Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)}"); + Log($"Started to Finished in {Time.FormatDuration(StartedToFinished)}"); + } + + private void Log(string msg) + { + log.Log($"[{PurchaseId}] {msg}"); } } } diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 6365d8b7..16943504 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -83,6 +83,10 @@ namespace CodexTests.BasicTests AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage."); Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); + + Assert.That(purchaseContract.PendingToSubmitted, Is.LessThan(TimeSpan.FromSeconds(30))); + Assert.That(purchaseContract.SubmittedToStarted, Is.LessThan(purchase.Expiry).Within(TimeSpan.FromSeconds(30))); + Assert.That(purchaseContract.SubmittedToFinished, Is.LessThan(purchase.Duration).Within(TimeSpan.FromSeconds(30))); } private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth) From a749f82ca5f405d91f0641cb5a7d46c1e04c5d10 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 23 May 2024 11:24:32 +0200 Subject: [PATCH 077/142] Switches blocktime logs to debug --- Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs | 2 +- Tests/CodexTests/BasicTests/MarketplaceTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs index a8e67f4d..d9a6ac29 100644 --- a/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs +++ b/Framework/NethereumWorkflow/BlockUtils/BlockTimeFinder.cs @@ -46,7 +46,7 @@ namespace NethereumWorkflow.BlockUtils private ulong Log(Func operation) { - var sw = Stopwatch.Begin(log, nameof(BlockTimeFinder)); + var sw = Stopwatch.Begin(log, nameof(BlockTimeFinder), true); var result = operation(); sw.End($"(Bounds: [{bounds.Genesis.BlockNumber}-{bounds.Current.BlockNumber}] Cache: {cache.Size})"); diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 16943504..2b7e52c1 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -96,7 +96,7 @@ namespace CodexTests.BasicTests var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()); var slotFilledEvents = contracts.GetSlotFilledEvents(blockRange); - Log($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); + Debug($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(); }, purchase.Expiry + TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5), "Checking SlotFilled events"); From 6c956c1a643375083962516d2a0576330a17aca2 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 23 May 2024 11:37:17 +0200 Subject: [PATCH 078/142] wip --- Tests/CodexTests/BasicTests/MarketplaceTests.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 2b7e52c1..e4e1797b 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -84,9 +84,9 @@ namespace CodexTests.BasicTests AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage."); Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); - Assert.That(purchaseContract.PendingToSubmitted, Is.LessThan(TimeSpan.FromSeconds(30))); - Assert.That(purchaseContract.SubmittedToStarted, Is.LessThan(purchase.Expiry).Within(TimeSpan.FromSeconds(30))); - Assert.That(purchaseContract.SubmittedToFinished, Is.LessThan(purchase.Duration).Within(TimeSpan.FromSeconds(30))); + AssertDuration(purchaseContract.PendingToSubmitted, 30, 1, nameof(purchaseContract.PendingToSubmitted)); + AssertDuration(purchaseContract.SubmittedToStarted, purchase.Expiry.TotalSeconds, 30, nameof(purchaseContract.SubmittedToStarted)); + AssertDuration(purchaseContract.SubmittedToFinished, purchase.Duration.TotalSeconds, 30, nameof(purchaseContract.SubmittedToFinished)); } private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth) @@ -121,5 +121,11 @@ namespace CodexTests.BasicTests var slotHost = contracts.GetSlotHost(request, contractSlotIndex); Assert.That(slotHost?.Address, Is.Not.Null); } + + private void AssertDuration(TimeSpan? span, double expectedSeconds, int tolerance, string message) + { + Assert.That(span.HasValue, "IsNull: " + message); + Assert.That(span!.Value.TotalSeconds, Is.EqualTo(expectedSeconds).Within(tolerance), message); + } } } From 5143361dcd4e3f457862fa8616676efe72d7c65b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 23 May 2024 11:37:57 +0200 Subject: [PATCH 079/142] wip --- .../UtilityTests/DiscordBotTests.cs | 160 ++++++++++++++---- 1 file changed, 128 insertions(+), 32 deletions(-) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 8099ec73..cfb1dfa5 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -5,6 +5,7 @@ using Core; using DiscordRewards; using GethPlugin; using KubernetesWorkflow.Types; +using Logging; using Newtonsoft.Json; using NUnit.Framework; using Utils; @@ -17,49 +18,54 @@ namespace CodexTests.UtilityTests private readonly RewardRepo repo = new RewardRepo(); private readonly TestToken hostInitialBalance = 3000000.TstWei(); private readonly TestToken clientInitialBalance = 1000000000.TstWei(); + private readonly EthAccount clientAccount = EthAccount.GenerateNew(); + private readonly EthAccount hostAccount = EthAccount.GenerateNew(); [Test] public void BotRewardTest() { - var clientAccount = EthAccount.GenerateNew(); - var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); var contracts = Ci.StartCodexContracts(geth); var gethInfo = CreateGethInfo(geth, contracts); + var monitor = new ChainMonitor(contracts, geth, GetTestLog()); + monitor.Start(); + var botContainer = StartDiscordBot(gethInfo); - var hostAccount = EthAccount.GenerateNew(); - StartHosts(hostAccount, geth, contracts); + StartHosts(geth, contracts); StartRewarderBot(gethInfo, botContainer); - var client = StartClient(geth, contracts, clientAccount); + var client = StartClient(geth, contracts); var purchaseContract = ClientPurchasesStorage(client); var apiCalls = new RewardApiCalls(Ci, botContainer); + apiCalls.Start(OnCommand); - Time.WaitUntil(() => apiCalls.Get().Length > 4, TimeSpan.FromMinutes(10), TimeSpan.FromSeconds(10), "Waiting for API calls"); + Thread.Sleep(TimeSpan.FromMinutes(10)); - var calls = apiCalls.Get(); - foreach (var call in calls) + apiCalls.Stop(); + monitor.Stop(); + } + + private void OnCommand(GiveRewardsCommand call) + { + var line = ""; + if (call.Averages.Any()) line += $"{call.Averages.Length} average. "; + if (call.EventsOverview.Any()) line += $"{call.EventsOverview.Length} events. "; + foreach (var r in call.Rewards) { - var line = ""; - if (call.Averages.Any()) line += $"{call.Averages.Length} average. "; - if (call.EventsOverview.Any()) line += $"{call.EventsOverview.Length} events. "; - foreach (var r in call.Rewards) - { - var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); - var isClient = r.UserAddresses.Any(a => a == clientAccount.EthAddress.Address); - var isHost = r.UserAddresses.Any(a => a == hostAccount.EthAddress.Address); - if (isHost && isClient) throw new Exception("what?"); - var name = isClient ? "Client" : "Host"; + var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); + var isClient = r.UserAddresses.Any(a => a == clientAccount.EthAddress.Address); + var isHost = r.UserAddresses.Any(a => a == hostAccount.EthAddress.Address); + if (isHost && isClient) throw new Exception("what?"); + var name = isClient ? "Client" : "Host"; - line += name + " = " + reward.Message; - } - Log(line); + line += name + " = " + reward.Message; } + Log(line); } private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) @@ -80,7 +86,7 @@ namespace CodexTests.UtilityTests return client.Marketplace.RequestStorage(purchase); } - private ICodexNode StartClient(IGethNode geth, ICodexContracts contracts, EthAccount clientAccount) + private ICodexNode StartClient(IGethNode geth, ICodexContracts contracts) { return StartCodex(s => s .WithName("Client") @@ -128,7 +134,7 @@ namespace CodexTests.UtilityTests return bot.Containers.Single(); } - private void StartHosts(EthAccount hostAccount, IGethNode geth, ICodexContracts contracts) + private void StartHosts(IGethNode geth, ICodexContracts contracts) { var hosts = StartCodex(GetNumberOfLiveHosts(), s => s .WithName("Host") @@ -192,6 +198,9 @@ namespace CodexTests.UtilityTests private readonly CoreInterface ci; private readonly RunningContainer botContainer; private readonly Dictionary commands = new Dictionary(); + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + private Task worker = Task.CompletedTask; + private Action onCommand = c => { }; public RewardApiCalls(CoreInterface ci, RunningContainer botContainer) { @@ -199,18 +208,37 @@ namespace CodexTests.UtilityTests this.botContainer = botContainer; } - public void Update() + public void Start(Action onCommand) { - var botLog = ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); - - var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) AddToCache(line); + this.onCommand = onCommand; + worker = Task.Run(Worker); } - public GiveRewardsCommand[] Get() + public void Stop() { - Update(); - return commands.Select(c => c.Value).ToArray(); + cts.Cancel(); + worker.Wait(); + } + + private void Worker() + { + while (!cts.IsCancellationRequested) + { + Update(); + } + } + + private void Update() + { + Thread.Sleep(TimeSpan.FromSeconds(10)); + if (cts.IsCancellationRequested) return; + + var botLog = ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); + var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + AddToCache(line); + } } private void AddToCache(string line) @@ -222,12 +250,80 @@ namespace CodexTests.UtilityTests var json = line.Substring(31); var cmd = JsonConvert.DeserializeObject(json); - if (cmd != null) commands.Add(timestamp, cmd); + if (cmd != null) + { + commands.Add(timestamp, cmd); + onCommand(cmd); + } } catch { } } } + + public class ChainMonitor + { + private readonly ICodexContracts contracts; + private readonly IGethNode geth; + private readonly ILog log; + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + private Task worker = Task.CompletedTask; + private DateTime last = DateTime.UtcNow; + + public ChainMonitor(ICodexContracts contracts, IGethNode geth, ILog log) + { + this.contracts = contracts; + this.geth = geth; + this.log = log; + } + + public void Start() + { + last = DateTime.UtcNow; + worker = Task.Run(Worker); + } + + public void Stop() + { + cts.Cancel(); + worker.Wait(); + } + + private void Worker() + { + while (!cts.IsCancellationRequested) + { + Thread.Sleep(TimeSpan.FromSeconds(10)); + if (cts.IsCancellationRequested) return; + + Update(); + + } + } + + private void Update() + { + var start = last; + var stop = DateTime.UtcNow; + last = stop; + + var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop)); + + + LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range); + LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range); + LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range); + LogEvents(nameof(contracts.GetSlotFilledEvents), contracts.GetSlotFilledEvents, range); + LogEvents(nameof(contracts.GetSlotFreedEvents), contracts.GetSlotFreedEvents, range); + } + + private void LogEvents(string n, Func f, BlockInterval r) + { + var a = (object[])f(r); + + a.ToList().ForEach(request => log.Log(n + " - " + JsonConvert.SerializeObject(request))); + } + } } } From 684a99027b248ba31235ef0ce5b7933b76883b71 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 24 May 2024 14:33:59 +0200 Subject: [PATCH 080/142] nice checks for timings of successful contract --- .../CodexPlugin/MarketplaceAccess.cs | 27 ++++++++++++++----- .../CodexTests/BasicTests/MarketplaceTests.cs | 10 ------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index 2b90af41..8944d95c 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -105,13 +105,13 @@ namespace CodexPlugin public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc; public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc; public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc; - public TimeSpan? StartedToFinished => contractFinishedUtc - contractStartedUtc; public void WaitForStorageContractSubmitted() { WaitForStorageContractState(gracePeriod, "submitted", sleep: 200); contractSubmittedUtc = DateTime.UtcNow; LogSubmittedDuration(); + AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted)); } public void WaitForStorageContractStarted() @@ -121,6 +121,7 @@ namespace CodexPlugin WaitForStorageContractState(timeout, "started"); contractStartedUtc = DateTime.UtcNow; LogStartedDuration(); + AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); } public void WaitForStorageContractFinished() @@ -129,11 +130,12 @@ namespace CodexPlugin { WaitForStorageContractStarted(); } - var currentContractTime = DateTime.UtcNow - contractStartedUtc!.Value; + var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value; var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; WaitForStorageContractState(timeout, "finished"); contractFinishedUtc = DateTime.UtcNow; LogFinishedDuration(); + AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); } public StoragePurchase GetPurchaseStatus(string purchaseId) @@ -173,18 +175,31 @@ namespace CodexPlugin private void LogSubmittedDuration() { - Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)}"); + Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " + + $"( < {Time.FormatDuration(gracePeriod)})"); } private void LogStartedDuration() { - Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)}"); + Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " + + $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})"); } private void LogFinishedDuration() { - Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)}"); - Log($"Started to Finished in {Time.FormatDuration(StartedToFinished)}"); + Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " + + $"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})"); + } + + private void AssertDuration(TimeSpan? span, TimeSpan max, string message) + { + if (span == null) throw new ArgumentNullException(nameof(MarketplaceAccess) + ": " + message + " (IsNull)"); + if (span.Value.TotalDays >= max.TotalSeconds) + { + throw new Exception(nameof(MarketplaceAccess) + + $": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " + + message); + } } private void Log(string msg) diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index e4e1797b..7cff4aa5 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -83,10 +83,6 @@ namespace CodexTests.BasicTests AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage."); Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); - - AssertDuration(purchaseContract.PendingToSubmitted, 30, 1, nameof(purchaseContract.PendingToSubmitted)); - AssertDuration(purchaseContract.SubmittedToStarted, purchase.Expiry.TotalSeconds, 30, nameof(purchaseContract.SubmittedToStarted)); - AssertDuration(purchaseContract.SubmittedToFinished, purchase.Duration.TotalSeconds, 30, nameof(purchaseContract.SubmittedToFinished)); } private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth) @@ -121,11 +117,5 @@ namespace CodexTests.BasicTests var slotHost = contracts.GetSlotHost(request, contractSlotIndex); Assert.That(slotHost?.Address, Is.Not.Null); } - - private void AssertDuration(TimeSpan? span, double expectedSeconds, int tolerance, string message) - { - Assert.That(span.HasValue, "IsNull: " + message); - Assert.That(span!.Value.TotalSeconds, Is.EqualTo(expectedSeconds).Within(tolerance), message); - } } } From 69aa3a998f518f53408fc86bb48bfa61c14c5a00 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 24 May 2024 15:34:42 +0200 Subject: [PATCH 081/142] Fixes bot test contract --- .../DiscordBotStartupConfig.cs | 4 +- .../RewarderBotContainerRecipe.cs | 2 +- ProjectPlugins/CodexPlugin/CodexNode.cs | 25 ++++++-- .../CodexPlugin/CodexNodeFactory.cs | 14 ++--- .../CodexTests/BasicTests/MarketplaceTests.cs | 4 +- .../UtilityTests/DiscordBotTests.cs | 63 +++++++++++++------ 6 files changed, 76 insertions(+), 36 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs index bbef0b1a..b990e60e 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs @@ -27,7 +27,7 @@ public class RewarderBotStartupConfig { - public RewarderBotStartupConfig(string discordBotHost, int discordBotPort, string intervalMinutes, DateTime historyStartUtc, DiscordBotGethInfo gethInfo, string? dataPath) + public RewarderBotStartupConfig(string discordBotHost, int discordBotPort, int intervalMinutes, DateTime historyStartUtc, DiscordBotGethInfo gethInfo, string? dataPath) { DiscordBotHost = discordBotHost; DiscordBotPort = discordBotPort; @@ -39,7 +39,7 @@ public string DiscordBotHost { get; } public int DiscordBotPort { get; } - public string IntervalMinutes { get; } + public int IntervalMinutes { get; } public DateTime HistoryStartUtc { get; } public DiscordBotGethInfo GethInfo { get; } public string? DataPath { get; set; } diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index 816c910d..ae31e696 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -17,7 +17,7 @@ namespace CodexDiscordBotPlugin AddEnvVar("DISCORDBOTHOST", config.DiscordBotHost); AddEnvVar("DISCORDBOTPORT", config.DiscordBotPort.ToString()); - AddEnvVar("INTERVALMINUTES", config.IntervalMinutes); + AddEnvVar("INTERVALMINUTES", config.IntervalMinutes.ToString()); var offset = new DateTimeOffset(config.HistoryStartUtc); AddEnvVar("CHECKHISTORY", offset.ToUnixTimeSeconds().ToString()); diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 36fb1dc0..33c249a7 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -23,6 +23,7 @@ namespace CodexPlugin CrashWatcher CrashWatcher { get; } PodInfo GetPodInfo(); ITransferSpeeds TransferSpeeds { get; } + EthAccount EthAccount { get; } void Stop(bool waitTillStopped); } @@ -30,13 +31,13 @@ namespace CodexPlugin { private const string UploadFailedMessage = "Unable to store block"; private readonly IPluginTools tools; - private readonly EthAddress? ethAddress; + private readonly EthAccount? ethAccount; private readonly TransferSpeeds transferSpeeds; - public CodexNode(IPluginTools tools, CodexAccess codexAccess, CodexNodeGroup group, IMarketplaceAccess marketplaceAccess, EthAddress? ethAddress) + public CodexNode(IPluginTools tools, CodexAccess codexAccess, CodexNodeGroup group, IMarketplaceAccess marketplaceAccess, EthAccount? ethAccount) { this.tools = tools; - this.ethAddress = ethAddress; + this.ethAccount = ethAccount; CodexAccess = codexAccess; Group = group; Marketplace = marketplaceAccess; @@ -66,8 +67,17 @@ namespace CodexPlugin { get { - if (ethAddress == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it."); - return ethAddress; + EnsureMarketplace(); + return ethAccount!.EthAddress; + } + } + + public EthAccount EthAccount + { + get + { + EnsureMarketplace(); + return ethAccount!; } } @@ -197,6 +207,11 @@ namespace CodexPlugin } } + private void EnsureMarketplace() + { + if (ethAccount == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it."); + } + private void Log(string msg) { tools.GetLog().Log($"{GetName()}: {msg}"); diff --git a/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs b/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs index 18483de6..425d489d 100644 --- a/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs +++ b/ProjectPlugins/CodexPlugin/CodexNodeFactory.cs @@ -22,22 +22,22 @@ namespace CodexPlugin public CodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group) { - var ethAddress = GetEthAddress(access); - var marketplaceAccess = GetMarketplaceAccess(access, ethAddress); - return new CodexNode(tools, access, group, marketplaceAccess, ethAddress); + var ethAccount = GetEthAccount(access); + var marketplaceAccess = GetMarketplaceAccess(access, ethAccount); + return new CodexNode(tools, access, group, marketplaceAccess, ethAccount); } - private IMarketplaceAccess GetMarketplaceAccess(CodexAccess codexAccess, EthAddress? ethAddress) + private IMarketplaceAccess GetMarketplaceAccess(CodexAccess codexAccess, EthAccount? ethAccount) { - if (ethAddress == null) return new MarketplaceUnavailable(); + if (ethAccount == null) return new MarketplaceUnavailable(); return new MarketplaceAccess(tools.GetLog(), codexAccess); } - private EthAddress? GetEthAddress(CodexAccess access) + private EthAccount? GetEthAccount(CodexAccess access) { var ethAccount = access.Container.Containers.Single().Recipe.Additionals.Get(); if (ethAccount == null) return null; - return ethAccount.EthAddress; + return ethAccount; } public CrashWatcher CreateCrashWatcher(RunningContainer c) diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 7cff4aa5..2ecc3254 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -65,8 +65,8 @@ namespace CodexTests.BasicTests MinRequiredNumberOfNodes = 5, NodeFailureTolerance = 2, ProofProbability = 5, - Duration = TimeSpan.FromMinutes(5), - Expiry = TimeSpan.FromMinutes(4) + Duration = TimeSpan.FromMinutes(6), + Expiry = TimeSpan.FromMinutes(5) }; var purchaseContract = client.Marketplace.RequestStorage(purchase); diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index cfb1dfa5..ab631120 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -19,7 +19,7 @@ namespace CodexTests.UtilityTests private readonly TestToken hostInitialBalance = 3000000.TstWei(); private readonly TestToken clientInitialBalance = 1000000000.TstWei(); private readonly EthAccount clientAccount = EthAccount.GenerateNew(); - private readonly EthAccount hostAccount = EthAccount.GenerateNew(); + private readonly List hostAccounts = new List(); [Test] public void BotRewardTest() @@ -44,28 +44,27 @@ namespace CodexTests.UtilityTests var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); - Thread.Sleep(TimeSpan.FromMinutes(10)); + purchaseContract.WaitForStorageContractFinished(); apiCalls.Stop(); monitor.Stop(); + + Log("Done!"); } private void OnCommand(GiveRewardsCommand call) { - var line = ""; - if (call.Averages.Any()) line += $"{call.Averages.Length} average. "; - if (call.EventsOverview.Any()) line += $"{call.EventsOverview.Length} events. "; + if (call.Averages.Any()) Log($"{call.Averages.Length} average."); + if (call.EventsOverview.Any()) Log($"{call.EventsOverview.Length} events."); foreach (var r in call.Rewards) { var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); - var isClient = r.UserAddresses.Any(a => a == clientAccount.EthAddress.Address); - var isHost = r.UserAddresses.Any(a => a == hostAccount.EthAddress.Address); - if (isHost && isClient) throw new Exception("what?"); - var name = isClient ? "Client" : "Host"; - - line += name + " = " + reward.Message; + foreach (var address in r.UserAddresses) + { + var user = IdentifyAccount(address); + Log(user + ": " + reward.Message); + } } - Log(line); } private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) @@ -88,11 +87,14 @@ namespace CodexTests.UtilityTests private ICodexNode StartClient(IGethNode geth, ICodexContracts contracts) { - return StartCodex(s => s + var node = StartCodex(s => s .WithName("Client") .EnableMarketplace(geth, contracts, m => m .WithAccount(clientAccount) .WithInitial(10.Eth(), clientInitialBalance))); + + Log($"Client {node.EthAccount.EthAddress}"); + return node; } private void StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) @@ -100,7 +102,7 @@ namespace CodexTests.UtilityTests Ci.DeployRewarderBot(new RewarderBotStartupConfig( discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, - intervalMinutes: "10", + intervalMinutes: 1, historyStartUtc: DateTime.UtcNow, gethInfo: gethInfo, dataPath: null @@ -142,23 +144,27 @@ namespace CodexTests.UtilityTests { ContractClock = CodexLogLevel.Trace, }) - .WithStorageQuota(GetFileSizePlus(50)) + .WithStorageQuota(Mult(GetMinFileSizePlus(50), GetNumberOfLiveHosts())) .EnableMarketplace(geth, contracts, m => m - .WithAccount(hostAccount) .WithInitial(10.Eth(), hostInitialBalance) .AsStorageNode() .AsValidator())); var availability = new StorageAvailability( - totalSpace: GetFileSizePlus(5), + totalSpace: Mult(GetMinFileSize(), GetNumberOfLiveHosts()), maxDuration: TimeSpan.FromMinutes(30), minPriceForTotalSpace: 1.TstWei(), maxCollateral: hostInitialBalance ); + var i = 0; foreach (var host in hosts) { + hostAccounts.Add(host.EthAccount); host.Marketplace.MakeStorageAvailable(availability); + + Log($"Host{i} {host.EthAccount.EthAddress}"); + i++; } } @@ -167,7 +173,12 @@ namespace CodexTests.UtilityTests return Convert.ToInt32(GetNumberOfRequiredHosts()) + 3; } - private ByteSize GetFileSizePlus(int plusMb) + private ByteSize Mult(ByteSize size, int mult) + { + return new ByteSize(size.SizeInBytes * mult); + } + + private ByteSize GetMinFileSizePlus(int plusMb) { return new ByteSize(GetMinFileSize().SizeInBytes + plusMb.MB().SizeInBytes); } @@ -184,7 +195,7 @@ namespace CodexTests.UtilityTests if (h > minNumHosts) minNumHosts = h; } - var minFileSize = (minSlotSize * minNumHosts) + 1024; + var minFileSize = ((minSlotSize + 1024) * minNumHosts); return new ByteSize(Convert.ToInt64(minFileSize)); } @@ -193,6 +204,20 @@ namespace CodexTests.UtilityTests return Convert.ToUInt32(repo.Rewards.Max(r => r.CheckConfig.MinNumberOfHosts)); } + private string IdentifyAccount(string address) + { + if (address == clientAccount.EthAddress.Address) return "Client"; + try + { + var index = hostAccounts.FindIndex(a => a.EthAddress.Address == address); + return "Host" + index; + } + catch + { + return "UNKNOWN"; + } + } + public class RewardApiCalls { private readonly CoreInterface ci; From ba9e4b098f06a034ca2b4309ea1676a89a438875 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 24 May 2024 16:11:51 +0200 Subject: [PATCH 082/142] checking that all rewards are sent. --- .../CodexPlugin/CodexContainerRecipe.cs | 5 ++- ProjectPlugins/CodexPlugin/CodexSetup.cs | 37 +++++++++++++++++-- ProjectPlugins/GethPlugin/EthAccount.cs | 5 +++ .../UtilityTests/DiscordBotTests.cs | 27 +++++++++++--- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 7bc308ac..07b94ac4 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -109,8 +109,9 @@ namespace CodexPlugin // Custom scripting in the Codex test image will write this variable to a private-key file, // and pass the correct filename to Codex. - AddEnvVar("PRIV_KEY", marketplaceSetup.EthAccount.PrivateKey); - Additional(marketplaceSetup.EthAccount); + var account = marketplaceSetup.EthAccountSetup.GetNew(); + AddEnvVar("PRIV_KEY", account.PrivateKey); + Additional(account); SetCommandOverride(marketplaceSetup); if (marketplaceSetup.IsValidator) diff --git a/ProjectPlugins/CodexPlugin/CodexSetup.cs b/ProjectPlugins/CodexPlugin/CodexSetup.cs index 04aaf427..44454c77 100644 --- a/ProjectPlugins/CodexPlugin/CodexSetup.cs +++ b/ProjectPlugins/CodexPlugin/CodexSetup.cs @@ -169,7 +169,7 @@ namespace CodexPlugin public bool IsValidator { get; private set; } public Ether InitialEth { get; private set; } = 0.Eth(); public TestToken InitialTestTokens { get; private set; } = 0.Tst(); - public EthAccount EthAccount { get; private set; } = EthAccount.GenerateNew(); + public EthAccountSetup EthAccountSetup { get; private set; } = new EthAccountSetup(); public IMarketplaceSetup AsStorageNode() { @@ -185,7 +185,7 @@ namespace CodexPlugin public IMarketplaceSetup WithAccount(EthAccount account) { - EthAccount = account; + EthAccountSetup.Pin(account); return this; } @@ -201,10 +201,41 @@ namespace CodexPlugin var result = "[(clientNode)"; // When marketplace is enabled, being a clientNode is implicit. result += IsStorageNode ? "(storageNode)" : "()"; result += IsValidator ? "(validator)" : "() "; - result += $"Address: '{EthAccount.EthAddress}' "; + result += $"Address: '{EthAccountSetup}' "; result += $"{InitialEth.Eth} / {InitialTestTokens}"; result += "] "; return result; } } + + public class EthAccountSetup + { + private readonly List accounts = new List(); + private bool pinned = false; + + public void Pin(EthAccount account) + { + accounts.Add(account); + pinned = true; + } + + public EthAccount GetNew() + { + if (pinned) return accounts.Last(); + + var a = EthAccount.GenerateNew(); + accounts.Add(a); + return a; + } + + public EthAccount[] GetAll() + { + return accounts.ToArray(); + } + + public override string ToString() + { + return string.Join(",", accounts.Select(a => a.ToString()).ToArray()); + } + } } diff --git a/ProjectPlugins/GethPlugin/EthAccount.cs b/ProjectPlugins/GethPlugin/EthAccount.cs index 9f1318b7..60bf40d8 100644 --- a/ProjectPlugins/GethPlugin/EthAccount.cs +++ b/ProjectPlugins/GethPlugin/EthAccount.cs @@ -24,5 +24,10 @@ namespace GethPlugin return new EthAccount(ethAddress, account.PrivateKey); } + + public override string ToString() + { + return EthAddress.ToString(); + } } } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index ab631120..d7ebd8db 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -20,6 +20,8 @@ namespace CodexTests.UtilityTests private readonly TestToken clientInitialBalance = 1000000000.TstWei(); private readonly EthAccount clientAccount = EthAccount.GenerateNew(); private readonly List hostAccounts = new List(); + private readonly List rewardsSeen = new List(); + private readonly TimeSpan rewarderInterval = TimeSpan.FromMinutes(1); [Test] public void BotRewardTest() @@ -50,6 +52,24 @@ namespace CodexTests.UtilityTests monitor.Stop(); Log("Done!"); + + Thread.Sleep(rewarderInterval * 2); + + Log("Seen:"); + foreach (var seen in rewardsSeen) + { + Log(seen.ToString()); + } + Log(""); + + foreach (var r in repo.Rewards) + { + var seen = rewardsSeen.Any(s => r.RoleId == s); + + Log($"{r.RoleId} = {seen}"); + } + + Assert.That(repo.Rewards.All(r => rewardsSeen.Contains(r.RoleId))); } private void OnCommand(GiveRewardsCommand call) @@ -59,6 +79,7 @@ namespace CodexTests.UtilityTests foreach (var r in call.Rewards) { var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); + if (r.UserAddresses.Any()) rewardsSeen.Add(reward.RoleId); foreach (var address in r.UserAddresses) { var user = IdentifyAccount(address); @@ -102,7 +123,7 @@ namespace CodexTests.UtilityTests Ci.DeployRewarderBot(new RewarderBotStartupConfig( discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, - intervalMinutes: 1, + intervalMinutes: Convert.ToInt32(Math.Round(rewarderInterval.TotalMinutes)), historyStartUtc: DateTime.UtcNow, gethInfo: gethInfo, dataPath: null @@ -157,14 +178,10 @@ namespace CodexTests.UtilityTests maxCollateral: hostInitialBalance ); - var i = 0; foreach (var host in hosts) { hostAccounts.Add(host.EthAccount); host.Marketplace.MakeStorageAvailable(availability); - - Log($"Host{i} {host.EthAccount.EthAddress}"); - i++; } } From ccc6c815e4ef30e8ef9096e768c4e9313990bd53 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 29 May 2024 14:05:16 +0200 Subject: [PATCH 083/142] Logging of entire chain state as seen by rewarder bot --- .../CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs | 1 + .../CodexDiscordBotPlugin/DiscordBotStartupConfig.cs | 4 +++- Tests/CodexTests/UtilityTests/DiscordBotTests.cs | 3 ++- Tools/TestNetRewarder/ChainState.cs | 11 +++++++++++ Tools/TestNetRewarder/HistoricState.cs | 5 +++++ Tools/TestNetRewarder/Processor.cs | 2 ++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index b2bb5973..b2e184be 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -56,6 +56,7 @@ namespace CodexDiscordBotPlugin private RunningPod StartRewarderContainer(IStartupWorkflow workflow, RewarderBotStartupConfig config) { var startupConfig = new StartupConfig(); + startupConfig.NameOverride = config.Name; startupConfig.Add(config); return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig).WaitForOnline(); } diff --git a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs index b990e60e..e77552e5 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotStartupConfig.cs @@ -27,8 +27,9 @@ public class RewarderBotStartupConfig { - public RewarderBotStartupConfig(string discordBotHost, int discordBotPort, int intervalMinutes, DateTime historyStartUtc, DiscordBotGethInfo gethInfo, string? dataPath) + public RewarderBotStartupConfig(string name, string discordBotHost, int discordBotPort, int intervalMinutes, DateTime historyStartUtc, DiscordBotGethInfo gethInfo, string? dataPath) { + Name = name; DiscordBotHost = discordBotHost; DiscordBotPort = discordBotPort; IntervalMinutes = intervalMinutes; @@ -37,6 +38,7 @@ DataPath = dataPath; } + public string Name { get; } public string DiscordBotHost { get; } public int DiscordBotPort { get; } public int IntervalMinutes { get; } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index d7ebd8db..671d6c07 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -121,6 +121,7 @@ namespace CodexTests.UtilityTests private void StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) { Ci.DeployRewarderBot(new RewarderBotStartupConfig( + name: "rewarder-bot", discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, intervalMinutes: Convert.ToInt32(Math.Round(rewarderInterval.TotalMinutes)), @@ -145,7 +146,7 @@ namespace CodexTests.UtilityTests private RunningContainer StartDiscordBot(DiscordBotGethInfo gethInfo) { var bot = Ci.DeployCodexDiscordBot(new DiscordBotStartupConfig( - name: "bot", + name: "discord-bot", token: "aaa", serverName: "ThatBen's server", adminRoleName: "bottest-admins", diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index c3ce8a24..50b25017 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -59,6 +59,17 @@ namespace TestNetRewarder public SlotFilledEventDTO[] SlotFilledEvents { get; } public SlotFreedEventDTO[] SlotFreedEvents { get; } + public string EntireString() + { + return + $"NewRequests: {JsonConvert.SerializeObject(NewRequests)}" + + $"FulfilledE: {JsonConvert.SerializeObject(RequestFulfilledEvents)}" + + $"CancelledE: {JsonConvert.SerializeObject(RequestCancelledEvents)}" + + $"FilledE: {JsonConvert.SerializeObject(SlotFilledEvents)}" + + $"FreedE: {JsonConvert.SerializeObject(SlotFreedEvents)}" + + $"Historic: {historicState.EntireString()}"; + } + public string[] GenerateOverview() { var entries = new List(); diff --git a/Tools/TestNetRewarder/HistoricState.cs b/Tools/TestNetRewarder/HistoricState.cs index 36d335df..3ae58820 100644 --- a/Tools/TestNetRewarder/HistoricState.cs +++ b/Tools/TestNetRewarder/HistoricState.cs @@ -29,6 +29,11 @@ namespace TestNetRewarder r.State == RequestState.Failed ); } + + public string EntireString() + { + return JsonConvert.SerializeObject(StorageRequests); + } } public class StorageRequest diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index c18247b4..a756b937 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -58,6 +58,8 @@ namespace TestNetRewarder private async Task ProcessChainState(ChainState chainState) { + log.Log($"Processing chain state: '{chainState.EntireString()}'"); + var outgoingRewards = new List(); foreach (var reward in rewardRepo.Rewards) { From 117a30bb829051da76bcacd2ed1b6364689926bb Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 30 May 2024 10:55:33 +0200 Subject: [PATCH 084/142] wip --- .../RewarderBotContainerRecipe.cs | 2 +- Tests/CodexTests/CodexTests.csproj | 1 + .../UtilityTests/DiscordBotTests.cs | 143 ++++++++++++++---- 3 files changed, 112 insertions(+), 34 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index ae31e696..8bf4344d 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin public class RewarderBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-rewarder"; - public override string Image => "codexstorage/codex-rewarderbot:sha-2ab84e2"; + public override string Image => "codexstorage/codex-rewarderbot:sha-ccc6c81"; protected override void Initialize(StartupConfig startupConfig) { diff --git a/Tests/CodexTests/CodexTests.csproj b/Tests/CodexTests/CodexTests.csproj index 10d1a225..4a9a3edb 100644 --- a/Tests/CodexTests/CodexTests.csproj +++ b/Tests/CodexTests/CodexTests.csproj @@ -19,6 +19,7 @@ + diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 671d6c07..29403eb2 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -37,17 +37,19 @@ namespace CodexTests.UtilityTests StartHosts(geth, contracts); - StartRewarderBot(gethInfo, botContainer); + var rewarderContainer = StartRewarderBot(gethInfo, botContainer); var client = StartClient(geth, contracts); - var purchaseContract = ClientPurchasesStorage(client); - var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); + var rewarderLog = new RewarderLogMonitor(Ci, rewarderContainer.Containers.Single()); + rewarderLog.Start(l => Log("Rewarder ChainState: " + l)); + var purchaseContract = ClientPurchasesStorage(client); purchaseContract.WaitForStorageContractFinished(); + rewarderLog.Stop(); apiCalls.Stop(); monitor.Stop(); @@ -118,9 +120,9 @@ namespace CodexTests.UtilityTests return node; } - private void StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) + private RunningPod StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) { - Ci.DeployRewarderBot(new RewarderBotStartupConfig( + return Ci.DeployRewarderBot(new RewarderBotStartupConfig( name: "rewarder-bot", discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, @@ -238,22 +240,113 @@ namespace CodexTests.UtilityTests public class RewardApiCalls { - private readonly CoreInterface ci; - private readonly RunningContainer botContainer; + private readonly ContainerFileMonitor monitor; private readonly Dictionary commands = new Dictionary(); - private readonly CancellationTokenSource cts = new CancellationTokenSource(); - private Task worker = Task.CompletedTask; - private Action onCommand = c => { }; public RewardApiCalls(CoreInterface ci, RunningContainer botContainer) { - this.ci = ci; - this.botContainer = botContainer; + monitor = new ContainerFileMonitor(ci, botContainer, "/app/datapath/logs/discordbot.log"); } public void Start(Action onCommand) { - this.onCommand = onCommand; + monitor.Start(line => ParseLine(line, onCommand)); + } + + public void Stop() + { + monitor.Stop(); + } + + private void ParseLine(string line, Action onCommand) + { + try + { + var timestamp = line.Substring(0, 30); + if (commands.ContainsKey(timestamp)) return; + var json = line.Substring(31); + + var cmd = JsonConvert.DeserializeObject(json); + if (cmd != null) + { + commands.Add(timestamp, cmd); + onCommand(cmd); + } + } + catch + { + } + } + } + + public class RewarderLogMonitor + { + private readonly ContainerFileMonitor monitor; + private readonly Dictionary commands = new Dictionary(); + + public RewarderLogMonitor(CoreInterface ci, RunningContainer botContainer) + { + monitor = new ContainerFileMonitor(ci, botContainer, "/app/datapath/logs/testnetrewarder.log"); + } + + public void Start(Action onCommand) + { + monitor.Start(l => ProcessLine(l, onCommand)); + } + + public void Stop() + { + monitor.Stop(); + } + + private void ProcessLine(string line, Action log) + { + // Processing chain state: ' + // NewRequests: [] + // FulfilledE: [] + // CancelledE: [] + // FilledE: [] + // FreedE: [] + // Historic: [{"Request":{"RequestId":"hvWlli4gHP5tUJoG5b8zh3tMBIr0wpcr/lHgIU/KRmU=","ClientAddress":{"Address":"0x760e722469cdeb086b78edd2b4b670621f43a923"},"Client":"0x760e722469Cdeb086b78EdD2b4b670621F43a923","Ask":{"Slots":4,"SlotSize":33554432,"Duration":360,"ProofProbability":5,"Reward":2,"Collateral":10,"MaxSlotLoss":2},"Content":{"Cid":"zDvZRwzm8DzbqdUcy7gS1wFKAsBjUHTQEUt1am1L6XGEWtsJ5U2X","MerkleRoot":"/KjFr+FezQ8m3huhdaPDy6zVgZs3ODtfE78K5eyieQo="},"Expiry":300,"Nonce":"2tANtfgVcMBuLLxe+4MbxMFWWO36Yv+l2yC4tpqdelI="},"Hosts":[{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"}],"State":"New"}] + // ' + + if (!line.Contains("Processing chain state: ")) return; + + log(Between(line, "'")); + //var tokens = state.Split("Historic: ", StringSplitOptions.RemoveEmptyEntries); + //var historics = JsonConvert.DeserializeObject(tokens[1]); + + //log(tokens[0] + " historic: " + string.Join(",", historics!.Select(h => h.Request.RequestId + " = " + h.State))); + } + + private string Between(string s, string lim) + { + var start = s.IndexOf(lim) + lim.Length; + var end = s.LastIndexOf(lim); + return s.Substring(start, end - start); + } + } + + public class ContainerFileMonitor + { + private readonly CoreInterface ci; + private readonly RunningContainer botContainer; + private readonly string filePath; + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + private readonly List seenLines = new List(); + private Task worker = Task.CompletedTask; + private Action onNewLine = c => { }; + + public ContainerFileMonitor(CoreInterface ci, RunningContainer botContainer, string filePath) + { + this.ci = ci; + this.botContainer = botContainer; + this.filePath = filePath; + } + + public void Start(Action onNewLine) + { + this.onNewLine = onNewLine; worker = Task.Run(Worker); } @@ -276,32 +369,16 @@ namespace CodexTests.UtilityTests Thread.Sleep(TimeSpan.FromSeconds(10)); if (cts.IsCancellationRequested) return; - var botLog = ci.ExecuteContainerCommand(botContainer, "cat", "/app/datapath/logs/discordbot.log"); + var botLog = ci.ExecuteContainerCommand(botContainer, "cat", filePath); var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { - AddToCache(line); - } - } - - private void AddToCache(string line) - { - try - { - var timestamp = line.Substring(0, 30); - if (commands.ContainsKey(timestamp)) return; - var json = line.Substring(31); - - var cmd = JsonConvert.DeserializeObject(json); - if (cmd != null) + if (!seenLines.Contains(line)) { - commands.Add(timestamp, cmd); - onCommand(cmd); + seenLines.Add(line); + onNewLine(line); } } - catch - { - } } } From 12dc7efd5b8fd14756728e77432e14069ac4f810 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 30 May 2024 11:17:13 +0200 Subject: [PATCH 085/142] wip --- .../UtilityTests/DiscordBotTests.cs | 35 +++++++++--------- Tools/TestNetRewarder/ChainState.cs | 36 +++++++++++++++---- Tools/TestNetRewarder/HistoricState.cs | 9 +++++ Tools/TestNetRewarder/Processor.cs | 2 +- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 29403eb2..e9dba306 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; using CodexDiscordBotPlugin; using CodexPlugin; using Core; @@ -8,6 +9,7 @@ using KubernetesWorkflow.Types; using Logging; using Newtonsoft.Json; using NUnit.Framework; +using TestNetRewarder; using Utils; namespace CodexTests.UtilityTests @@ -301,28 +303,29 @@ namespace CodexTests.UtilityTests private void ProcessLine(string line, Action log) { - // Processing chain state: ' - // NewRequests: [] - // FulfilledE: [] - // CancelledE: [] - // FilledE: [] - // FreedE: [] - // Historic: [{"Request":{"RequestId":"hvWlli4gHP5tUJoG5b8zh3tMBIr0wpcr/lHgIU/KRmU=","ClientAddress":{"Address":"0x760e722469cdeb086b78edd2b4b670621f43a923"},"Client":"0x760e722469Cdeb086b78EdD2b4b670621F43a923","Ask":{"Slots":4,"SlotSize":33554432,"Duration":360,"ProofProbability":5,"Reward":2,"Collateral":10,"MaxSlotLoss":2},"Content":{"Cid":"zDvZRwzm8DzbqdUcy7gS1wFKAsBjUHTQEUt1am1L6XGEWtsJ5U2X","MerkleRoot":"/KjFr+FezQ8m3huhdaPDy6zVgZs3ODtfE78K5eyieQo="},"Expiry":300,"Nonce":"2tANtfgVcMBuLLxe+4MbxMFWWO36Yv+l2yC4tpqdelI="},"Hosts":[{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"},{"Address":"0x0000000000000000000000000000000000000000"}],"State":"New"}] - // ' + //$"ChainState=[{JsonConvert.SerializeObject(this)}]" + + //$"HistoricState=[{historicState.EntireString()}]"; - if (!line.Contains("Processing chain state: ")) return; + var stateOpenTag = "ChainState=["; + var historicOpenTag = "]HistoricState=["; - log(Between(line, "'")); - //var tokens = state.Split("Historic: ", StringSplitOptions.RemoveEmptyEntries); - //var historics = JsonConvert.DeserializeObject(tokens[1]); + if (!line.Contains(stateOpenTag)) return; + if (!line.Contains(historicOpenTag)) return; - //log(tokens[0] + " historic: " + string.Join(",", historics!.Select(h => h.Request.RequestId + " = " + h.State))); + var stateStr = Between(line, stateOpenTag, historicOpenTag); + var historicStr = Between(line, historicOpenTag, "]"); + + var chainState = JsonConvert.DeserializeObject(stateStr); + var historicState = JsonConvert.DeserializeObject(historicStr)!; + chainState!.Set(new HistoricState(historicState)); + + log(string.Join(",", chainState!.GenerateOverview())); } - private string Between(string s, string lim) + private string Between(string s, string open, string close) { - var start = s.IndexOf(lim) + lim.Length; - var end = s.LastIndexOf(lim); + var start = s.IndexOf(open) + open.Length; + var end = s.LastIndexOf(close); return s.Substring(start, end - start); } } diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index 50b25017..50fa87dd 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -8,7 +8,7 @@ namespace TestNetRewarder { public class ChainState { - private readonly HistoricState historicState; + private HistoricState historicState; private readonly string[] colorIcons = new[] { "🔴", @@ -50,9 +50,30 @@ namespace TestNetRewarder SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); } + public ChainState( + Request[] newRequests, + RequestFulfilledEventDTO[] requestFulfilledEvents, + RequestCancelledEventDTO[] requestCancelledEvents, + SlotFilledEventDTO[] slotFilledEvents, + SlotFreedEventDTO[] slotFreedEvents) + { + NewRequests = newRequests; + RequestFulfilledEvents = requestFulfilledEvents; + RequestCancelledEvents = requestCancelledEvents; + SlotFilledEvents = slotFilledEvents; + SlotFreedEvents = slotFreedEvents; + + historicState = new HistoricState(); + StartedRequests = Array.Empty(); + FinishedRequests = Array.Empty(); + } + public Request[] NewRequests { get; } + [JsonIgnore] public StorageRequest[] AllRequests => historicState.StorageRequests; + [JsonIgnore] public StorageRequest[] StartedRequests { get; private set; } + [JsonIgnore] public StorageRequest[] FinishedRequests { get; private set; } public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; } public RequestCancelledEventDTO[] RequestCancelledEvents { get; } @@ -62,12 +83,13 @@ namespace TestNetRewarder public string EntireString() { return - $"NewRequests: {JsonConvert.SerializeObject(NewRequests)}" + - $"FulfilledE: {JsonConvert.SerializeObject(RequestFulfilledEvents)}" + - $"CancelledE: {JsonConvert.SerializeObject(RequestCancelledEvents)}" + - $"FilledE: {JsonConvert.SerializeObject(SlotFilledEvents)}" + - $"FreedE: {JsonConvert.SerializeObject(SlotFreedEvents)}" + - $"Historic: {historicState.EntireString()}"; + $"ChainState=[{JsonConvert.SerializeObject(this)}]" + + $"HistoricState=[{historicState.EntireString()}]"; + } + + public void Set(HistoricState h) + { + historicState = h; } public string[] GenerateOverview() diff --git a/Tools/TestNetRewarder/HistoricState.cs b/Tools/TestNetRewarder/HistoricState.cs index 3ae58820..8487aa06 100644 --- a/Tools/TestNetRewarder/HistoricState.cs +++ b/Tools/TestNetRewarder/HistoricState.cs @@ -34,6 +34,15 @@ namespace TestNetRewarder { return JsonConvert.SerializeObject(StorageRequests); } + + public HistoricState() + { + } + + public HistoricState(StorageRequest[] requests) + { + storageRequests.AddRange(requests); + } } public class StorageRequest diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index a756b937..678d4f84 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -58,7 +58,7 @@ namespace TestNetRewarder private async Task ProcessChainState(ChainState chainState) { - log.Log($"Processing chain state: '{chainState.EntireString()}'"); + log.Log(chainState.EntireString()); var outgoingRewards = new List(); foreach (var reward in rewardRepo.Rewards) From f1d453251cb532c30c2bb0ed70f6e7c718776952 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 30 May 2024 11:33:16 +0200 Subject: [PATCH 086/142] cleanup --- .../RewarderBotContainerRecipe.cs | 2 +- .../CodexPlugin/MarketplaceAccess.cs | 134 +---------------- .../CodexPlugin/StoragePurchaseContract.cs | 140 ++++++++++++++++++ .../UtilityTests/DiscordBotTests.cs | 2 +- 4 files changed, 145 insertions(+), 133 deletions(-) create mode 100644 ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index 8bf4344d..55b60a23 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin public class RewarderBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-rewarder"; - public override string Image => "codexstorage/codex-rewarderbot:sha-ccc6c81"; + public override string Image => "codexstorage/codex-rewarderbot:sha-12dc7ef"; protected override void Initialize(StartupConfig startupConfig) { diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index 8944d95c..11f3b60e 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -1,5 +1,4 @@ using Logging; -using Newtonsoft.Json; using Utils; namespace CodexPlugin @@ -7,7 +6,7 @@ namespace CodexPlugin public interface IMarketplaceAccess { string MakeStorageAvailable(StorageAvailability availability); - StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase); + IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase); } public class MarketplaceAccess : IMarketplaceAccess @@ -21,7 +20,7 @@ namespace CodexPlugin this.codexAccess = codexAccess; } - public StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) + public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) { purchase.Log(log); @@ -68,7 +67,7 @@ namespace CodexPlugin throw new NotImplementedException(); } - public StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) + public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) { Unavailable(); throw new NotImplementedException(); @@ -80,131 +79,4 @@ namespace CodexPlugin throw new InvalidOperationException(); } } - - public class StoragePurchaseContract - { - private readonly ILog log; - private readonly CodexAccess codexAccess; - private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); - private readonly DateTime contractPendingUtc = DateTime.UtcNow; - private DateTime? contractSubmittedUtc = DateTime.UtcNow; - private DateTime? contractStartedUtc; - private DateTime? contractFinishedUtc; - - public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) - { - this.log = log; - this.codexAccess = codexAccess; - PurchaseId = purchaseId; - Purchase = purchase; - } - - public string PurchaseId { get; } - public StoragePurchaseRequest Purchase { get; } - - public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc; - public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc; - public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc; - - public void WaitForStorageContractSubmitted() - { - WaitForStorageContractState(gracePeriod, "submitted", sleep: 200); - contractSubmittedUtc = DateTime.UtcNow; - LogSubmittedDuration(); - AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted)); - } - - public void WaitForStorageContractStarted() - { - var timeout = Purchase.Expiry + gracePeriod; - - WaitForStorageContractState(timeout, "started"); - contractStartedUtc = DateTime.UtcNow; - LogStartedDuration(); - AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); - } - - public void WaitForStorageContractFinished() - { - if (!contractStartedUtc.HasValue) - { - WaitForStorageContractStarted(); - } - var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value; - var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; - WaitForStorageContractState(timeout, "finished"); - contractFinishedUtc = DateTime.UtcNow; - LogFinishedDuration(); - AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); - } - - public StoragePurchase GetPurchaseStatus(string purchaseId) - { - return codexAccess.GetPurchaseStatus(purchaseId); - } - - private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000) - { - var lastState = ""; - var waitStart = DateTime.UtcNow; - - Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'."); - while (lastState != desiredState) - { - var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId); - var statusJson = JsonConvert.SerializeObject(purchaseStatus); - if (purchaseStatus != null && purchaseStatus.State != lastState) - { - lastState = purchaseStatus.State; - log.Debug("Purchase status: " + statusJson); - } - - Thread.Sleep(sleep); - - if (lastState == "errored") - { - FrameworkAssert.Fail("Contract errored: " + statusJson); - } - - if (DateTime.UtcNow - waitStart > timeout) - { - FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}"); - } - } - } - - private void LogSubmittedDuration() - { - Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " + - $"( < {Time.FormatDuration(gracePeriod)})"); - } - - private void LogStartedDuration() - { - Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " + - $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})"); - } - - private void LogFinishedDuration() - { - Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " + - $"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})"); - } - - private void AssertDuration(TimeSpan? span, TimeSpan max, string message) - { - if (span == null) throw new ArgumentNullException(nameof(MarketplaceAccess) + ": " + message + " (IsNull)"); - if (span.Value.TotalDays >= max.TotalSeconds) - { - throw new Exception(nameof(MarketplaceAccess) + - $": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " + - message); - } - } - - private void Log(string msg) - { - log.Log($"[{PurchaseId}] {msg}"); - } - } } diff --git a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs new file mode 100644 index 00000000..3a9e0b92 --- /dev/null +++ b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs @@ -0,0 +1,140 @@ +using Logging; +using Newtonsoft.Json; +using Utils; + +namespace CodexPlugin +{ + public interface IStoragePurchaseContract + { + void WaitForStorageContractSubmitted(); + void WaitForStorageContractStarted(); + void WaitForStorageContractFinished(); + } + + public class StoragePurchaseContract : IStoragePurchaseContract + { + private readonly ILog log; + private readonly CodexAccess codexAccess; + private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); + private readonly DateTime contractPendingUtc = DateTime.UtcNow; + private DateTime? contractSubmittedUtc = DateTime.UtcNow; + private DateTime? contractStartedUtc; + private DateTime? contractFinishedUtc; + + public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) + { + this.log = log; + this.codexAccess = codexAccess; + PurchaseId = purchaseId; + Purchase = purchase; + } + + public string PurchaseId { get; } + public StoragePurchaseRequest Purchase { get; } + + public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc; + public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc; + public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc; + + public void WaitForStorageContractSubmitted() + { + WaitForStorageContractState(gracePeriod, "submitted", sleep: 200); + contractSubmittedUtc = DateTime.UtcNow; + LogSubmittedDuration(); + AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted)); + } + + public void WaitForStorageContractStarted() + { + var timeout = Purchase.Expiry + gracePeriod; + + WaitForStorageContractState(timeout, "started"); + contractStartedUtc = DateTime.UtcNow; + LogStartedDuration(); + AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); + } + + public void WaitForStorageContractFinished() + { + if (!contractStartedUtc.HasValue) + { + WaitForStorageContractStarted(); + } + var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value; + var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; + WaitForStorageContractState(timeout, "finished"); + contractFinishedUtc = DateTime.UtcNow; + LogFinishedDuration(); + AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); + } + + public StoragePurchase GetPurchaseStatus(string purchaseId) + { + return codexAccess.GetPurchaseStatus(purchaseId); + } + + private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000) + { + var lastState = ""; + var waitStart = DateTime.UtcNow; + + Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'."); + while (lastState != desiredState) + { + var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId); + var statusJson = JsonConvert.SerializeObject(purchaseStatus); + if (purchaseStatus != null && purchaseStatus.State != lastState) + { + lastState = purchaseStatus.State; + log.Debug("Purchase status: " + statusJson); + } + + Thread.Sleep(sleep); + + if (lastState == "errored") + { + FrameworkAssert.Fail("Contract errored: " + statusJson); + } + + if (DateTime.UtcNow - waitStart > timeout) + { + FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}"); + } + } + } + + private void LogSubmittedDuration() + { + Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " + + $"( < {Time.FormatDuration(gracePeriod)})"); + } + + private void LogStartedDuration() + { + Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " + + $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})"); + } + + private void LogFinishedDuration() + { + Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " + + $"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})"); + } + + private void AssertDuration(TimeSpan? span, TimeSpan max, string message) + { + if (span == null) throw new ArgumentNullException(nameof(MarketplaceAccess) + ": " + message + " (IsNull)"); + if (span.Value.TotalDays >= max.TotalSeconds) + { + throw new Exception(nameof(MarketplaceAccess) + + $": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " + + message); + } + } + + private void Log(string msg) + { + log.Log($"[{PurchaseId}] {msg}"); + } + } +} diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index e9dba306..4a567781 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -92,7 +92,7 @@ namespace CodexTests.UtilityTests } } - private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) + private IStoragePurchaseContract ClientPurchasesStorage(ICodexNode client) { var testFile = GenerateTestFile(GetMinFileSize()); var contentId = client.UploadFile(testFile); From f94a67adb4463d96f75059d9a39ad76104e03a02 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 31 May 2024 10:06:34 +0200 Subject: [PATCH 087/142] Extracts event methods from CodexContractsAccess --- .../ChainMonitor/ChainEvents.cs | 7 ++ .../ChainMonitor/ChainState.cs | 7 ++ .../CodexContractsAccess.cs | 79 +------------ .../CodexContractsEvents.cs | 106 ++++++++++++++++++ .../CodexTests/BasicTests/MarketplaceTests.cs | 7 +- .../UtilityTests/DiscordBotTests.cs | 11 +- Tools/TestNetRewarder/ChainState.cs | 22 ++-- 7 files changed, 148 insertions(+), 91 deletions(-) create mode 100644 ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs create mode 100644 ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs create mode 100644 ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs new file mode 100644 index 00000000..b7a6e24b --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs @@ -0,0 +1,7 @@ +namespace CodexContractsPlugin.ChainMonitor +{ + public class ChainEvents + { + + } +} diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs new file mode 100644 index 00000000..6dc95f1c --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -0,0 +1,7 @@ +namespace CodexContractsPlugin.ChainMonitor +{ + public class ChainState + { + + } +} diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index 197d4f99..58fbb29b 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -2,10 +2,8 @@ using GethPlugin; using Logging; using Nethereum.ABI; -using Nethereum.Hex.HexTypes; using Nethereum.Util; using NethereumWorkflow; -using NethereumWorkflow.BlockUtils; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Utils; @@ -22,13 +20,10 @@ namespace CodexContractsPlugin TestToken GetTestTokenBalance(IHasEthAddress owner); TestToken GetTestTokenBalance(EthAddress ethAddress); - Request[] GetStorageRequests(BlockInterval blockRange); + ICodexContractsEvents GetEvents(TimeRange timeRange); + ICodexContractsEvents GetEvents(BlockInterval blockInterval); EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex); RequestState GetRequestState(Request request); - RequestFulfilledEventDTO[] GetRequestFulfilledEvents(BlockInterval blockRange); - RequestCancelledEventDTO[] GetRequestCancelledEvents(BlockInterval blockRange); - SlotFilledEventDTO[] GetSlotFilledEvents(BlockInterval blockRange); - SlotFreedEventDTO[] GetSlotFreedEvents(BlockInterval blockRange); } [JsonConverter(typeof(StringEnumConverter))] @@ -81,65 +76,14 @@ namespace CodexContractsPlugin return balance.TstWei(); } - public Request[] GetStorageRequests(BlockInterval blockRange) + public ICodexContractsEvents GetEvents(TimeRange timeRange) { - var events = gethNode.GetEvents(Deployment.MarketplaceAddress, blockRange); - var i = StartInteraction(); - return events - .Select(e => - { - var requestEvent = i.GetRequest(Deployment.MarketplaceAddress, e.Event.RequestId); - var result = requestEvent.ReturnValue1; - result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); - result.RequestId = e.Event.RequestId; - return result; - }) - .ToArray(); + return GetEvents(gethNode.ConvertTimeRangeToBlockRange(timeRange)); } - public RequestFulfilledEventDTO[] GetRequestFulfilledEvents(BlockInterval blockRange) + public ICodexContractsEvents GetEvents(BlockInterval blockInterval) { - var events = gethNode.GetEvents(Deployment.MarketplaceAddress, blockRange); - return events.Select(e => - { - var result = e.Event; - result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); - return result; - }).ToArray(); - } - - public RequestCancelledEventDTO[] GetRequestCancelledEvents(BlockInterval blockRange) - { - var events = gethNode.GetEvents(Deployment.MarketplaceAddress, blockRange); - return events.Select(e => - { - var result = e.Event; - result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); - return result; - }).ToArray(); - } - - public SlotFilledEventDTO[] GetSlotFilledEvents(BlockInterval blockRange) - { - var events = gethNode.GetEvents(Deployment.MarketplaceAddress, blockRange); - return events.Select(e => - { - var result = e.Event; - result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); - result.Host = GetEthAddressFromTransaction(e.Log.TransactionHash); - return result; - }).ToArray(); - } - - public SlotFreedEventDTO[] GetSlotFreedEvents(BlockInterval blockRange) - { - var events = gethNode.GetEvents(Deployment.MarketplaceAddress, blockRange); - return events.Select(e => - { - var result = e.Event; - result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); - return result; - }).ToArray(); + return new CodexContractsEvents(log, gethNode, Deployment, blockInterval); } public EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex) @@ -170,17 +114,6 @@ namespace CodexContractsPlugin return gethNode.Call(Deployment.MarketplaceAddress, func); } - private BlockTimeEntry GetBlock(ulong number) - { - return gethNode.GetBlockForNumber(number); - } - - private EthAddress GetEthAddressFromTransaction(string transactionHash) - { - var transaction = gethNode.GetTransaction(transactionHash); - return new EthAddress(transaction.From); - } - private ContractInteractions StartInteraction() { return new ContractInteractions(log, gethNode); diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs new file mode 100644 index 00000000..4b2c2f6d --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -0,0 +1,106 @@ +using CodexContractsPlugin.Marketplace; +using GethPlugin; +using Logging; +using Nethereum.Hex.HexTypes; +using NethereumWorkflow.BlockUtils; +using Utils; + +namespace CodexContractsPlugin +{ + public interface ICodexContractsEvents + { + Request[] GetStorageRequests(); + RequestFulfilledEventDTO[] GetRequestFulfilledEvents(); + RequestCancelledEventDTO[] GetRequestCancelledEvents(); + SlotFilledEventDTO[] GetSlotFilledEvents(); + SlotFreedEventDTO[] GetSlotFreedEvents(); + } + + public class CodexContractsEvents : ICodexContractsEvents + { + private readonly ILog log; + private readonly IGethNode gethNode; + private readonly CodexContractsDeployment deployment; + private readonly BlockInterval blockInterval; + + public CodexContractsEvents(ILog log, IGethNode gethNode, CodexContractsDeployment deployment, BlockInterval blockInterval) + { + this.log = log; + this.gethNode = gethNode; + this.deployment = deployment; + this.blockInterval = blockInterval; + } + + public Request[] GetStorageRequests() + { + var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var i = new ContractInteractions(log, gethNode); + return events + .Select(e => + { + var requestEvent = i.GetRequest(deployment.MarketplaceAddress, e.Event.RequestId); + var result = requestEvent.ReturnValue1; + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); + result.RequestId = e.Event.RequestId; + return result; + }) + .ToArray(); + } + + public RequestFulfilledEventDTO[] GetRequestFulfilledEvents() + { + var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + return events.Select(e => + { + var result = e.Event; + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); + return result; + }).ToArray(); + } + + public RequestCancelledEventDTO[] GetRequestCancelledEvents() + { + var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + return events.Select(e => + { + var result = e.Event; + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); + return result; + }).ToArray(); + } + + public SlotFilledEventDTO[] GetSlotFilledEvents() + { + var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + return events.Select(e => + { + var result = e.Event; + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); + result.Host = GetEthAddressFromTransaction(e.Log.TransactionHash); + return result; + }).ToArray(); + } + + public SlotFreedEventDTO[] GetSlotFreedEvents() + { + var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + return events.Select(e => + { + var result = e.Event; + result.Block = GetBlock(e.Log.BlockNumber.ToUlong()); + return result; + }).ToArray(); + } + + private BlockTimeEntry GetBlock(ulong number) + { + return gethNode.GetBlockForNumber(number); + } + + private EthAddress GetEthAddressFromTransaction(string transactionHash) + { + var transaction = gethNode.GetTransaction(transactionHash); + return new EthAddress(transaction.From); + } + } +} diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 2ecc3254..bd3ce419 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -89,8 +89,8 @@ namespace CodexTests.BasicTests { Time.Retry(() => { - var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()); - var slotFilledEvents = contracts.GetSlotFilledEvents(blockRange); + var events = contracts.GetEvents(GetTestRunTimeRange()); + var slotFilledEvents = events.GetSlotFilledEvents(); Debug($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); @@ -107,7 +107,8 @@ namespace CodexTests.BasicTests private Request GetOnChainStorageRequest(ICodexContracts contracts, IGethNode geth) { - var requests = contracts.GetStorageRequests(geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange())); + var events = contracts.GetEvents(GetTestRunTimeRange()); + var requests = events.GetStorageRequests(); Assert.That(requests.Length, Is.EqualTo(1)); return requests.Single(); } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 4a567781..998699f2 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -434,11 +434,12 @@ namespace CodexTests.UtilityTests var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop)); - LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range); - LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range); - LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range); - LogEvents(nameof(contracts.GetSlotFilledEvents), contracts.GetSlotFilledEvents, range); - LogEvents(nameof(contracts.GetSlotFreedEvents), contracts.GetSlotFreedEvents, range); + throw new Exception(); + //LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range); + //LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range); + //LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range); + //LogEvents(nameof(contracts.GetSlotFilledEvents), contracts.GetSlotFilledEvents, range); + //LogEvents(nameof(contracts.GetSlotFreedEvents), contracts.GetSlotFreedEvents, range); } private void LogEvents(string n, Func f, BlockInterval r) diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index 50fa87dd..cef08082 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -37,17 +37,19 @@ namespace TestNetRewarder { this.historicState = historicState; - NewRequests = contracts.GetStorageRequests(blockRange); - historicState.CleanUpOldRequests(); - historicState.ProcessNewRequests(NewRequests); - historicState.UpdateStorageRequests(contracts); + throw new Exception("This is getting a rewrite"); - StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); - FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); - RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); - RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); - SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); - SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); + //NewRequests = contracts.GetStorageRequests(blockRange); + //historicState.CleanUpOldRequests(); + //historicState.ProcessNewRequests(NewRequests); + //historicState.UpdateStorageRequests(contracts); + + //StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); + //FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); + //RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); + //RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); + //SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); + //SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); } public ChainState( From f5da80dc9c4113fc3ddd6d3f5cfe0b01c978cf38 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 31 May 2024 10:16:57 +0200 Subject: [PATCH 088/142] Sets up ChainEvents object --- .../NethereumWorkflow/NethereumInteraction.cs | 1 + Framework/Utils/BlockInterval.cs | 4 +- .../ChainMonitor/ChainEvents.cs | 49 ++++++++++++++++++- .../CodexContractsEvents.cs | 16 +++--- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index f965c35a..01f26bf5 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -117,6 +117,7 @@ namespace NethereumWorkflow } return new BlockInterval( + timeRange: timeRange, from: fromBlock.Value, to: toBlock.Value ); diff --git a/Framework/Utils/BlockInterval.cs b/Framework/Utils/BlockInterval.cs index 79229bfa..2daa8f24 100644 --- a/Framework/Utils/BlockInterval.cs +++ b/Framework/Utils/BlockInterval.cs @@ -2,7 +2,7 @@ { public class BlockInterval { - public BlockInterval(ulong from, ulong to) + public BlockInterval(TimeRange timeRange, ulong from, ulong to) { if (from < to) { @@ -14,10 +14,12 @@ From = to; To = from; } + TimeRange = timeRange; } public ulong From { get; } public ulong To { get; } + public TimeRange TimeRange { get; } public override string ToString() { diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs index b7a6e24b..51a84d07 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs @@ -1,7 +1,54 @@ -namespace CodexContractsPlugin.ChainMonitor +using CodexContractsPlugin.Marketplace; +using Utils; + +namespace CodexContractsPlugin.ChainMonitor { public class ChainEvents { + private ChainEvents( + BlockInterval blockInterval, + Request[] requests, + RequestFulfilledEventDTO[] fulfilled, + RequestCancelledEventDTO[] cancelled, + SlotFilledEventDTO[] slotFilled, + SlotFreedEventDTO[] slotFreed + ) + { + BlockInterval = blockInterval; + Requests = requests; + Fulfilled = fulfilled; + Cancelled = cancelled; + SlotFilled = slotFilled; + SlotFreed = slotFreed; + } + public BlockInterval BlockInterval { get; } + public Request[] Requests { get; } + public RequestFulfilledEventDTO[] Fulfilled { get; } + public RequestCancelledEventDTO[] Cancelled { get; } + public SlotFilledEventDTO[] SlotFilled { get; } + public SlotFreedEventDTO[] SlotFreed { get; } + + public static ChainEvents FromBlockInterval(ICodexContracts contracts, BlockInterval blockInterval) + { + return FromContractEvents(contracts.GetEvents(blockInterval)); + } + + public static ChainEvents FromTimeRange(ICodexContracts contracts, TimeRange timeRange) + { + return FromContractEvents(contracts.GetEvents(timeRange)); + } + + public static ChainEvents FromContractEvents(ICodexContractsEvents events) + { + return new ChainEvents( + events.BlockInterval, + events.GetStorageRequests(), + events.GetRequestFulfilledEvents(), + events.GetRequestCancelledEvents(), + events.GetSlotFilledEvents(), + events.GetSlotFreedEvents() + ); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs index 4b2c2f6d..cf7b2d74 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -9,6 +9,7 @@ namespace CodexContractsPlugin { public interface ICodexContractsEvents { + BlockInterval BlockInterval { get; } Request[] GetStorageRequests(); RequestFulfilledEventDTO[] GetRequestFulfilledEvents(); RequestCancelledEventDTO[] GetRequestCancelledEvents(); @@ -21,19 +22,20 @@ namespace CodexContractsPlugin private readonly ILog log; private readonly IGethNode gethNode; private readonly CodexContractsDeployment deployment; - private readonly BlockInterval blockInterval; public CodexContractsEvents(ILog log, IGethNode gethNode, CodexContractsDeployment deployment, BlockInterval blockInterval) { this.log = log; this.gethNode = gethNode; this.deployment = deployment; - this.blockInterval = blockInterval; + BlockInterval = blockInterval; } + + public BlockInterval BlockInterval { get; } public Request[] GetStorageRequests() { - var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); var i = new ContractInteractions(log, gethNode); return events .Select(e => @@ -49,7 +51,7 @@ namespace CodexContractsPlugin public RequestFulfilledEventDTO[] GetRequestFulfilledEvents() { - var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); return events.Select(e => { var result = e.Event; @@ -60,7 +62,7 @@ namespace CodexContractsPlugin public RequestCancelledEventDTO[] GetRequestCancelledEvents() { - var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); return events.Select(e => { var result = e.Event; @@ -71,7 +73,7 @@ namespace CodexContractsPlugin public SlotFilledEventDTO[] GetSlotFilledEvents() { - var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); return events.Select(e => { var result = e.Event; @@ -83,7 +85,7 @@ namespace CodexContractsPlugin public SlotFreedEventDTO[] GetSlotFreedEvents() { - var events = gethNode.GetEvents(deployment.MarketplaceAddress, blockInterval); + var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); return events.Select(e => { var result = e.Event; From a846d51c0c75a906ffb8d01cc1bac727271fdf7d Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 31 May 2024 11:19:50 +0200 Subject: [PATCH 089/142] Implements ChainState in CodexContracts plugin --- Framework/Utils/BlockInterval.cs | 1 + .../ChainMonitor/ChainEvents.cs | 14 ++ .../ChainMonitor/ChainState.cs | 130 +++++++++++++++++- .../ChainMonitor/ChainStateRequest.cs | 41 ++++++ .../Marketplace/Customizations.cs | 15 +- 5 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs diff --git a/Framework/Utils/BlockInterval.cs b/Framework/Utils/BlockInterval.cs index 2daa8f24..64430488 100644 --- a/Framework/Utils/BlockInterval.cs +++ b/Framework/Utils/BlockInterval.cs @@ -20,6 +20,7 @@ public ulong From { get; } public ulong To { get; } public TimeRange TimeRange { get; } + public ulong NumberOfBlocks => To - From; public override string ToString() { diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs index 51a84d07..25d08513 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs @@ -29,6 +29,20 @@ namespace CodexContractsPlugin.ChainMonitor public SlotFilledEventDTO[] SlotFilled { get; } public SlotFreedEventDTO[] SlotFreed { get; } + public IHasBlock[] All + { + get + { + var all = new List(); + all.AddRange(Requests); + all.AddRange(Fulfilled); + all.AddRange(Cancelled); + all.AddRange(SlotFilled); + all.AddRange(SlotFreed); + return all.ToArray(); + } + } + public static ChainEvents FromBlockInterval(ICodexContracts contracts, BlockInterval blockInterval) { return FromContractEvents(contracts.GetEvents(blockInterval)); diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 6dc95f1c..b3870dcf 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -1,7 +1,135 @@ -namespace CodexContractsPlugin.ChainMonitor +using CodexContractsPlugin.Marketplace; +using Logging; +using System.Numerics; +using Utils; + +namespace CodexContractsPlugin.ChainMonitor { + public interface IChainStateChangeHandler + { + void OnNewRequest(IChainStateRequest request); + void OnRequestStarted(IChainStateRequest request); + void OnRequestFinished(IChainStateRequest request); + void OnRequestFulfilled(IChainStateRequest request); + void OnRequestCancelled(IChainStateRequest request); + void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex); + void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex); + } + public class ChainState { + private readonly List requests = new List(); + private readonly ILog log; + private readonly IChainStateChangeHandler handler; + private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) + { + this.log = log; + handler = changeHandler; + TotalSpan = timeRange; + } + + public static ChainState FromEvents(ILog log, ChainEvents events, IChainStateChangeHandler changeHandler) + { + var state = new ChainState(log, changeHandler, events.BlockInterval.TimeRange); + state.Apply(events); + return state; + } + + public TimeRange TotalSpan { get; private set; } + public IChainStateRequest[] Requests => requests.ToArray(); + + public void Apply(ChainEvents events) + { + if (events.BlockInterval.TimeRange.From < TotalSpan.From) + throw new Exception("Attempt to update ChainState with set of events from before its current record."); + + log.Log($"ChainState updating: {events.BlockInterval}"); + + // Run through each block and apply the events to the state in order. + var span = events.BlockInterval.TimeRange.Duration; + var numBlocks = events.BlockInterval.NumberOfBlocks; + var spanPerBlock = span / numBlocks; + + var eventUtc = events.BlockInterval.TimeRange.From; + for (var b = events.BlockInterval.From; b < events.BlockInterval.To; b++) + { + var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray(); + ApplyEvents(blockEvents, eventUtc); + + eventUtc += spanPerBlock; + } + } + + private void ApplyEvents(IHasBlock[] blockEvents, DateTime eventsUtc) + { + foreach (var e in blockEvents) + { + dynamic d = e; + ApplyEvent(d); + } + + ApplyTimeImplicitEvents(eventsUtc); + } + + private void ApplyEvent(Request request) + { + if (requests.Any(r => Equal(r.Request.RequestId, request.RequestId))) + throw new Exception("Received NewRequest event for id that already exists."); + + var newRequest = new ChainStateRequest(log, request, RequestState.New); + requests.Add(newRequest); + + handler.OnNewRequest(newRequest); + } + + private void ApplyEvent(RequestFulfilledEventDTO request) + { + var r = FindRequest(request.RequestId); + r.UpdateState(RequestState.Started); + handler.OnRequestFulfilled(r); + } + + private void ApplyEvent(RequestCancelledEventDTO request) + { + var r = FindRequest(request.RequestId); + r.UpdateState(RequestState.Cancelled); + handler.OnRequestCancelled(r); + } + + private void ApplyEvent(SlotFilledEventDTO request) + { + var r = FindRequest(request.RequestId); + handler.OnSlotFilled(r, request.SlotIndex); + } + + private void ApplyEvent(SlotFreedEventDTO request) + { + var r = FindRequest(request.RequestId); + handler.OnSlotFreed(r, request.SlotIndex); + } + + private void ApplyTimeImplicitEvents(DateTime eventsUtc) + { + foreach (var r in requests) + { + if (r.State == RequestState.Started + && r.FinishedUtc < eventsUtc) + { + r.UpdateState(RequestState.Finished); + handler.OnRequestFinished(r); + } + } + } + + private ChainStateRequest FindRequest(byte[] requestId) + { + return requests.Single(r => Equal(r.Request.RequestId, requestId)); + } + + private bool Equal(byte[] a, byte[] b) + { + return a.SequenceEqual(b); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs new file mode 100644 index 00000000..ae735b4c --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -0,0 +1,41 @@ +using CodexContractsPlugin.Marketplace; +using Logging; + +namespace CodexContractsPlugin.ChainMonitor +{ + public interface IChainStateRequest + { + Request Request { get; } + RequestState State { get; } + DateTime ExpiryUtc { get; } + DateTime FinishedUtc { get; } + } + + public class ChainStateRequest : IChainStateRequest + { + private readonly ILog log; + + public ChainStateRequest(ILog log, Request request, RequestState state) + { + this.log = log; + Request = request; + State = state; + + ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); + FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); + + log.Log($"Request created as {State}."); + } + + public Request Request { get; } + public RequestState State { get; private set; } + public DateTime ExpiryUtc { get; } + public DateTime FinishedUtc { get; } + + public void UpdateState(RequestState newState) + { + log.Log($"Request transit: {State} -> {newState}"); + State = newState; + } + } +} diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 68427b60..5094e12b 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -5,7 +5,12 @@ using Newtonsoft.Json; namespace CodexContractsPlugin.Marketplace { - public partial class Request : RequestBase + public interface IHasBlock + { + BlockTimeEntry Block { get; set; } + } + + public partial class Request : RequestBase, IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } @@ -14,26 +19,26 @@ namespace CodexContractsPlugin.Marketplace public EthAddress ClientAddress { get { return new EthAddress(Client); } } } - public partial class RequestFulfilledEventDTO + public partial class RequestFulfilledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class RequestCancelledEventDTO + public partial class RequestCancelledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class SlotFilledEventDTO + public partial class SlotFilledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } public EthAddress Host { get; set; } } - public partial class SlotFreedEventDTO + public partial class SlotFreedEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } From ac7d32320145a9195520b43a67a5e2e2514a018d Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 3 Jun 2024 10:38:48 +0200 Subject: [PATCH 090/142] wip --- .../DoNothingChainEventHandler.cs | 35 ++++++++++++++ .../UtilityTests/DiscordBotTests.cs | 47 ++++++++++++------- 2 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs new file mode 100644 index 00000000..65e038b2 --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -0,0 +1,35 @@ +using System.Numerics; + +namespace CodexContractsPlugin.ChainMonitor +{ + public class DoNothingChainEventHandler : IChainStateChangeHandler + { + public void OnNewRequest(IChainStateRequest request) + { + } + + public void OnRequestCancelled(IChainStateRequest request) + { + } + + public void OnRequestFinished(IChainStateRequest request) + { + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + } + + public void OnRequestStarted(IChainStateRequest request) + { + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + } + } +} diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 998699f2..c73cd23d 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; using CodexDiscordBotPlugin; using CodexPlugin; @@ -43,6 +44,14 @@ namespace CodexTests.UtilityTests var client = StartClient(geth, contracts); + + var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); + var chainState = CodexContractsPlugin.ChainMonitor.ChainState.FromEvents( + GetTestLog(), + events, + new DoNothingChainEventHandler()); + + var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); var rewarderLog = new RewarderLogMonitor(Ci, rewarderContainer.Containers.Single()); @@ -59,6 +68,8 @@ namespace CodexTests.UtilityTests Thread.Sleep(rewarderInterval * 2); + chainState.Apply(ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange())); + Log("Seen:"); foreach (var seen in rewardsSeen) { @@ -78,8 +89,8 @@ namespace CodexTests.UtilityTests private void OnCommand(GiveRewardsCommand call) { - if (call.Averages.Any()) Log($"{call.Averages.Length} average."); - if (call.EventsOverview.Any()) Log($"{call.EventsOverview.Length} events."); + if (call.Averages.Any()) Log($"API call: {call.Averages.Length} average."); + if (call.EventsOverview.Any()) Log($"API call: {call.EventsOverview.Length} events."); foreach (var r in call.Rewards) { var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); @@ -87,7 +98,7 @@ namespace CodexTests.UtilityTests foreach (var address in r.UserAddresses) { var user = IdentifyAccount(address); - Log(user + ": " + reward.Message); + Log("API call: " + user + ": " + reward.Message); } } } @@ -306,20 +317,20 @@ namespace CodexTests.UtilityTests //$"ChainState=[{JsonConvert.SerializeObject(this)}]" + //$"HistoricState=[{historicState.EntireString()}]"; - var stateOpenTag = "ChainState=["; - var historicOpenTag = "]HistoricState=["; + //var stateOpenTag = "ChainState=["; + //var historicOpenTag = "]HistoricState=["; - if (!line.Contains(stateOpenTag)) return; - if (!line.Contains(historicOpenTag)) return; + //if (!line.Contains(stateOpenTag)) return; + //if (!line.Contains(historicOpenTag)) return; - var stateStr = Between(line, stateOpenTag, historicOpenTag); - var historicStr = Between(line, historicOpenTag, "]"); + //var stateStr = Between(line, stateOpenTag, historicOpenTag); + //var historicStr = Between(line, historicOpenTag, "]"); - var chainState = JsonConvert.DeserializeObject(stateStr); - var historicState = JsonConvert.DeserializeObject(historicStr)!; - chainState!.Set(new HistoricState(historicState)); + //var chainState = JsonConvert.DeserializeObject(stateStr); + //var historicState = JsonConvert.DeserializeObject(historicStr)!; + //chainState!.Set(new HistoricState(historicState)); - log(string.Join(",", chainState!.GenerateOverview())); + //log(string.Join(",", chainState!.GenerateOverview())); } private string Between(string s, string open, string close) @@ -427,14 +438,14 @@ namespace CodexTests.UtilityTests private void Update() { - var start = last; - var stop = DateTime.UtcNow; - last = stop; + //var start = last; + //var stop = DateTime.UtcNow; + //last = stop; - var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop)); + //var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop)); - throw new Exception(); + //throw new Exception(); //LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range); //LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range); //LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range); From 3da36725e36109fb3bdb13a6cbfb922d5f67fd4c Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 3 Jun 2024 10:58:04 +0200 Subject: [PATCH 091/142] Updates codex image --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 7bc308ac..d060d751 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-267266a-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-5e3183a-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; diff --git a/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs index 9e647eaf..7800e132 100644 --- a/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs +++ b/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs @@ -3,7 +3,7 @@ using Logging; using NUnit.Framework; using Utils; -namespace CodexTests.ScalabilityTests +namespace CodexTests.UtilityTests { [TestFixture] public class ClusterDiscSpeedTests : DistTest @@ -18,7 +18,7 @@ namespace CodexTests.ScalabilityTests ) { long targetSize = (long)(1024 * 1024 * 1024) * 2; - long bufferSizeBytes = ((long)bufferSizeKb) * 1024; + long bufferSizeBytes = (long)bufferSizeKb * 1024; var filename = nameof(DiscSpeedTest); @@ -28,7 +28,7 @@ namespace CodexTests.ScalabilityTests var writeSpeed = PerformWrite(targetSize, bufferSizeBytes, filename); Thread.Sleep(2000); var readSpeed = PerformRead(targetSize, bufferSizeBytes, filename); - + Log($"Write speed: {writeSpeed} per second."); Log($"Read speed: {readSpeed} per second."); } From 383102b9886cebb81ff9311d0aad2c2c7158f351 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 3 Jun 2024 11:36:48 +0200 Subject: [PATCH 092/142] Adds container name to crash messge. --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index e35a9f9d..121ccf92 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -141,7 +141,7 @@ namespace CodexPlugin private void CheckContainerCrashed(HttpClient client) { - if (hasContainerCrashed) throw new Exception("Container has crashed."); + if (hasContainerCrashed) throw new Exception($"Container {GetName()} has crashed."); } public void Log(Stream crashLog) From 02baa72c1e388bb072707f6839752ccacb60f705 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 3 Jun 2024 11:42:52 +0200 Subject: [PATCH 093/142] DebugInfo log --- ProjectPlugins/CodexPlugin/CodexNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 36fb1dc0..034fe63e 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -80,7 +80,7 @@ namespace CodexPlugin { var debugInfo = CodexAccess.GetDebugInfo(); var known = string.Join(",", debugInfo.Table.Nodes.Select(n => n.PeerId)); - Log($"Got DebugInfo with id: '{debugInfo.Id}'. This node knows: {known}"); + Log($"Got DebugInfo with id: {debugInfo.Id}. This node knows: [{known}]"); return debugInfo; } From 67fc2183b7e9faee3aa4834e4d3c03a6961263e3 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 3 Jun 2024 12:56:19 +0200 Subject: [PATCH 094/142] Better logging for slot-filled event checking --- Tests/CodexTests/BasicTests/MarketplaceTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 7cff4aa5..5d69387e 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -92,9 +92,9 @@ namespace CodexTests.BasicTests var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()); var slotFilledEvents = contracts.GetSlotFilledEvents(blockRange); - Debug($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); - - if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(); + var msg = $"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"; + Debug(msg); + if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(msg); }, purchase.Expiry + TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5), "Checking SlotFilled events"); } From 11b866986d6b98ecd9d8687b13cd8d90efaaba5d Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Jun 2024 11:06:19 +0200 Subject: [PATCH 095/142] wip retry upgrade --- Framework/Utils/Retry.cs | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 Framework/Utils/Retry.cs diff --git a/Framework/Utils/Retry.cs b/Framework/Utils/Retry.cs new file mode 100644 index 00000000..45790e87 --- /dev/null +++ b/Framework/Utils/Retry.cs @@ -0,0 +1,125 @@ + +namespace Utils +{ + public class Retry + { + private readonly string description; + private readonly Func task; + private readonly TimeSpan maxTimeout; + private readonly int maxRetries; + private readonly TimeSpan sleepAfterFail; + private readonly Action onFail; + + public Retry(string description, Func task, TimeSpan maxTimeout, int maxRetries, TimeSpan sleepAfterFail, Action onFail) + { + this.description = description; + this.task = task; + this.maxTimeout = maxTimeout; + this.maxRetries = maxRetries; + this.sleepAfterFail = sleepAfterFail; + this.onFail = onFail; + } + + public T Run() + { + var run = new RetryRun(description, task, maxTimeout, maxRetries, sleepAfterFail, onFail); + return run.Run(); + } + + private class RetryRun + { + private readonly string description; + private readonly Func task; + private readonly TimeSpan maxTimeout; + private readonly int maxRetries; + private readonly TimeSpan sleepAfterFail; + private readonly Action onFail; + private readonly DateTime start = DateTime.UtcNow; + private readonly List failures = new List(); + private int tryNumber; + private DateTime tryStart; + + public RetryRun(string description, Func task, TimeSpan maxTimeout, int maxRetries, TimeSpan sleepAfterFail, Action onFail) + { + this.description = description; + this.task = task; + this.maxTimeout = maxTimeout; + this.maxRetries = maxRetries; + this.sleepAfterFail = sleepAfterFail; + this.onFail = onFail; + + tryNumber = 0; + tryStart = DateTime.UtcNow; + } + + public T Run() + { + while (true) + { + CheckMaximums(); + + tryNumber++; + tryStart = DateTime.UtcNow; + try + { + return task(); + } + catch (Exception ex) + { + var failure = CaptureFailure(ex); + onFail(failure); + Time.Sleep(sleepAfterFail); + } + } + } + + private Failure CaptureFailure(Exception ex) + { + var f = new Failure(ex, DateTime.UtcNow - tryStart, tryNumber); + failures.Add(f); + return f; + } + + private void CheckMaximums() + { + if (Duration() > maxTimeout) Fail(); + if (tryNumber > maxRetries) Fail(); + } + + private void Fail() + { + throw new TimeoutException($"Retry '{description}' timed out after {tryNumber} tries over {Time.FormatDuration(Duration())}: {GetFailureReport}", + new AggregateException(failures.Select(f => f.Exception))); + } + + private string GetFailureReport() + { + return Environment.NewLine + string.Join(Environment.NewLine, failures.Select(f => f.Describe())); + } + + private TimeSpan Duration() + { + return DateTime.UtcNow - start; + } + } + } + + public class Failure + { + public Failure(Exception exception, TimeSpan duration, int tryNumber) + { + Exception = exception; + Duration = duration; + TryNumber = tryNumber; + } + + public Exception Exception { get; } + public TimeSpan Duration { get; } + public int TryNumber { get; } + + public string Describe() + { + return $"Try {TryNumber} failed after {Time.FormatDuration(Duration)} with exception '{Exception}'"; + } + } +} From e9555cf99e465d5b530b4deaa33ee2c90f6652a8 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 5 Jun 2024 09:20:00 +0200 Subject: [PATCH 096/142] Upload/download failure now automatically investigates node --- Framework/Core/Http.cs | 13 +- Framework/Core/PluginTools.cs | 10 +- Framework/Utils/Retry.cs | 42 ++++--- Framework/Utils/Time.cs | 78 ++---------- ProjectPlugins/CodexPlugin/CodexAccess.cs | 116 ++++++++++++++++-- ProjectPlugins/CodexPlugin/CodexNode.cs | 6 + ProjectPlugins/CodexPlugin/CodexTypes.cs | 13 ++ ProjectPlugins/CodexPlugin/Mapper.cs | 12 ++ .../BasicTests/LargeFileTests.cs | 13 -- 9 files changed, 188 insertions(+), 115 deletions(-) diff --git a/Framework/Core/Http.cs b/Framework/Core/Http.cs index 49b3b097..44cb3c66 100644 --- a/Framework/Core/Http.cs +++ b/Framework/Core/Http.cs @@ -7,6 +7,7 @@ namespace Core { T OnClient(Func action); T OnClient(Func action, string description); + T OnClient(Func action, Retry retry); IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null); } @@ -35,13 +36,19 @@ namespace Core } public T OnClient(Func action, string description) + { + var retry = new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), f => { }); + return OnClient(action, retry); + } + + public T OnClient(Func action, Retry retry) { var client = GetClient(); return LockRetry(() => { return action(client); - }, description); + }, retry); } public IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null) @@ -54,11 +61,11 @@ namespace Core return DebugStack.GetCallerName(skipFrames: 2); } - private T LockRetry(Func operation, string description) + private T LockRetry(Func operation, Retry retry) { lock (httpLock) { - return Time.Retry(operation, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), description); + return retry.Run(operation); } } diff --git a/Framework/Core/PluginTools.cs b/Framework/Core/PluginTools.cs index 5e1faeed..78f7814e 100644 --- a/Framework/Core/PluginTools.cs +++ b/Framework/Core/PluginTools.cs @@ -6,6 +6,7 @@ namespace Core { public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool { + ITimeSet TimeSet { get; } void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles); } @@ -33,7 +34,6 @@ namespace Core internal class PluginTools : IPluginTools { - private readonly ITimeSet timeSet; private readonly WorkflowCreator workflowCreator; private readonly IFileManager fileManager; private readonly LogPrefixer log; @@ -42,10 +42,12 @@ namespace Core { this.log = new LogPrefixer(log); this.workflowCreator = workflowCreator; - this.timeSet = timeSet; + TimeSet = timeSet; fileManager = new FileManager(log, fileManagerRootFolder); } + public ITimeSet TimeSet { get; } + public void ApplyLogPrefix(string prefix) { log.Prefix = prefix; @@ -53,7 +55,7 @@ namespace Core public IHttp CreateHttp(Action onClientCreated) { - return CreateHttp(onClientCreated, timeSet); + return CreateHttp(onClientCreated, TimeSet); } public IHttp CreateHttp(Action onClientCreated, ITimeSet ts) @@ -63,7 +65,7 @@ namespace Core public IHttp CreateHttp() { - return new Http(log, timeSet); + return new Http(log, TimeSet); } public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null) diff --git a/Framework/Utils/Retry.cs b/Framework/Utils/Retry.cs index 45790e87..ec05ea66 100644 --- a/Framework/Utils/Retry.cs +++ b/Framework/Utils/Retry.cs @@ -1,37 +1,44 @@ - -namespace Utils +namespace Utils { - public class Retry + public class Retry { private readonly string description; - private readonly Func task; private readonly TimeSpan maxTimeout; - private readonly int maxRetries; private readonly TimeSpan sleepAfterFail; private readonly Action onFail; - public Retry(string description, Func task, TimeSpan maxTimeout, int maxRetries, TimeSpan sleepAfterFail, Action onFail) + public Retry(string description, TimeSpan maxTimeout, TimeSpan sleepAfterFail, Action onFail) { this.description = description; - this.task = task; this.maxTimeout = maxTimeout; - this.maxRetries = maxRetries; this.sleepAfterFail = sleepAfterFail; this.onFail = onFail; } - public T Run() + public void Run(Action task) { - var run = new RetryRun(description, task, maxTimeout, maxRetries, sleepAfterFail, onFail); - return run.Run(); + var run = new RetryRun(description, task, maxTimeout, sleepAfterFail, onFail); + run.Run(); + } + + public T Run(Func task) + { + T? result = default; + + var run = new RetryRun(description, () => + { + result = task(); + }, maxTimeout, sleepAfterFail, onFail); + run.Run(); + + return result!; } private class RetryRun { private readonly string description; - private readonly Func task; + private readonly Action task; private readonly TimeSpan maxTimeout; - private readonly int maxRetries; private readonly TimeSpan sleepAfterFail; private readonly Action onFail; private readonly DateTime start = DateTime.UtcNow; @@ -39,12 +46,11 @@ namespace Utils private int tryNumber; private DateTime tryStart; - public RetryRun(string description, Func task, TimeSpan maxTimeout, int maxRetries, TimeSpan sleepAfterFail, Action onFail) + public RetryRun(string description, Action task, TimeSpan maxTimeout, TimeSpan sleepAfterFail, Action onFail) { this.description = description; this.task = task; this.maxTimeout = maxTimeout; - this.maxRetries = maxRetries; this.sleepAfterFail = sleepAfterFail; this.onFail = onFail; @@ -52,7 +58,7 @@ namespace Utils tryStart = DateTime.UtcNow; } - public T Run() + public void Run() { while (true) { @@ -62,7 +68,8 @@ namespace Utils tryStart = DateTime.UtcNow; try { - return task(); + task(); + return; } catch (Exception ex) { @@ -83,7 +90,6 @@ namespace Utils private void CheckMaximums() { if (Duration() > maxTimeout) Fail(); - if (tryNumber > maxRetries) Fail(); } private void Fail() diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index 10caaf47..f22242fe 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -111,78 +111,24 @@ namespace Utils public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { - var start = DateTime.UtcNow; - var tries = 1; - var tryInfo = new List<(Exception, TimeSpan)>(); - - while (true) - { - var duration = DateTime.UtcNow - start; - if (duration > maxTimeout) - { - var info = FormatTryInfos(tryInfo); - throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.{Environment.NewLine}{info}"); - } - - var sw = Stopwatch.StartNew(); - try - { - action(); - return; - } - catch (Exception ex) - { - tryInfo.Add((ex, sw.Elapsed)); - tries++; - } - - Sleep(retryTime); - } - } - - private static string FormatTryInfos(List<(Exception, TimeSpan)> tryInfo) - { - return string.Join(Environment.NewLine, tryInfo.Select(FormatTryInfo).ToArray()); - } - - private static string FormatTryInfo((Exception, TimeSpan) info, int index) - { - return $"Attempt {index} took {FormatDuration(info.Item2)} and failed with exception {info.Item1}."; - } - - private static Action failedCallback = i => { }; - public static void SetRetryFailedCallback(Action onRetryFailed) - { - failedCallback = onRetryFailed; + Retry(action, maxTimeout, retryTime, description, f => { }); } public static T Retry(Func action, TimeSpan maxTimeout, TimeSpan retryTime, string description) { - var start = DateTime.UtcNow; - var tries = 1; - var exceptions = new List(); + return Retry(action, maxTimeout, retryTime, description, f => { }); + } - while (true) - { - var duration = DateTime.UtcNow - start; - if (duration > maxTimeout) - { - throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.", new AggregateException(exceptions)); - } + public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description, Action onFail) + { + var r = new Retry(description, maxTimeout, retryTime, onFail); + r.Run(action); + } - try - { - return action(); - } - catch (Exception ex) - { - exceptions.Add(ex); - failedCallback(tries); - tries++; - } - - Sleep(retryTime); - } + public static T Retry(Func action, TimeSpan maxTimeout, TimeSpan retryTime, string description, Action onFail) + { + var r = new Retry(description, maxTimeout, retryTime, onFail); + return r.Run(action); } } } diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 121ccf92..78a38942 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -2,6 +2,7 @@ using Core; using KubernetesWorkflow; using KubernetesWorkflow.Types; +using Logging; using Newtonsoft.Json; using Utils; @@ -61,12 +62,17 @@ namespace CodexPlugin public string UploadFile(FileStream fileStream) { - return OnCodex(api => api.UploadAsync(fileStream)); + return OnCodex( + api => api.UploadAsync(fileStream), + CreateRetryConfig(nameof(UploadFile))); } public Stream DownloadFile(string contentId) { - var fileResponse = OnCodex(api => api.DownloadNetworkAsync(contentId)); + var fileResponse = OnCodex( + api => api.DownloadNetworkAsync(contentId), + CreateRetryConfig(nameof(DownloadFile))); + if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; } @@ -89,6 +95,12 @@ namespace CodexPlugin return OnCodex(api => api.CreateStorageRequestAsync(request.ContentId.Id, body)); } + public CodexSpace Space() + { + var space = OnCodex(api => api.SpaceAsync()); + return mapper.Map(space); + } + public StoragePurchase GetPurchaseStatus(string purchaseId) { var endpoint = GetEndpoint(); @@ -116,17 +128,24 @@ namespace CodexPlugin private T OnCodex(Func> action) { - var address = GetAddress(); - var result = tools.CreateHttp(CheckContainerCrashed) - .OnClient(client => - { - var api = new CodexApi(client); - api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1"; - return Time.Wait(action(api)); - }); + var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action)); return result; } + private T OnCodex(Func> action, Retry retry) + { + var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry); + return result; + } + + private T CallCodex(HttpClient client, Func> action) + { + var address = GetAddress(); + var api = new CodexApi(client); + api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1"; + return Time.Wait(action(api)); + } + private IEndpoint GetEndpoint() { return tools @@ -144,7 +163,7 @@ namespace CodexPlugin if (hasContainerCrashed) throw new Exception($"Container {GetName()} has crashed."); } - public void Log(Stream crashLog) + void ILogHandler.Log(Stream crashLog) { var log = tools.GetLog(); var file = log.CreateSubfile(); @@ -162,5 +181,80 @@ namespace CodexPlugin log.Log("Crash log successfully downloaded."); hasContainerCrashed = true; } + + private Retry CreateRetryConfig(string description) + { + var timeSet = tools.TimeSet; + var log = tools.GetLog(); + + return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => + { + if (failure.TryNumber < 3) return; + if (failure.Duration.TotalSeconds < timeSet.HttpCallTimeout().TotalSeconds) + { + Investigate(log, failure, timeSet); + } + }); + } + + private void Investigate(ILog log, Failure failure, ITimeSet timeSet) + { + log.Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)}. (HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " + + $"Checking if node responds to debug/info..."); + try + { + var debugInfo = GetDebugInfo(); + if (string.IsNullOrEmpty(debugInfo.Spr)) + { + log.Log("Did not get value debug/info response."); + DownloadLog(); + Throw(failure); + } + else + { + log.Log("Got valid response from debug/info. Checking storage statistics..."); + CheckSpaceStatistics(log, failure); + } + } + catch (Exception ex) + { + log.Log("Got exception from debug/info call: " + ex); + DownloadLog(); + Throw(failure); + } + } + + private void CheckSpaceStatistics(ILog log, Failure failure) + { + try + { + var space = Space(); + log.Log($"Got space statistics: {space}"); + var freeSpace = space.QuotaMaxBytes - (space.QuotaUsedBytes + space.QuotaReservedBytes); + log.Log($"Free space: {freeSpace}"); + + if (freeSpace < 1.MB().SizeInBytes) + { + log.Log("There's less than 1MB free. Stopping..."); + Throw(failure); + } + } + catch (Exception e) + { + log.Log("Failed to get space statistics: " + e); + DownloadLog(); + Throw(failure); + } + } + + private void Throw(Failure failure) + { + throw failure.Exception; + } + + private void DownloadLog() + { + tools.CreateWorkflow().DownloadContainerLog(Container.Containers.Single(), this); + } } } diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 034fe63e..dc8531c8 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -17,6 +17,7 @@ namespace CodexPlugin ContentId UploadFile(TrackedFile file); TrackedFile? DownloadContent(ContentId contentId, string fileLabel = ""); LocalDatasetList LocalFiles(); + CodexSpace Space(); void ConnectToPeer(ICodexNode node); DebugInfoVersion Version { get; } IMarketplaceAccess Marketplace { get; } @@ -126,6 +127,11 @@ namespace CodexPlugin return CodexAccess.LocalFiles(); } + public CodexSpace Space() + { + return CodexAccess.Space(); + } + public void ConnectToPeer(ICodexNode node) { var peer = (CodexNode)node; diff --git a/ProjectPlugins/CodexPlugin/CodexTypes.cs b/ProjectPlugins/CodexPlugin/CodexTypes.cs index 945251e5..b82aa789 100644 --- a/ProjectPlugins/CodexPlugin/CodexTypes.cs +++ b/ProjectPlugins/CodexPlugin/CodexTypes.cs @@ -105,4 +105,17 @@ namespace CodexPlugin return HashCode.Combine(Id); } } + + public class CodexSpace + { + public int TotalBlocks { get; set; } + public int QuotaMaxBytes { get; set; } + public int QuotaUsedBytes { get; set; } + public int QuotaReservedBytes { get; set; } + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } } diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 61c95b4b..0c695e05 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin; +using CodexOpenApi; using Newtonsoft.Json.Linq; using System.Numerics; using Utils; @@ -84,6 +85,17 @@ namespace CodexPlugin }; } + public CodexSpace Map(Space space) + { + return new CodexSpace + { + QuotaMaxBytes = space.QuotaMaxBytes, + QuotaReservedBytes = space.QuotaReservedBytes, + QuotaUsedBytes = space.QuotaUsedBytes, + TotalBlocks = space.TotalBlocks + }; + } + private DebugInfoVersion MapDebugInfoVersion(JObject obj) { return new DebugInfoVersion diff --git a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs index d182ed8b..0470e35a 100644 --- a/Tests/CodexLongTests/BasicTests/LargeFileTests.cs +++ b/Tests/CodexLongTests/BasicTests/LargeFileTests.cs @@ -50,8 +50,6 @@ namespace CodexLongTests.BasicTests var node = StartCodex(s => s.WithStorageQuota((size + 10).MB())); - Time.SetRetryFailedCallback(i => OnFailed(i, node)); - var uploadStart = DateTime.UtcNow; var cid = node.UploadFile(expectedFile); var downloadStart = DateTime.UtcNow; @@ -62,17 +60,6 @@ namespace CodexLongTests.BasicTests AssertTimeConstraint(uploadStart, downloadStart, downloadFinished, size); } - private void OnFailed(int tries, ICodexNode node) - { - if (tries < 5) return; - - if (tries % 10 == 0) - { - Log($"After try {tries}, downloading node log."); - Ci.DownloadLog(node); - } - } - private void AssertTimeConstraint(DateTime uploadStart, DateTime downloadStart, DateTime downloadFinished, long size) { float sizeInMB = size; From b888999b61c5aed30dc4d4be742de9c1d6535fb4 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 5 Jun 2024 09:34:43 +0200 Subject: [PATCH 097/142] Adds space check for upload call --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 27 +++++++++++++++++------ ProjectPlugins/CodexPlugin/CodexTypes.cs | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 78a38942..e56bc1eb 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -62,16 +62,18 @@ namespace CodexPlugin public string UploadFile(FileStream fileStream) { + CheckSpaceIsAvailable(fileStream); + return OnCodex( api => api.UploadAsync(fileStream), - CreateRetryConfig(nameof(UploadFile))); + CreateRetryConfig(nameof(UploadFile), f => CheckSpaceIsAvailable(fileStream))); } public Stream DownloadFile(string contentId) { var fileResponse = OnCodex( api => api.DownloadNetworkAsync(contentId), - CreateRetryConfig(nameof(DownloadFile))); + CreateRetryConfig(nameof(DownloadFile), f => { })); if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; @@ -158,6 +160,18 @@ namespace CodexPlugin return Container.Containers.Single().GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag); } + private void CheckSpaceIsAvailable(FileStream fileStream) + { + var space = Space(); + + if (space.FreeBytes < fileStream.Length) + { + var msg = $"Not enough space available. File: {fileStream.Length} Free: {space.FreeBytes}"; + tools.GetLog().Error(msg); + throw new Exception(msg); + } + } + private void CheckContainerCrashed(HttpClient client) { if (hasContainerCrashed) throw new Exception($"Container {GetName()} has crashed."); @@ -182,14 +196,14 @@ namespace CodexPlugin hasContainerCrashed = true; } - private Retry CreateRetryConfig(string description) + private Retry CreateRetryConfig(string description, Action onFailure) { var timeSet = tools.TimeSet; var log = tools.GetLog(); return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => { - if (failure.TryNumber < 3) return; + onFailure(failure); if (failure.Duration.TotalSeconds < timeSet.HttpCallTimeout().TotalSeconds) { Investigate(log, failure, timeSet); @@ -230,10 +244,9 @@ namespace CodexPlugin { var space = Space(); log.Log($"Got space statistics: {space}"); - var freeSpace = space.QuotaMaxBytes - (space.QuotaUsedBytes + space.QuotaReservedBytes); - log.Log($"Free space: {freeSpace}"); + log.Log($"Free space: {space.FreeBytes}"); - if (freeSpace < 1.MB().SizeInBytes) + if (space.FreeBytes < 1.MB().SizeInBytes) { log.Log("There's less than 1MB free. Stopping..."); Throw(failure); diff --git a/ProjectPlugins/CodexPlugin/CodexTypes.cs b/ProjectPlugins/CodexPlugin/CodexTypes.cs index b82aa789..ea7546c1 100644 --- a/ProjectPlugins/CodexPlugin/CodexTypes.cs +++ b/ProjectPlugins/CodexPlugin/CodexTypes.cs @@ -112,6 +112,7 @@ namespace CodexPlugin public int QuotaMaxBytes { get; set; } public int QuotaUsedBytes { get; set; } public int QuotaReservedBytes { get; set; } + public int FreeBytes => QuotaMaxBytes - (QuotaUsedBytes + QuotaReservedBytes); public override string ToString() { From bf75c2515230b2a4d6cf5cff704b3b59d7a2a95a Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 5 Jun 2024 09:59:03 +0200 Subject: [PATCH 098/142] Cannot assume retry uploads will require as much space as file is large --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 42 ++++++++--------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index e56bc1eb..537dee00 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -62,18 +62,22 @@ namespace CodexPlugin public string UploadFile(FileStream fileStream) { - CheckSpaceIsAvailable(fileStream); + LogSpaceStatistics("Before upload"); - return OnCodex( + var str = OnCodex( api => api.UploadAsync(fileStream), - CreateRetryConfig(nameof(UploadFile), f => CheckSpaceIsAvailable(fileStream))); + CreateRetryConfig(nameof(UploadFile))); + + LogSpaceStatistics("After upload"); + + return str; } public Stream DownloadFile(string contentId) { var fileResponse = OnCodex( api => api.DownloadNetworkAsync(contentId), - CreateRetryConfig(nameof(DownloadFile), f => { })); + CreateRetryConfig(nameof(DownloadFile))); if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; @@ -160,18 +164,6 @@ namespace CodexPlugin return Container.Containers.Single().GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag); } - private void CheckSpaceIsAvailable(FileStream fileStream) - { - var space = Space(); - - if (space.FreeBytes < fileStream.Length) - { - var msg = $"Not enough space available. File: {fileStream.Length} Free: {space.FreeBytes}"; - tools.GetLog().Error(msg); - throw new Exception(msg); - } - } - private void CheckContainerCrashed(HttpClient client) { if (hasContainerCrashed) throw new Exception($"Container {GetName()} has crashed."); @@ -196,14 +188,13 @@ namespace CodexPlugin hasContainerCrashed = true; } - private Retry CreateRetryConfig(string description, Action onFailure) + private Retry CreateRetryConfig(string description) { var timeSet = tools.TimeSet; var log = tools.GetLog(); return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => { - onFailure(failure); if (failure.Duration.TotalSeconds < timeSet.HttpCallTimeout().TotalSeconds) { Investigate(log, failure, timeSet); @@ -242,15 +233,7 @@ namespace CodexPlugin { try { - var space = Space(); - log.Log($"Got space statistics: {space}"); - log.Log($"Free space: {space.FreeBytes}"); - - if (space.FreeBytes < 1.MB().SizeInBytes) - { - log.Log("There's less than 1MB free. Stopping..."); - Throw(failure); - } + LogSpaceStatistics(); } catch (Exception e) { @@ -260,6 +243,11 @@ namespace CodexPlugin } } + private void LogSpaceStatistics(string prefix = "") + { + tools.GetLog().Log($"{prefix} Space statistics: {Space()}"); + } + private void Throw(Failure failure) { throw failure.Exception; From f4d1dae478469c02ca2c3fa7b86ed4456ec3bb33 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 5 Jun 2024 10:00:50 +0200 Subject: [PATCH 099/142] Disables large network scalability test for now --- Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index b87be971..b4c1b398 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -18,7 +18,7 @@ public class ScalabilityTests : CodexDistTest [UseLongTimeouts] [DontDownloadLogs] public void ShouldMaintainFileInNetwork( - [Values(10, 40, 80, 100)] int numberOfNodes, + [Values(10, 40)] int numberOfNodes, // TODO: include 80 and 100 [Values(100, 1000, 5000, 10000)] int fileSizeInMb ) { From 62b56e198b4fe023256a6bd6e93c7de33c0c7751 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 6 Jun 2024 09:54:50 +0200 Subject: [PATCH 100/142] Adds logging of node status to base codexdistTest --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 23 +++++------- ProjectPlugins/CodexPlugin/CodexNode.cs | 24 ++++++++++--- ProjectPlugins/MetricsPlugin/MetricsQuery.cs | 36 +++++++++++++++++++ .../ScalabilityTests/ScalabilityTests.cs | 11 ++++-- Tests/CodexTests/CodexDistTest.cs | 23 ++++++++++++ 5 files changed, 97 insertions(+), 20 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 537dee00..5d97bece 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -60,24 +60,18 @@ namespace CodexPlugin }); } - public string UploadFile(FileStream fileStream) + public string UploadFile(FileStream fileStream, Action onFailure) { - LogSpaceStatistics("Before upload"); - - var str = OnCodex( + return OnCodex( api => api.UploadAsync(fileStream), - CreateRetryConfig(nameof(UploadFile))); - - LogSpaceStatistics("After upload"); - - return str; + CreateRetryConfig(nameof(UploadFile), onFailure)); } - public Stream DownloadFile(string contentId) + public Stream DownloadFile(string contentId, Action onFailure) { var fileResponse = OnCodex( api => api.DownloadNetworkAsync(contentId), - CreateRetryConfig(nameof(DownloadFile))); + CreateRetryConfig(nameof(DownloadFile), onFailure)); if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; @@ -188,13 +182,14 @@ namespace CodexPlugin hasContainerCrashed = true; } - private Retry CreateRetryConfig(string description) + private Retry CreateRetryConfig(string description, Action onFailure) { var timeSet = tools.TimeSet; var log = tools.GetLog(); return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => { + onFailure(failure); if (failure.Duration.TotalSeconds < timeSet.HttpCallTimeout().TotalSeconds) { Investigate(log, failure, timeSet); @@ -243,9 +238,9 @@ namespace CodexPlugin } } - private void LogSpaceStatistics(string prefix = "") + private void LogSpaceStatistics() { - tools.GetLog().Log($"{prefix} Space statistics: {Space()}"); + tools.GetLog().Log($"Space statistics: {Space()}"); } private void Throw(Failure failure) diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index dc8531c8..5179aae4 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -15,7 +15,9 @@ namespace CodexPlugin DebugInfo GetDebugInfo(); DebugPeer GetDebugPeer(string peerId); ContentId UploadFile(TrackedFile file); + ContentId UploadFile(TrackedFile file, Action onFailure); TrackedFile? DownloadContent(ContentId contentId, string fileLabel = ""); + TrackedFile? DownloadContent(ContentId contentId, Action onFailure, string fileLabel = ""); LocalDatasetList LocalFiles(); CodexSpace Space(); void ConnectToPeer(ICodexNode node); @@ -91,6 +93,11 @@ namespace CodexPlugin } public ContentId UploadFile(TrackedFile file) + { + return UploadFile(file, DoNothing); + } + + public ContentId UploadFile(TrackedFile file, Action onFailure) { using var fileStream = File.OpenRead(file.Filename); @@ -98,7 +105,7 @@ namespace CodexPlugin Log(logMessage); var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () => { - return CodexAccess.UploadFile(fileStream); + return CodexAccess.UploadFile(fileStream, onFailure); }); var response = measurement.Value; @@ -112,11 +119,16 @@ namespace CodexPlugin } public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "") + { + return DownloadContent(contentId, DoNothing, fileLabel); + } + + public TrackedFile? DownloadContent(ContentId contentId, Action onFailure, string fileLabel = "") { var logMessage = $"Downloading for contentId: '{contentId.Id}'..."; Log(logMessage); var file = tools.GetFileManager().CreateEmptyFile(fileLabel); - var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () => DownloadToFile(contentId.Id, file)); + var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () => DownloadToFile(contentId.Id, file, onFailure)); transferSpeeds.AddDownloadSample(file.GetFilesize(), measurement); Log($"Downloaded file {file.Describe()} to '{file.Filename}'."); return file; @@ -188,12 +200,12 @@ namespace CodexPlugin .ToArray(); } - private void DownloadToFile(string contentId, TrackedFile file) + private void DownloadToFile(string contentId, TrackedFile file, Action onFailure) { using var fileStream = File.OpenWrite(file.Filename); try { - using var downloadStream = CodexAccess.DownloadFile(contentId); + using var downloadStream = CodexAccess.DownloadFile(contentId, onFailure); downloadStream.CopyTo(fileStream); } catch @@ -207,5 +219,9 @@ namespace CodexPlugin { tools.GetLog().Log($"{GetName()}: {msg}"); } + + private void DoNothing(Failure failure) + { + } } } diff --git a/ProjectPlugins/MetricsPlugin/MetricsQuery.cs b/ProjectPlugins/MetricsPlugin/MetricsQuery.cs index 199015bc..cdab6971 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsQuery.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsQuery.cs @@ -1,4 +1,5 @@ using Core; +using IdentityModel; using KubernetesWorkflow.Types; using Logging; using System.Globalization; @@ -177,6 +178,41 @@ namespace MetricsPlugin { return "[" + string.Join(',', Sets.Select(s => s.ToString())) + "]"; } + + public string AsCsv() + { + var allTimestamps = Sets.SelectMany(s => s.Values.Select(v => v.Timestamp)).Distinct().OrderDescending().ToArray(); + + var lines = new List(); + MakeLine(lines, e => + { + e.Add("Metrics"); + foreach (var ts in allTimestamps) e.Add(ts.ToEpochTime().ToString()); + }); + + foreach (var set in Sets) + { + MakeLine(lines, e => + { + e.Add(set.Name); + foreach (var ts in allTimestamps) + { + var value = set.Values.SingleOrDefault(v => v.Timestamp == ts); + if (value == null) e.Add(" "); + else e.Add(value.Value.ToString()); + } + }); + } + + return string.Join(Environment.NewLine, lines.ToArray()); + } + + private void MakeLine(List lines, Action> values) + { + var list = new List(); + values(list); + lines.Add(string.Join(",", list)); + } } public class MetricsSet diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index b4c1b398..e75d189e 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -1,4 +1,5 @@ using CodexPlugin; +using MetricsPlugin; using DistTestCore; using FileUtils; using NUnit.Framework; @@ -26,6 +27,7 @@ public class ScalabilityTests : CodexDistTest var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); var nodes = StartCodex(numberOfNodes - 1, s => s + .EnableMetrics() .WithBootstrapNode(bootstrap) .WithLogLevel(logLevel) .WithStorageQuota((fileSizeInMb + 50).MB()) @@ -33,10 +35,15 @@ public class ScalabilityTests : CodexDistTest var uploader = nodes.PickOneRandom(); var downloader = nodes.PickOneRandom(); + var metrics = Ci.GetMetricsFor(uploader, downloader); var testFile = GenerateTestFile(fileSizeInMb.MB()); - var contentId = uploader.UploadFile(testFile); - var downloadedFile = downloader.DownloadContent(contentId); + + LogNodeStatus(uploader, metrics[0]); + var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader, metrics[0])); + + LogNodeStatus(downloader, metrics[1]); + var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader, metrics[1])); downloadedFile!.AssertIsEqual(testFile); diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 7058b8e1..10ca6a44 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -6,6 +6,8 @@ using Core; using DistTestCore; using DistTestCore.Helpers; using DistTestCore.Logs; +using MetricsPlugin; +using Newtonsoft.Json; using NUnit.Framework.Constraints; namespace CodexTests @@ -99,6 +101,27 @@ namespace CodexTests log.AssertLogDoesNotContain("ERR "); } + public void LogNodeStatus(ICodexNode node, IMetricsAccess? metrics = null) + { + Log("Status for " + node.GetName() + Environment.NewLine + + GetBasicNodeStatus(node) + + GetNodeMetrics(metrics)); + } + + private string GetBasicNodeStatus(ICodexNode node) + { + return JsonConvert.SerializeObject(node.GetDebugInfo(), Formatting.Indented) + Environment.NewLine + + node.Space().ToString() + Environment.NewLine; + } + + private string GetNodeMetrics(IMetricsAccess? metrics) + { + if (metrics == null) return "No metrics enabled"; + var m = metrics.GetAllMetrics(); + if (m == null) return "No metrics received"; + return m.AsCsv(); + } + protected virtual void OnCodexSetup(ICodexSetup setup) { } From e53a64471834b7ce8fadf0bcae3a036eecff1e7e Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 6 Jun 2024 10:11:51 +0200 Subject: [PATCH 101/142] Updates to new codex image --- ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- ProjectPlugins/CodexPlugin/openapi.yaml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index f3e4e10a..12fe874e 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -9,7 +9,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "0F-C8-02-1E-2C-2C-15-F6-91-6A-01-31-11-49-95-06-79-26-25-BF-27-3C-A8-2E-5F-7F-34-FD-C0-57-A0-9A"; + private const string OpenApiYamlHash = "27-D0-F6-EB-B9-A6-66-41-AA-EA-19-62-07-AF-47-41-25-5E-75-7E-97-35-CC-E1-C0-75-58-17-2D-87-11-75"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index d060d751..56ceabcf 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-5e3183a-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-a518ec6-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; diff --git a/ProjectPlugins/CodexPlugin/openapi.yaml b/ProjectPlugins/CodexPlugin/openapi.yaml index 49c75e64..6887de96 100644 --- a/ProjectPlugins/CodexPlugin/openapi.yaml +++ b/ProjectPlugins/CodexPlugin/openapi.yaml @@ -289,6 +289,7 @@ components: description: "Root hash of the content" originalBytes: type: integer + format: uint64 description: "Length of original content in bytes" blockSize: type: integer @@ -303,14 +304,18 @@ components: totalBlocks: description: "Number of blocks stored by the node" type: integer + format: uint64 quotaMaxBytes: type: integer + format: uint64 description: "Maximum storage space used by the node" quotaUsedBytes: type: integer + format: uint64 description: "Amount of storage space currently in use" quotaReservedBytes: type: integer + format: uint64 description: "Amount of storage space reserved" servers: From 9900db8d2562e062345b868db2193988a0bcaf52 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 6 Jun 2024 10:12:11 +0200 Subject: [PATCH 102/142] simply scalability test failure handling --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 27 ++--------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 5d97bece..469c8ca0 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -190,10 +190,7 @@ namespace CodexPlugin return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => { onFailure(failure); - if (failure.Duration.TotalSeconds < timeSet.HttpCallTimeout().TotalSeconds) - { - Investigate(log, failure, timeSet); - } + Investigate(log, failure, timeSet); }); } @@ -212,8 +209,7 @@ namespace CodexPlugin } else { - log.Log("Got valid response from debug/info. Checking storage statistics..."); - CheckSpaceStatistics(log, failure); + log.Log("Got valid response from debug/info."); } } catch (Exception ex) @@ -224,25 +220,6 @@ namespace CodexPlugin } } - private void CheckSpaceStatistics(ILog log, Failure failure) - { - try - { - LogSpaceStatistics(); - } - catch (Exception e) - { - log.Log("Failed to get space statistics: " + e); - DownloadLog(); - Throw(failure); - } - } - - private void LogSpaceStatistics() - { - tools.GetLog().Log($"Space statistics: {Space()}"); - } - private void Throw(Failure failure) { throw failure.Exception; From 38c2d1749abb05204e3137ee5b0d766804465d20 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 6 Jun 2024 10:31:31 +0200 Subject: [PATCH 103/142] Links up status method for checking. --- Tests/CodexTests/BasicTests/ExampleTests.cs | 3 +++ Tests/CodexTests/BasicTests/OneClientTests.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Tests/CodexTests/BasicTests/ExampleTests.cs b/Tests/CodexTests/BasicTests/ExampleTests.cs index 03562981..f9d5b89e 100644 --- a/Tests/CodexTests/BasicTests/ExampleTests.cs +++ b/Tests/CodexTests/BasicTests/ExampleTests.cs @@ -45,6 +45,9 @@ namespace CodexTests.BasicTests metrics[0].AssertThat("libp2p_peers", Is.EqualTo(1)); metrics[1].AssertThat("libp2p_peers", Is.EqualTo(1)); + + LogNodeStatus(primary, metrics[0]); + LogNodeStatus(primary2, metrics[1]); } [Test] diff --git a/Tests/CodexTests/BasicTests/OneClientTests.cs b/Tests/CodexTests/BasicTests/OneClientTests.cs index 11e81256..451b1cd0 100644 --- a/Tests/CodexTests/BasicTests/OneClientTests.cs +++ b/Tests/CodexTests/BasicTests/OneClientTests.cs @@ -13,6 +13,8 @@ namespace CodexTests.BasicTests var primary = StartCodex(); PerformOneClientTest(primary); + + LogNodeStatus(primary); } [Test] From 3a61fc89c6423019706630bf494014fb5d1f97f0 Mon Sep 17 00:00:00 2001 From: benbierens Date: Thu, 6 Jun 2024 15:09:52 +0200 Subject: [PATCH 104/142] Adds WaitForCleanup test attribute to allow tests to wait for resources to be cleaned up --- Framework/Core/EntryPoint.cs | 10 +++++++--- Framework/Core/PluginManager.cs | 4 ++-- Framework/Core/PluginTools.cs | 11 ++++++++--- Framework/KubernetesWorkflow/K8sController.cs | 16 ++++++++++++---- .../KubernetesWorkflow/StartupWorkflow.cs | 12 ++++++------ ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- .../CodexPlugin/CodexContainerRecipe.cs | 2 +- ProjectPlugins/CodexPlugin/CodexTypes.cs | 10 +++++----- ProjectPlugins/CodexPlugin/openapi.yaml | 10 +++++----- .../ContinuousTestRunner.cs | 4 ++-- Tests/CodexContinuousTests/NodeRunner.cs | 2 +- Tests/CodexContinuousTests/SingleTestRun.cs | 3 ++- .../ScalabilityTests/ScalabilityTests.cs | 2 ++ Tests/DistTestCore/DistTest.cs | 18 +++++++++++++++--- Tests/DistTestCore/TestLifecycle.cs | 8 +++++--- Tests/DistTestCore/WaitForCleanupAttribute.cs | 15 +++++++++++++++ 16 files changed, 89 insertions(+), 40 deletions(-) create mode 100644 Tests/DistTestCore/WaitForCleanupAttribute.cs diff --git a/Framework/Core/EntryPoint.cs b/Framework/Core/EntryPoint.cs index 7977eb3f..0db40f3d 100644 --- a/Framework/Core/EntryPoint.cs +++ b/Framework/Core/EntryPoint.cs @@ -38,10 +38,14 @@ namespace Core return new CoreInterface(this); } - public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles) + /// + /// Deletes kubernetes and tracked file resources. + /// when `waitTillDone` is true, this function will block until resources are deleted. + /// + public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone) { - manager.DecommissionPlugins(deleteKubernetesResources, deleteTrackedFiles); - Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles); + manager.DecommissionPlugins(deleteKubernetesResources, deleteTrackedFiles, waitTillDone); + Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone); } internal T GetPlugin() where T : IProjectPlugin diff --git a/Framework/Core/PluginManager.cs b/Framework/Core/PluginManager.cs index e2b2a5cc..27b08fe3 100644 --- a/Framework/Core/PluginManager.cs +++ b/Framework/Core/PluginManager.cs @@ -34,12 +34,12 @@ return metadata; } - internal void DecommissionPlugins(bool deleteKubernetesResources, bool deleteTrackedFiles) + internal void DecommissionPlugins(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone) { foreach (var pair in pairs) { pair.Plugin.Decommission(); - pair.Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles); + pair.Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone); } } diff --git a/Framework/Core/PluginTools.cs b/Framework/Core/PluginTools.cs index 78f7814e..aa8e0a6e 100644 --- a/Framework/Core/PluginTools.cs +++ b/Framework/Core/PluginTools.cs @@ -7,7 +7,12 @@ namespace Core public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool { ITimeSet TimeSet { get; } - void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles); + + /// + /// Deletes kubernetes and tracked file resources. + /// when `waitTillDone` is true, this function will block until resources are deleted. + /// + void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone); } public interface IWorkflowTool @@ -73,9 +78,9 @@ namespace Core return workflowCreator.CreateWorkflow(namespaceOverride); } - public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles) + public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone) { - if (deleteKubernetesResources) CreateWorkflow().DeleteNamespace(); + if (deleteKubernetesResources) CreateWorkflow().DeleteNamespace(waitTillDone); if (deleteTrackedFiles) fileManager.DeleteAllFiles(); } diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 0f50d874..30de0c97 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -115,7 +115,7 @@ namespace KubernetesWorkflow }); } - public void DeleteAllNamespacesStartingWith(string prefix) + public void DeleteAllNamespacesStartingWith(string prefix, bool wait) { log.Debug(); @@ -124,25 +124,28 @@ namespace KubernetesWorkflow foreach (var ns in namespaces) { - DeleteNamespace(ns); + DeleteNamespace(ns, wait); } } - public void DeleteNamespace() + public void DeleteNamespace(bool wait) { log.Debug(); if (IsNamespaceOnline(K8sNamespace)) { client.Run(c => c.DeleteNamespace(K8sNamespace, null, null, gracePeriodSeconds: 0)); + + if (wait) WaitUntilNamespaceDeleted(K8sNamespace); } } - public void DeleteNamespace(string ns) + public void DeleteNamespace(string ns, bool wait) { log.Debug(); if (IsNamespaceOnline(ns)) { client.Run(c => c.DeleteNamespace(ns, null, null, gracePeriodSeconds: 0)); + if (wait) WaitUntilNamespaceDeleted(ns); } } @@ -871,6 +874,11 @@ namespace KubernetesWorkflow WaitUntil(() => IsNamespaceOnline(K8sNamespace), nameof(WaitUntilNamespaceCreated)); } + private void WaitUntilNamespaceDeleted(string @namespace) + { + WaitUntil(() => !IsNamespaceOnline(@namespace), nameof(WaitUntilNamespaceDeleted)); + } + private void WaitUntilDeploymentOnline(string deploymentName) { WaitUntil(() => diff --git a/Framework/KubernetesWorkflow/StartupWorkflow.cs b/Framework/KubernetesWorkflow/StartupWorkflow.cs index cda61489..3a7326df 100644 --- a/Framework/KubernetesWorkflow/StartupWorkflow.cs +++ b/Framework/KubernetesWorkflow/StartupWorkflow.cs @@ -17,8 +17,8 @@ namespace KubernetesWorkflow void Stop(RunningPod pod, bool waitTillStopped); void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null); string ExecuteCommand(RunningContainer container, string command, params string[] args); - void DeleteNamespace(); - void DeleteNamespacesStartingWith(string namespacePrefix); + void DeleteNamespace(bool wait); + void DeleteNamespacesStartingWith(string namespacePrefix, bool wait); } public class StartupWorkflow : IStartupWorkflow @@ -122,19 +122,19 @@ namespace KubernetesWorkflow }); } - public void DeleteNamespace() + public void DeleteNamespace(bool wait) { K8s(controller => { - controller.DeleteNamespace(); + controller.DeleteNamespace(wait); }); } - public void DeleteNamespacesStartingWith(string namespacePrefix) + public void DeleteNamespacesStartingWith(string namespacePrefix, bool wait) { K8s(controller => { - controller.DeleteAllNamespacesStartingWith(namespacePrefix); + controller.DeleteAllNamespacesStartingWith(namespacePrefix, wait); }); } diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 12fe874e..d394405c 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -9,7 +9,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "27-D0-F6-EB-B9-A6-66-41-AA-EA-19-62-07-AF-47-41-25-5E-75-7E-97-35-CC-E1-C0-75-58-17-2D-87-11-75"; + private const string OpenApiYamlHash = "67-76-AB-FC-54-4F-EB-81-F5-E4-F8-27-DF-82-92-41-63-A5-EA-1B-17-14-0C-BE-20-9C-B3-DF-CE-E4-AA-38"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 56ceabcf..531f82aa 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-a518ec6-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-b89493e-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; diff --git a/ProjectPlugins/CodexPlugin/CodexTypes.cs b/ProjectPlugins/CodexPlugin/CodexTypes.cs index ea7546c1..d40fb13b 100644 --- a/ProjectPlugins/CodexPlugin/CodexTypes.cs +++ b/ProjectPlugins/CodexPlugin/CodexTypes.cs @@ -108,11 +108,11 @@ namespace CodexPlugin public class CodexSpace { - public int TotalBlocks { get; set; } - public int QuotaMaxBytes { get; set; } - public int QuotaUsedBytes { get; set; } - public int QuotaReservedBytes { get; set; } - public int FreeBytes => QuotaMaxBytes - (QuotaUsedBytes + QuotaReservedBytes); + public long TotalBlocks { get; set; } + public long QuotaMaxBytes { get; set; } + public long QuotaUsedBytes { get; set; } + public long QuotaReservedBytes { get; set; } + public long FreeBytes => QuotaMaxBytes - (QuotaUsedBytes + QuotaReservedBytes); public override string ToString() { diff --git a/ProjectPlugins/CodexPlugin/openapi.yaml b/ProjectPlugins/CodexPlugin/openapi.yaml index 6887de96..94450bf3 100644 --- a/ProjectPlugins/CodexPlugin/openapi.yaml +++ b/ProjectPlugins/CodexPlugin/openapi.yaml @@ -289,7 +289,7 @@ components: description: "Root hash of the content" originalBytes: type: integer - format: uint64 + format: int64 description: "Length of original content in bytes" blockSize: type: integer @@ -304,18 +304,18 @@ components: totalBlocks: description: "Number of blocks stored by the node" type: integer - format: uint64 + format: int64 quotaMaxBytes: type: integer - format: uint64 + format: int64 description: "Maximum storage space used by the node" quotaUsedBytes: type: integer - format: uint64 + format: int64 description: "Amount of storage space currently in use" quotaReservedBytes: type: integer - format: uint64 + format: int64 description: "Amount of storage space reserved" servers: diff --git a/Tests/CodexContinuousTests/ContinuousTestRunner.cs b/Tests/CodexContinuousTests/ContinuousTestRunner.cs index b860c86f..fde65aa3 100644 --- a/Tests/CodexContinuousTests/ContinuousTestRunner.cs +++ b/Tests/CodexContinuousTests/ContinuousTestRunner.cs @@ -148,7 +148,7 @@ namespace ContinuousTests log.Log($"Clearing namespace '{test.CustomK8sNamespace}'..."); var entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, test.CustomK8sNamespace, log); - entryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(test.CustomK8sNamespace); + entryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(test.CustomK8sNamespace, wait: true); } private void PerformCleanup(ILog log) @@ -157,7 +157,7 @@ namespace ContinuousTests log.Log("Cleaning up test namespace..."); var entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, config.CodexDeployment.Metadata.KubeNamespace, log); - entryPoint.Decommission(deleteKubernetesResources: true, deleteTrackedFiles: true); + entryPoint.Decommission(deleteKubernetesResources: true, deleteTrackedFiles: true, waitTillDone: true); log.Log("Cleanup finished."); } } diff --git a/Tests/CodexContinuousTests/NodeRunner.cs b/Tests/CodexContinuousTests/NodeRunner.cs index 31f1f2eb..e58facca 100644 --- a/Tests/CodexContinuousTests/NodeRunner.cs +++ b/Tests/CodexContinuousTests/NodeRunner.cs @@ -64,7 +64,7 @@ namespace ContinuousTests } finally { - entryPoint.Tools.CreateWorkflow().DeleteNamespace(); + entryPoint.Tools.CreateWorkflow().DeleteNamespace(wait: false); } } diff --git a/Tests/CodexContinuousTests/SingleTestRun.cs b/Tests/CodexContinuousTests/SingleTestRun.cs index 38bc5021..95d04660 100644 --- a/Tests/CodexContinuousTests/SingleTestRun.cs +++ b/Tests/CodexContinuousTests/SingleTestRun.cs @@ -54,7 +54,8 @@ namespace ContinuousTests entryPoint.Decommission( deleteKubernetesResources: false, // This would delete the continuous test net. - deleteTrackedFiles: true + deleteTrackedFiles: true, + waitTillDone: false ); runFinishedHandle.Set(); } diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index e75d189e..1b759ac4 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -18,6 +18,7 @@ public class ScalabilityTests : CodexDistTest [Combinatorial] [UseLongTimeouts] [DontDownloadLogs] + [WaitForCleanup] public void ShouldMaintainFileInNetwork( [Values(10, 40)] int numberOfNodes, // TODO: include 80 and 100 [Values(100, 1000, 5000, 10000)] int fileSizeInMb @@ -64,6 +65,7 @@ public class ScalabilityTests : CodexDistTest [Combinatorial] [UseLongTimeouts] [DontDownloadLogs] + [WaitForCleanup] public void EveryoneGetsAFile( [Values(10, 40, 80, 100)] int numberOfNodes, [Values(100, 1000, 5000, 10000)] int fileSizeInMb diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index ed99fe94..a2edfece 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -52,7 +52,7 @@ namespace DistTestCore { Stopwatch.Measure(fixtureLog, "Global setup", () => { - globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix); + globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true); }); } catch (Exception ex) @@ -72,7 +72,8 @@ namespace DistTestCore globalEntryPoint.Decommission( // There shouldn't be any of either, but clean everything up regardless. deleteKubernetesResources: true, - deleteTrackedFiles: true + deleteTrackedFiles: true, + waitTillDone: true ); } @@ -185,7 +186,13 @@ namespace DistTestCore lock (lifecycleLock) { var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); - var lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration, GetTimeSet(), testNamespace, deployId); + var lifecycle = new TestLifecycle( + fixtureLog.CreateTestLog(), + configuration, + GetTimeSet(), + testNamespace, + deployId, + ShouldWaitForCleanup()); lifecycles.Add(testName, lifecycle); LifecycleStart(lifecycle); } @@ -235,6 +242,11 @@ namespace DistTestCore return new DefaultTimeSet(); } + private bool ShouldWaitForCleanup() + { + return CurrentTestMethodHasAttribute(); + } + private bool ShouldUseLongTimeouts() { return CurrentTestMethodHasAttribute(); diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 542ca27c..4191cd83 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -16,7 +16,7 @@ namespace DistTestCore private readonly List runningContainers = new(); private readonly string deployId; - public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace, string deployId) + public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace, string deployId, bool waitForCleanup) { Log = log; Configuration = configuration; @@ -27,7 +27,7 @@ namespace DistTestCore metadata = entryPoint.GetPluginMetadata(); CoreInterface = entryPoint.CreateInterface(); this.deployId = deployId; - + WaitForCleanup = waitForCleanup; log.WriteLogTag(); } @@ -35,13 +35,15 @@ namespace DistTestCore public TestLog Log { get; } public Configuration Configuration { get; } public ITimeSet TimeSet { get; } + public bool WaitForCleanup { get; } public CoreInterface CoreInterface { get; } public void DeleteAllResources() { entryPoint.Decommission( deleteKubernetesResources: true, - deleteTrackedFiles: true + deleteTrackedFiles: true, + waitTillDone: WaitForCleanup ); } diff --git a/Tests/DistTestCore/WaitForCleanupAttribute.cs b/Tests/DistTestCore/WaitForCleanupAttribute.cs new file mode 100644 index 00000000..928e3c20 --- /dev/null +++ b/Tests/DistTestCore/WaitForCleanupAttribute.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; + +namespace DistTestCore +{ + /// + /// By default, test system does not wait until all resources are destroyed before starting the + /// next test. This saves a lot of time but it's not always what you want. + /// If you want to be sure the resources of your test are destroyed before the next test starts, + /// add this attribute to your test method. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class WaitForCleanupAttribute : PropertyAttribute + { + } +} From 18a02b1717211b2444fc9b770519883fbdfc50f6 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 7 Jun 2024 16:37:31 +0200 Subject: [PATCH 105/142] Implements mapping of debug/info routing table nodes --- ProjectPlugins/CodexPlugin/Mapper.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 0c695e05..6e59c68d 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -110,7 +110,7 @@ namespace CodexPlugin return new DebugInfoTable { LocalNode = MapDebugInfoTableNode(obj.GetValue("localNode")), - Nodes = new DebugInfoTableNode[0] + Nodes = MapDebugInfoTableNodeArray(obj.GetValue("nodes") as JArray) }; } @@ -129,6 +129,16 @@ namespace CodexPlugin }; } + private DebugInfoTableNode[] MapDebugInfoTableNodeArray(JArray? nodes) + { + if (nodes == null || nodes.Count == 0) + { + return new DebugInfoTableNode[0]; + } + + return nodes.Select(MapDebugInfoTableNode).ToArray(); + } + private Manifest MapManifest(CodexOpenApi.ManifestItem manifest) { return new Manifest From 5ffff1ed079df7070efc60b831651bb39b46aae9 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 7 Jun 2024 17:07:35 +0200 Subject: [PATCH 106/142] Disables metrics in scalability test. Downloads container log when retry attempt failed too quickly to be a timeout. --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 13 +++++++++++-- .../ScalabilityTests/ScalabilityTests.cs | 13 ++++++------- Tests/CodexTests/CodexDistTest.cs | 18 +++++++++--------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 469c8ca0..70139009 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -196,8 +196,10 @@ namespace CodexPlugin private void Investigate(ILog log, Failure failure, ITimeSet timeSet) { - log.Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)}. (HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " + - $"Checking if node responds to debug/info..."); + log.Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " + + $"(HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " + + $"Checking if node responds to debug/info..."); + try { var debugInfo = GetDebugInfo(); @@ -218,6 +220,13 @@ namespace CodexPlugin DownloadLog(); Throw(failure); } + + if (failure.Duration < timeSet.HttpCallTimeout()) + { + log.Log("Retry failed within HTTP timeout duration."); + DownloadLog(); + Throw(failure); + } } private void Throw(Failure failure) diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 1b759ac4..b63d5bd5 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -1,5 +1,4 @@ using CodexPlugin; -using MetricsPlugin; using DistTestCore; using FileUtils; using NUnit.Framework; @@ -28,7 +27,7 @@ public class ScalabilityTests : CodexDistTest var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); var nodes = StartCodex(numberOfNodes - 1, s => s - .EnableMetrics() + //.EnableMetrics() .WithBootstrapNode(bootstrap) .WithLogLevel(logLevel) .WithStorageQuota((fileSizeInMb + 50).MB()) @@ -36,15 +35,15 @@ public class ScalabilityTests : CodexDistTest var uploader = nodes.PickOneRandom(); var downloader = nodes.PickOneRandom(); - var metrics = Ci.GetMetricsFor(uploader, downloader); + //var metrics = Ci.GetMetricsFor(uploader, downloader); var testFile = GenerateTestFile(fileSizeInMb.MB()); - LogNodeStatus(uploader, metrics[0]); - var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader, metrics[0])); + LogNodeStatus(uploader); + var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader)); - LogNodeStatus(downloader, metrics[1]); - var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader, metrics[1])); + LogNodeStatus(downloader); + var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader)); downloadedFile!.AssertIsEqual(testFile); diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 10ca6a44..1c184bcd 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -104,8 +104,7 @@ namespace CodexTests public void LogNodeStatus(ICodexNode node, IMetricsAccess? metrics = null) { Log("Status for " + node.GetName() + Environment.NewLine + - GetBasicNodeStatus(node) + - GetNodeMetrics(metrics)); + GetBasicNodeStatus(node)); } private string GetBasicNodeStatus(ICodexNode node) @@ -114,13 +113,14 @@ namespace CodexTests node.Space().ToString() + Environment.NewLine; } - private string GetNodeMetrics(IMetricsAccess? metrics) - { - if (metrics == null) return "No metrics enabled"; - var m = metrics.GetAllMetrics(); - if (m == null) return "No metrics received"; - return m.AsCsv(); - } + // Disabled for now: Makes huge log files! + //private string GetNodeMetrics(IMetricsAccess? metrics) + //{ + // if (metrics == null) return "No metrics enabled"; + // var m = metrics.GetAllMetrics(); + // if (m == null) return "No metrics received"; + // return m.AsCsv(); + //} protected virtual void OnCodexSetup(ICodexSetup setup) { From 52ae67123464fa5d37714212dd2fc188a34327be Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 7 Jun 2024 17:08:14 +0200 Subject: [PATCH 107/142] focussing on the interesting numbers for now --- Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index b63d5bd5..6b6a2dc2 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -19,8 +19,8 @@ public class ScalabilityTests : CodexDistTest [DontDownloadLogs] [WaitForCleanup] public void ShouldMaintainFileInNetwork( - [Values(10, 40)] int numberOfNodes, // TODO: include 80 and 100 - [Values(100, 1000, 5000, 10000)] int fileSizeInMb + [Values(10)] int numberOfNodes, // TODO: include 40, 80 and 100 + [Values(5000, 10000)] int fileSizeInMb // TODO: include 100, 1000 ) { var logLevel = CodexLogLevel.Info; From 16bf5ce5ab9c3d52ac6cf880320354742a2480b2 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 7 Jun 2024 18:16:19 +0200 Subject: [PATCH 108/142] I think I may be on to something --- .../ScalabilityTests/ScalabilityTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 6b6a2dc2..4c9ce65b 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -9,6 +9,12 @@ namespace CodexTests.ScalabilityTests; [TestFixture] public class ScalabilityTests : CodexDistTest { + private const int Below2 = (Int32.MaxValue / (1024 * 1024)) - 20; + private const int Below1 = (Int32.MaxValue / (1024 * 1024)) - 10; + private const int Exact = (Int32.MaxValue / (1024 * 1024)) + 0; + private const int Above1 = (Int32.MaxValue / (1024 * 1024)) + 10; + private const int Above2 = (Int32.MaxValue / (1024 * 1024)) + 20; + /// /// We upload a file to node A, then download it with B. /// Then we stop node A, and download again with node C. @@ -19,8 +25,8 @@ public class ScalabilityTests : CodexDistTest [DontDownloadLogs] [WaitForCleanup] public void ShouldMaintainFileInNetwork( - [Values(10)] int numberOfNodes, // TODO: include 40, 80 and 100 - [Values(5000, 10000)] int fileSizeInMb // TODO: include 100, 1000 + [Values(5)] int numberOfNodes, // TODO: include 10, 40, 80 and 100, not 5 + [Values(Below2, Below1, Exact, Above1, Above2)] int fileSizeInMb // TODO: include 100, 1000, 5000, 10000 ) { var logLevel = CodexLogLevel.Info; From aa416d50b3658c4361fd4095d6db2a6a4dfa4594 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 8 Jun 2024 10:36:23 +0200 Subject: [PATCH 109/142] ensuring enough mounted disk space --- .../KubernetesWorkflow/ByteSizeExtensions.cs | 41 ------------------- Framework/KubernetesWorkflow/CrashWatcher.cs | 5 ++- Framework/KubernetesWorkflow/K8sController.cs | 2 +- .../Recipe/ContainerRecipeFactory.cs | 4 +- ProjectPlugins/CodexPlugin/CodexAccess.cs | 4 +- .../CodexPlugin/CodexContainerRecipe.cs | 2 +- .../ScalabilityTests/ScalabilityTests.cs | 12 ++---- 7 files changed, 13 insertions(+), 57 deletions(-) delete mode 100644 Framework/KubernetesWorkflow/ByteSizeExtensions.cs diff --git a/Framework/KubernetesWorkflow/ByteSizeExtensions.cs b/Framework/KubernetesWorkflow/ByteSizeExtensions.cs deleted file mode 100644 index c3cca244..00000000 --- a/Framework/KubernetesWorkflow/ByteSizeExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Utils; - -namespace KubernetesWorkflow -{ - public static class ByteSizeExtensions - { - public static string ToSuffixNotation(this ByteSize b) - { - long x = 1024; - var map = new Dictionary - { - { Pow(x, 4), "Ti" }, - { Pow(x, 3), "Gi" }, - { Pow(x, 2), "Mi" }, - { (x), "Ki" }, - }; - - var bytes = b.SizeInBytes; - foreach (var pair in map) - { - if (bytes > pair.Key) - { - double bytesD = bytes; - double divD = pair.Key; - double numD = Math.Ceiling(bytesD / divD); - var v = Convert.ToInt64(numD); - return $"{v}{pair.Value}"; - } - } - - return $"{bytes}"; - } - - private static long Pow(long x, int v) - { - long result = 1; - for (var i = 0; i < v; i++) result *= x; - return result; - } - } -} diff --git a/Framework/KubernetesWorkflow/CrashWatcher.cs b/Framework/KubernetesWorkflow/CrashWatcher.cs index 5cb2e034..eb1ced7b 100644 --- a/Framework/KubernetesWorkflow/CrashWatcher.cs +++ b/Framework/KubernetesWorkflow/CrashWatcher.cs @@ -83,12 +83,13 @@ namespace KubernetesWorkflow private bool HasContainerBeenRestarted(Kubernetes client) { var podInfo = client.ReadNamespacedPod(podName, k8sNamespace); - return podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0); + var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0); + if (result) log.Log("Pod crash detected for " + containerName); + return result; } private void DownloadCrashedContainerLogs(Kubernetes client) { - log.Log("Pod crash detected for " + containerName); using var stream = client.ReadNamespacedPodLog(podName, k8sNamespace, recipeName, previous: true); logHandler!.Log(stream); } diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 30de0c97..00c7563d 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -535,7 +535,7 @@ namespace KubernetesWorkflow } if (set.Memory.SizeInBytes != 0) { - result.Add("memory", new ResourceQuantity(set.Memory.ToSuffixNotation())); + result.Add("memory", new ResourceQuantity(set.Memory.SizeInBytes.ToString())); } return result; } diff --git a/Framework/KubernetesWorkflow/Recipe/ContainerRecipeFactory.cs b/Framework/KubernetesWorkflow/Recipe/ContainerRecipeFactory.cs index 6b6ae2df..2c42143a 100644 --- a/Framework/KubernetesWorkflow/Recipe/ContainerRecipeFactory.cs +++ b/Framework/KubernetesWorkflow/Recipe/ContainerRecipeFactory.cs @@ -105,7 +105,7 @@ namespace KubernetesWorkflow.Recipe protected void AddVolume(string name, string mountPath, string? subPath = null, string? secret = null, string? hostPath = null) { - var size = 10.MB().ToSuffixNotation(); + var size = 10.MB().SizeInBytes.ToString(); volumeMounts.Add(new VolumeMount(name, mountPath, subPath, size, secret, hostPath)); } @@ -114,7 +114,7 @@ namespace KubernetesWorkflow.Recipe volumeMounts.Add(new VolumeMount( $"autovolume-{Guid.NewGuid().ToString().ToLowerInvariant()}", mountPath, - resourceQuantity: volumeSize.ToSuffixNotation())); + resourceQuantity: volumeSize.SizeInBytes.ToString())); } protected void Additional(object userData) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 70139009..b63b9f45 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -167,8 +167,8 @@ namespace CodexPlugin { var log = tools.GetLog(); var file = log.CreateSubfile(); - log.Log($"Container {Container.Name} has crashed. Downloading crash log to '{file.FullFilename}'..."); - file.Write($"Container Crash Log for {Container.Name}."); + log.Log($"Downloading log to '{file.FullFilename}'..."); + file.Write($"Container log for {Container.Name}."); using var reader = new StreamReader(crashLog); var line = reader.ReadLine(); diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 531f82aa..ac29ce18 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -159,7 +159,7 @@ namespace CodexPlugin private ByteSize GetVolumeCapacity(CodexStartupConfig config) { - if (config.StorageQuota != null) return config.StorageQuota; + if (config.StorageQuota != null) return config.StorageQuota.Multiply(1.2); // Default Codex quota: 8 Gb, using +20% to be safe. return 8.GB().Multiply(1.2); } diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 4c9ce65b..2e6eb149 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -9,12 +9,6 @@ namespace CodexTests.ScalabilityTests; [TestFixture] public class ScalabilityTests : CodexDistTest { - private const int Below2 = (Int32.MaxValue / (1024 * 1024)) - 20; - private const int Below1 = (Int32.MaxValue / (1024 * 1024)) - 10; - private const int Exact = (Int32.MaxValue / (1024 * 1024)) + 0; - private const int Above1 = (Int32.MaxValue / (1024 * 1024)) + 10; - private const int Above2 = (Int32.MaxValue / (1024 * 1024)) + 20; - /// /// We upload a file to node A, then download it with B. /// Then we stop node A, and download again with node C. @@ -25,8 +19,8 @@ public class ScalabilityTests : CodexDistTest [DontDownloadLogs] [WaitForCleanup] public void ShouldMaintainFileInNetwork( - [Values(5)] int numberOfNodes, // TODO: include 10, 40, 80 and 100, not 5 - [Values(Below2, Below1, Exact, Above1, Above2)] int fileSizeInMb // TODO: include 100, 1000, 5000, 10000 + [Values(4, 5, 6)] int numberOfNodes, // TODO: include 10, 40, 80 and 100, not 5 + [Values(2000, 2200, 2500, 2800, 3000, 3200, 3500, 3800, 4200, 4500, 4800, 5000)] int fileSizeInMb // TODO: include 100, 1000, 5000, 10000 ) { var logLevel = CodexLogLevel.Info; @@ -47,9 +41,11 @@ public class ScalabilityTests : CodexDistTest LogNodeStatus(uploader); var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader)); + LogNodeStatus(uploader); LogNodeStatus(downloader); var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader)); + LogNodeStatus(downloader); downloadedFile!.AssertIsEqual(testFile); From 25663b59a0bf08e9233eecc49cdcfad46e84fac4 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sat, 8 Jun 2024 10:50:30 +0200 Subject: [PATCH 110/142] sets trace level for scalability tests --- Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 2e6eb149..14f363b0 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -23,7 +23,7 @@ public class ScalabilityTests : CodexDistTest [Values(2000, 2200, 2500, 2800, 3000, 3200, 3500, 3800, 4200, 4500, 4800, 5000)] int fileSizeInMb // TODO: include 100, 1000, 5000, 10000 ) { - var logLevel = CodexLogLevel.Info; + var logLevel = CodexLogLevel.Trace; var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); var nodes = StartCodex(numberOfNodes - 1, s => s From 22527a5d93606f5c8c5d5bffb2354112f161f9ce Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 10 Jun 2024 10:58:50 +0200 Subject: [PATCH 111/142] Deletes block repostore folder in container when Codex node stops. --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 8 ++++++++ ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- ProjectPlugins/CodexPlugin/CodexNode.cs | 11 ++++++----- Tests/CodexTests/CodexDistTest.cs | 9 +++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index b63b9f45..ffc9258b 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -126,6 +126,14 @@ namespace CodexPlugin return workflow.GetPodInfo(Container); } + public void DeleteRepoFolder() + { + var containerNumber = Container.Containers.First().Recipe.Number; + var dataDir = $"datadir{containerNumber}"; + var workflow = tools.CreateWorkflow(); + workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo"); + } + private T OnCodex(Func> action) { var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action)); diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index ac29ce18..f1183779 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -119,7 +119,7 @@ namespace CodexPlugin } } - if(!string.IsNullOrEmpty(config.NameOverride)) + if (!string.IsNullOrEmpty(config.NameOverride)) { AddEnvVar("CODEX_NODENAME", config.NameOverride); } diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 5179aae4..4965dbcb 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -26,6 +26,7 @@ namespace CodexPlugin CrashWatcher CrashWatcher { get; } PodInfo GetPodInfo(); ITransferSpeeds TransferSpeeds { get; } + void DeleteRepoFolder(); void Stop(bool waitTillStopped); } @@ -160,15 +161,15 @@ namespace CodexPlugin return CodexAccess.GetPodInfo(); } + public void DeleteRepoFolder() + { + CodexAccess.DeleteRepoFolder(); + } + public void Stop(bool waitTillStopped) { CrashWatcher.Stop(); Group.Stop(this, waitTillStopped); - // if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " + - // "individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " + - // "available for codex-nodes in groups of 1."); - // - // Group.BringOffline(waitTillStopped); } public void EnsureOnlineGetVersionResponse() diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 1c184bcd..d3e9beb6 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -38,6 +38,7 @@ namespace CodexTests protected override void LifecycleStop(TestLifecycle lifecycle) { + DeleteBlockRepo(onlineCodexNodes[lifecycle]); onlineCodexNodes.Remove(lifecycle); } @@ -134,5 +135,13 @@ namespace CodexTests if (upload != null) data.Add("avgupload", upload.ToString()); if (download != null) data.Add("avgdownload", download.ToString()); } + + private void DeleteBlockRepo(List codexNodes) + { + foreach (var node in codexNodes) + { + node.DeleteRepoFolder(); + } + } } } From cc2513bd2f0b0fbfdeeddf9042986bcc3b43950a Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 10 Jun 2024 14:04:25 +0200 Subject: [PATCH 112/142] Better chain state representation --- .../ChainMonitor/ChainState.cs | 32 +++- .../ChainMonitor/ChainStateRequest.cs | 9 +- .../Marketplace/Customizations.cs | 11 ++ .../CodexPlugin/StoragePurchaseContract.cs | 1 + .../UtilityTests/DiscordBotTests.cs | 158 ++---------------- 5 files changed, 63 insertions(+), 148 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index b3870dcf..e3993f28 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) { - this.log = log; + this.log = new LogPrefixer(log, "(ChainState) "); handler = changeHandler; TotalSpan = timeRange; } @@ -39,7 +39,21 @@ namespace CodexContractsPlugin.ChainMonitor public TimeRange TotalSpan { get; private set; } public IChainStateRequest[] Requests => requests.ToArray(); - public void Apply(ChainEvents events) + public void Update(ICodexContracts contracts) + { + Update(contracts, DateTime.UtcNow); + } + + public void Update(ICodexContracts contracts, DateTime toUtc) + { + var span = new TimeRange(TotalSpan.To, toUtc); + var events = ChainEvents.FromTimeRange(contracts, span); + Apply(events); + + TotalSpan = new TimeRange(TotalSpan.From, span.To); + } + + private void Apply(ChainEvents events) { if (events.BlockInterval.TimeRange.From < TotalSpan.From) throw new Exception("Attempt to update ChainState with set of events from before its current record."); @@ -52,7 +66,7 @@ namespace CodexContractsPlugin.ChainMonitor var spanPerBlock = span / numBlocks; var eventUtc = events.BlockInterval.TimeRange.From; - for (var b = events.BlockInterval.From; b < events.BlockInterval.To; b++) + for (var b = events.BlockInterval.From; b <= events.BlockInterval.To; b++) { var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray(); ApplyEvents(blockEvents, eventUtc); @@ -86,6 +100,7 @@ namespace CodexContractsPlugin.ChainMonitor private void ApplyEvent(RequestFulfilledEventDTO request) { var r = FindRequest(request.RequestId); + if (r == null) return; r.UpdateState(RequestState.Started); handler.OnRequestFulfilled(r); } @@ -93,6 +108,7 @@ namespace CodexContractsPlugin.ChainMonitor private void ApplyEvent(RequestCancelledEventDTO request) { var r = FindRequest(request.RequestId); + if (r == null) return; r.UpdateState(RequestState.Cancelled); handler.OnRequestCancelled(r); } @@ -100,12 +116,16 @@ namespace CodexContractsPlugin.ChainMonitor private void ApplyEvent(SlotFilledEventDTO request) { var r = FindRequest(request.RequestId); + if (r == null) return; + r.Log("SlotFilled"); handler.OnSlotFilled(r, request.SlotIndex); } private void ApplyEvent(SlotFreedEventDTO request) { var r = FindRequest(request.RequestId); + if (r == null) return; + r.Log("SlotFreed"); handler.OnSlotFreed(r, request.SlotIndex); } @@ -122,9 +142,11 @@ namespace CodexContractsPlugin.ChainMonitor } } - private ChainStateRequest FindRequest(byte[] requestId) + private ChainStateRequest? FindRequest(byte[] requestId) { - return requests.Single(r => Equal(r.Request.RequestId, requestId)); + var r = requests.SingleOrDefault(r => Equal(r.Request.RequestId, requestId)); + if (r == null) log.Log("Unable to find request by ID!"); + return r; } private bool Equal(byte[] a, byte[] b) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs index ae735b4c..4783e3af 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); - log.Log($"Request created as {State}."); + log.Log($"Created as {State}."); } public Request Request { get; } @@ -34,8 +34,13 @@ namespace CodexContractsPlugin.ChainMonitor public void UpdateState(RequestState newState) { - log.Log($"Request transit: {State} -> {newState}"); + Log($"Transit: {State} -> {newState}"); State = newState; } + + public void Log(string msg) + { + log.Log($"Request '{Request.Id}': {msg}"); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 5094e12b..557e021a 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -17,6 +17,17 @@ namespace CodexContractsPlugin.Marketplace public byte[] RequestId { get; set; } public EthAddress ClientAddress { get { return new EthAddress(Client); } } + + [JsonIgnore] + public string Id + { + get + { + var id = ""; + foreach (var b in RequestId) id += b.ToString(); + return id; + } + } } public partial class RequestFulfilledEventDTO : IHasBlock diff --git a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs index 3a9e0b92..d812f51c 100644 --- a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs +++ b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs @@ -6,6 +6,7 @@ namespace CodexPlugin { public interface IStoragePurchaseContract { + string PurchaseId { get; } void WaitForStorageContractSubmitted(); void WaitForStorageContractStarted(); void WaitForStorageContractFinished(); diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index c73cd23d..3ae94741 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -1,16 +1,13 @@ using CodexContractsPlugin; using CodexContractsPlugin.ChainMonitor; -using CodexContractsPlugin.Marketplace; using CodexDiscordBotPlugin; using CodexPlugin; using Core; using DiscordRewards; using GethPlugin; using KubernetesWorkflow.Types; -using Logging; using Newtonsoft.Json; using NUnit.Framework; -using TestNetRewarder; using Utils; namespace CodexTests.UtilityTests @@ -33,9 +30,6 @@ namespace CodexTests.UtilityTests var contracts = Ci.StartCodexContracts(geth); var gethInfo = CreateGethInfo(geth, contracts); - var monitor = new ChainMonitor(contracts, geth, GetTestLog()); - monitor.Start(); - var botContainer = StartDiscordBot(gethInfo); StartHosts(geth, contracts); @@ -44,49 +38,45 @@ namespace CodexTests.UtilityTests var client = StartClient(geth, contracts); - var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); - var chainState = CodexContractsPlugin.ChainMonitor.ChainState.FromEvents( + var chainState = ChainState.FromEvents( GetTestLog(), events, new DoNothingChainEventHandler()); - var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); - var rewarderLog = new RewarderLogMonitor(Ci, rewarderContainer.Containers.Single()); - rewarderLog.Start(l => Log("Rewarder ChainState: " + l)); var purchaseContract = ClientPurchasesStorage(client); + chainState.Update(contracts); + Assert.That(chainState.Requests.Length, Is.EqualTo(1)); + + purchaseContract.WaitForStorageContractStarted(); + chainState.Update(contracts); + purchaseContract.WaitForStorageContractFinished(); - rewarderLog.Stop(); - apiCalls.Stop(); - monitor.Stop(); - - Log("Done!"); - Thread.Sleep(rewarderInterval * 2); - - chainState.Apply(ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange())); - - Log("Seen:"); - foreach (var seen in rewardsSeen) - { - Log(seen.ToString()); - } - Log(""); + + apiCalls.Stop(); + chainState.Update(contracts); foreach (var r in repo.Rewards) { var seen = rewardsSeen.Any(s => r.RoleId == s); - Log($"{r.RoleId} = {seen}"); + Log($"{Lookup(r.RoleId)} = {seen}"); } Assert.That(repo.Rewards.All(r => rewardsSeen.Contains(r.RoleId))); } + private string Lookup(ulong rewardId) + { + var reward = repo.Rewards.Single(r => r.RoleId == rewardId); + return $"({rewardId})'{reward.Message}'"; + } + private void OnCommand(GiveRewardsCommand call) { if (call.Averages.Any()) Log($"API call: {call.Averages.Length} average."); @@ -292,55 +282,6 @@ namespace CodexTests.UtilityTests } } - public class RewarderLogMonitor - { - private readonly ContainerFileMonitor monitor; - private readonly Dictionary commands = new Dictionary(); - - public RewarderLogMonitor(CoreInterface ci, RunningContainer botContainer) - { - monitor = new ContainerFileMonitor(ci, botContainer, "/app/datapath/logs/testnetrewarder.log"); - } - - public void Start(Action onCommand) - { - monitor.Start(l => ProcessLine(l, onCommand)); - } - - public void Stop() - { - monitor.Stop(); - } - - private void ProcessLine(string line, Action log) - { - //$"ChainState=[{JsonConvert.SerializeObject(this)}]" + - //$"HistoricState=[{historicState.EntireString()}]"; - - //var stateOpenTag = "ChainState=["; - //var historicOpenTag = "]HistoricState=["; - - //if (!line.Contains(stateOpenTag)) return; - //if (!line.Contains(historicOpenTag)) return; - - //var stateStr = Between(line, stateOpenTag, historicOpenTag); - //var historicStr = Between(line, historicOpenTag, "]"); - - //var chainState = JsonConvert.DeserializeObject(stateStr); - //var historicState = JsonConvert.DeserializeObject(historicStr)!; - //chainState!.Set(new HistoricState(historicState)); - - //log(string.Join(",", chainState!.GenerateOverview())); - } - - private string Between(string s, string open, string close) - { - var start = s.IndexOf(open) + open.Length; - var end = s.LastIndexOf(close); - return s.Substring(start, end - start); - } - } - public class ContainerFileMonitor { private readonly CoreInterface ci; @@ -395,70 +336,5 @@ namespace CodexTests.UtilityTests } } } - - public class ChainMonitor - { - private readonly ICodexContracts contracts; - private readonly IGethNode geth; - private readonly ILog log; - private readonly CancellationTokenSource cts = new CancellationTokenSource(); - private Task worker = Task.CompletedTask; - private DateTime last = DateTime.UtcNow; - - public ChainMonitor(ICodexContracts contracts, IGethNode geth, ILog log) - { - this.contracts = contracts; - this.geth = geth; - this.log = log; - } - - public void Start() - { - last = DateTime.UtcNow; - worker = Task.Run(Worker); - } - - public void Stop() - { - cts.Cancel(); - worker.Wait(); - } - - private void Worker() - { - while (!cts.IsCancellationRequested) - { - Thread.Sleep(TimeSpan.FromSeconds(10)); - if (cts.IsCancellationRequested) return; - - Update(); - - } - } - - private void Update() - { - //var start = last; - //var stop = DateTime.UtcNow; - //last = stop; - - //var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop)); - - - //throw new Exception(); - //LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range); - //LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range); - //LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range); - //LogEvents(nameof(contracts.GetSlotFilledEvents), contracts.GetSlotFilledEvents, range); - //LogEvents(nameof(contracts.GetSlotFreedEvents), contracts.GetSlotFreedEvents, range); - } - - private void LogEvents(string n, Func f, BlockInterval r) - { - var a = (object[])f(r); - - a.ToList().ForEach(request => log.Log(n + " - " + JsonConvert.SerializeObject(request))); - } - } } } From 1c856f7615ad3e4c69783406ee491f40dfdbe079 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 10 Jun 2024 14:17:19 +0200 Subject: [PATCH 113/142] Fixes log --- .../CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs index 4783e3af..50088e78 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); - log.Log($"Created as {State}."); + Log($"Created as {State}."); } public Request Request { get; } From 74ca512f92955ac914996b3194d8ac123fb5678a Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 10 Jun 2024 15:15:27 +0200 Subject: [PATCH 114/142] Sets up test for downloading content by contract CID --- ProjectPlugins/CodexPlugin/Mapper.cs | 49 ++++++++++++++++--- .../CodexPlugin/MarketplaceAccess.cs | 3 ++ .../CodexPlugin/MarketplaceTypes.cs | 30 ++++++++++++ .../CodexTests/BasicTests/MarketplaceTests.cs | 41 ++++++++++++++++ 4 files changed, 115 insertions(+), 8 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 6e59c68d..c175ae14 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -63,14 +63,47 @@ namespace CodexPlugin }; } - public StoragePurchase Map(CodexOpenApi.Purchase purchase) - { - return new StoragePurchase - { - State = purchase.State, - Error = purchase.Error - }; - } + // TODO: Fix openapi spec for this call. + //public StoragePurchase Map(CodexOpenApi.Purchase purchase) + //{ + // return new StoragePurchase(Map(purchase.Request)) + // { + // State = purchase.State, + // Error = purchase.Error + // }; + //} + + //public StorageRequest Map(CodexOpenApi.StorageRequest request) + //{ + // return new StorageRequest(Map(request.Ask), Map(request.Content)) + // { + // Id = request.Id, + // Client = request.Client, + // Expiry = TimeSpan.FromSeconds(Convert.ToInt64(request.Expiry)), + // Nonce = request.Nonce + // }; + //} + + //public StorageAsk Map(CodexOpenApi.StorageAsk ask) + //{ + // return new StorageAsk + // { + // Duration = TimeSpan.FromSeconds(Convert.ToInt64(ask.Duration)), + // MaxSlotLoss = ask.MaxSlotLoss, + // ProofProbability = ask.ProofProbability, + // Reward = Convert.ToDecimal(ask.Reward).TstWei(), + // Slots = ask.Slots, + // SlotSize = new ByteSize(Convert.ToInt64(ask.SlotSize)) + // }; + //} + + //public StorageContent Map(CodexOpenApi.Content content) + //{ + // return new StorageContent + // { + // Cid = content.Cid + // }; + //} public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD read) { diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index 8944d95c..6f5e47e7 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -97,10 +97,13 @@ namespace CodexPlugin this.codexAccess = codexAccess; PurchaseId = purchaseId; Purchase = purchase; + + ContentId = new ContentId(codexAccess.GetPurchaseStatus(purchaseId).Request.Content.Cid); } public string PurchaseId { get; } public StoragePurchaseRequest Purchase { get; } + public ContentId ContentId { get; } public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc; public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc; diff --git a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs index caeda252..21aa86a6 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs @@ -1,5 +1,7 @@ using CodexContractsPlugin; +using CodexOpenApi; using Logging; +using System.Data; using Utils; namespace CodexPlugin @@ -37,6 +39,34 @@ namespace CodexPlugin { public string State { get; set; } = string.Empty; public string Error { get; set; } = string.Empty; + public StorageRequest Request { get; set; } = null!; + } + + public class StorageRequest + { + public string Id { get; set; } = string.Empty; + public string Client { get; set; } = string.Empty; + public StorageAsk Ask { get; set; } = null!; + public StorageContent Content { get; set; } = null!; + public string Expiry { get; set; } = string.Empty; + public string Nonce { get; set; } = string.Empty; + } + + public class StorageAsk + { + public int Slots { get; set; } + public string SlotSize { get; set; } = string.Empty; + public string Duration { get; set; } = string.Empty; + public string ProofProbability { get; set; } = string.Empty; + public string Reward { get; set; } = string.Empty; + public int MaxSlotLoss { get; set; } + } + + public class StorageContent + { + public string Cid { get; set; } = string.Empty; + //public ErasureParameters Erasure { get; set; } + //public PoRParameters Por { get; set; } } public class StorageAvailability diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 5d69387e..493d18c3 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -85,6 +85,47 @@ namespace CodexTests.BasicTests Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); } + [Test] + public void CanDownloadContentFromContractCid() + { + var fileSize = 10.MB(); + var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); + var contracts = Ci.StartCodexContracts(geth); + var testFile = GenerateTestFile(fileSize); + + var client = StartCodex(s => s + .WithName("Client") + .EnableMarketplace(geth, contracts, m => m + .WithInitial(10.Eth(), 10.Tst()))); + + var uploadCid = client.UploadFile(testFile); + + var purchase = new StoragePurchaseRequest(uploadCid) + { + PricePerSlotPerSecond = 2.TstWei(), + RequiredCollateral = 10.TstWei(), + MinRequiredNumberOfNodes = 5, + NodeFailureTolerance = 2, + ProofProbability = 5, + Duration = TimeSpan.FromMinutes(5), + Expiry = TimeSpan.FromMinutes(4) + }; + + var purchaseContract = client.Marketplace.RequestStorage(purchase); + + var contractCid = purchaseContract.ContentId; + + Assert.That(uploadCid.Id, Is.Not.EqualTo(contractCid.Id)); + + var downloader = StartCodex(s => s.WithName("Downloader")); + + var uploadedFile = downloader.DownloadContent(uploadCid); + testFile.AssertIsEqual(uploadedFile); + + var contractFile = downloader.DownloadContent(contractCid); + testFile.AssertIsEqual(contractFile); + } + private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth) { Time.Retry(() => From 9a46e20b4aa95171ef0b26da0ad3e1234ab19c62 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 11 Jun 2024 11:33:02 +0200 Subject: [PATCH 115/142] Fixes exception in CodexDistTests --- Framework/Utils/Time.cs | 4 +--- Tests/CodexTests/CodexDistTest.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Framework/Utils/Time.cs b/Framework/Utils/Time.cs index f22242fe..42051af7 100644 --- a/Framework/Utils/Time.cs +++ b/Framework/Utils/Time.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; - -namespace Utils +namespace Utils { public static class Time { diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index d3e9beb6..97580604 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -140,7 +140,14 @@ namespace CodexTests { foreach (var node in codexNodes) { - node.DeleteRepoFolder(); + try + { + node.DeleteRepoFolder(); + } + catch (Exception ex) + { + Log($"Failed to delete repo folder for node {node.GetName()} : {ex}"); + } } } } From 4fc9835f430f0b393996e2a75e7678ed4e4dc80b Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 12 Jun 2024 10:48:52 +0200 Subject: [PATCH 116/142] Attempt to log disk space before and after uploads and downloads --- Framework/KubernetesWorkflow/RunningPod.cs | 5 +- ProjectPlugins/CodexPlugin/CodexAccess.cs | 57 +++++++++++++++++----- ProjectPlugins/CodexPlugin/CodexStarter.cs | 2 +- Tests/CodexTests/CodexDistTest.cs | 4 ++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/Framework/KubernetesWorkflow/RunningPod.cs b/Framework/KubernetesWorkflow/RunningPod.cs index e86b2aa6..43a05cc1 100644 --- a/Framework/KubernetesWorkflow/RunningPod.cs +++ b/Framework/KubernetesWorkflow/RunningPod.cs @@ -1,8 +1,5 @@ -using k8s; -using k8s.Models; -using KubernetesWorkflow.Recipe; +using KubernetesWorkflow.Recipe; using KubernetesWorkflow.Types; -using Newtonsoft.Json; namespace KubernetesWorkflow { diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index ffc9258b..34459f41 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -10,6 +10,7 @@ namespace CodexPlugin { public class CodexAccess : ILogHandler { + private readonly ILog log; private readonly IPluginTools tools; private readonly Mapper mapper = new Mapper(); private bool hasContainerCrashed; @@ -17,6 +18,7 @@ namespace CodexPlugin public CodexAccess(IPluginTools tools, RunningPod container, CrashWatcher crashWatcher) { this.tools = tools; + log = tools.GetLog(); Container = container; CrashWatcher = crashWatcher; hasContainerCrashed = false; @@ -62,17 +64,27 @@ namespace CodexPlugin public string UploadFile(FileStream fileStream, Action onFailure) { - return OnCodex( + LogDiskSpace("Before upload"); + + var response = OnCodex( api => api.UploadAsync(fileStream), CreateRetryConfig(nameof(UploadFile), onFailure)); + + LogDiskSpace("After upload"); + + return response; } public Stream DownloadFile(string contentId, Action onFailure) { + LogDiskSpace("Before download"); + var fileResponse = OnCodex( api => api.DownloadNetworkAsync(contentId), CreateRetryConfig(nameof(DownloadFile), onFailure)); + LogDiskSpace("After download"); + if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; } @@ -132,6 +144,7 @@ namespace CodexPlugin var dataDir = $"datadir{containerNumber}"; var workflow = tools.CreateWorkflow(); workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo"); + Log("Deleted repo folder."); } private T OnCodex(Func> action) @@ -163,7 +176,7 @@ namespace CodexPlugin private Address GetAddress() { - return Container.Containers.Single().GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag); + return Container.Containers.Single().GetAddress(log, CodexContainerRecipe.ApiPortTag); } private void CheckContainerCrashed(HttpClient client) @@ -173,9 +186,8 @@ namespace CodexPlugin void ILogHandler.Log(Stream crashLog) { - var log = tools.GetLog(); var file = log.CreateSubfile(); - log.Log($"Downloading log to '{file.FullFilename}'..."); + Log($"Downloading log to '{file.FullFilename}'..."); file.Write($"Container log for {Container.Name}."); using var reader = new StreamReader(crashLog); @@ -186,62 +198,81 @@ namespace CodexPlugin line = reader.ReadLine(); } - log.Log("Crash log successfully downloaded."); + Log("Container log successfully downloaded."); hasContainerCrashed = true; } private Retry CreateRetryConfig(string description, Action onFailure) { var timeSet = tools.TimeSet; - var log = tools.GetLog(); return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure => { onFailure(failure); - Investigate(log, failure, timeSet); + Investigate(failure, timeSet); }); } - private void Investigate(ILog log, Failure failure, ITimeSet timeSet) + private void Investigate(Failure failure, ITimeSet timeSet) { - log.Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " + + Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " + $"(HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " + $"Checking if node responds to debug/info..."); + LogDiskSpace("After retry failure"); + try { var debugInfo = GetDebugInfo(); if (string.IsNullOrEmpty(debugInfo.Spr)) { - log.Log("Did not get value debug/info response."); + Log("Did not get value debug/info response."); DownloadLog(); Throw(failure); } else { - log.Log("Got valid response from debug/info."); + Log("Got valid response from debug/info."); } } catch (Exception ex) { - log.Log("Got exception from debug/info call: " + ex); + Log("Got exception from debug/info call: " + ex); DownloadLog(); Throw(failure); } if (failure.Duration < timeSet.HttpCallTimeout()) { - log.Log("Retry failed within HTTP timeout duration."); + Log("Retry failed within HTTP timeout duration."); DownloadLog(); Throw(failure); } } + private void LogDiskSpace(string msg) + { + try + { + var diskInfo = tools.CreateWorkflow().ExecuteCommand(Container.Containers.Single(), "df", "-h"); + Log($"{GetName()} - {msg} - Disk info: {diskInfo}"); + } + catch (Exception e) + { + Log("Failed to get disk info: " + e); + } + } + private void Throw(Failure failure) { throw failure.Exception; } + private void Log(string msg) + { + log.Log(msg); + } + private void DownloadLog() { tools.CreateWorkflow().DownloadContainerLog(Container.Containers.Single(), this); diff --git a/ProjectPlugins/CodexPlugin/CodexStarter.cs b/ProjectPlugins/CodexPlugin/CodexStarter.cs index dec13d38..77fd89a6 100644 --- a/ProjectPlugins/CodexPlugin/CodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/CodexStarter.cs @@ -33,7 +33,7 @@ namespace CodexPlugin foreach (var rc in containers) { var podInfo = GetPodInfo(rc); - var podInfos = string.Join(", ", rc.Containers.Select(c => $"Container: '{c.Name}' runs at '{podInfo.K8SNodeName}'={podInfo.Ip}")); + var podInfos = string.Join(", ", rc.Containers.Select(c => $"Container: '{c.Name}' PodLabel: '{c.RunningPod.StartResult.Deployment.PodLabel}' runs at '{podInfo.K8SNodeName}'={podInfo.Ip}")); Log($"Started {codexSetup.NumberOfNodes} nodes of image '{containers.First().Containers.First().Recipe.Image}'. ({podInfos})"); } LogSeparator(); diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 97580604..437dbf54 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -142,6 +142,10 @@ namespace CodexTests { try { + if (node.CrashWatcher.HasContainerCrashed()) + { + Log("Crash detected!"); + } node.DeleteRepoFolder(); } catch (Exception ex) From 390b9de54accedd9aaaca9025140545b6fd57722 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 12 Jun 2024 15:28:08 +0200 Subject: [PATCH 117/142] Replaces delete with disk info log. --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 43 +++++------------ ProjectPlugins/CodexPlugin/CodexNode.cs | 15 +++--- .../LongFullyConnectedDownloadTests.cs | 4 +- Tests/CodexTests/CodexDistTest.cs | 48 +------------------ .../FullyConnectedDownloadTests.cs | 17 +++---- .../LayeredDiscoveryTests.cs | 17 ++++--- .../PeerDiscoveryTests/PeerDiscoveryTests.cs | 19 ++++---- Tests/DistTestCore/DistTest.cs | 2 +- 8 files changed, 54 insertions(+), 111 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 34459f41..a99a2ec6 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -64,27 +64,17 @@ namespace CodexPlugin public string UploadFile(FileStream fileStream, Action onFailure) { - LogDiskSpace("Before upload"); - - var response = OnCodex( + return OnCodex( api => api.UploadAsync(fileStream), CreateRetryConfig(nameof(UploadFile), onFailure)); - - LogDiskSpace("After upload"); - - return response; } public Stream DownloadFile(string contentId, Action onFailure) { - LogDiskSpace("Before download"); - var fileResponse = OnCodex( api => api.DownloadNetworkAsync(contentId), CreateRetryConfig(nameof(DownloadFile), onFailure)); - LogDiskSpace("After download"); - if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); return fileResponse.Stream; } @@ -138,13 +128,17 @@ namespace CodexPlugin return workflow.GetPodInfo(Container); } - public void DeleteRepoFolder() + public void LogDiskSpace(string msg) { - var containerNumber = Container.Containers.First().Recipe.Number; - var dataDir = $"datadir{containerNumber}"; - var workflow = tools.CreateWorkflow(); - workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo"); - Log("Deleted repo folder."); + try + { + var diskInfo = tools.CreateWorkflow().ExecuteCommand(Container.Containers.Single(), "df", "--sync"); + Log($"{msg} - Disk info: {diskInfo}"); + } + catch (Exception e) + { + Log("Failed to get disk info: " + e); + } } private T OnCodex(Func> action) @@ -250,19 +244,6 @@ namespace CodexPlugin } } - private void LogDiskSpace(string msg) - { - try - { - var diskInfo = tools.CreateWorkflow().ExecuteCommand(Container.Containers.Single(), "df", "-h"); - Log($"{GetName()} - {msg} - Disk info: {diskInfo}"); - } - catch (Exception e) - { - Log("Failed to get disk info: " + e); - } - } - private void Throw(Failure failure) { throw failure.Exception; @@ -270,7 +251,7 @@ namespace CodexPlugin private void Log(string msg) { - log.Log(msg); + log.Log($"{GetName()} {msg}"); } private void DownloadLog() diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 4965dbcb..f0e064b0 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -26,7 +26,6 @@ namespace CodexPlugin CrashWatcher CrashWatcher { get; } PodInfo GetPodInfo(); ITransferSpeeds TransferSpeeds { get; } - void DeleteRepoFolder(); void Stop(bool waitTillStopped); } @@ -100,6 +99,8 @@ namespace CodexPlugin public ContentId UploadFile(TrackedFile file, Action onFailure) { + CodexAccess.LogDiskSpace("Before upload"); + using var fileStream = File.OpenRead(file.Filename); var logMessage = $"Uploading file {file.Describe()}..."; @@ -116,6 +117,8 @@ namespace CodexPlugin if (response.StartsWith(UploadFailedMessage)) FrameworkAssert.Fail("Node failed to store block."); Log($"Uploaded file. Received contentId: '{response}'."); + CodexAccess.LogDiskSpace("After upload"); + return new ContentId(response); } @@ -161,13 +164,9 @@ namespace CodexPlugin return CodexAccess.GetPodInfo(); } - public void DeleteRepoFolder() - { - CodexAccess.DeleteRepoFolder(); - } - public void Stop(bool waitTillStopped) { + Log("Stopping..."); CrashWatcher.Stop(); Group.Stop(this, waitTillStopped); } @@ -203,6 +202,8 @@ namespace CodexPlugin private void DownloadToFile(string contentId, TrackedFile file, Action onFailure) { + CodexAccess.LogDiskSpace("Before download"); + using var fileStream = File.OpenWrite(file.Filename); try { @@ -214,6 +215,8 @@ namespace CodexPlugin Log($"Failed to download file '{contentId}'."); throw; } + + CodexAccess.LogDiskSpace("After download"); } private void Log(string msg) diff --git a/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs b/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs index 15fc9f34..eaa999eb 100644 --- a/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs +++ b/Tests/CodexLongTests/DownloadConnectivityTests/LongFullyConnectedDownloadTests.cs @@ -15,9 +15,9 @@ namespace CodexLongTests.DownloadConnectivityTests [Values(10, 15, 20)] int numberOfNodes, [Values(10, 100)] int sizeMBs) { - for (var i = 0; i < numberOfNodes; i++) StartCodex(); + var nodes = StartCodex(numberOfNodes); - CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB()); + CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(nodes, sizeMBs.MB()); } } } diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/CodexTests/CodexDistTest.cs index 437dbf54..e62627ff 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/CodexTests/CodexDistTest.cs @@ -14,8 +14,6 @@ namespace CodexTests { public class CodexDistTest : DistTest { - private readonly Dictionary> onlineCodexNodes = new Dictionary>(); - public CodexDistTest() { ProjectPlugin.Load(); @@ -31,17 +29,6 @@ namespace CodexTests localBuilder.Build(); } - protected override void LifecycleStart(TestLifecycle lifecycle) - { - onlineCodexNodes.Add(lifecycle, new List()); - } - - protected override void LifecycleStop(TestLifecycle lifecycle) - { - DeleteBlockRepo(onlineCodexNodes[lifecycle]); - onlineCodexNodes.Remove(lifecycle); - } - public ICodexNode StartCodex() { return StartCodex(s => { }); @@ -64,7 +51,7 @@ namespace CodexTests setup(s); OnCodexSetup(s); }); - onlineCodexNodes[Get()].AddRange(group); + return group; } @@ -78,11 +65,6 @@ namespace CodexTests return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); } - public IEnumerable GetAllOnlineCodexNodes() - { - return onlineCodexNodes[Get()]; - } - public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "") { AssertHelpers.RetryAssert(constraint, () => contracts.GetTestTokenBalance(codexNode), nameof(AssertBalance) + msg); @@ -126,33 +108,5 @@ namespace CodexTests protected virtual void OnCodexSetup(ICodexSetup setup) { } - - protected override void CollectStatusLogData(TestLifecycle lifecycle, Dictionary data) - { - var nodes = onlineCodexNodes[lifecycle]; - var upload = nodes.Select(n => n.TransferSpeeds.GetUploadSpeed()).ToList()!.OptionalAverage(); - var download = nodes.Select(n => n.TransferSpeeds.GetDownloadSpeed()).ToList()!.OptionalAverage(); - if (upload != null) data.Add("avgupload", upload.ToString()); - if (download != null) data.Add("avgdownload", download.ToString()); - } - - private void DeleteBlockRepo(List codexNodes) - { - foreach (var node in codexNodes) - { - try - { - if (node.CrashWatcher.HasContainerCrashed()) - { - Log("Crash detected!"); - } - node.DeleteRepoFolder(); - } - catch (Exception ex) - { - Log($"Failed to delete repo folder for node {node.GetName()} : {ex}"); - } - } - } } } diff --git a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs index e40d9fbd..e6a42c50 100644 --- a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs +++ b/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin; +using CodexPlugin; using GethPlugin; using NUnit.Framework; using Utils; @@ -11,9 +12,9 @@ namespace CodexTests.DownloadConnectivityTests [Test] public void MetricsDoesNotInterfereWithPeerDownload() { - StartCodex(2, s => s.EnableMetrics()); + var nodes = StartCodex(2, s => s.EnableMetrics()); - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes); } [Test] @@ -21,10 +22,10 @@ namespace CodexTests.DownloadConnectivityTests { var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); - StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m + var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes); } [Test] @@ -33,14 +34,14 @@ namespace CodexTests.DownloadConnectivityTests [Values(2, 5)] int numberOfNodes, [Values(1, 10)] int sizeMBs) { - StartCodex(numberOfNodes); + var nodes = StartCodex(numberOfNodes); - AssertAllNodesConnected(sizeMBs); + AssertAllNodesConnected(nodes, sizeMBs); } - private void AssertAllNodesConnected(int sizeMBs = 10) + private void AssertAllNodesConnected(IEnumerable nodes, int sizeMBs = 10) { - CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB()); + CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(nodes, sizeMBs.MB()); } } } diff --git a/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs index 9c884fe1..33225d6f 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using CodexPlugin; +using NUnit.Framework; namespace CodexTests.PeerDiscoveryTests { @@ -13,7 +14,7 @@ namespace CodexTests.PeerDiscoveryTests var l1Node = StartCodex(s => s.WithBootstrapNode(root)); var l2Target = StartCodex(s => s.WithBootstrapNode(l1Node)); - AssertAllNodesConnected(); + AssertAllNodesConnected(root, l1Source, l1Node, l2Target); } [Test] @@ -25,7 +26,7 @@ namespace CodexTests.PeerDiscoveryTests var l2Node = StartCodex(s => s.WithBootstrapNode(l1Node)); var l3Target = StartCodex(s => s.WithBootstrapNode(l2Node)); - AssertAllNodesConnected(); + AssertAllNodesConnected(root, l1Source, l1Node, l2Node, l3Target); } [TestCase(3)] @@ -33,18 +34,22 @@ namespace CodexTests.PeerDiscoveryTests [TestCase(10)] public void NodeChainTest(int chainLength) { + var nodes = new List(); var node = StartCodex(); + nodes.Add(node); + for (var i = 1; i < chainLength; i++) { node = StartCodex(s => s.WithBootstrapNode(node)); + nodes.Add(node); } - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes.ToArray()); } - private void AssertAllNodesConnected() + private void AssertAllNodesConnected(params ICodexNode[] nodes) { - CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes()); + CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes); } } } diff --git a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 34da8d4a..52619ddc 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -21,9 +21,9 @@ namespace CodexTests.PeerDiscoveryTests [Test] public void MetricsDoesNotInterfereWithPeerDiscovery() { - StartCodex(2, s => s.EnableMetrics()); + var nodes = StartCodex(2, s => s.EnableMetrics()); - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes); } [Test] @@ -31,10 +31,10 @@ namespace CodexTests.PeerDiscoveryTests { var geth = Ci.StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth); - StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m + var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes); } [TestCase(2)] @@ -42,16 +42,15 @@ namespace CodexTests.PeerDiscoveryTests [TestCase(10)] public void VariableNodes(int number) { - StartCodex(number); + var nodes = StartCodex(number); - AssertAllNodesConnected(); + AssertAllNodesConnected(nodes); } - private void AssertAllNodesConnected() + private void AssertAllNodesConnected(IEnumerable nodes) { - var allNodes = GetAllOnlineCodexNodes(); - CreatePeerConnectionTestHelpers().AssertFullyConnected(allNodes); - CheckRoutingTable(allNodes); + CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes); + CheckRoutingTable(nodes); } private void CheckRoutingTable(IEnumerable allNodes) diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index a2edfece..7b1d09e1 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -215,7 +215,7 @@ namespace DistTestCore IncludeLogsOnTestFailure(lifecycle); LifecycleStop(lifecycle); lifecycle.DeleteAllResources(); - lifecycle = null!; + lifecycles.Remove(GetCurrentTestName()); }); } From 7b5e802efba139a533fe60a094d13b9f278dc94b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 13 Jun 2024 08:51:52 +0200 Subject: [PATCH 118/142] Applies delete in scalability tests --- ProjectPlugins/CodexPlugin/CodexAccess.cs | 16 ++++++++++++++++ ProjectPlugins/CodexPlugin/CodexNode.cs | 12 ++++++++++++ .../ScalabilityTests/ScalabilityTests.cs | 8 +++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index a99a2ec6..4d7ca224 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -141,6 +141,22 @@ namespace CodexPlugin } } + public void DeleteRepoFolder() + { + try + { + var containerNumber = Container.Containers.First().Recipe.Number; + var dataDir = $"datadir{containerNumber}"; + var workflow = tools.CreateWorkflow(); + workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo"); + Log("Deleted repo folder."); + } + catch (Exception e) + { + Log("Unable to delete repo folder: " + e); + } + } + private T OnCodex(Func> action) { var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action)); diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index f0e064b0..fd6d1f70 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -26,6 +26,13 @@ namespace CodexPlugin CrashWatcher CrashWatcher { get; } PodInfo GetPodInfo(); ITransferSpeeds TransferSpeeds { get; } + + /// + /// Warning! The node is not usable after this. + /// TODO: Replace with delete-blocks debug call once available in Codex. + /// + void DeleteRepoFolder(); + void Stop(bool waitTillStopped); } @@ -164,6 +171,11 @@ namespace CodexPlugin return CodexAccess.GetPodInfo(); } + public void DeleteRepoFolder() + { + CodexAccess.DeleteRepoFolder(); + } + public void Stop(bool waitTillStopped) { Log("Stopping..."); diff --git a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs index 14f363b0..c3b05f7a 100644 --- a/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs +++ b/Tests/CodexLongTests/ScalabilityTests/ScalabilityTests.cs @@ -20,14 +20,13 @@ public class ScalabilityTests : CodexDistTest [WaitForCleanup] public void ShouldMaintainFileInNetwork( [Values(4, 5, 6)] int numberOfNodes, // TODO: include 10, 40, 80 and 100, not 5 - [Values(2000, 2200, 2500, 2800, 3000, 3200, 3500, 3800, 4200, 4500, 4800, 5000)] int fileSizeInMb // TODO: include 100, 1000, 5000, 10000 + [Values(4000, 5000, 6000, 7000, 8000, 9000, 10000)] int fileSizeInMb ) { var logLevel = CodexLogLevel.Trace; var bootstrap = StartCodex(s => s.WithLogLevel(logLevel)); var nodes = StartCodex(numberOfNodes - 1, s => s - //.EnableMetrics() .WithBootstrapNode(bootstrap) .WithLogLevel(logLevel) .WithStorageQuota((fileSizeInMb + 50).MB()) @@ -35,7 +34,6 @@ public class ScalabilityTests : CodexDistTest var uploader = nodes.PickOneRandom(); var downloader = nodes.PickOneRandom(); - //var metrics = Ci.GetMetricsFor(uploader, downloader); var testFile = GenerateTestFile(fileSizeInMb.MB()); @@ -49,12 +47,16 @@ public class ScalabilityTests : CodexDistTest downloadedFile!.AssertIsEqual(testFile); + uploader.DeleteRepoFolder(); uploader.Stop(true); var otherDownloader = nodes.PickOneRandom(); downloadedFile = otherDownloader.DownloadContent(contentId); downloadedFile!.AssertIsEqual(testFile); + + downloader.DeleteRepoFolder(); + otherDownloader.DeleteRepoFolder(); } /// From 54f053cfccde4a6e284d9c85cc00078b82bd6600 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 14 Jun 2024 09:05:56 +0200 Subject: [PATCH 119/142] Improves crash detection --- Framework/KubernetesWorkflow/CrashWatcher.cs | 4 +- Framework/KubernetesWorkflow/K8sCluster.cs | 1 + ProjectPlugins/CodexPlugin/CodexAccess.cs | 64 +++++++++++-------- .../CodexTests/BasicTests/MarketplaceTests.cs | 15 ++--- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/Framework/KubernetesWorkflow/CrashWatcher.cs b/Framework/KubernetesWorkflow/CrashWatcher.cs index eb1ced7b..57e30eb6 100644 --- a/Framework/KubernetesWorkflow/CrashWatcher.cs +++ b/Framework/KubernetesWorkflow/CrashWatcher.cs @@ -50,7 +50,9 @@ namespace KubernetesWorkflow public bool HasContainerCrashed() { using var client = new Kubernetes(config); - return HasContainerBeenRestarted(client); + var result = HasContainerBeenRestarted(client); + if (result) DownloadCrashedContainerLogs(client); + return result; } private void Worker() diff --git a/Framework/KubernetesWorkflow/K8sCluster.cs b/Framework/KubernetesWorkflow/K8sCluster.cs index 366855fa..be87e1fe 100644 --- a/Framework/KubernetesWorkflow/K8sCluster.cs +++ b/Framework/KubernetesWorkflow/K8sCluster.cs @@ -16,6 +16,7 @@ namespace KubernetesWorkflow { var config = GetConfig(); UpdateHostAddress(config); + config.SkipTlsVerify = true; // Required for operation on Wings cluster. return config; } diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 4d7ca224..80f10611 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -37,20 +37,23 @@ namespace CodexPlugin public DebugPeer GetDebugPeer(string peerId) { // Cannot use openAPI: debug/peer endpoint is not specified there. - var endpoint = GetEndpoint(); - var str = endpoint.HttpGetString($"debug/peer/{peerId}"); - - if (str.ToLowerInvariant() == "unable to find peer!") + return CrashCheck(() => { - return new DebugPeer - { - IsPeerFound = false - }; - } + var endpoint = GetEndpoint(); + var str = endpoint.HttpGetString($"debug/peer/{peerId}"); - var result = endpoint.Deserialize(str); - result.IsPeerFound = true; - return result; + if (str.ToLowerInvariant() == "unable to find peer!") + { + return new DebugPeer + { + IsPeerFound = false + }; + } + + var result = endpoint.Deserialize(str); + result.IsPeerFound = true; + return result; + }); } public void ConnectToPeer(string peerId, string[] peerMultiAddresses) @@ -105,13 +108,16 @@ namespace CodexPlugin public StoragePurchase GetPurchaseStatus(string purchaseId) { - var endpoint = GetEndpoint(); - return Time.Retry(() => + return CrashCheck(() => { - var str = endpoint.HttpGetString($"storage/purchases/{purchaseId}"); - if (string.IsNullOrEmpty(str)) throw new Exception("Empty response."); - return JsonConvert.DeserializeObject(str)!; - }, nameof(GetPurchaseStatus)); + var endpoint = GetEndpoint(); + return Time.Retry(() => + { + var str = endpoint.HttpGetString($"storage/purchases/{purchaseId}"); + if (string.IsNullOrEmpty(str)) throw new Exception("Empty response."); + return JsonConvert.DeserializeObject(str)!; + }, nameof(GetPurchaseStatus)); + }); // TODO: current getpurchase api does not line up with its openapi spec. // return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId))); @@ -174,7 +180,19 @@ namespace CodexPlugin var address = GetAddress(); var api = new CodexApi(client); api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1"; - return Time.Wait(action(api)); + return CrashCheck(() => Time.Wait(action(api))); + } + + private T CrashCheck(Func action) + { + try + { + return action(); + } + finally + { + CrashWatcher.HasContainerCrashed(); + } } private IEndpoint GetEndpoint() @@ -237,7 +255,6 @@ namespace CodexPlugin if (string.IsNullOrEmpty(debugInfo.Spr)) { Log("Did not get value debug/info response."); - DownloadLog(); Throw(failure); } else @@ -248,14 +265,12 @@ namespace CodexPlugin catch (Exception ex) { Log("Got exception from debug/info call: " + ex); - DownloadLog(); Throw(failure); } if (failure.Duration < timeSet.HttpCallTimeout()) { Log("Retry failed within HTTP timeout duration."); - DownloadLog(); Throw(failure); } } @@ -269,10 +284,5 @@ namespace CodexPlugin { log.Log($"{GetName()} {msg}"); } - - private void DownloadLog() - { - tools.CreateWorkflow().DownloadContainerLog(Container.Containers.Single(), this); - } } } diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 493d18c3..756f8637 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -112,18 +112,17 @@ namespace CodexTests.BasicTests }; var purchaseContract = client.Marketplace.RequestStorage(purchase); - var contractCid = purchaseContract.ContentId; - Assert.That(uploadCid.Id, Is.Not.EqualTo(contractCid.Id)); - var downloader = StartCodex(s => s.WithName("Downloader")); - - var uploadedFile = downloader.DownloadContent(uploadCid); - testFile.AssertIsEqual(uploadedFile); + // Download both from client. + testFile.AssertIsEqual(client.DownloadContent(uploadCid)); + testFile.AssertIsEqual(client.DownloadContent(contractCid)); - var contractFile = downloader.DownloadContent(contractCid); - testFile.AssertIsEqual(contractFile); + // Download both from another node. + var downloader = StartCodex(s => s.WithName("Downloader")); + testFile.AssertIsEqual(downloader.DownloadContent(uploadCid)); + testFile.AssertIsEqual(downloader.DownloadContent(contractCid)); } private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth) From d3488dc907c9aab8440e70dcac6d39d38e41f0fb Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 14 Jun 2024 10:22:30 +0200 Subject: [PATCH 120/142] Adds block numbers to chainstate event logging --- .../ChainMonitor/ChainState.cs | 24 ++++++++++++------- .../ChainMonitor/ChainStateRequest.cs | 6 ++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index e3993f28..952d4ecc 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -29,6 +29,12 @@ namespace CodexContractsPlugin.ChainMonitor TotalSpan = timeRange; } + public static ChainState FromTimeRange(ILog log, ICodexContracts contracts, TimeRange timeRange, IChainStateChangeHandler changeHandler) + { + var events = ChainEvents.FromTimeRange(contracts, timeRange); + return FromEvents(log, events, changeHandler); + } + public static ChainState FromEvents(ILog log, ChainEvents events, IChainStateChangeHandler changeHandler) { var state = new ChainState(log, changeHandler, events.BlockInterval.TimeRange); @@ -69,13 +75,13 @@ namespace CodexContractsPlugin.ChainMonitor for (var b = events.BlockInterval.From; b <= events.BlockInterval.To; b++) { var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray(); - ApplyEvents(blockEvents, eventUtc); + ApplyEvents(b, blockEvents, eventUtc); eventUtc += spanPerBlock; } } - private void ApplyEvents(IHasBlock[] blockEvents, DateTime eventsUtc) + private void ApplyEvents(ulong blockNumber, IHasBlock[] blockEvents, DateTime eventsUtc) { foreach (var e in blockEvents) { @@ -83,7 +89,7 @@ namespace CodexContractsPlugin.ChainMonitor ApplyEvent(d); } - ApplyTimeImplicitEvents(eventsUtc); + ApplyTimeImplicitEvents(blockNumber, eventsUtc); } private void ApplyEvent(Request request) @@ -101,7 +107,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; - r.UpdateState(RequestState.Started); + r.UpdateState(request.Block.BlockNumber, RequestState.Started); handler.OnRequestFulfilled(r); } @@ -109,7 +115,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; - r.UpdateState(RequestState.Cancelled); + r.UpdateState(request.Block.BlockNumber, RequestState.Cancelled); handler.OnRequestCancelled(r); } @@ -117,7 +123,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; - r.Log("SlotFilled"); + r.Log($"[{request.Block.BlockNumber}] SlotFilled"); handler.OnSlotFilled(r, request.SlotIndex); } @@ -125,18 +131,18 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; - r.Log("SlotFreed"); + r.Log($"[{request.Block.BlockNumber}] SlotFreed"); handler.OnSlotFreed(r, request.SlotIndex); } - private void ApplyTimeImplicitEvents(DateTime eventsUtc) + private void ApplyTimeImplicitEvents(ulong blockNumber, DateTime eventsUtc) { foreach (var r in requests) { if (r.State == RequestState.Started && r.FinishedUtc < eventsUtc) { - r.UpdateState(RequestState.Finished); + r.UpdateState(blockNumber, RequestState.Finished); handler.OnRequestFinished(r); } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs index 50088e78..419e78c5 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); - Log($"Created as {State}."); + Log($"[{request.Block.BlockNumber}] Created as {State}."); } public Request Request { get; } @@ -32,9 +32,9 @@ namespace CodexContractsPlugin.ChainMonitor public DateTime ExpiryUtc { get; } public DateTime FinishedUtc { get; } - public void UpdateState(RequestState newState) + public void UpdateState(ulong blockNumber, RequestState newState) { - Log($"Transit: {State} -> {newState}"); + Log($"[{blockNumber}] Transit: {State} -> {newState}"); State = newState; } From c38a2242ba813e74d9f2168da622df4e7e42da6e Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 14 Jun 2024 11:05:29 +0200 Subject: [PATCH 121/142] wip --- .../ChainMonitor/ChainState.cs | 28 ++-- Tools/TestNetRewarder/ChainState.cs | 153 +----------------- Tools/TestNetRewarder/Configuration.cs | 9 ++ Tools/TestNetRewarder/MarketTracker.cs | 40 ++++- Tools/TestNetRewarder/Processor.cs | 109 +++++-------- Tools/TestNetRewarder/Program.cs | 10 +- Tools/TestNetRewarder/RewardChecker.cs | 102 ++++++++++++ Tools/TestNetRewarder/TimeSegmenter.cs | 55 ++++--- 8 files changed, 237 insertions(+), 269 deletions(-) create mode 100644 Tools/TestNetRewarder/RewardChecker.cs diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 952d4ecc..875ea8ea 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -20,37 +20,29 @@ namespace CodexContractsPlugin.ChainMonitor { private readonly List requests = new List(); private readonly ILog log; + private readonly ICodexContracts contracts; private readonly IChainStateChangeHandler handler; - private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) + public ChainState(ILog log, ICodexContracts contracts, IChainStateChangeHandler changeHandler, DateTime startUtc) { this.log = new LogPrefixer(log, "(ChainState) "); + this.contracts = contracts; handler = changeHandler; - TotalSpan = timeRange; - } - - public static ChainState FromTimeRange(ILog log, ICodexContracts contracts, TimeRange timeRange, IChainStateChangeHandler changeHandler) - { - var events = ChainEvents.FromTimeRange(contracts, timeRange); - return FromEvents(log, events, changeHandler); - } - - public static ChainState FromEvents(ILog log, ChainEvents events, IChainStateChangeHandler changeHandler) - { - var state = new ChainState(log, changeHandler, events.BlockInterval.TimeRange); - state.Apply(events); - return state; + StartUtc = startUtc; + TotalSpan = new TimeRange(startUtc, startUtc); } public TimeRange TotalSpan { get; private set; } public IChainStateRequest[] Requests => requests.ToArray(); - public void Update(ICodexContracts contracts) + public DateTime StartUtc { get; } + + public void Update() { - Update(contracts, DateTime.UtcNow); + Update(DateTime.UtcNow); } - public void Update(ICodexContracts contracts, DateTime toUtc) + public void Update(DateTime toUtc) { var span = new TimeRange(TotalSpan.To, toUtc); var events = ChainEvents.FromTimeRange(contracts, span); diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs index cef08082..a22537f0 100644 --- a/Tools/TestNetRewarder/ChainState.cs +++ b/Tools/TestNetRewarder/ChainState.cs @@ -6,9 +6,8 @@ using Utils; namespace TestNetRewarder { - public class ChainState + public class Keepers { - private HistoricState historicState; private readonly string[] colorIcons = new[] { "🔴", @@ -32,155 +31,5 @@ namespace TestNetRewarder "🔶", "🔷" }; - - public ChainState(HistoricState historicState, ICodexContracts contracts, BlockInterval blockRange) - { - this.historicState = historicState; - - throw new Exception("This is getting a rewrite"); - - //NewRequests = contracts.GetStorageRequests(blockRange); - //historicState.CleanUpOldRequests(); - //historicState.ProcessNewRequests(NewRequests); - //historicState.UpdateStorageRequests(contracts); - - //StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray(); - //FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray(); - //RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); - //RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange); - //SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange); - //SlotFreedEvents = contracts.GetSlotFreedEvents(blockRange); - } - - public ChainState( - Request[] newRequests, - RequestFulfilledEventDTO[] requestFulfilledEvents, - RequestCancelledEventDTO[] requestCancelledEvents, - SlotFilledEventDTO[] slotFilledEvents, - SlotFreedEventDTO[] slotFreedEvents) - { - NewRequests = newRequests; - RequestFulfilledEvents = requestFulfilledEvents; - RequestCancelledEvents = requestCancelledEvents; - SlotFilledEvents = slotFilledEvents; - SlotFreedEvents = slotFreedEvents; - - historicState = new HistoricState(); - StartedRequests = Array.Empty(); - FinishedRequests = Array.Empty(); - } - - public Request[] NewRequests { get; } - [JsonIgnore] - public StorageRequest[] AllRequests => historicState.StorageRequests; - [JsonIgnore] - public StorageRequest[] StartedRequests { get; private set; } - [JsonIgnore] - public StorageRequest[] FinishedRequests { get; private set; } - public RequestFulfilledEventDTO[] RequestFulfilledEvents { get; } - public RequestCancelledEventDTO[] RequestCancelledEvents { get; } - public SlotFilledEventDTO[] SlotFilledEvents { get; } - public SlotFreedEventDTO[] SlotFreedEvents { get; } - - public string EntireString() - { - return - $"ChainState=[{JsonConvert.SerializeObject(this)}]" + - $"HistoricState=[{historicState.EntireString()}]"; - } - - public void Set(HistoricState h) - { - historicState = h; - } - - public string[] GenerateOverview() - { - var entries = new List(); - - entries.AddRange(NewRequests.Select(ToPair)); - entries.AddRange(RequestFulfilledEvents.Select(ToPair)); - entries.AddRange(RequestCancelledEvents.Select(ToPair)); - entries.AddRange(SlotFilledEvents.Select(ToPair)); - entries.AddRange(SlotFreedEvents.Select(ToPair)); - entries.AddRange(FinishedRequests.Select(ToPair)); - - entries.Sort(new StringUtcComparer()); - - return entries.Select(ToLine).ToArray(); - } - - private StringBlockNumberPair ToPair(Request r) - { - return new StringBlockNumberPair("NewRequest", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - public StringBlockNumberPair ToPair(StorageRequest r) - { - return new StringBlockNumberPair("FinishedRequest", JsonConvert.SerializeObject(r), r.Request.Block, r.Request.RequestId); - } - - private StringBlockNumberPair ToPair(RequestFulfilledEventDTO r) - { - return new StringBlockNumberPair("Fulfilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(RequestCancelledEventDTO r) - { - return new StringBlockNumberPair("Cancelled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(SlotFilledEventDTO r) - { - return new StringBlockNumberPair("SlotFilled", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private StringBlockNumberPair ToPair(SlotFreedEventDTO r) - { - return new StringBlockNumberPair("SlotFreed", JsonConvert.SerializeObject(r), r.Block, r.RequestId); - } - - private string ToLine(StringBlockNumberPair pair) - { - var nl = Environment.NewLine; - var colorIcon = GetColorIcon(pair.RequestId); - return $"{colorIcon} {pair.Block} ({pair.Name}){nl}" + - $"```json{nl}" + - $"{pair.Str}{nl}" + - $"```"; - } - - private string GetColorIcon(byte[] requestId) - { - var index = requestId[0] % colorIcons.Length; - return colorIcons[index]; - } - - public class StringBlockNumberPair - { - public StringBlockNumberPair(string name, string str, BlockTimeEntry block, byte[] requestId) - { - Name = name; - Str = str; - Block = block; - RequestId = requestId; - } - - public string Name { get; } - public string Str { get; } - public BlockTimeEntry Block { get; } - public byte[] RequestId { get; } - } - - public class StringUtcComparer : IComparer - { - public int Compare(StringBlockNumberPair? x, StringBlockNumberPair? y) - { - if (x == null && y == null) return 0; - if (x == null) return 1; - if (y == null) return -1; - return x.Block.BlockNumber.CompareTo(y.Block.BlockNumber); - } - } } } diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index c88a0aa8..5d6ae51d 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -40,5 +40,14 @@ namespace TestNetRewarder return TimeSpan.FromMinutes(IntervalMinutes); } } + + public DateTime HistoryStartUtc + { + get + { + if (CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet."); + return DateTimeOffset.FromUnixTimeSeconds(CheckHistoryTimestamp).UtcDateTime; + } + } } } diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index 17e5e82d..c0abad3b 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,10 +1,11 @@ -using CodexContractsPlugin.Marketplace; +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; using DiscordRewards; using System.Numerics; namespace TestNetRewarder { - public class MarketTracker + public class MarketTracker : IChainStateChangeHandler { private readonly List buffer = new List(); @@ -120,5 +121,40 @@ namespace TestNetRewarder } return Array.Empty(); } + + public void OnNewRequest(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestStarted(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestFinished(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); + } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 678d4f84..b3586659 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -1,39 +1,37 @@ -using DiscordRewards; +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; using GethPlugin; using Logging; using Newtonsoft.Json; +using System.Numerics; using Utils; namespace TestNetRewarder { - public class Processor + public class Processor : ITimeSegmentHandler, IChainStateChangeHandler { - private static readonly HistoricState historicState = new HistoricState(); - private static readonly RewardRepo rewardRepo = new RewardRepo(); - private static readonly MarketTracker marketTracker = new MarketTracker(); + private readonly RewardChecker rewardChecker = new RewardChecker(); + private readonly MarketTracker marketTracker = new MarketTracker(); + private readonly ChainState chainState; + private readonly Configuration config; private readonly ILog log; private BlockInterval? lastBlockRange; - public Processor(ILog log) + public Processor(Configuration config, ICodexContracts contracts, ILog log) { + this.config = config; this.log = log; + + chainState = new ChainState(log, contracts, this, config.HistoryStartUtc); } - public async Task ProcessTimeSegment(TimeRange timeRange) + public async Task OnNewSegment(TimeRange timeRange) { - var connector = GethConnector.GethConnector.Initialize(log); - if (connector == null) throw new Exception("Invalid Geth information"); - try { - 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); + chainState.Update(timeRange.To); + await ProcessChainState(chainState); } catch (Exception ex) @@ -43,19 +41,6 @@ namespace TestNetRewarder } } - private bool IsNewBlockRange(BlockInterval blockRange) - { - if (lastBlockRange == null || - lastBlockRange.From != blockRange.From || - lastBlockRange.To != blockRange.To) - { - lastBlockRange = blockRange; - return true; - } - - return false; - } - private async Task ProcessChainState(ChainState chainState) { log.Log(chainState.EntireString()); @@ -92,57 +77,39 @@ namespace TestNetRewarder return marketTracker.ProcessChainState(chainState); } - private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + public void OnNewRequest(IChainStateRequest request) { - var cmd = new GiveRewardsCommand - { - Rewards = outgoingRewards.ToArray(), - Averages = marketAverages.ToArray(), - EventsOverview = eventsOverview - }; - - log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); - return await Program.BotClient.SendRewards(cmd); + throw new NotImplementedException(); } - private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) + public void OnRequestStarted(IChainStateRequest request) { - var winningAddresses = PerformCheck(reward, chainState); - foreach (var win in winningAddresses) - { - log.Log($"Address '{win.Address}' wins '{reward.Message}'"); - } - if (winningAddresses.Any()) - { - outgoingRewards.Add(new RewardUsersCommand - { - RewardId = reward.RoleId, - UserAddresses = winningAddresses.Select(a => a.Address).ToArray() - }); - } + throw new NotImplementedException(); } - private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) + public void OnRequestFinished(IChainStateRequest request) { - var check = GetCheck(reward.CheckConfig); - return check.Check(chainState).Distinct().ToArray(); + throw new NotImplementedException(); } - private ICheck GetCheck(CheckConfig config) + public void OnRequestFulfilled(IChainStateRequest request) { - 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 NotImplementedException(); + } - throw new Exception("Unknown check type: " + config.Type); + public void OnRequestCancelled(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); } } } diff --git a/Tools/TestNetRewarder/Program.cs b/Tools/TestNetRewarder/Program.cs index 0aaa41c7..1106ff76 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -1,5 +1,6 @@ using ArgsUniform; using Logging; +using Nethereum.Model; using Utils; namespace TestNetRewarder @@ -27,8 +28,11 @@ namespace TestNetRewarder new ConsoleLog() ); + var connector = GethConnector.GethConnector.Initialize(Log); + if (connector == null) throw new Exception("Invalid Geth information"); + BotClient = new BotClient(Config, Log); - processor = new Processor(Log); + processor = new Processor(Config, connector.CodexContracts, Log); EnsurePath(Config.DataPath); EnsurePath(Config.LogPath); @@ -41,12 +45,12 @@ namespace TestNetRewarder EnsureGethOnline(); Log.Log("Starting TestNet Rewarder..."); - var segmenter = new TimeSegmenter(Log, Config); + var segmenter = new TimeSegmenter(Log, Config, processor); while (!CancellationToken.IsCancellationRequested) { await EnsureBotOnline(); - await segmenter.WaitForNextSegment(processor.ProcessTimeSegment); + await segmenter.ProcessNextSegment(); await Task.Delay(100, CancellationToken); } } diff --git a/Tools/TestNetRewarder/RewardChecker.cs b/Tools/TestNetRewarder/RewardChecker.cs new file mode 100644 index 00000000..63151969 --- /dev/null +++ b/Tools/TestNetRewarder/RewardChecker.cs @@ -0,0 +1,102 @@ +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; +using GethPlugin; +using Nethereum.Model; +using Newtonsoft.Json; +using System.Numerics; + +namespace TestNetRewarder +{ + public class RewardChecker : IChainStateChangeHandler + { + private static readonly RewardRepo rewardRepo = new RewardRepo(); + + private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + { + var cmd = new GiveRewardsCommand + { + Rewards = outgoingRewards.ToArray(), + Averages = marketAverages.ToArray(), + EventsOverview = eventsOverview + }; + + log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); + return await Program.BotClient.SendRewards(cmd); + } + + private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) + { + var winningAddresses = PerformCheck(reward, chainState); + foreach (var win in winningAddresses) + { + log.Log($"Address '{win.Address}' wins '{reward.Message}'"); + } + if (winningAddresses.Any()) + { + outgoingRewards.Add(new RewardUsersCommand + { + RewardId = reward.RoleId, + UserAddresses = winningAddresses.Select(a => a.Address).ToArray() + }); + } + } + + private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) + { + var check = GetCheck(reward.CheckConfig); + return check.Check(chainState).Distinct().ToArray(); + } + + private ICheck GetCheck(CheckConfig config) + { + switch (config.Type) + { + case CheckType.FilledSlot: + return new FilledAnySlotCheck(); + case CheckType.FinishedSlot: + return new FinishedSlotCheck(config.MinSlotSize, config.MinDuration); + case CheckType.PostedContract: + return new PostedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); + case CheckType.StartedContract: + return new StartedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); + } + + throw new Exception("Unknown check type: " + config.Type); + } + + public void OnNewRequest(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestStarted(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestFinished(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + throw new NotImplementedException(); + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + } +} diff --git a/Tools/TestNetRewarder/TimeSegmenter.cs b/Tools/TestNetRewarder/TimeSegmenter.cs index 44062f8f..24899ed1 100644 --- a/Tools/TestNetRewarder/TimeSegmenter.cs +++ b/Tools/TestNetRewarder/TimeSegmenter.cs @@ -3,51 +3,60 @@ using Utils; namespace TestNetRewarder { + public interface ITimeSegmentHandler + { + Task OnNewSegment(TimeRange timeRange); + } + public class TimeSegmenter { private readonly ILog log; + private readonly ITimeSegmentHandler handler; private readonly TimeSpan segmentSize; - private DateTime start; + private DateTime latest; - public TimeSegmenter(ILog log, Configuration configuration) + public TimeSegmenter(ILog log, Configuration configuration, ITimeSegmentHandler handler) { this.log = log; - + this.handler = handler; if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 1; - if (configuration.CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet."); segmentSize = configuration.Interval; - start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime; + latest = configuration.HistoryStartUtc; - log.Log("Starting time segments at " + start); + log.Log("Starting time segments at " + latest); log.Log("Segment size: " + Time.FormatDuration(segmentSize)); } - public async Task WaitForNextSegment(Func onSegment) + public async Task ProcessNextSegment() { - 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; - 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); + var end = latest + segmentSize; + var waited = await WaitUntilTimeSegmentInPast(end); if (Program.CancellationToken.IsCancellationRequested) return; var postfix = "(Catching up...)"; if (waited) postfix = "(Real-time)"; + log.Log($"Time segment [{latest} to {end}] {postfix}"); + + var range = new TimeRange(latest, end); + latest = end; - log.Log($"Time segment [{start} to {end}] {postfix}"); - var range = new TimeRange(start, end); - start = end; + await handler.OnNewSegment(range); + } - await onSegment(range); + private async Task WaitUntilTimeSegmentInPast(DateTime end) + { + await Task.Delay(TimeSpan.FromSeconds(3), Program.CancellationToken); + + var now = DateTime.UtcNow; + while (end > now) + { + var delay = (end - now) + TimeSpan.FromSeconds(3); + await Task.Delay(delay, Program.CancellationToken); + return true; + } + return false; } } } From bed57dd35b6ad854301f22103c6bc9664d8e5a23 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Jun 2024 15:34:08 +0200 Subject: [PATCH 122/142] Finish replace of old chainstate object in rewarder bot --- Framework/DiscordRewards/CheckConfig.cs | 8 +- .../DiscordRewards/GiveRewardsCommand.cs | 5 + Framework/DiscordRewards/RewardRepo.cs | 12 +- .../ChainMonitor/ChainState.cs | 3 +- .../ChainMonitor/ChainStateRequest.cs | 34 +++++ .../DoNothingChainEventHandler.cs | 4 - .../UtilityTests/DiscordBotTests.cs | 11 +- Tools/TestNetRewarder/BufferLogger.cs | 52 +++++++ Tools/TestNetRewarder/ChainChangeMux.cs | 45 ++++++ Tools/TestNetRewarder/ChainState.cs | 35 ----- Tools/TestNetRewarder/Checks.cs | 141 ----------------- Tools/TestNetRewarder/MarketBuffer.cs | 70 +++++++++ Tools/TestNetRewarder/MarketTracker.cs | 143 ++++-------------- Tools/TestNetRewarder/Processor.cs | 113 ++++---------- Tools/TestNetRewarder/Program.cs | 11 +- Tools/TestNetRewarder/RequestBuilder.cs | 40 +++++ Tools/TestNetRewarder/RewardCheck.cs | 98 ++++++++++++ Tools/TestNetRewarder/RewardChecker.cs | 97 +----------- 18 files changed, 427 insertions(+), 495 deletions(-) create mode 100644 Tools/TestNetRewarder/BufferLogger.cs create mode 100644 Tools/TestNetRewarder/ChainChangeMux.cs delete mode 100644 Tools/TestNetRewarder/ChainState.cs delete mode 100644 Tools/TestNetRewarder/Checks.cs create mode 100644 Tools/TestNetRewarder/MarketBuffer.cs create mode 100644 Tools/TestNetRewarder/RequestBuilder.cs create mode 100644 Tools/TestNetRewarder/RewardCheck.cs diff --git a/Framework/DiscordRewards/CheckConfig.cs b/Framework/DiscordRewards/CheckConfig.cs index 9c4fccb0..34425cea 100644 --- a/Framework/DiscordRewards/CheckConfig.cs +++ b/Framework/DiscordRewards/CheckConfig.cs @@ -13,9 +13,9 @@ namespace DiscordRewards public enum CheckType { Uninitialized, - FilledSlot, - FinishedSlot, - PostedContract, - StartedContract, + HostFilledSlot, + HostFinishedSlot, + ClientPostedContract, + ClientStartedContract, } } diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/GiveRewardsCommand.cs index 48dabcca..dffb4ede 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/GiveRewardsCommand.cs @@ -5,6 +5,11 @@ public RewardUsersCommand[] Rewards { get; set; } = Array.Empty(); public MarketAverage[] Averages { get; set; } = Array.Empty(); public string[] EventsOverview { get; set; } = Array.Empty(); + + public bool HasAny() + { + return Rewards.Any() || Averages.Any() || EventsOverview.Any(); + } } public class RewardUsersCommand diff --git a/Framework/DiscordRewards/RewardRepo.cs b/Framework/DiscordRewards/RewardRepo.cs index 51ac3fcb..28f6173a 100644 --- a/Framework/DiscordRewards/RewardRepo.cs +++ b/Framework/DiscordRewards/RewardRepo.cs @@ -11,19 +11,19 @@ namespace DiscordRewards // Filled any slot new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig { - Type = CheckType.FilledSlot + Type = CheckType.HostFilledSlot }), // Finished any slot new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig { - Type = CheckType.FinishedSlot + Type = CheckType.HostFinishedSlot }), // Finished a sizable slot new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig { - Type = CheckType.FinishedSlot, + Type = CheckType.HostFinishedSlot, MinSlotSize = 10.MB(), MinDuration = TimeSpan.FromMinutes(5.0), }), @@ -31,19 +31,19 @@ namespace DiscordRewards // Posted any contract new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig { - Type = CheckType.PostedContract + Type = CheckType.ClientPostedContract }), // Started any contract new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig { - Type = CheckType.StartedContract + Type = CheckType.ClientStartedContract }), // Started a sizable contract new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig { - Type = CheckType.StartedContract, + Type = CheckType.ClientStartedContract, MinNumberOfHosts = 4, MinSlotSize = 10.MB(), MinDuration = TimeSpan.FromMinutes(5.0), diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 875ea8ea..0d6af676 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -8,7 +8,6 @@ namespace CodexContractsPlugin.ChainMonitor public interface IChainStateChangeHandler { void OnNewRequest(IChainStateRequest request); - void OnRequestStarted(IChainStateRequest request); void OnRequestFinished(IChainStateRequest request); void OnRequestFulfilled(IChainStateRequest request); void OnRequestCancelled(IChainStateRequest request); @@ -115,6 +114,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; + r.Hosts.Add(request.Host, (int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFilled"); handler.OnSlotFilled(r, request.SlotIndex); } @@ -123,6 +123,7 @@ namespace CodexContractsPlugin.ChainMonitor { var r = FindRequest(request.RequestId); if (r == null) return; + r.Hosts.RemoveHost((int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFreed"); handler.OnSlotFreed(r, request.SlotIndex); } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs index 419e78c5..d908d193 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.Marketplace; +using GethPlugin; using Logging; namespace CodexContractsPlugin.ChainMonitor @@ -9,6 +10,8 @@ namespace CodexContractsPlugin.ChainMonitor RequestState State { get; } DateTime ExpiryUtc { get; } DateTime FinishedUtc { get; } + EthAddress Client { get; } + RequestHosts Hosts { get; } } public class ChainStateRequest : IChainStateRequest @@ -25,12 +28,17 @@ namespace CodexContractsPlugin.ChainMonitor FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); Log($"[{request.Block.BlockNumber}] Created as {State}."); + + Client = new EthAddress(request.Client); + Hosts = new RequestHosts(); } public Request Request { get; } public RequestState State { get; private set; } public DateTime ExpiryUtc { get; } public DateTime FinishedUtc { get; } + public EthAddress Client { get; } + public RequestHosts Hosts { get; } public void UpdateState(ulong blockNumber, RequestState newState) { @@ -43,4 +51,30 @@ namespace CodexContractsPlugin.ChainMonitor log.Log($"Request '{Request.Id}': {msg}"); } } + + public class RequestHosts + { + private readonly Dictionary hosts = new Dictionary(); + + public void Add(EthAddress host, int index) + { + hosts.Add(index, host); + } + + public void RemoveHost(int index) + { + hosts.Remove(index); + } + + public EthAddress? GetHost(int index) + { + if (!hosts.ContainsKey(index)) return null; + return hosts[index]; + } + + public EthAddress[] GetHosts() + { + return hosts.Values.ToArray(); + } + } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs index 65e038b2..8a3709cc 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -20,10 +20,6 @@ namespace CodexContractsPlugin.ChainMonitor { } - public void OnRequestStarted(IChainStateRequest request) - { - } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) { } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 3ae94741..7aafab6b 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -39,27 +39,24 @@ namespace CodexTests.UtilityTests var client = StartClient(geth, contracts); var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); - var chainState = ChainState.FromEvents( - GetTestLog(), - events, - new DoNothingChainEventHandler()); + var chainState = new ChainState(GetTestLog(), contracts, new DoNothingChainEventHandler(), GetTestRunTimeRange().From); var apiCalls = new RewardApiCalls(Ci, botContainer); apiCalls.Start(OnCommand); var purchaseContract = ClientPurchasesStorage(client); - chainState.Update(contracts); + chainState.Update(); Assert.That(chainState.Requests.Length, Is.EqualTo(1)); purchaseContract.WaitForStorageContractStarted(); - chainState.Update(contracts); + chainState.Update(); purchaseContract.WaitForStorageContractFinished(); Thread.Sleep(rewarderInterval * 2); apiCalls.Stop(); - chainState.Update(contracts); + chainState.Update(); foreach (var r in repo.Rewards) { diff --git a/Tools/TestNetRewarder/BufferLogger.cs b/Tools/TestNetRewarder/BufferLogger.cs new file mode 100644 index 00000000..f7f9925e --- /dev/null +++ b/Tools/TestNetRewarder/BufferLogger.cs @@ -0,0 +1,52 @@ +using Logging; + +namespace TestNetRewarder +{ + public class BufferLogger : ILog + { + private readonly List lines = new List(); + + public void AddStringReplace(string from, string to) + { + throw new NotImplementedException(); + } + + public LogFile CreateSubfile(string ext = "log") + { + throw new NotImplementedException(); + } + + public void Debug(string message = "", int skipFrames = 0) + { + lines.Add(message); + Trim(); + } + + public void Error(string message) + { + lines.Add($"Error: {message}"); + Trim(); + } + + public void Log(string message) + { + lines.Add(message); + Trim(); + } + + public string[] Get() + { + var result = lines.ToArray(); + lines.Clear(); + return result; + } + + private void Trim() + { + if (lines.Count > 100) + { + lines.RemoveRange(0, 50); + } + } + } +} diff --git a/Tools/TestNetRewarder/ChainChangeMux.cs b/Tools/TestNetRewarder/ChainChangeMux.cs new file mode 100644 index 00000000..f70490bf --- /dev/null +++ b/Tools/TestNetRewarder/ChainChangeMux.cs @@ -0,0 +1,45 @@ +using CodexContractsPlugin.ChainMonitor; +using System.Numerics; + +namespace TestNetRewarder +{ + public class ChainChangeMux : IChainStateChangeHandler + { + private readonly IChainStateChangeHandler[] handlers; + + public ChainChangeMux(params IChainStateChangeHandler[] handlers) + { + this.handlers = handlers; + } + + public void OnNewRequest(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnNewRequest(request); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestCancelled(request); + } + + public void OnRequestFinished(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestFinished(request); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + foreach (var handler in handlers) handler.OnRequestFulfilled(request); + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + foreach (var handler in handlers) handler.OnSlotFilled(request, slotIndex); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + foreach (var handler in handlers) handler.OnSlotFreed(request, slotIndex); + } + } +} diff --git a/Tools/TestNetRewarder/ChainState.cs b/Tools/TestNetRewarder/ChainState.cs deleted file mode 100644 index a22537f0..00000000 --- a/Tools/TestNetRewarder/ChainState.cs +++ /dev/null @@ -1,35 +0,0 @@ -using CodexContractsPlugin; -using CodexContractsPlugin.Marketplace; -using NethereumWorkflow.BlockUtils; -using Newtonsoft.Json; -using Utils; - -namespace TestNetRewarder -{ - public class Keepers - { - private readonly string[] colorIcons = new[] - { - "🔴", - "🟠", - "🟡", - "🟢", - "🔵", - "🟣", - "🟤", - "⚫", - "⚪", - "🟥", - "🟧", - "🟨", - "🟩", - "🟦", - "🟪", - "🟫", - "⬛", - "⬜", - "🔶", - "🔷" - }; - } -} diff --git a/Tools/TestNetRewarder/Checks.cs b/Tools/TestNetRewarder/Checks.cs deleted file mode 100644 index 43102e06..00000000 --- a/Tools/TestNetRewarder/Checks.cs +++ /dev/null @@ -1,141 +0,0 @@ -using CodexContractsPlugin.Marketplace; -using GethPlugin; -using NethereumWorkflow; -using Utils; - -namespace TestNetRewarder -{ - public interface ICheck - { - EthAddress[] Check(ChainState state); - } - - public class FilledAnySlotCheck : ICheck - { - public EthAddress[] Check(ChainState state) - { - return state.SlotFilledEvents.Select(e => e.Host).ToArray(); - } - } - - public class FinishedSlotCheck : ICheck - { - private readonly ByteSize minSize; - private readonly TimeSpan minDuration; - - public FinishedSlotCheck(ByteSize minSize, TimeSpan minDuration) - { - this.minSize = minSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.FinishedRequests - .Where(r => - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .SelectMany(r => r.Hosts) - .ToArray(); - } - - private bool MeetsSizeRequirement(StorageRequest r) - { - var slotSize = r.Request.Ask.SlotSize.ToDecimal(); - decimal min = minSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(StorageRequest r) - { - var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); - return duration >= minDuration; - } - } - - public class PostedContractCheck : ICheck - { - private readonly ulong minNumberOfHosts; - private readonly ByteSize minSlotSize; - private readonly TimeSpan minDuration; - - public PostedContractCheck(ulong minNumberOfHosts, ByteSize minSlotSize, TimeSpan minDuration) - { - this.minNumberOfHosts = minNumberOfHosts; - this.minSlotSize = minSlotSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.NewRequests - .Where(r => - MeetsNumSlotsRequirement(r) && - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .Select(r => r.ClientAddress) - .ToArray(); - } - - private bool MeetsNumSlotsRequirement(Request r) - { - return r.Ask.Slots >= minNumberOfHosts; - } - - private bool MeetsSizeRequirement(Request r) - { - var slotSize = r.Ask.SlotSize.ToDecimal(); - decimal min = minSlotSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(Request r) - { - var duration = TimeSpan.FromSeconds((double)r.Ask.Duration); - return duration >= minDuration; - } - } - - public class StartedContractCheck : ICheck - { - private readonly ulong minNumberOfHosts; - private readonly ByteSize minSlotSize; - private readonly TimeSpan minDuration; - - public StartedContractCheck(ulong minNumberOfHosts, ByteSize minSlotSize, TimeSpan minDuration) - { - this.minNumberOfHosts = minNumberOfHosts; - this.minSlotSize = minSlotSize; - this.minDuration = minDuration; - } - - public EthAddress[] Check(ChainState state) - { - return state.StartedRequests - .Where(r => - MeetsNumSlotsRequirement(r) && - MeetsSizeRequirement(r) && - MeetsDurationRequirement(r)) - .Select(r => r.Request.ClientAddress) - .ToArray(); - } - - private bool MeetsNumSlotsRequirement(StorageRequest r) - { - return r.Request.Ask.Slots >= minNumberOfHosts; - } - - private bool MeetsSizeRequirement(StorageRequest r) - { - var slotSize = r.Request.Ask.SlotSize.ToDecimal(); - decimal min = minSlotSize.SizeInBytes; - return slotSize >= min; - } - - private bool MeetsDurationRequirement(StorageRequest r) - { - var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); - return duration >= minDuration; - } - } -} diff --git a/Tools/TestNetRewarder/MarketBuffer.cs b/Tools/TestNetRewarder/MarketBuffer.cs new file mode 100644 index 00000000..45573785 --- /dev/null +++ b/Tools/TestNetRewarder/MarketBuffer.cs @@ -0,0 +1,70 @@ +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; +using DiscordRewards; +using System.Numerics; + +namespace TestNetRewarder +{ + public class MarketBuffer + { + private readonly List requests = new List(); + private readonly TimeSpan bufferSpan; + + public MarketBuffer(TimeSpan bufferSpan) + { + this.bufferSpan = bufferSpan; + } + + public void Add(IChainStateRequest request) + { + requests.Add(request); + } + + public void Update() + { + var now = DateTime.UtcNow; + requests.RemoveAll(r => (now - r.FinishedUtc) > bufferSpan); + } + + public MarketAverage? GetAverage() + { + if (requests.Count == 0) return null; + + return new MarketAverage + { + NumberOfFinished = requests.Count, + TimeRangeSeconds = (int)bufferSpan.TotalSeconds, + Price = Average(s => s.Request.Ask.Reward), + Duration = Average(s => s.Request.Ask.Duration), + Size = Average(s => GetTotalSize(s.Request.Ask)), + Collateral = Average(s => s.Request.Ask.Collateral), + ProofProbability = Average(s => s.Request.Ask.ProofProbability) + }; + } + + private float Average(Func getValue) + { + return Average(s => Convert.ToInt32(getValue(s))); + } + + private float Average(Func getValue) + { + var sum = 0.0f; + float count = requests.Count; + foreach (var r in requests) + { + sum += getValue(r); + } + + if (count < 1.0f) return 0.0f; + return sum / count; + } + + private int GetTotalSize(Ask ask) + { + var nSlots = Convert.ToInt32(ask.Slots); + var slotSize = Convert.ToInt32(ask.SlotSize); + return nSlots * slotSize; + } + } +} diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index c0abad3b..669dd3aa 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,160 +1,73 @@ using CodexContractsPlugin.ChainMonitor; -using CodexContractsPlugin.Marketplace; using DiscordRewards; +using Logging; using System.Numerics; namespace TestNetRewarder { public class MarketTracker : IChainStateChangeHandler { - private readonly List buffer = new List(); + private readonly List buffers = new List(); + private readonly ILog log; - public MarketAverage[] ProcessChainState(ChainState chainState) + public MarketTracker(Configuration config, ILog log) { - var intervalCounts = GetInsightCounts(); - if (!intervalCounts.Any()) return Array.Empty(); + var intervals = GetInsightCounts(config); - UpdateBuffer(chainState, intervalCounts.Max()); - var result = intervalCounts - .Select(GenerateMarketAverage) - .Where(a => a != null) - .Cast() - .ToArray(); - - if (!result.Any()) result = Array.Empty(); - return result; - } - - private void UpdateBuffer(ChainState chainState, int maxNumberOfIntervals) - { - buffer.Add(chainState); - while (buffer.Count > maxNumberOfIntervals) + foreach (var i in intervals) { - buffer.RemoveAt(0); - } - } - - private MarketAverage? GenerateMarketAverage(int numberOfIntervals) - { - var states = SelectStates(numberOfIntervals); - return CreateAverage(states); - } - - private ChainState[] SelectStates(int numberOfIntervals) - { - if (numberOfIntervals < 1) return Array.Empty(); - if (numberOfIntervals > buffer.Count) return Array.Empty(); - return buffer.TakeLast(numberOfIntervals).ToArray(); - } - - private MarketAverage? CreateAverage(ChainState[] states) - { - try - { - return new MarketAverage - { - NumberOfFinished = CountNumberOfFinishedRequests(states), - TimeRangeSeconds = GetTotalTimeRange(states), - Price = Average(states, s => s.Request.Ask.Reward), - Duration = Average(states, s => s.Request.Ask.Duration), - Size = Average(states, s => GetTotalSize(s.Request.Ask)), - Collateral = Average(states, s => s.Request.Ask.Collateral), - ProofProbability = Average(states, s => s.Request.Ask.ProofProbability) - }; - } - catch (Exception ex) - { - Program.Log.Error($"Exception in CreateAverage: {ex}"); - return null; - } - } - - private int GetTotalSize(Ask ask) - { - var nSlots = Convert.ToInt32(ask.Slots); - var slotSize = Convert.ToInt32(ask.SlotSize); - return nSlots * slotSize; - } - - private float Average(ChainState[] states, Func getValue) - { - return Average(states, s => Convert.ToInt32(getValue(s))); - } - - private float Average(ChainState[] states, Func getValue) - { - var sum = 0.0f; - var count = 0.0f; - foreach (var state in states) - { - foreach (var finishedRequest in state.FinishedRequests) - { - sum += getValue(finishedRequest); - count++; - } + buffers.Add(new MarketBuffer( + config.Interval * i + )); } - if (count < 1.0f) return 0.0f; - return sum / count; + this.log = log; } - private int GetTotalTimeRange(ChainState[] states) + public MarketAverage[] GetAverages() { - return Convert.ToInt32((Program.Config.Interval * states.Length).TotalSeconds); - } + foreach (var b in buffers) b.Update(); - private int CountNumberOfFinishedRequests(ChainState[] states) - { - return states.Sum(s => s.FinishedRequests.Length); - } - - private int[] GetInsightCounts() - { - try - { - var tokens = Program.Config.MarketInsights.Split(';').ToArray(); - return tokens.Select(t => Convert.ToInt32(t)).ToArray(); - } - catch (Exception ex) - { - Program.Log.Error($"Exception when parsing MarketInsights config parameters: {ex}"); - } - return Array.Empty(); + return buffers.Select(b => b.GetAverage()).Where(a => a != null).Cast().ToArray(); } public void OnNewRequest(IChainStateRequest request) { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); } public void OnRequestFinished(IChainStateRequest request) { - throw new NotImplementedException(); + foreach (var b in buffers) b.Add(request); } public void OnRequestFulfilled(IChainStateRequest request) { - throw new NotImplementedException(); } public void OnRequestCancelled(IChainStateRequest request) { - throw new NotImplementedException(); } public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) { - throw new NotImplementedException(); } public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) { - throw new NotImplementedException(); + } + + private int[] GetInsightCounts(Configuration config) + { + try + { + var tokens = config.MarketInsights.Split(';').ToArray(); + return tokens.Select(t => Convert.ToInt32(t)).ToArray(); + } + catch (Exception ex) + { + log.Error($"Exception when parsing MarketInsights config parameters: {ex}"); + } + return Array.Empty(); } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index b3586659..8cbc4dbd 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -1,29 +1,36 @@ using CodexContractsPlugin; using CodexContractsPlugin.ChainMonitor; -using DiscordRewards; -using GethPlugin; using Logging; -using Newtonsoft.Json; -using System.Numerics; using Utils; namespace TestNetRewarder { - public class Processor : ITimeSegmentHandler, IChainStateChangeHandler + public class Processor : ITimeSegmentHandler { - private readonly RewardChecker rewardChecker = new RewardChecker(); - private readonly MarketTracker marketTracker = new MarketTracker(); + private readonly RequestBuilder builder; + private readonly RewardChecker rewardChecker; + private readonly MarketTracker marketTracker; + private readonly BufferLogger bufferLogger; private readonly ChainState chainState; - private readonly Configuration config; + private readonly BotClient client; private readonly ILog log; - private BlockInterval? lastBlockRange; - public Processor(Configuration config, ICodexContracts contracts, ILog log) + public Processor(Configuration config, BotClient client, ICodexContracts contracts, ILog log) { - this.config = config; + this.client = client; this.log = log; - chainState = new ChainState(log, contracts, this, config.HistoryStartUtc); + builder = new RequestBuilder(); + rewardChecker = new RewardChecker(builder); + marketTracker = new MarketTracker(config, log); + bufferLogger = new BufferLogger(); + + var handler = new ChainChangeMux( + rewardChecker.Handler, + marketTracker + ); + + chainState = new ChainState(new LogSplitter(log, bufferLogger), contracts, handler, config.HistoryStartUtc); } public async Task OnNewSegment(TimeRange timeRange) @@ -31,8 +38,15 @@ namespace TestNetRewarder try { chainState.Update(timeRange.To); - - await ProcessChainState(chainState); + + var averages = marketTracker.GetAverages(); + var lines = bufferLogger.Get(); + + var request = builder.Build(averages, lines); + if (request.HasAny()) + { + await client.SendRewards(request); + } } catch (Exception ex) { @@ -40,76 +54,5 @@ namespace TestNetRewarder throw; } } - - private async Task ProcessChainState(ChainState chainState) - { - log.Log(chainState.EntireString()); - - var outgoingRewards = new List(); - foreach (var reward in rewardRepo.Rewards) - { - ProcessReward(outgoingRewards, reward, chainState); - } - - var marketAverages = GetMarketAverages(chainState); - var eventsOverview = GenerateEventsOverview(chainState); - - log.Log($"Found {outgoingRewards.Count} rewards. " + - $"Found {marketAverages.Length} market averages. " + - $"Found {eventsOverview.Length} events."); - - if (outgoingRewards.Any() || marketAverages.Any() || eventsOverview.Any()) - { - if (!await SendRewardsCommand(outgoingRewards, marketAverages, eventsOverview)) - { - log.Error("Failed to send reward command."); - } - } - } - - private string[] GenerateEventsOverview(ChainState chainState) - { - return chainState.GenerateOverview(); - } - - private MarketAverage[] GetMarketAverages(ChainState chainState) - { - return marketTracker.ProcessChainState(chainState); - } - - public void OnNewRequest(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFinished(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFulfilled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestCancelled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } - - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } } } diff --git a/Tools/TestNetRewarder/Program.cs b/Tools/TestNetRewarder/Program.cs index 1106ff76..0056ffec 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -1,16 +1,15 @@ using ArgsUniform; using Logging; -using Nethereum.Model; using Utils; namespace TestNetRewarder { public class Program { - public static Configuration Config { get; private set; } = null!; - public static ILog Log { get; private set; } = null!; - public static CancellationToken CancellationToken { get; private set; } - public static BotClient BotClient { get; private set; } = null!; + public static CancellationToken CancellationToken; + private static Configuration Config = null!; + private static ILog Log = null!; + private static BotClient BotClient = null!; private static Processor processor = null!; private static DateTime lastCheck = DateTime.MinValue; @@ -32,7 +31,7 @@ namespace TestNetRewarder if (connector == null) throw new Exception("Invalid Geth information"); BotClient = new BotClient(Config, Log); - processor = new Processor(Config, connector.CodexContracts, Log); + processor = new Processor(Config, BotClient, connector.CodexContracts, Log); EnsurePath(Config.DataPath); EnsurePath(Config.LogPath); diff --git a/Tools/TestNetRewarder/RequestBuilder.cs b/Tools/TestNetRewarder/RequestBuilder.cs new file mode 100644 index 00000000..ea06eaac --- /dev/null +++ b/Tools/TestNetRewarder/RequestBuilder.cs @@ -0,0 +1,40 @@ +using DiscordRewards; +using GethPlugin; + +namespace TestNetRewarder +{ + public class RequestBuilder : IRewardGiver + { + private readonly Dictionary> rewards = new Dictionary>(); + + public void Give(RewardConfig reward, EthAddress receiver) + { + if (rewards.ContainsKey(reward.RoleId)) + { + rewards[reward.RoleId].Add(receiver); + } + else + { + rewards.Add(reward.RoleId, new List { receiver }); + } + } + + public GiveRewardsCommand Build(MarketAverage[] marketAverages, string[] lines) + { + var result = new GiveRewardsCommand + { + Rewards = rewards.Select(p => new RewardUsersCommand + { + RewardId = p.Key, + UserAddresses = p.Value.Select(v => v.Address).ToArray() + }).ToArray(), + Averages = marketAverages, + EventsOverview = lines + }; + + rewards.Clear(); + + return result; + } + } +} diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs new file mode 100644 index 00000000..9be87d22 --- /dev/null +++ b/Tools/TestNetRewarder/RewardCheck.cs @@ -0,0 +1,98 @@ +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; +using GethPlugin; +using NethereumWorkflow; +using System.Numerics; + +namespace TestNetRewarder +{ + public interface IRewardGiver + { + void Give(RewardConfig reward, EthAddress receiver); + } + + public class RewardCheck : IChainStateChangeHandler + { + private readonly RewardConfig reward; + private readonly IRewardGiver giver; + + public RewardCheck(RewardConfig reward, IRewardGiver giver) + { + this.reward = reward; + this.giver = giver; + } + + public void OnNewRequest(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.ClientPostedContract, request)) + { + GiveReward(reward, request.Client); + } + } + + public void OnRequestCancelled(IChainStateRequest request) + { + } + + public void OnRequestFinished(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.HostFinishedSlot, request)) + { + foreach (var host in request.Hosts.GetHosts()) + { + GiveReward(reward, host); + } + } + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + if (MeetsRequirements(CheckType.ClientStartedContract, request)) + { + GiveReward(reward, request.Client); + } + } + + public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + { + if (MeetsRequirements(CheckType.HostFilledSlot, request)) + { + var host = request.Hosts.GetHost((int)slotIndex); + if (host != null) + { + GiveReward(reward, host); + } + } + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + } + + private void GiveReward(RewardConfig reward, EthAddress receiver) + { + giver.Give(reward, receiver); + } + + private bool MeetsRequirements(CheckType type, IChainStateRequest request) + { + return + reward.CheckConfig.Type == type && + MeetsDurationRequirement(request) && + MeetsSizeRequirement(request); + } + + private bool MeetsSizeRequirement(IChainStateRequest r) + { + var slotSize = r.Request.Ask.SlotSize.ToDecimal(); + decimal min = reward.CheckConfig.MinSlotSize.SizeInBytes; + return slotSize >= min; + } + + private bool MeetsDurationRequirement(IChainStateRequest r) + { + var duration = TimeSpan.FromSeconds((double)r.Request.Ask.Duration); + return duration >= reward.CheckConfig.MinDuration; + } + } +} diff --git a/Tools/TestNetRewarder/RewardChecker.cs b/Tools/TestNetRewarder/RewardChecker.cs index 63151969..0d22baf4 100644 --- a/Tools/TestNetRewarder/RewardChecker.cs +++ b/Tools/TestNetRewarder/RewardChecker.cs @@ -1,102 +1,17 @@ using CodexContractsPlugin.ChainMonitor; using DiscordRewards; -using GethPlugin; -using Nethereum.Model; -using Newtonsoft.Json; -using System.Numerics; namespace TestNetRewarder { - public class RewardChecker : IChainStateChangeHandler + public class RewardChecker { - private static readonly RewardRepo rewardRepo = new RewardRepo(); - - private async Task SendRewardsCommand(List outgoingRewards, MarketAverage[] marketAverages, string[] eventsOverview) + public RewardChecker(IRewardGiver giver) { - var cmd = new GiveRewardsCommand - { - Rewards = outgoingRewards.ToArray(), - Averages = marketAverages.ToArray(), - EventsOverview = eventsOverview - }; - - log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd)); - return await Program.BotClient.SendRewards(cmd); + var repo = new RewardRepo(); + var checks = repo.Rewards.Select(r => new RewardCheck(r, giver)).ToArray(); + Handler = new ChainChangeMux(checks); } - private void ProcessReward(List outgoingRewards, RewardConfig reward, ChainState chainState) - { - var winningAddresses = PerformCheck(reward, chainState); - foreach (var win in winningAddresses) - { - log.Log($"Address '{win.Address}' wins '{reward.Message}'"); - } - if (winningAddresses.Any()) - { - outgoingRewards.Add(new RewardUsersCommand - { - RewardId = reward.RoleId, - UserAddresses = winningAddresses.Select(a => a.Address).ToArray() - }); - } - } - - private EthAddress[] PerformCheck(RewardConfig reward, ChainState chainState) - { - var check = GetCheck(reward.CheckConfig); - return check.Check(chainState).Distinct().ToArray(); - } - - private ICheck GetCheck(CheckConfig config) - { - switch (config.Type) - { - case CheckType.FilledSlot: - return new FilledAnySlotCheck(); - case CheckType.FinishedSlot: - return new FinishedSlotCheck(config.MinSlotSize, config.MinDuration); - case CheckType.PostedContract: - return new PostedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); - case CheckType.StartedContract: - return new StartedContractCheck(config.MinNumberOfHosts, config.MinSlotSize, config.MinDuration); - } - - throw new Exception("Unknown check type: " + config.Type); - } - - public void OnNewRequest(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestStarted(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFinished(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestFulfilled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnRequestCancelled(IChainStateRequest request) - { - throw new NotImplementedException(); - } - - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } - - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) - { - throw new NotImplementedException(); - } + public IChainStateChangeHandler Handler { get; } } } From cb4cdfe69a621e954babe580e5a853743e8b0630 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Jun 2024 15:59:54 +0200 Subject: [PATCH 123/142] better logging --- .../ChainMonitor/ChainState.cs | 4 ++-- .../UtilityTests/DiscordBotTests.cs | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 0d6af676..bc284eb2 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -115,7 +115,7 @@ namespace CodexContractsPlugin.ChainMonitor var r = FindRequest(request.RequestId); if (r == null) return; r.Hosts.Add(request.Host, (int)request.SlotIndex); - r.Log($"[{request.Block.BlockNumber}] SlotFilled"); + r.Log($"[{request.Block.BlockNumber}] SlotFilled (host:'{request.Host}', slotIndex:{request.SlotIndex})"); handler.OnSlotFilled(r, request.SlotIndex); } @@ -124,7 +124,7 @@ namespace CodexContractsPlugin.ChainMonitor var r = FindRequest(request.RequestId); if (r == null) return; r.Hosts.RemoveHost((int)request.SlotIndex); - r.Log($"[{request.Block.BlockNumber}] SlotFreed"); + r.Log($"[{request.Block.BlockNumber}] SlotFreed (slotIndex:{request.SlotIndex})"); handler.OnSlotFreed(r, request.SlotIndex); } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 7aafab6b..18ba6630 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -4,6 +4,7 @@ using CodexDiscordBotPlugin; using CodexPlugin; using Core; using DiscordRewards; +using DistTestCore; using GethPlugin; using KubernetesWorkflow.Types; using Newtonsoft.Json; @@ -24,6 +25,7 @@ namespace CodexTests.UtilityTests private readonly TimeSpan rewarderInterval = TimeSpan.FromMinutes(1); [Test] + [DontDownloadLogs] public void BotRewardTest() { var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); @@ -31,11 +33,9 @@ namespace CodexTests.UtilityTests var gethInfo = CreateGethInfo(geth, contracts); var botContainer = StartDiscordBot(gethInfo); - - StartHosts(geth, contracts); - var rewarderContainer = StartRewarderBot(gethInfo, botContainer); + StartHosts(geth, contracts); var client = StartClient(geth, contracts); var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); @@ -76,8 +76,15 @@ namespace CodexTests.UtilityTests private void OnCommand(GiveRewardsCommand call) { - if (call.Averages.Any()) Log($"API call: {call.Averages.Length} average."); - if (call.EventsOverview.Any()) Log($"API call: {call.EventsOverview.Length} events."); + Log($"API call:"); + foreach (var a in call.Averages) + { + Log("Average: " + JsonConvert.SerializeObject(a)); + } + foreach (var e in call.EventsOverview) + { + Log("Event: " + e); + } foreach (var r in call.Rewards) { var reward = repo.Rewards.Single(a => a.RoleId == r.RewardId); @@ -85,7 +92,7 @@ namespace CodexTests.UtilityTests foreach (var address in r.UserAddresses) { var user = IdentifyAccount(address); - Log("API call: " + user + ": " + reward.Message); + Log("Reward: " + user + ": " + reward.Message); } } } From f7c45d17d740adf43482ac9e8e1f2fbf25be69da Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 18 Jun 2024 09:57:38 +0200 Subject: [PATCH 124/142] wip --- .../CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index 55b60a23..2d11cbd4 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -7,7 +7,8 @@ namespace CodexDiscordBotPlugin public class RewarderBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-rewarder"; - public override string Image => "codexstorage/codex-rewarderbot:sha-12dc7ef"; + public override string Image => "thatbenbierens/codex-rewardbot:newstate"; + //"codexstorage/codex-rewarderbot:sha-12dc7ef"; protected override void Initialize(StartupConfig startupConfig) { From e1da38b6e8465aefc7c805c1fed46cf711839b52 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 18 Jun 2024 11:04:04 +0200 Subject: [PATCH 125/142] Adds test for making sure node does not crash after mysterious CID is downloaded --- Tests/CodexTests/BasicTests/OneClientTests.cs | 12 ------- .../CodexTests/BasicTests/ThreeClientTest.cs | 32 ++++++++++++++++++- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Tests/CodexTests/BasicTests/OneClientTests.cs b/Tests/CodexTests/BasicTests/OneClientTests.cs index 451b1cd0..908fb937 100644 --- a/Tests/CodexTests/BasicTests/OneClientTests.cs +++ b/Tests/CodexTests/BasicTests/OneClientTests.cs @@ -17,18 +17,6 @@ namespace CodexTests.BasicTests LogNodeStatus(primary); } - [Test] - public void RestartTest() - { - var primary = StartCodex(); - - primary.Stop(waitTillStopped: true); - - primary = StartCodex(); - - PerformOneClientTest(primary); - } - private void PerformOneClientTest(ICodexNode primary) { var testFile = GenerateTestFile(1.MB()); diff --git a/Tests/CodexTests/BasicTests/ThreeClientTest.cs b/Tests/CodexTests/BasicTests/ThreeClientTest.cs index 2285aa95..e6f9428e 100644 --- a/Tests/CodexTests/BasicTests/ThreeClientTest.cs +++ b/Tests/CodexTests/BasicTests/ThreeClientTest.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using CodexPlugin; +using NUnit.Framework; using Utils; namespace CodexTests.BasicTests @@ -20,5 +21,34 @@ namespace CodexTests.BasicTests testFile.AssertIsEqual(downloadedFile); } + + [Test] + public void DownloadingUnknownCidDoesNotCauseCrash() + { + var node = StartCodex(2).First(); + + var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ"); + + try + { + node.DownloadContent(unknownCid); + } + catch (Exception ex) + { + if (!ex.Message.StartsWith("Retry 'DownloadFile' timed out")) + { + throw; + } + } + + // Check that the node stays alive for at least another 5 minutes. + var start = DateTime.UtcNow; + while ((DateTime.UtcNow - start) < TimeSpan.FromMinutes(5)) + { + Thread.Sleep(5000); + var info = node.GetDebugInfo(); + Assert.That(!string.IsNullOrEmpty(info.Id)); + } + } } } From 5ca135646af03f5a7064f95de1dda4aac9c3623b Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 19 Jun 2024 10:39:14 +0200 Subject: [PATCH 126/142] Container crash detection during start-up --- Framework/Core/CoreInterface.cs | 8 ++--- Framework/Core/DownloadedLog.cs | 7 ++-- Framework/Core/LogDownloadHandler.cs | 28 --------------- Framework/KubernetesWorkflow/CrashWatcher.cs | 7 ++-- Framework/KubernetesWorkflow/K8sController.cs | 34 ++++++++++++++++--- Framework/KubernetesWorkflow/LogHandler.cs | 25 +++++++++++++- .../KubernetesWorkflow/StartupWorkflow.cs | 6 ++-- ProjectPlugins/CodexPlugin/CodexAccess.cs | 26 ++------------ 8 files changed, 70 insertions(+), 71 deletions(-) delete mode 100644 Framework/Core/LogDownloadHandler.cs diff --git a/Framework/Core/CoreInterface.cs b/Framework/Core/CoreInterface.cs index b43c1c20..81483a8b 100644 --- a/Framework/Core/CoreInterface.cs +++ b/Framework/Core/CoreInterface.cs @@ -30,11 +30,11 @@ namespace Core public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null) { var workflow = entryPoint.Tools.CreateWorkflow(); - var file = entryPoint.Tools.GetLog().CreateSubfile(); - entryPoint.Tools.GetLog().Log($"Downloading container log for '{container.Name}' to file '{file.FullFilename}'..."); - var logHandler = new LogDownloadHandler(container.Name, file); + var msg = $"Downloading container log for '{container.Name}'"; + entryPoint.Tools.GetLog().Log(msg); + var logHandler = new WriteToFileLogHandler(entryPoint.Tools.GetLog(), msg); workflow.DownloadContainerLog(container, logHandler, tailLines); - return logHandler.DownloadLog(); + return new DownloadedLog(logHandler); } public string ExecuteContainerCommand(IHasContainer containerSource, string command, params string[] args) diff --git a/Framework/Core/DownloadedLog.cs b/Framework/Core/DownloadedLog.cs index fa4d5597..3979f3ee 100644 --- a/Framework/Core/DownloadedLog.cs +++ b/Framework/Core/DownloadedLog.cs @@ -1,4 +1,5 @@ -using Logging; +using KubernetesWorkflow; +using Logging; namespace Core { @@ -14,9 +15,9 @@ namespace Core { private readonly LogFile logFile; - internal DownloadedLog(LogFile logFile) + internal DownloadedLog(WriteToFileLogHandler logHandler) { - this.logFile = logFile; + logFile = logHandler.LogFile; } public void IterateLines(Action action) diff --git a/Framework/Core/LogDownloadHandler.cs b/Framework/Core/LogDownloadHandler.cs deleted file mode 100644 index e1736ed1..00000000 --- a/Framework/Core/LogDownloadHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using KubernetesWorkflow; -using Logging; - -namespace Core -{ - internal class LogDownloadHandler : LogHandler, ILogHandler - { - private readonly LogFile log; - - internal LogDownloadHandler(string description, LogFile log) - { - this.log = log; - - log.Write($"{description} -->> {log.FullFilename}"); - log.WriteRaw(description); - } - - internal IDownloadedLog DownloadLog() - { - return new DownloadedLog(log); - } - - protected override void ProcessLine(string line) - { - log.WriteRaw(line); - } - } -} diff --git a/Framework/KubernetesWorkflow/CrashWatcher.cs b/Framework/KubernetesWorkflow/CrashWatcher.cs index 57e30eb6..7f38cfb5 100644 --- a/Framework/KubernetesWorkflow/CrashWatcher.cs +++ b/Framework/KubernetesWorkflow/CrashWatcher.cs @@ -11,7 +11,6 @@ namespace KubernetesWorkflow private readonly string podName; private readonly string recipeName; private readonly string k8sNamespace; - private ILogHandler? logHandler; private CancellationTokenSource cts; private Task? worker; private Exception? workerException; @@ -27,11 +26,10 @@ namespace KubernetesWorkflow cts = new CancellationTokenSource(); } - public void Start(ILogHandler logHandler) + public void Start() { if (worker != null) throw new InvalidOperationException(); - this.logHandler = logHandler; cts = new CancellationTokenSource(); worker = Task.Run(Worker); } @@ -93,7 +91,8 @@ namespace KubernetesWorkflow private void DownloadCrashedContainerLogs(Kubernetes client) { using var stream = client.ReadNamespacedPodLog(podName, k8sNamespace, recipeName, previous: true); - logHandler!.Log(stream); + var handler = new WriteToFileLogHandler(log, "Crash detected for " + containerName); + handler.Log(stream); } } } diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 00c7563d..e5fac7c4 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -45,7 +45,7 @@ namespace KubernetesWorkflow public void WaitUntilOnline(RunningContainer container) { - WaitUntilDeploymentOnline(container.Recipe.Name); + WaitUntilDeploymentOnline(container); } public PodInfo GetPodInfo(RunningDeployment deployment) @@ -64,14 +64,14 @@ namespace KubernetesWorkflow if (waitTillStopped) WaitUntilPodsForDeploymentAreOffline(startResult.Deployment); } - public void DownloadPodLog(RunningContainer container, ILogHandler logHandler, int? tailLines) + public void DownloadPodLog(RunningContainer container, ILogHandler logHandler, int? tailLines, bool? previous) { log.Debug(); var podName = GetPodName(container); var recipeName = container.Recipe.Name; - using var stream = client.Run(c => c.ReadNamespacedPodLog(podName, K8sNamespace, recipeName, tailLines: tailLines)); + using var stream = client.Run(c => c.ReadNamespacedPodLog(podName, K8sNamespace, recipeName, tailLines: tailLines, previous: previous)); logHandler.Log(stream); } @@ -879,15 +879,39 @@ namespace KubernetesWorkflow WaitUntil(() => !IsNamespaceOnline(@namespace), nameof(WaitUntilNamespaceDeleted)); } - private void WaitUntilDeploymentOnline(string deploymentName) + private void WaitUntilDeploymentOnline(RunningContainer container) { WaitUntil(() => { - var deployment = client.Run(c => c.ReadNamespacedDeployment(deploymentName, K8sNamespace)); + CheckForCrash(container); + + var deployment = client.Run(c => c.ReadNamespacedDeployment(container.Recipe.Name, K8sNamespace)); return deployment?.Status.AvailableReplicas != null && deployment.Status.AvailableReplicas > 0; }, nameof(WaitUntilDeploymentOnline)); } + private void CheckForCrash(RunningContainer container) + { + var deploymentName = container.Recipe.Name; + var podName = GetPodName(container); + + var podInfo = client.Run(c => c.ReadNamespacedPod(podName, K8sNamespace)); + if (podInfo == null) return; + if (podInfo.Status == null) return; + if (podInfo.Status.ContainerStatuses == null) return; + + var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0); + if (result) + { + var msg = $"Pod crash detected for deployment {deploymentName} (pod:{podName})"; + log.Error(msg); + + DownloadPodLog(container, new WriteToFileLogHandler(log, msg), tailLines: null, previous: true); + + throw new Exception(msg); + } + } + private void WaitUntilDeploymentOffline(string deploymentName) { WaitUntil(() => diff --git a/Framework/KubernetesWorkflow/LogHandler.cs b/Framework/KubernetesWorkflow/LogHandler.cs index 77e57468..f185ba17 100644 --- a/Framework/KubernetesWorkflow/LogHandler.cs +++ b/Framework/KubernetesWorkflow/LogHandler.cs @@ -1,4 +1,6 @@ -namespace KubernetesWorkflow +using Logging; + +namespace KubernetesWorkflow { public interface ILogHandler { @@ -20,4 +22,25 @@ protected abstract void ProcessLine(string line); } + + public class WriteToFileLogHandler : LogHandler, ILogHandler + { + public WriteToFileLogHandler(ILog sourceLog, string description) + { + LogFile = sourceLog.CreateSubfile(); + + var msg = $"{description} -->> {LogFile.FullFilename}"; + sourceLog.Log(msg); + + LogFile.Write(msg); + LogFile.WriteRaw(description); + } + + public LogFile LogFile { get; } + + protected override void ProcessLine(string line) + { + LogFile.WriteRaw(line); + } + } } diff --git a/Framework/KubernetesWorkflow/StartupWorkflow.cs b/Framework/KubernetesWorkflow/StartupWorkflow.cs index 3a7326df..6a4d50ca 100644 --- a/Framework/KubernetesWorkflow/StartupWorkflow.cs +++ b/Framework/KubernetesWorkflow/StartupWorkflow.cs @@ -15,7 +15,7 @@ namespace KubernetesWorkflow PodInfo GetPodInfo(RunningPod pod); CrashWatcher CreateCrashWatcher(RunningContainer container); void Stop(RunningPod pod, bool waitTillStopped); - void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null); + void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null, bool? previous = null); string ExecuteCommand(RunningContainer container, string command, params string[] args); void DeleteNamespace(bool wait); void DeleteNamespacesStartingWith(string namespacePrefix, bool wait); @@ -106,11 +106,11 @@ namespace KubernetesWorkflow }); } - public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null) + public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null, bool? previous = null) { K8s(controller => { - controller.DownloadPodLog(container, logHandler, tailLines); + controller.DownloadPodLog(container, logHandler, tailLines, previous); }); } diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index 80f10611..110a386f 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -8,12 +8,11 @@ using Utils; namespace CodexPlugin { - public class CodexAccess : ILogHandler + public class CodexAccess { private readonly ILog log; private readonly IPluginTools tools; private readonly Mapper mapper = new Mapper(); - private bool hasContainerCrashed; public CodexAccess(IPluginTools tools, RunningPod container, CrashWatcher crashWatcher) { @@ -21,9 +20,8 @@ namespace CodexPlugin log = tools.GetLog(); Container = container; CrashWatcher = crashWatcher; - hasContainerCrashed = false; - CrashWatcher.Start(this); + CrashWatcher.Start(); } public RunningPod Container { get; } @@ -209,25 +207,7 @@ namespace CodexPlugin private void CheckContainerCrashed(HttpClient client) { - if (hasContainerCrashed) throw new Exception($"Container {GetName()} has crashed."); - } - - void ILogHandler.Log(Stream crashLog) - { - var file = log.CreateSubfile(); - Log($"Downloading log to '{file.FullFilename}'..."); - file.Write($"Container log for {Container.Name}."); - - using var reader = new StreamReader(crashLog); - var line = reader.ReadLine(); - while (line != null) - { - file.Write(line); - line = reader.ReadLine(); - } - - Log("Container log successfully downloaded."); - hasContainerCrashed = true; + if (CrashWatcher.HasContainerCrashed()) throw new Exception($"Container {GetName()} has crashed."); } private Retry CreateRetryConfig(string description, Action onFailure) From 5280f0732cce172aca876c67926d8d08f4c56065 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 19 Jun 2024 14:04:27 +0200 Subject: [PATCH 127/142] Sets up docker image for creating keys --- .github/workflows/docker-keymaker.yml | 27 ++++++++++++ Tools/KeyMaker/Controllers/KeyController.cs | 23 +++++++++++ Tools/KeyMaker/KeyMaker.csproj | 18 ++++++++ Tools/KeyMaker/KeyMaker.csproj.user | 6 +++ Tools/KeyMaker/KeyResponse.cs | 9 ++++ Tools/KeyMaker/Program.cs | 31 ++++++++++++++ Tools/KeyMaker/Properties/launchSettings.json | 41 +++++++++++++++++++ Tools/KeyMaker/appsettings.Development.json | 8 ++++ Tools/KeyMaker/appsettings.json | 9 ++++ Tools/KeyMaker/docker/Dockerfile | 26 ++++++++++++ cs-codex-dist-testing.sln | 7 ++++ 11 files changed, 205 insertions(+) create mode 100644 .github/workflows/docker-keymaker.yml create mode 100644 Tools/KeyMaker/Controllers/KeyController.cs create mode 100644 Tools/KeyMaker/KeyMaker.csproj create mode 100644 Tools/KeyMaker/KeyMaker.csproj.user create mode 100644 Tools/KeyMaker/KeyResponse.cs create mode 100644 Tools/KeyMaker/Program.cs create mode 100644 Tools/KeyMaker/Properties/launchSettings.json create mode 100644 Tools/KeyMaker/appsettings.Development.json create mode 100644 Tools/KeyMaker/appsettings.json create mode 100644 Tools/KeyMaker/docker/Dockerfile diff --git a/.github/workflows/docker-keymaker.yml b/.github/workflows/docker-keymaker.yml new file mode 100644 index 00000000..db067c3c --- /dev/null +++ b/.github/workflows/docker-keymaker.yml @@ -0,0 +1,27 @@ +name: Docker - KeyMaker + + +on: + push: + branches: + - master + tags: + - 'v*.*.*' + paths: + - 'Tools/KeyMaker/**' + - 'Framework/**' + - 'ProjectPlugins/**' + - .github/workflows/docker-KeyMaker.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/KeyMaker/docker/Dockerfile + docker_repo: codexstorage/codex-keymaker + secrets: inherit + diff --git a/Tools/KeyMaker/Controllers/KeyController.cs b/Tools/KeyMaker/Controllers/KeyController.cs new file mode 100644 index 00000000..dd218893 --- /dev/null +++ b/Tools/KeyMaker/Controllers/KeyController.cs @@ -0,0 +1,23 @@ +using GethPlugin; +using Microsoft.AspNetCore.Mvc; + +namespace KeyMaker.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class KeyController : ControllerBase + { + [HttpGet] + public KeyResponse Get() + { + var account = EthAccount.GenerateNew(); + + return new KeyResponse + { + Public = account.EthAddress.Address, + Private = account.PrivateKey, + Secure = "Not Secure! For demo/development purposes only!" + }; + } + } +} diff --git a/Tools/KeyMaker/KeyMaker.csproj b/Tools/KeyMaker/KeyMaker.csproj new file mode 100644 index 00000000..9ee1ab24 --- /dev/null +++ b/Tools/KeyMaker/KeyMaker.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + diff --git a/Tools/KeyMaker/KeyMaker.csproj.user b/Tools/KeyMaker/KeyMaker.csproj.user new file mode 100644 index 00000000..9ff5820a --- /dev/null +++ b/Tools/KeyMaker/KeyMaker.csproj.user @@ -0,0 +1,6 @@ + + + + https + + \ No newline at end of file diff --git a/Tools/KeyMaker/KeyResponse.cs b/Tools/KeyMaker/KeyResponse.cs new file mode 100644 index 00000000..df8fb0b7 --- /dev/null +++ b/Tools/KeyMaker/KeyResponse.cs @@ -0,0 +1,9 @@ +namespace KeyMaker +{ + public class KeyResponse + { + public string Public { get; set; } = string.Empty; + public string Private { get; set; } = string.Empty; + public string Secure { get; set; } = string.Empty; + } +} diff --git a/Tools/KeyMaker/Program.cs b/Tools/KeyMaker/Program.cs new file mode 100644 index 00000000..9c24f8fc --- /dev/null +++ b/Tools/KeyMaker/Program.cs @@ -0,0 +1,31 @@ +var builder = WebApplication.CreateBuilder(args); + +var listenPort = Environment.GetEnvironmentVariable("APIPORT"); +if (string.IsNullOrEmpty(listenPort)) listenPort = "31090"; + +builder.WebHost.ConfigureKestrel((context, options) => +{ + options.ListenAnyIP(Convert.ToInt32(listenPort)); +}); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +Console.WriteLine("KeyMaker listening on port " + listenPort); + +app.Run(); diff --git a/Tools/KeyMaker/Properties/launchSettings.json b/Tools/KeyMaker/Properties/launchSettings.json new file mode 100644 index 00000000..7517b23f --- /dev/null +++ b/Tools/KeyMaker/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:17248", + "sslPort": 44396 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5069", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7056;http://localhost:5069", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Tools/KeyMaker/appsettings.Development.json b/Tools/KeyMaker/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Tools/KeyMaker/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Tools/KeyMaker/appsettings.json b/Tools/KeyMaker/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Tools/KeyMaker/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Tools/KeyMaker/docker/Dockerfile b/Tools/KeyMaker/docker/Dockerfile new file mode 100644 index 00000000..74d929bb --- /dev/null +++ b/Tools/KeyMaker/docker/Dockerfile @@ -0,0 +1,26 @@ +# Variables +ARG BUILDER=mcr.microsoft.com/dotnet/sdk:7.0 +ARG IMAGE=${BUILDER} +ARG APP_HOME=/app + + +# Build +FROM ${IMAGE} AS builder +ARG APP_HOME + +WORKDIR ${APP_HOME} +COPY ./Tools/KeyMaker ./Tools/KeyMaker +COPY ./Framework ./Framework +COPY ./ProjectPlugins ./ProjectPlugins +RUN dotnet restore Tools/KeyMaker +RUN dotnet publish Tools/KeyMaker -c Release -o out + + +# Create +FROM ${IMAGE} +ARG APP_HOME +ENV APP_HOME=${APP_HOME} + +WORKDIR ${APP_HOME} +COPY --from=builder ${APP_HOME}/out . +CMD dotnet ${APP_HOME}/KeyMaker.dll diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index ada68933..beec9e6d 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyMaker", "Tools\KeyMaker\KeyMaker.csproj", "{B57A4789-D8EF-42E0-8D20-581C4057FFD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -172,6 +174,10 @@ Global {88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU {88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.Build.0 = Release|Any CPU + {B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -202,6 +208,7 @@ Global {F730DA73-1C92-4107-BCFB-D33759DAB0C3} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {B07820C4-309F-4454-BCC1-1D4902C9C67B} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {88C212E9-308A-46A4-BAAD-468E8EBD8EDF} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124} + {B57A4789-D8EF-42E0-8D20-581C4057FFD3} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} From cc3eddf02d671a3bc236f4b01f3af6f28cc9eaf8 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 20 Jun 2024 12:15:59 +0200 Subject: [PATCH 128/142] wip --- .../Marketplace/Customizations.cs | 4 +--- Tests/CodexTests/UtilityTests/DiscordBotTests.cs | 11 ++++++----- Tools/TestNetRewarder/Processor.cs | 8 +++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 557e021a..43311430 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -23,9 +23,7 @@ namespace CodexContractsPlugin.Marketplace { get { - var id = ""; - foreach (var b in RequestId) id += b.ToString(); - return id; + return BitConverter.ToString(RequestId).Replace("-", "").ToLowerInvariant(); } } } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 18ba6630..66601c81 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -53,7 +53,7 @@ namespace CodexTests.UtilityTests purchaseContract.WaitForStorageContractFinished(); - Thread.Sleep(rewarderInterval * 2); + Thread.Sleep(rewarderInterval * 3); apiCalls.Stop(); chainState.Update(); @@ -76,14 +76,14 @@ namespace CodexTests.UtilityTests private void OnCommand(GiveRewardsCommand call) { - Log($"API call:"); + Log($""); foreach (var a in call.Averages) { - Log("Average: " + JsonConvert.SerializeObject(a)); + Log("\tAverage: " + JsonConvert.SerializeObject(a)); } foreach (var e in call.EventsOverview) { - Log("Event: " + e); + Log("\tEvent: " + e); } foreach (var r in call.Rewards) { @@ -92,9 +92,10 @@ namespace CodexTests.UtilityTests foreach (var address in r.UserAddresses) { var user = IdentifyAccount(address); - Log("Reward: " + user + ": " + reward.Message); + Log("\tReward: " + user + ": " + reward.Message); } } + Log($""); } private IStoragePurchaseContract ClientPurchasesStorage(ICodexNode client) diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 8cbc4dbd..b30ca791 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -40,7 +40,7 @@ namespace TestNetRewarder chainState.Update(timeRange.To); var averages = marketTracker.GetAverages(); - var lines = bufferLogger.Get(); + var lines = RemoveFirstLine(bufferLogger.Get()); var request = builder.Build(averages, lines); if (request.HasAny()) @@ -54,5 +54,11 @@ namespace TestNetRewarder throw; } } + + private string[] RemoveFirstLine(string[] lines) + { + if (!lines.Any()) return Array.Empty(); + return lines.Skip(1).ToArray(); + } } } From 32e702802956eccee3a086ab816eaa9eb99b25ea Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 08:56:20 +0200 Subject: [PATCH 129/142] wip --- .../UtilityTests/DiscordBotTests.cs | 55 ++++++++++++------- Tools/TestNetRewarder/BufferLogger.cs | 11 ---- Tools/TestNetRewarder/Processor.cs | 9 ++- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 66601c81..01c8fc5a 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -7,6 +7,7 @@ using DiscordRewards; using DistTestCore; using GethPlugin; using KubernetesWorkflow.Types; +using Logging; using Newtonsoft.Json; using NUnit.Framework; using Utils; @@ -38,25 +39,37 @@ namespace CodexTests.UtilityTests StartHosts(geth, contracts); var client = StartClient(geth, contracts); - var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); - var chainState = new ChainState(GetTestLog(), contracts, new DoNothingChainEventHandler(), GetTestRunTimeRange().From); + //var chainState = new ChainState(GetTestLog(), contracts, new DoNothingChainEventHandler(), GetTestRunTimeRange().From); - var apiCalls = new RewardApiCalls(Ci, botContainer); + //var running = true; + //var task = Task.Run(() => + //{ + // while (running) + // { + // Thread.Sleep(TimeSpan.FromMinutes(1)); + // chainState.Update(); + // } + //}); + + var apiCalls = new RewardApiCalls(GetTestLog(), Ci, botContainer); apiCalls.Start(OnCommand); var purchaseContract = ClientPurchasesStorage(client); - chainState.Update(); - Assert.That(chainState.Requests.Length, Is.EqualTo(1)); + //chainState.Update(); + //Assert.That(chainState.Requests.Length, Is.EqualTo(1)); purchaseContract.WaitForStorageContractStarted(); - chainState.Update(); + //chainState.Update(); purchaseContract.WaitForStorageContractFinished(); Thread.Sleep(rewarderInterval * 3); - + + //running = false; + //task.Wait(); + apiCalls.Stop(); - chainState.Update(); + //chainState.Update(); foreach (var r in repo.Rewards) { @@ -74,9 +87,9 @@ namespace CodexTests.UtilityTests return $"({rewardId})'{reward.Message}'"; } - private void OnCommand(GiveRewardsCommand call) + private void OnCommand(string timestamp, GiveRewardsCommand call) { - Log($""); + Log($""); foreach (var a in call.Averages) { Log("\tAverage: " + JsonConvert.SerializeObject(a)); @@ -249,14 +262,13 @@ namespace CodexTests.UtilityTests public class RewardApiCalls { private readonly ContainerFileMonitor monitor; - private readonly Dictionary commands = new Dictionary(); - public RewardApiCalls(CoreInterface ci, RunningContainer botContainer) + public RewardApiCalls(ILog log, CoreInterface ci, RunningContainer botContainer) { - monitor = new ContainerFileMonitor(ci, botContainer, "/app/datapath/logs/discordbot.log"); + monitor = new ContainerFileMonitor(log, ci, botContainer, "/app/datapath/logs/discordbot.log"); } - public void Start(Action onCommand) + public void Start(Action onCommand) { monitor.Start(line => ParseLine(line, onCommand)); } @@ -266,19 +278,17 @@ namespace CodexTests.UtilityTests monitor.Stop(); } - private void ParseLine(string line, Action onCommand) + private void ParseLine(string line, Action onCommand) { try { var timestamp = line.Substring(0, 30); - if (commands.ContainsKey(timestamp)) return; var json = line.Substring(31); var cmd = JsonConvert.DeserializeObject(json); if (cmd != null) { - commands.Add(timestamp, cmd); - onCommand(cmd); + onCommand(timestamp, cmd); } } catch @@ -289,6 +299,7 @@ namespace CodexTests.UtilityTests public class ContainerFileMonitor { + private readonly ILog log; private readonly CoreInterface ci; private readonly RunningContainer botContainer; private readonly string filePath; @@ -297,8 +308,9 @@ namespace CodexTests.UtilityTests private Task worker = Task.CompletedTask; private Action onNewLine = c => { }; - public ContainerFileMonitor(CoreInterface ci, RunningContainer botContainer, string filePath) + public ContainerFileMonitor(ILog log, CoreInterface ci, RunningContainer botContainer, string filePath) { + this.log = log; this.ci = ci; this.botContainer = botContainer; this.filePath = filePath; @@ -316,6 +328,9 @@ namespace CodexTests.UtilityTests worker.Wait(); } + // did any container crash? that's why it repeats? + + private void Worker() { while (!cts.IsCancellationRequested) @@ -333,6 +348,8 @@ namespace CodexTests.UtilityTests var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { + // log.Log("line: " + line); + if (!seenLines.Contains(line)) { seenLines.Add(line); diff --git a/Tools/TestNetRewarder/BufferLogger.cs b/Tools/TestNetRewarder/BufferLogger.cs index f7f9925e..1f323323 100644 --- a/Tools/TestNetRewarder/BufferLogger.cs +++ b/Tools/TestNetRewarder/BufferLogger.cs @@ -19,19 +19,16 @@ namespace TestNetRewarder public void Debug(string message = "", int skipFrames = 0) { lines.Add(message); - Trim(); } public void Error(string message) { lines.Add($"Error: {message}"); - Trim(); } public void Log(string message) { lines.Add(message); - Trim(); } public string[] Get() @@ -40,13 +37,5 @@ namespace TestNetRewarder lines.Clear(); return result; } - - private void Trim() - { - if (lines.Count > 100) - { - lines.RemoveRange(0, 50); - } - } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index b30ca791..f43a9241 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -50,15 +50,18 @@ namespace TestNetRewarder } catch (Exception ex) { - log.Error("Exception processing time segment: " + ex); + var msg = "Exception processing time segment: " + ex; + log.Error(msg); + bufferLogger.Error(msg); throw; } } private string[] RemoveFirstLine(string[] lines) { - if (!lines.Any()) return Array.Empty(); - return lines.Skip(1).ToArray(); + //if (!lines.Any()) return Array.Empty(); + //return lines.Skip(1).ToArray(); + return lines; } } } From 2b7ba61543044fedf477a4af0f71da2045bf3682 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 09:18:00 +0200 Subject: [PATCH 130/142] Cleanup --- .../CodexDiscordBotPlugin.cs | 5 ++- .../UtilityTests/DiscordBotTests.cs | 43 ++++++++----------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index b2e184be..556ef152 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -50,6 +50,7 @@ namespace CodexDiscordBotPlugin startupConfig.Add(config); var pod = workflow.Start(1, new DiscordBotContainerRecipe(), startupConfig).WaitForOnline(); WaitForStartupMessage(workflow, pod); + workflow.CreateCrashWatcher(pod.Containers.Single()).Start(); return pod; } @@ -58,7 +59,9 @@ namespace CodexDiscordBotPlugin var startupConfig = new StartupConfig(); startupConfig.NameOverride = config.Name; startupConfig.Add(config); - return workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig).WaitForOnline(); + var pod = workflow.Start(1, new RewarderBotContainerRecipe(), startupConfig).WaitForOnline(); + workflow.CreateCrashWatcher(pod.Containers.Single()).Start(); + return pod; } private void WaitForStartupMessage(IStartupWorkflow workflow, RunningPod pod) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index 01c8fc5a..f78a46c6 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -1,5 +1,4 @@ using CodexContractsPlugin; -using CodexContractsPlugin.ChainMonitor; using CodexDiscordBotPlugin; using CodexPlugin; using Core; @@ -24,7 +23,9 @@ namespace CodexTests.UtilityTests private readonly List hostAccounts = new List(); private readonly List rewardsSeen = new List(); private readonly TimeSpan rewarderInterval = TimeSpan.FromMinutes(1); - + private readonly List receivedEvents = new List(); + private readonly List receivedAverages = new List(); + [Test] [DontDownloadLogs] public void BotRewardTest() @@ -39,37 +40,20 @@ namespace CodexTests.UtilityTests StartHosts(geth, contracts); var client = StartClient(geth, contracts); - //var chainState = new ChainState(GetTestLog(), contracts, new DoNothingChainEventHandler(), GetTestRunTimeRange().From); - - //var running = true; - //var task = Task.Run(() => - //{ - // while (running) - // { - // Thread.Sleep(TimeSpan.FromMinutes(1)); - // chainState.Update(); - // } - //}); - var apiCalls = new RewardApiCalls(GetTestLog(), Ci, botContainer); apiCalls.Start(OnCommand); var purchaseContract = ClientPurchasesStorage(client); - //chainState.Update(); - //Assert.That(chainState.Requests.Length, Is.EqualTo(1)); - purchaseContract.WaitForStorageContractStarted(); - //chainState.Update(); - purchaseContract.WaitForStorageContractFinished(); - Thread.Sleep(rewarderInterval * 3); - //running = false; - //task.Wait(); - apiCalls.Stop(); - //chainState.Update(); + + Assert.That(receivedEvents.Count(e => e.Contains("Created as New.")), Is.EqualTo(1)); + Assert.That(receivedEvents.Count(e => e.Contains("SlotFilled")), Is.EqualTo(GetNumberOfRequiredHosts())); + Assert.That(receivedEvents.Count(e => e.Contains("Transit: New -> Started")), Is.EqualTo(1)); + Assert.That(receivedEvents.Count(e => e.Contains("Transit: Started -> Finished")), Is.EqualTo(1)); foreach (var r in repo.Rewards) { @@ -90,10 +74,12 @@ namespace CodexTests.UtilityTests private void OnCommand(string timestamp, GiveRewardsCommand call) { Log($""); + receivedAverages.AddRange(call.Averages); foreach (var a in call.Averages) { Log("\tAverage: " + JsonConvert.SerializeObject(a)); } + receivedEvents.AddRange(call.EventsOverview); foreach (var e in call.EventsOverview) { Log("\tEvent: " + e); @@ -122,8 +108,8 @@ namespace CodexTests.UtilityTests MinRequiredNumberOfNodes = GetNumberOfRequiredHosts(), NodeFailureTolerance = 2, ProofProbability = 5, - Duration = TimeSpan.FromMinutes(6), - Expiry = TimeSpan.FromMinutes(5) + Duration = GetMinRequiredRequestDuration(), + Expiry = GetMinRequiredRequestDuration() - TimeSpan.FromMinutes(1) }; return client.Marketplace.RequestStorage(purchase); @@ -245,6 +231,11 @@ namespace CodexTests.UtilityTests return Convert.ToUInt32(repo.Rewards.Max(r => r.CheckConfig.MinNumberOfHosts)); } + private TimeSpan GetMinRequiredRequestDuration() + { + return repo.Rewards.Max(r => r.CheckConfig.MinDuration) + TimeSpan.FromSeconds(10); + } + private string IdentifyAccount(string address) { if (address == clientAccount.EthAddress.Address) return "Client"; From 0c4d3be91250c3e317554cd6523252dc7cc10d52 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 09:33:52 +0200 Subject: [PATCH 131/142] Fixes crash in marketBuffer --- Tests/CodexTests/UtilityTests/DiscordBotTests.cs | 14 ++++++++++---- Tools/TestNetRewarder/MarketBuffer.cs | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index f78a46c6..bcaacbeb 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -50,10 +50,10 @@ namespace CodexTests.UtilityTests apiCalls.Stop(); - Assert.That(receivedEvents.Count(e => e.Contains("Created as New.")), Is.EqualTo(1)); - Assert.That(receivedEvents.Count(e => e.Contains("SlotFilled")), Is.EqualTo(GetNumberOfRequiredHosts())); - Assert.That(receivedEvents.Count(e => e.Contains("Transit: New -> Started")), Is.EqualTo(1)); - Assert.That(receivedEvents.Count(e => e.Contains("Transit: Started -> Finished")), Is.EqualTo(1)); + AssertEventOccurance("Created as New.", 1); + AssertEventOccurance("SlotFilled", Convert.ToInt32(GetNumberOfRequiredHosts())); + AssertEventOccurance("Transit: New -> Started", 1); + AssertEventOccurance("Transit: Started -> Finished", 1); foreach (var r in repo.Rewards) { @@ -71,6 +71,12 @@ namespace CodexTests.UtilityTests return $"({rewardId})'{reward.Message}'"; } + private void AssertEventOccurance(string msg, int expectedCount) + { + Assert.That(receivedEvents.Count(e => e.Contains(msg)), Is.EqualTo(expectedCount), + $"Event '{msg}' did not occure correct number of times."); + } + private void OnCommand(string timestamp, GiveRewardsCommand call) { Log($""); diff --git a/Tools/TestNetRewarder/MarketBuffer.cs b/Tools/TestNetRewarder/MarketBuffer.cs index 45573785..b03ad7a6 100644 --- a/Tools/TestNetRewarder/MarketBuffer.cs +++ b/Tools/TestNetRewarder/MarketBuffer.cs @@ -44,7 +44,11 @@ namespace TestNetRewarder private float Average(Func getValue) { - return Average(s => Convert.ToInt32(getValue(s))); + return Average(s => + { + var value = getValue(s); + return (int)value; + }); } private float Average(Func getValue) From a905f0ce5335ad9f42019ca430a9d5ff2eb3bc16 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 09:59:24 +0200 Subject: [PATCH 132/142] fixes crash in market buffer again --- Tools/TestNetRewarder/MarketBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/TestNetRewarder/MarketBuffer.cs b/Tools/TestNetRewarder/MarketBuffer.cs index b03ad7a6..d8ec91d5 100644 --- a/Tools/TestNetRewarder/MarketBuffer.cs +++ b/Tools/TestNetRewarder/MarketBuffer.cs @@ -67,7 +67,7 @@ namespace TestNetRewarder private int GetTotalSize(Ask ask) { var nSlots = Convert.ToInt32(ask.Slots); - var slotSize = Convert.ToInt32(ask.SlotSize); + var slotSize = (int)ask.SlotSize; return nSlots * slotSize; } } From 4be9b9df9a450ef3ac6ac9b4caa7659123182695 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 10:19:09 +0200 Subject: [PATCH 133/142] Finished! --- Tests/CodexTests/UtilityTests/DiscordBotTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index bcaacbeb..c6b6b41f 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -55,6 +55,8 @@ namespace CodexTests.UtilityTests AssertEventOccurance("Transit: New -> Started", 1); AssertEventOccurance("Transit: Started -> Finished", 1); + AssertMarketAverage(); + foreach (var r in repo.Rewards) { var seen = rewardsSeen.Any(s => r.RoleId == s); @@ -77,6 +79,20 @@ namespace CodexTests.UtilityTests $"Event '{msg}' did not occure correct number of times."); } + private void AssertMarketAverage() + { + Assert.That(receivedAverages.Count, Is.EqualTo(1)); + var a = receivedAverages.Single(); + + Assert.That(a.NumberOfFinished, Is.EqualTo(1)); + Assert.That(a.TimeRangeSeconds, Is.EqualTo(5760)); + Assert.That(a.Price, Is.EqualTo(2.0f).Within(0.1f)); + Assert.That(a.Size, Is.EqualTo(GetMinFileSize().SizeInBytes).Within(1.0f)); + Assert.That(a.Duration, Is.EqualTo(GetMinRequiredRequestDuration().TotalSeconds).Within(1.0f)); + Assert.That(a.Collateral, Is.EqualTo(10.0f).Within(0.1f)); + Assert.That(a.ProofProbability, Is.EqualTo(5.0f).Within(0.1f)); + } + private void OnCommand(string timestamp, GiveRewardsCommand call) { Log($""); From 8033da1176fae6c432081e82a3a11d82f6f4f6a0 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 21 Jun 2024 11:29:05 +0200 Subject: [PATCH 134/142] Updates to new Codex image. Fixes tests --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- Tests/CodexTests/BasicTests/MarketplaceTests.cs | 3 +-- Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs | 2 ++ Tests/CodexTests/UtilityTests/DiscordBotTests.cs | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 564e81a1..1d24678a 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-b89493e-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-132c4db-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs index 3f45469d..4a0f404a 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -33,10 +33,9 @@ namespace CodexTests.BasicTests .AsStorageNode() .AsValidator())); - var expectedHostBalance = (numberOfHosts * hostInitialBalance.TstWei).TstWei(); foreach (var host in hosts) { - AssertBalance(contracts, host, Is.EqualTo(expectedHostBalance)); + AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance)); var availability = new StorageAvailability( totalSpace: 10.GB(), diff --git a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 52619ddc..938e873f 100644 --- a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -49,6 +49,8 @@ namespace CodexTests.PeerDiscoveryTests private void AssertAllNodesConnected(IEnumerable nodes) { + nodes = nodes.Concat(new[] { BootstrapNode }).ToArray()!; + CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes); CheckRoutingTable(nodes); } diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index c6b6b41f..2896dbb7 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -28,6 +28,7 @@ namespace CodexTests.UtilityTests [Test] [DontDownloadLogs] + [Ignore("Used to debug testnet bots.")] public void BotRewardTest() { var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); From c2712daccb3d78c77aac37d302c9f3f6b99cb20b Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 25 Jun 2024 13:02:05 +0200 Subject: [PATCH 135/142] latest images --- .../CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs | 2 +- .../CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs | 3 +-- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs index 86ee1e25..0a9d9872 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/DiscordBotContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin public class DiscordBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-bibliotech"; - public override string Image => "codexstorage/codex-discordbot:sha-22cf82b"; + public override string Image => "codexstorage/codex-discordbot:sha-8033da1"; public static string RewardsPort = "bot_rewards_port"; diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index 2d11cbd4..b35195b3 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -7,8 +7,7 @@ namespace CodexDiscordBotPlugin public class RewarderBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-rewarder"; - public override string Image => "thatbenbierens/codex-rewardbot:newstate"; - //"codexstorage/codex-rewarderbot:sha-12dc7ef"; + public override string Image => "codexstorage/codex-rewarderbot:sha-8033da1"; protected override void Initialize(StartupConfig startupConfig) { diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 1d24678a..793c9acf 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-132c4db-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-305b80a-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; From 8341807d927755a7ccd3599c6055e37b11d2e5a6 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 26 Jun 2024 11:27:19 +0200 Subject: [PATCH 136/142] Disables role rewards (not configured in real server) --- Framework/DiscordRewards/RewardRepo.cs | 82 +++++++++++++------------- Tools/BiblioTech/CommandHandler.cs | 14 +++-- Tools/BiblioTech/Program.cs | 2 +- Tools/BiblioTech/Rewards/RoleDriver.cs | 68 +++++++++++++-------- 4 files changed, 92 insertions(+), 74 deletions(-) diff --git a/Framework/DiscordRewards/RewardRepo.cs b/Framework/DiscordRewards/RewardRepo.cs index 28f6173a..1c97caf9 100644 --- a/Framework/DiscordRewards/RewardRepo.cs +++ b/Framework/DiscordRewards/RewardRepo.cs @@ -1,53 +1,53 @@ -using Utils; - -namespace DiscordRewards +namespace DiscordRewards { public class RewardRepo { private static string Tag => RewardConfig.UsernameTag; - public RewardConfig[] Rewards { get; } = new RewardConfig[] - { - // Filled any slot - new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig - { - Type = CheckType.HostFilledSlot - }), + public RewardConfig[] Rewards { get; } = new RewardConfig[0]; - // Finished any slot - new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig - { - Type = CheckType.HostFinishedSlot - }), + // Example configuration, from test server: + //{ + // // Filled any slot + // new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig + // { + // Type = CheckType.HostFilledSlot + // }), - // Finished a sizable slot - new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig - { - Type = CheckType.HostFinishedSlot, - MinSlotSize = 10.MB(), - MinDuration = TimeSpan.FromMinutes(5.0), - }), + // // Finished any slot + // new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig + // { + // Type = CheckType.HostFinishedSlot + // }), - // Posted any contract - new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig - { - Type = CheckType.ClientPostedContract - }), + // // Finished a sizable slot + // new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig + // { + // Type = CheckType.HostFinishedSlot, + // MinSlotSize = 10.MB(), + // MinDuration = TimeSpan.FromMinutes(5.0), + // }), - // Started any contract - new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig - { - Type = CheckType.ClientStartedContract - }), + // // Posted any contract + // new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig + // { + // Type = CheckType.ClientPostedContract + // }), - // Started a sizable contract - new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig - { - Type = CheckType.ClientStartedContract, - MinNumberOfHosts = 4, - MinSlotSize = 10.MB(), - MinDuration = TimeSpan.FromMinutes(5.0), - }) - }; + // // Started any contract + // new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig + // { + // Type = CheckType.ClientStartedContract + // }), + + // // Started a sizable contract + // new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig + // { + // Type = CheckType.ClientStartedContract, + // MinNumberOfHosts = 4, + // MinSlotSize = 10.MB(), + // MinDuration = TimeSpan.FromMinutes(5.0), + // }) + //}; } } diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index c7263c5b..4f6d7b8d 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -3,6 +3,7 @@ using Discord.WebSocket; using Discord; using Newtonsoft.Json; using BiblioTech.Rewards; +using Logging; namespace BiblioTech { @@ -10,12 +11,13 @@ namespace BiblioTech { private readonly DiscordSocketClient client; private readonly BaseCommand[] commands; + private readonly ILog log; - public CommandHandler(DiscordSocketClient client, params BaseCommand[] commands) + public CommandHandler(ILog log, DiscordSocketClient client, params BaseCommand[] commands) { this.client = client; this.commands = commands; - + this.log = log; client.Ready += Client_Ready; client.SlashCommandExecuted += SlashCommandHandler; } @@ -24,12 +26,12 @@ namespace BiblioTech { var guild = client.Guilds.Single(g => g.Id == Program.Config.ServerId); Program.AdminChecker.SetGuild(guild); - Program.Log.Log($"Initializing for guild: '{guild.Name}'"); + log.Log($"Initializing for guild: '{guild.Name}'"); var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray(); if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel"); Program.AdminChecker.SetAdminChannel(adminChannels.First()); - Program.RoleDriver = new RoleDriver(client); + Program.RoleDriver = new RoleDriver(client, log); var builders = commands.Select(c => { @@ -44,7 +46,7 @@ namespace BiblioTech builder.AddOption(option.Build()); } - Program.Log.Log(msg); + log.Log(msg); return builder; }); @@ -58,7 +60,7 @@ namespace BiblioTech catch (HttpException exception) { var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); - Program.Log.Error(json); + log.Error(json); } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 6ec7dae9..39bd58b2 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -73,7 +73,7 @@ namespace BiblioTech var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); - var handler = new CommandHandler(client, + var handler = new CommandHandler(Log, client, new GetBalanceCommand(associateCommand), new MintCommand(associateCommand), sprCommand, diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index f0ea48f7..c7173c10 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using DiscordRewards; +using Logging; using Newtonsoft.Json; namespace BiblioTech.Rewards @@ -8,21 +9,22 @@ namespace BiblioTech.Rewards public class RoleDriver : IDiscordRoleDriver { private readonly DiscordSocketClient client; + private readonly ILog log; private readonly SocketTextChannel? rewardsChannel; private readonly SocketTextChannel? eventsChannel; private readonly RewardRepo repo = new RewardRepo(); - public RoleDriver(DiscordSocketClient client) + public RoleDriver(DiscordSocketClient client, ILog log) { this.client = client; - + this.log = log; rewardsChannel = GetChannel(Program.Config.RewardsChannelId); eventsChannel = GetChannel(Program.Config.ChainEventsChannelId); } public async Task GiveRewards(GiveRewardsCommand rewards) { - Program.Log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'"); + log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'"); if (rewards.Rewards.Any()) { @@ -34,15 +36,22 @@ namespace BiblioTech.Rewards private async Task ProcessRewards(GiveRewardsCommand rewards) { - var guild = GetGuild(); - // We load all role and user information first, - // so we don't ask the server for the same info multiple times. - var context = new RewardContext( - await LoadAllUsers(guild), - LookUpAllRoles(guild, rewards), - rewardsChannel); + try + { + var guild = GetGuild(); + // We load all role and user information first, + // so we don't ask the server for the same info multiple times. + var context = new RewardContext( + await LoadAllUsers(guild), + LookUpAllRoles(guild, rewards), + rewardsChannel); - await context.ProcessGiveRewardsCommand(LookUpUsers(rewards)); + await context.ProcessGiveRewardsCommand(LookUpUsers(rewards)); + } + catch (Exception ex) + { + log.Error("Failed to process rewards: " + ex); + } } private SocketTextChannel? GetChannel(ulong id) @@ -54,22 +63,29 @@ namespace BiblioTech.Rewards private async Task ProcessChainEvents(string[] eventsOverview) { if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; - await Task.Run(async () => + try { - foreach (var e in eventsOverview) + await Task.Run(async () => { - if (!string.IsNullOrEmpty(e)) + foreach (var e in eventsOverview) { - await eventsChannel.SendMessageAsync(e); - await Task.Delay(3000); + if (!string.IsNullOrEmpty(e)) + { + await eventsChannel.SendMessageAsync(e); + await Task.Delay(3000); + } } - } - }); + }); + } + catch (Exception ex) + { + log.Error("Failed to process chain events: " + ex); + } } private async Task> LoadAllUsers(SocketGuild guild) { - Program.Log.Log("Loading all users:"); + log.Log("Loading all users.."); var result = new Dictionary(); var users = guild.GetUsersAsync(); await foreach (var ulist in users) @@ -77,8 +93,8 @@ namespace BiblioTech.Rewards foreach (var u in ulist) { result.Add(u.Id, u); - var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray()); - Program.Log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]"); + //var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray()); + //log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]"); } } return result; @@ -94,14 +110,14 @@ namespace BiblioTech.Rewards var rewardConfig = repo.Rewards.SingleOrDefault(rr => rr.RoleId == r.RewardId); if (rewardConfig == null) { - Program.Log.Log($"No Reward is configured for id '{r.RewardId}'."); + log.Log($"No Reward is configured for id '{r.RewardId}'."); } else { var socketRole = guild.GetRole(r.RewardId); if (socketRole == null) { - Program.Log.Log($"Guild Role by id '{r.RewardId}' not found."); + log.Log($"Guild Role by id '{r.RewardId}' not found."); } else { @@ -134,13 +150,13 @@ namespace BiblioTech.Rewards try { var userData = Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(address)); - if (userData != null) Program.Log.Log($"User '{userData.Name}' was looked up."); - else Program.Log.Log($"Lookup for user was unsuccessful. EthAddress: '{address}'"); + if (userData != null) log.Log($"User '{userData.Name}' was looked up."); + else log.Log($"Lookup for user was unsuccessful. EthAddress: '{address}'"); return userData; } catch (Exception ex) { - Program.Log.Error("Error during UserData lookup: " + ex); + log.Error("Error during UserData lookup: " + ex); return null; } } From 0ef55abdf49c4f4253de4851f72e6e2ff4ebdcd6 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:07:10 +0200 Subject: [PATCH 137/142] Adds events formatter --- .../ChainMonitor/ChainState.cs | 5 +- .../DoNothingChainEventHandler.cs | 5 +- Tools/TestNetRewarder/BufferLogger.cs | 41 ------ Tools/TestNetRewarder/ChainChangeMux.cs | 5 +- Tools/TestNetRewarder/EventsFormatter.cs | 122 ++++++++++++++++++ Tools/TestNetRewarder/MarketTracker.cs | 3 +- Tools/TestNetRewarder/Processor.cs | 22 ++-- Tools/TestNetRewarder/RewardCheck.cs | 3 +- 8 files changed, 142 insertions(+), 64 deletions(-) delete mode 100644 Tools/TestNetRewarder/BufferLogger.cs create mode 100644 Tools/TestNetRewarder/EventsFormatter.cs diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index bc284eb2..92169b7c 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.Marketplace; +using GethPlugin; using Logging; using System.Numerics; using Utils; @@ -11,7 +12,7 @@ namespace CodexContractsPlugin.ChainMonitor void OnRequestFinished(IChainStateRequest request); void OnRequestFulfilled(IChainStateRequest request); void OnRequestCancelled(IChainStateRequest request); - void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex); + void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex); void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex); } @@ -116,7 +117,7 @@ namespace CodexContractsPlugin.ChainMonitor if (r == null) return; r.Hosts.Add(request.Host, (int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFilled (host:'{request.Host}', slotIndex:{request.SlotIndex})"); - handler.OnSlotFilled(r, request.SlotIndex); + handler.OnSlotFilled(r, request.Host, request.SlotIndex); } private void ApplyEvent(SlotFreedEventDTO request) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs index 8a3709cc..41882c38 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using GethPlugin; +using System.Numerics; namespace CodexContractsPlugin.ChainMonitor { @@ -20,7 +21,7 @@ namespace CodexContractsPlugin.ChainMonitor { } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { } diff --git a/Tools/TestNetRewarder/BufferLogger.cs b/Tools/TestNetRewarder/BufferLogger.cs deleted file mode 100644 index 1f323323..00000000 --- a/Tools/TestNetRewarder/BufferLogger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Logging; - -namespace TestNetRewarder -{ - public class BufferLogger : ILog - { - private readonly List lines = new List(); - - public void AddStringReplace(string from, string to) - { - throw new NotImplementedException(); - } - - public LogFile CreateSubfile(string ext = "log") - { - throw new NotImplementedException(); - } - - public void Debug(string message = "", int skipFrames = 0) - { - lines.Add(message); - } - - public void Error(string message) - { - lines.Add($"Error: {message}"); - } - - public void Log(string message) - { - lines.Add(message); - } - - public string[] Get() - { - var result = lines.ToArray(); - lines.Clear(); - return result; - } - } -} diff --git a/Tools/TestNetRewarder/ChainChangeMux.cs b/Tools/TestNetRewarder/ChainChangeMux.cs index f70490bf..740ba318 100644 --- a/Tools/TestNetRewarder/ChainChangeMux.cs +++ b/Tools/TestNetRewarder/ChainChangeMux.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.ChainMonitor; +using GethPlugin; using System.Numerics; namespace TestNetRewarder @@ -32,9 +33,9 @@ namespace TestNetRewarder foreach (var handler in handlers) handler.OnRequestFulfilled(request); } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { - foreach (var handler in handlers) handler.OnSlotFilled(request, slotIndex); + foreach (var handler in handlers) handler.OnSlotFilled(request, host, slotIndex); } public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) diff --git a/Tools/TestNetRewarder/EventsFormatter.cs b/Tools/TestNetRewarder/EventsFormatter.cs new file mode 100644 index 00000000..0ddf6ee4 --- /dev/null +++ b/Tools/TestNetRewarder/EventsFormatter.cs @@ -0,0 +1,122 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using GethPlugin; +using System.Numerics; +using Utils; + +namespace TestNetRewarder +{ + public class EventsFormatter : IChainStateChangeHandler + { + private static readonly string nl = Environment.NewLine; + private readonly List events = new List(); + + public string[] GetEvents() + { + var result = events.ToArray(); + events.Clear(); + return result; + } + + public void AddError(string error) + { + AddBlock("📢 **Error**", error); + } + + public void OnNewRequest(IChainStateRequest request) + { + AddRequestBlock(request, "New Request", + $"Client: {request.Client}", + $"Content: {request.Request.Content.Cid}", + $"Duration: {BigIntToDuration(request.Request.Ask.Duration)}", + $"Expiry: {BigIntToDuration(request.Request.Expiry)}", + $"Collateral: {BitIntToTestTokens(request.Request.Ask.Collateral)}", + $"Reward: {BitIntToTestTokens(request.Request.Ask.Reward)}", + $"Number of Slots: {request.Request.Ask.Slots}", + $"Slot Tolerance: {request.Request.Ask.MaxSlotLoss}", + $"Slot Size: {BigIntToByteSize(request.Request.Ask.SlotSize)}" + ); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + AddRequestBlock(request, "Cancelled"); + } + + public void OnRequestFinished(IChainStateRequest request) + { + AddRequestBlock(request, "Finished"); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + AddRequestBlock(request, "Started"); + } + + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + { + AddRequestBlock(request, "Slot Filled", + $"Host: {host}", + $"Slot Index: {slotIndex}" + ); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + AddRequestBlock(request, "Slot Freed", + $"Slot Index: {slotIndex}" + ); + } + + private void AddRequestBlock(IChainStateRequest request, string eventName, params string[] content) + { + var blockNumber = $"[{request.Request.Block.BlockNumber}]"; + var title = $"{blockNumber} **{eventName}** `{request.Request.Id}`"; + AddBlock(title, content); + } + + private void AddBlock(string title, params string[] content) + { + events.Add(FormatBlock(title, content)); + } + + private string FormatBlock(string title, params string[] content) + { + if (content == null || !content.Any()) + { + return $"{title}{nl}{nl}"; + } + + return string.Join(nl, + new string[] + { + title, + "```" + } + .Concat(content) + .Concat(new string[] + { + "```" + }) + ) + nl + nl; + } + + private string BigIntToDuration(BigInteger big) + { + var span = TimeSpan.FromSeconds((int)big); + return Time.FormatDuration(span); + } + + private string BigIntToByteSize(BigInteger big) + { + var size = new ByteSize((long)big); + return size.ToString(); + } + + private string BitIntToTestTokens(BigInteger big) + { + var tt = new TestToken(big); + return tt.ToString(); + } + } +} diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index 669dd3aa..a850e322 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,5 +1,6 @@ using CodexContractsPlugin.ChainMonitor; using DiscordRewards; +using GethPlugin; using Logging; using System.Numerics; @@ -48,7 +49,7 @@ namespace TestNetRewarder { } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index f43a9241..642be072 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -10,7 +10,7 @@ namespace TestNetRewarder private readonly RequestBuilder builder; private readonly RewardChecker rewardChecker; private readonly MarketTracker marketTracker; - private readonly BufferLogger bufferLogger; + private readonly EventsFormatter eventsFormatter; private readonly ChainState chainState; private readonly BotClient client; private readonly ILog log; @@ -23,14 +23,15 @@ namespace TestNetRewarder builder = new RequestBuilder(); rewardChecker = new RewardChecker(builder); marketTracker = new MarketTracker(config, log); - bufferLogger = new BufferLogger(); + eventsFormatter = new EventsFormatter(); var handler = new ChainChangeMux( rewardChecker.Handler, - marketTracker + marketTracker, + eventsFormatter ); - chainState = new ChainState(new LogSplitter(log, bufferLogger), contracts, handler, config.HistoryStartUtc); + chainState = new ChainState(log, contracts, handler, config.HistoryStartUtc); } public async Task OnNewSegment(TimeRange timeRange) @@ -40,9 +41,9 @@ namespace TestNetRewarder chainState.Update(timeRange.To); var averages = marketTracker.GetAverages(); - var lines = RemoveFirstLine(bufferLogger.Get()); + var events = eventsFormatter.GetEvents(); - var request = builder.Build(averages, lines); + var request = builder.Build(averages, events); if (request.HasAny()) { await client.SendRewards(request); @@ -52,16 +53,9 @@ namespace TestNetRewarder { var msg = "Exception processing time segment: " + ex; log.Error(msg); - bufferLogger.Error(msg); + eventsFormatter.AddError(msg); throw; } } - - private string[] RemoveFirstLine(string[] lines) - { - //if (!lines.Any()) return Array.Empty(); - //return lines.Skip(1).ToArray(); - return lines; - } } } diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs index 9be87d22..0132f040 100644 --- a/Tools/TestNetRewarder/RewardCheck.cs +++ b/Tools/TestNetRewarder/RewardCheck.cs @@ -53,11 +53,10 @@ namespace TestNetRewarder } } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { if (MeetsRequirements(CheckType.HostFilledSlot, request)) { - var host = request.Hosts.GetHost((int)slotIndex); if (host != null) { GiveReward(reward, host); From 1eb30329c6171878707dd6aeb9da2d2197646b31 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:14:23 +0200 Subject: [PATCH 138/142] Pulls out chain events sender --- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 40 +++++++++++++++++++ Tools/BiblioTech/Rewards/RoleDriver.cs | 29 ++------------ 2 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/ChainEventsSender.cs diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs new file mode 100644 index 00000000..2e1e9ec7 --- /dev/null +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -0,0 +1,40 @@ +using Discord.WebSocket; +using Logging; + +namespace BiblioTech.Rewards +{ + public class ChainEventsSender + { + private readonly ILog log; + private SocketTextChannel? eventsChannel; + + public ChainEventsSender(ILog log, SocketTextChannel? eventsChannel) + { + this.log = log; + this.eventsChannel = eventsChannel; + } + + public async Task ProcessChainEvents(string[] eventsOverview) + { + if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; + try + { + await Task.Run(async () => + { + foreach (var e in eventsOverview) + { + if (!string.IsNullOrEmpty(e)) + { + await eventsChannel.SendMessageAsync(e); + await Task.Delay(3000); + } + } + }); + } + catch (Exception ex) + { + log.Error("Failed to process chain events: " + ex); + } + } + } +} diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index c7173c10..6f1819d3 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -11,7 +11,7 @@ namespace BiblioTech.Rewards private readonly DiscordSocketClient client; private readonly ILog log; private readonly SocketTextChannel? rewardsChannel; - private readonly SocketTextChannel? eventsChannel; + private readonly ChainEventsSender eventsSender; private readonly RewardRepo repo = new RewardRepo(); public RoleDriver(DiscordSocketClient client, ILog log) @@ -19,7 +19,7 @@ namespace BiblioTech.Rewards this.client = client; this.log = log; rewardsChannel = GetChannel(Program.Config.RewardsChannelId); - eventsChannel = GetChannel(Program.Config.ChainEventsChannelId); + eventsSender = new ChainEventsSender(log, GetChannel(Program.Config.ChainEventsChannelId)); } public async Task GiveRewards(GiveRewardsCommand rewards) @@ -31,7 +31,7 @@ namespace BiblioTech.Rewards await ProcessRewards(rewards); } - await ProcessChainEvents(rewards.EventsOverview); + await eventsSender.ProcessChainEvents(rewards.EventsOverview); } private async Task ProcessRewards(GiveRewardsCommand rewards) @@ -60,29 +60,6 @@ namespace BiblioTech.Rewards return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id); } - private async Task ProcessChainEvents(string[] eventsOverview) - { - if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; - try - { - await Task.Run(async () => - { - foreach (var e in eventsOverview) - { - if (!string.IsNullOrEmpty(e)) - { - await eventsChannel.SendMessageAsync(e); - await Task.Delay(3000); - } - } - }); - } - catch (Exception ex) - { - log.Error("Failed to process chain events: " + ex); - } - } - private async Task> LoadAllUsers(SocketGuild guild) { log.Log("Loading all users.."); From 01ee514c730d0f5b1baf8b09ab5ff7fbc797be53 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:44:37 +0200 Subject: [PATCH 139/142] Implements known username replacement --- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 26 ++++- Tools/BiblioTech/UserData.cs | 66 ++++++++++++ Tools/BiblioTech/UserRepo.cs | 102 ++++++++---------- 3 files changed, 135 insertions(+), 59 deletions(-) create mode 100644 Tools/BiblioTech/UserData.cs diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs index 2e1e9ec7..bbad8044 100644 --- a/Tools/BiblioTech/Rewards/ChainEventsSender.cs +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -21,11 +21,14 @@ namespace BiblioTech.Rewards { await Task.Run(async () => { + var users = Program.UserRepo.GetAllUserData(); + foreach (var e in eventsOverview) { if (!string.IsNullOrEmpty(e)) { - await eventsChannel.SendMessageAsync(e); + var @event = ApplyReplacements(users, e); + await eventsChannel.SendMessageAsync(@event); await Task.Delay(3000); } } @@ -36,5 +39,26 @@ namespace BiblioTech.Rewards log.Error("Failed to process chain events: " + ex); } } + + private string ApplyReplacements(UserData[] users, string msg) + { + var result = ApplyUserAddressReplacements(users, msg); + return result; + } + + private string ApplyUserAddressReplacements(UserData[] users, string msg) + { + foreach (var user in users) + { + if (user.CurrentAddress != null && + !string.IsNullOrEmpty(user.CurrentAddress.Address) && + !string.IsNullOrEmpty(user.Name)) + { + msg = msg.Replace(user.CurrentAddress.Address, user.Name); + } + } + + return msg; + } } } diff --git a/Tools/BiblioTech/UserData.cs b/Tools/BiblioTech/UserData.cs new file mode 100644 index 00000000..540c326a --- /dev/null +++ b/Tools/BiblioTech/UserData.cs @@ -0,0 +1,66 @@ +using CodexContractsPlugin; +using GethPlugin; + +namespace BiblioTech +{ + public class UserData + { + public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List associateEvents, List mintEvents, bool notificationsEnabled) + { + DiscordId = discordId; + Name = name; + CreatedUtc = createdUtc; + CurrentAddress = currentAddress; + AssociateEvents = associateEvents; + MintEvents = mintEvents; + NotificationsEnabled = notificationsEnabled; + } + + public ulong DiscordId { get; } + public string Name { get; } + public DateTime CreatedUtc { get; } + public EthAddress? CurrentAddress { get; set; } + public List AssociateEvents { get; } + public List MintEvents { get; } + public bool NotificationsEnabled { get; set; } + + public string[] CreateOverview() + { + return new[] + { + $"name: '{Name}' - id:{DiscordId}", + $"joined: {CreatedUtc.ToString("o")}", + $"current address: {CurrentAddress}", + $"{AssociateEvents.Count + MintEvents.Count} total bot events." + }; + } + } + + public class UserAssociateAddressEvent + { + public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress) + { + Utc = utc; + NewAddress = newAddress; + } + + public DateTime Utc { get; } + public EthAddress? NewAddress { get; } + } + + public class UserMintEvent + { + public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction? ethReceived, Transaction? testTokensMinted) + { + Utc = utc; + UsedAddress = usedAddress; + EthReceived = ethReceived; + TestTokensMinted = testTokensMinted; + } + + public DateTime Utc { get; } + public EthAddress UsedAddress { get; } + public Transaction? EthReceived { get; } + public Transaction? TestTokensMinted { get; } + } +} diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index 01afbe90..7cd03fd5 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -8,6 +8,7 @@ namespace BiblioTech public class UserRepo { private readonly object repoLock = new object(); + private readonly Dictionary cache = new Dictionary(); public bool AssociateUserWithAddress(IUser user, EthAddress address) { @@ -33,6 +34,12 @@ namespace BiblioTech } } + public UserData[] GetAllUserData() + { + if (cache.Count == 0) LoadAllUserData(); + return cache.Values.ToArray(); + } + public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction? eth, Transaction? tokens) { lock (repoLock) @@ -151,12 +158,19 @@ namespace BiblioTech private UserData? GetUserData(IUser user) { + if (cache.ContainsKey(user.Id)) + { + return cache[user.Id]; + } + var filename = GetFilename(user); if (!File.Exists(filename)) { return null; } - return JsonConvert.DeserializeObject(File.ReadAllText(filename))!; + var userData = JsonConvert.DeserializeObject(File.ReadAllText(filename))!; + cache.Add(userData.DiscordId, userData); + return userData; } private UserData GetOrCreate(IUser user) @@ -181,6 +195,15 @@ namespace BiblioTech var filename = GetFilename(userData); if (File.Exists(filename)) File.Delete(filename); File.WriteAllText(filename, JsonConvert.SerializeObject(userData)); + + if (cache.ContainsKey(userData.DiscordId)) + { + cache[userData.DiscordId] = userData; + } + else + { + cache.Add(userData.DiscordId, userData); + } } private static string GetFilename(IUser user) @@ -197,66 +220,29 @@ namespace BiblioTech { return Path.Combine(Program.Config.UserDataPath, discordId.ToString() + ".json"); } - } - public class UserData - { - public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List associateEvents, List mintEvents, bool notificationsEnabled) + private void LoadAllUserData() { - DiscordId = discordId; - Name = name; - CreatedUtc = createdUtc; - CurrentAddress = currentAddress; - AssociateEvents = associateEvents; - MintEvents = mintEvents; - NotificationsEnabled = notificationsEnabled; - } - - public ulong DiscordId { get; } - public string Name { get; } - public DateTime CreatedUtc { get; } - public EthAddress? CurrentAddress { get; set; } - public List AssociateEvents { get; } - public List MintEvents { get; } - public bool NotificationsEnabled { get; set; } - - public string[] CreateOverview() - { - return new[] + try { - $"name: '{Name}' - id:{DiscordId}", - $"joined: {CreatedUtc.ToString("o")}", - $"current address: {CurrentAddress}", - $"{AssociateEvents.Count + MintEvents.Count} total bot events." - }; + var files = Directory.GetFiles(Program.Config.UserDataPath); + foreach (var file in files) + { + try + { + var userData = JsonConvert.DeserializeObject(File.ReadAllText(file))!; + if (userData != null && userData.DiscordId > 0) + { + cache.Add(userData.DiscordId, userData); + } + } + catch { } + } + } + catch (Exception ex) + { + Program.Log.Error("Exception while trying to load all user data: " + ex); + } } } - - public class UserAssociateAddressEvent - { - public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress) - { - Utc = utc; - NewAddress = newAddress; - } - - public DateTime Utc { get; } - public EthAddress? NewAddress { get; } - } - - public class UserMintEvent - { - public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction? ethReceived, Transaction? testTokensMinted) - { - Utc = utc; - UsedAddress = usedAddress; - EthReceived = ethReceived; - TestTokensMinted = testTokensMinted; - } - - public DateTime Utc { get; } - public EthAddress UsedAddress { get; } - public Transaction? EthReceived { get; } - public Transaction? TestTokensMinted { get; } - } } From ba43fd90c6a5dea68465ea2593dfdafa6424d9ea Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 11:16:17 +0200 Subject: [PATCH 140/142] Adds admin option to set custom replacements --- Tools/BiblioTech/CommandHandler.cs | 6 +- Tools/BiblioTech/Commands/AdminCommand.cs | 62 ++++++++++++++++++- Tools/BiblioTech/Program.cs | 5 +- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 12 +++- Tools/BiblioTech/Rewards/CustomReplacement.cs | 34 ++++++++++ Tools/BiblioTech/Rewards/RoleDriver.cs | 4 +- 6 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/CustomReplacement.cs diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 4f6d7b8d..29a17774 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -10,12 +10,14 @@ namespace BiblioTech public class CommandHandler { private readonly DiscordSocketClient client; + private readonly CustomReplacement replacement; private readonly BaseCommand[] commands; private readonly ILog log; - public CommandHandler(ILog log, DiscordSocketClient client, params BaseCommand[] commands) + public CommandHandler(ILog log, DiscordSocketClient client, CustomReplacement replacement, params BaseCommand[] commands) { this.client = client; + this.replacement = replacement; this.commands = commands; this.log = log; client.Ready += Client_Ready; @@ -31,7 +33,7 @@ namespace BiblioTech 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, log); + Program.RoleDriver = new RoleDriver(client, log, replacement); var builders = commands.Select(c => { diff --git a/Tools/BiblioTech/Commands/AdminCommand.cs b/Tools/BiblioTech/Commands/AdminCommand.cs index 698fb006..d80326fb 100644 --- a/Tools/BiblioTech/Commands/AdminCommand.cs +++ b/Tools/BiblioTech/Commands/AdminCommand.cs @@ -1,4 +1,5 @@ using BiblioTech.Options; +using BiblioTech.Rewards; namespace BiblioTech.Commands { @@ -10,12 +11,14 @@ namespace BiblioTech.Commands private readonly AddSprCommand addSprCommand; private readonly ClearSprsCommand clearSprsCommand; private readonly GetSprCommand getSprCommand; + private readonly LogReplaceCommand logReplaceCommand; - public AdminCommand(SprCommand sprCommand) + public AdminCommand(SprCommand sprCommand, CustomReplacement replacement) { addSprCommand = new AddSprCommand(sprCommand); clearSprsCommand = new ClearSprsCommand(sprCommand); getSprCommand = new GetSprCommand(sprCommand); + logReplaceCommand = new LogReplaceCommand(replacement); } public override string Name => "admin"; @@ -29,7 +32,8 @@ namespace BiblioTech.Commands whoIsCommand, addSprCommand, clearSprsCommand, - getSprCommand + getSprCommand, + logReplaceCommand }; protected override async Task Invoke(CommandContext context) @@ -52,6 +56,7 @@ namespace BiblioTech.Commands await addSprCommand.CommandHandler(context); await clearSprsCommand.CommandHandler(context); await getSprCommand.CommandHandler(context); + await logReplaceCommand.CommandHandler(context); } public class ClearUserAssociationCommand : SubCommandOption @@ -194,7 +199,7 @@ namespace BiblioTech.Commands } } - public class GetSprCommand: SubCommandOption + public class GetSprCommand : SubCommandOption { private readonly SprCommand sprCommand; @@ -210,5 +215,56 @@ namespace BiblioTech.Commands await context.Followup("SPRs: " + string.Join(", ", sprCommand.Get().Select(s => $"'{s}'"))); } } + + public class LogReplaceCommand : SubCommandOption + { + private readonly CustomReplacement replacement; + private readonly StringOption fromOption = new StringOption("from", "string to replace", true); + private readonly StringOption toOption = new StringOption("to", "string to replace with", false); + + public LogReplaceCommand(CustomReplacement replacement) + : base(name: "logreplace", + description: "Replaces all occurances of 'from' with 'to' in ChainEvent messages. Leave 'to' empty to remove a replacement.") + { + this.replacement = replacement; + } + + public override CommandOption[] Options => new[] { fromOption, toOption }; + + protected override async Task onSubCommand(CommandContext context) + { + var from = await fromOption.Parse(context); + var to = await toOption.Parse(context); + + if (string.IsNullOrEmpty(from)) + { + await context.Followup("'from' not received"); + return; + } + + if (from.Length < 5) + { + await context.Followup("'from' must be length 5 or greater."); + return; + } + + if (string.IsNullOrEmpty(to)) + { + replacement.Remove(from); + await context.Followup($"Replace for '{from}' removed."); + } + else + { + if (to.Length < 5) + { + await context.Followup("'to' must be length 5 or greater."); + return; + } + + replacement.Add(from, to); + await context.Followup($"Replace added '{from}' -->> '{to}'."); + } + } + } } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 39bd58b2..587c3a88 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -11,6 +11,7 @@ namespace BiblioTech public class Program { private DiscordSocketClient client = null!; + private readonly CustomReplacement replacement = new CustomReplacement(); public static Configuration Config { get; private set; } = null!; public static UserRepo UserRepo { get; } = new UserRepo(); @@ -73,13 +74,13 @@ namespace BiblioTech var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); - var handler = new CommandHandler(Log, client, + var handler = new CommandHandler(Log, client, replacement, new GetBalanceCommand(associateCommand), new MintCommand(associateCommand), sprCommand, associateCommand, notifyCommand, - new AdminCommand(sprCommand), + new AdminCommand(sprCommand, replacement), new MarketCommand() ); diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs index bbad8044..0ec88d11 100644 --- a/Tools/BiblioTech/Rewards/ChainEventsSender.cs +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -6,11 +6,13 @@ namespace BiblioTech.Rewards public class ChainEventsSender { private readonly ILog log; - private SocketTextChannel? eventsChannel; + private readonly CustomReplacement replacement; + private readonly SocketTextChannel? eventsChannel; - public ChainEventsSender(ILog log, SocketTextChannel? eventsChannel) + public ChainEventsSender(ILog log, CustomReplacement replacement, SocketTextChannel? eventsChannel) { this.log = log; + this.replacement = replacement; this.eventsChannel = eventsChannel; } @@ -43,6 +45,7 @@ namespace BiblioTech.Rewards private string ApplyReplacements(UserData[] users, string msg) { var result = ApplyUserAddressReplacements(users, msg); + result = ApplyCustomReplacements(result); return result; } @@ -60,5 +63,10 @@ namespace BiblioTech.Rewards return msg; } + + private string ApplyCustomReplacements(string result) + { + return replacement.Apply(result); + } } } diff --git a/Tools/BiblioTech/Rewards/CustomReplacement.cs b/Tools/BiblioTech/Rewards/CustomReplacement.cs new file mode 100644 index 00000000..359411a9 --- /dev/null +++ b/Tools/BiblioTech/Rewards/CustomReplacement.cs @@ -0,0 +1,34 @@ +namespace BiblioTech.Rewards +{ + public class CustomReplacement + { + private readonly Dictionary replacements = new Dictionary(); + + public void Add(string from, string to) + { + if (replacements.ContainsKey(from)) + { + replacements[from] = to; + } + else + { + replacements.Add(from, to); + } + } + + public void Remove(string from) + { + replacements.Remove(from); + } + + public string Apply(string msg) + { + var result = msg; + foreach (var pair in replacements) + { + result.Replace(pair.Key, pair.Value); + } + return result; + } + } +} diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 6f1819d3..71019b78 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -14,12 +14,12 @@ namespace BiblioTech.Rewards private readonly ChainEventsSender eventsSender; private readonly RewardRepo repo = new RewardRepo(); - public RoleDriver(DiscordSocketClient client, ILog log) + public RoleDriver(DiscordSocketClient client, ILog log, CustomReplacement replacement) { this.client = client; this.log = log; rewardsChannel = GetChannel(Program.Config.RewardsChannelId); - eventsSender = new ChainEventsSender(log, GetChannel(Program.Config.ChainEventsChannelId)); + eventsSender = new ChainEventsSender(log, replacement, GetChannel(Program.Config.ChainEventsChannelId)); } public async Task GiveRewards(GiveRewardsCommand rewards) From a92455b2a594675a940508a1d59438b3cabd1856 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 11:16:50 +0200 Subject: [PATCH 141/142] bump codex image to v0.1.2 + prover --- ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 793c9acf..bb884acf 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-305b80a-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-471ebb2-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; From 52a02abd3f47656d4ffcb03f20e16e30dc6c269c Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 11:43:25 +0200 Subject: [PATCH 142/142] Fixes incorrect block numbers in eventFormatter --- .../ChainMonitor/ChainState.cs | 65 +++++++++++-------- .../DoNothingChainEventHandler.cs | 12 ++-- Tools/TestNetRewarder/ChainChangeMux.cs | 24 +++---- Tools/TestNetRewarder/EventsFormatter.cs | 31 ++++----- Tools/TestNetRewarder/MarketBuffer.cs | 18 ++--- Tools/TestNetRewarder/MarketTracker.cs | 14 ++-- Tools/TestNetRewarder/RewardCheck.cs | 32 ++++----- 7 files changed, 105 insertions(+), 91 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 92169b7c..23ac7dc5 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -1,6 +1,7 @@ using CodexContractsPlugin.Marketplace; using GethPlugin; using Logging; +using NethereumWorkflow.BlockUtils; using System.Numerics; using Utils; @@ -8,12 +9,24 @@ namespace CodexContractsPlugin.ChainMonitor { public interface IChainStateChangeHandler { - void OnNewRequest(IChainStateRequest request); - void OnRequestFinished(IChainStateRequest request); - void OnRequestFulfilled(IChainStateRequest request); - void OnRequestCancelled(IChainStateRequest request); - void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex); - void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex); + void OnNewRequest(RequestEvent requestEvent); + void OnRequestFinished(RequestEvent requestEvent); + void OnRequestFulfilled(RequestEvent requestEvent); + void OnRequestCancelled(RequestEvent requestEvent); + void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex); + void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex); + } + + public class RequestEvent + { + public RequestEvent(BlockTimeEntry block, IChainStateRequest request) + { + Block = block; + Request = request; + } + + public BlockTimeEntry Block { get; } + public IChainStateRequest Request { get; } } public class ChainState @@ -92,41 +105,41 @@ namespace CodexContractsPlugin.ChainMonitor var newRequest = new ChainStateRequest(log, request, RequestState.New); requests.Add(newRequest); - handler.OnNewRequest(newRequest); + handler.OnNewRequest(new RequestEvent(request.Block, newRequest)); } - private void ApplyEvent(RequestFulfilledEventDTO request) + private void ApplyEvent(RequestFulfilledEventDTO @event) { - var r = FindRequest(request.RequestId); + var r = FindRequest(@event.RequestId); if (r == null) return; - r.UpdateState(request.Block.BlockNumber, RequestState.Started); - handler.OnRequestFulfilled(r); + r.UpdateState(@event.Block.BlockNumber, RequestState.Started); + handler.OnRequestFulfilled(new RequestEvent(@event.Block, r)); } - private void ApplyEvent(RequestCancelledEventDTO request) + private void ApplyEvent(RequestCancelledEventDTO @event) { - var r = FindRequest(request.RequestId); + var r = FindRequest(@event.RequestId); if (r == null) return; - r.UpdateState(request.Block.BlockNumber, RequestState.Cancelled); - handler.OnRequestCancelled(r); + r.UpdateState(@event.Block.BlockNumber, RequestState.Cancelled); + handler.OnRequestCancelled(new RequestEvent(@event.Block, r)); } - private void ApplyEvent(SlotFilledEventDTO request) + private void ApplyEvent(SlotFilledEventDTO @event) { - var r = FindRequest(request.RequestId); + var r = FindRequest(@event.RequestId); if (r == null) return; - r.Hosts.Add(request.Host, (int)request.SlotIndex); - r.Log($"[{request.Block.BlockNumber}] SlotFilled (host:'{request.Host}', slotIndex:{request.SlotIndex})"); - handler.OnSlotFilled(r, request.Host, request.SlotIndex); + r.Hosts.Add(@event.Host, (int)@event.SlotIndex); + r.Log($"[{@event.Block.BlockNumber}] SlotFilled (host:'{@event.Host}', slotIndex:{@event.SlotIndex})"); + handler.OnSlotFilled(new RequestEvent(@event.Block, r), @event.Host, @event.SlotIndex); } - private void ApplyEvent(SlotFreedEventDTO request) + private void ApplyEvent(SlotFreedEventDTO @event) { - var r = FindRequest(request.RequestId); + var r = FindRequest(@event.RequestId); if (r == null) return; - r.Hosts.RemoveHost((int)request.SlotIndex); - r.Log($"[{request.Block.BlockNumber}] SlotFreed (slotIndex:{request.SlotIndex})"); - handler.OnSlotFreed(r, request.SlotIndex); + r.Hosts.RemoveHost((int)@event.SlotIndex); + r.Log($"[{@event.Block.BlockNumber}] SlotFreed (slotIndex:{@event.SlotIndex})"); + handler.OnSlotFreed(new RequestEvent(@event.Block, r), @event.SlotIndex); } private void ApplyTimeImplicitEvents(ulong blockNumber, DateTime eventsUtc) @@ -137,7 +150,7 @@ namespace CodexContractsPlugin.ChainMonitor && r.FinishedUtc < eventsUtc) { r.UpdateState(blockNumber, RequestState.Finished); - handler.OnRequestFinished(r); + handler.OnRequestFinished(new RequestEvent(new BlockTimeEntry(blockNumber, eventsUtc), r)); } } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs index 41882c38..c564ec8a 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -5,27 +5,27 @@ namespace CodexContractsPlugin.ChainMonitor { public class DoNothingChainEventHandler : IChainStateChangeHandler { - public void OnNewRequest(IChainStateRequest request) + public void OnNewRequest(RequestEvent requestEvent) { } - public void OnRequestCancelled(IChainStateRequest request) + public void OnRequestCancelled(RequestEvent requestEvent) { } - public void OnRequestFinished(IChainStateRequest request) + public void OnRequestFinished(RequestEvent requestEvent) { } - public void OnRequestFulfilled(IChainStateRequest request) + public void OnRequestFulfilled(RequestEvent requestEvent) { } - public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { } - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { } } diff --git a/Tools/TestNetRewarder/ChainChangeMux.cs b/Tools/TestNetRewarder/ChainChangeMux.cs index 740ba318..f906a20f 100644 --- a/Tools/TestNetRewarder/ChainChangeMux.cs +++ b/Tools/TestNetRewarder/ChainChangeMux.cs @@ -13,34 +13,34 @@ namespace TestNetRewarder this.handlers = handlers; } - public void OnNewRequest(IChainStateRequest request) + public void OnNewRequest(RequestEvent requestEvent) { - foreach (var handler in handlers) handler.OnNewRequest(request); + foreach (var handler in handlers) handler.OnNewRequest(requestEvent); } - public void OnRequestCancelled(IChainStateRequest request) + public void OnRequestCancelled(RequestEvent requestEvent) { - foreach (var handler in handlers) handler.OnRequestCancelled(request); + foreach (var handler in handlers) handler.OnRequestCancelled(requestEvent); } - public void OnRequestFinished(IChainStateRequest request) + public void OnRequestFinished(RequestEvent requestEvent) { - foreach (var handler in handlers) handler.OnRequestFinished(request); + foreach (var handler in handlers) handler.OnRequestFinished(requestEvent); } - public void OnRequestFulfilled(IChainStateRequest request) + public void OnRequestFulfilled(RequestEvent requestEvent) { - foreach (var handler in handlers) handler.OnRequestFulfilled(request); + foreach (var handler in handlers) handler.OnRequestFulfilled(requestEvent); } - public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { - foreach (var handler in handlers) handler.OnSlotFilled(request, host, slotIndex); + foreach (var handler in handlers) handler.OnSlotFilled(requestEvent, host, slotIndex); } - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { - foreach (var handler in handlers) handler.OnSlotFreed(request, slotIndex); + foreach (var handler in handlers) handler.OnSlotFreed(requestEvent, slotIndex); } } } diff --git a/Tools/TestNetRewarder/EventsFormatter.cs b/Tools/TestNetRewarder/EventsFormatter.cs index 0ddf6ee4..048a3a55 100644 --- a/Tools/TestNetRewarder/EventsFormatter.cs +++ b/Tools/TestNetRewarder/EventsFormatter.cs @@ -23,9 +23,10 @@ namespace TestNetRewarder AddBlock("📢 **Error**", error); } - public void OnNewRequest(IChainStateRequest request) + public void OnNewRequest(RequestEvent requestEvent) { - AddRequestBlock(request, "New Request", + var request = requestEvent.Request; + AddRequestBlock(requestEvent, "New Request", $"Client: {request.Client}", $"Content: {request.Request.Content.Cid}", $"Duration: {BigIntToDuration(request.Request.Ask.Duration)}", @@ -38,40 +39,40 @@ namespace TestNetRewarder ); } - public void OnRequestCancelled(IChainStateRequest request) + public void OnRequestCancelled(RequestEvent requestEvent) { - AddRequestBlock(request, "Cancelled"); + AddRequestBlock(requestEvent, "Cancelled"); } - public void OnRequestFinished(IChainStateRequest request) + public void OnRequestFinished(RequestEvent requestEvent) { - AddRequestBlock(request, "Finished"); + AddRequestBlock(requestEvent, "Finished"); } - public void OnRequestFulfilled(IChainStateRequest request) + public void OnRequestFulfilled(RequestEvent requestEvent) { - AddRequestBlock(request, "Started"); + AddRequestBlock(requestEvent, "Started"); } - public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { - AddRequestBlock(request, "Slot Filled", + AddRequestBlock(requestEvent, "Slot Filled", $"Host: {host}", $"Slot Index: {slotIndex}" ); } - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { - AddRequestBlock(request, "Slot Freed", + AddRequestBlock(requestEvent, "Slot Freed", $"Slot Index: {slotIndex}" ); } - private void AddRequestBlock(IChainStateRequest request, string eventName, params string[] content) + private void AddRequestBlock(RequestEvent requestEvent, string eventName, params string[] content) { - var blockNumber = $"[{request.Request.Block.BlockNumber}]"; - var title = $"{blockNumber} **{eventName}** `{request.Request.Id}`"; + var blockNumber = $"[{requestEvent.Block.BlockNumber}]"; + var title = $"{blockNumber} **{eventName}** `{requestEvent.Request.Request.Id}`"; AddBlock(title, content); } diff --git a/Tools/TestNetRewarder/MarketBuffer.cs b/Tools/TestNetRewarder/MarketBuffer.cs index d8ec91d5..0fadf7e7 100644 --- a/Tools/TestNetRewarder/MarketBuffer.cs +++ b/Tools/TestNetRewarder/MarketBuffer.cs @@ -7,7 +7,7 @@ namespace TestNetRewarder { public class MarketBuffer { - private readonly List requests = new List(); + private readonly List requestEvents = new List(); private readonly TimeSpan bufferSpan; public MarketBuffer(TimeSpan bufferSpan) @@ -15,24 +15,24 @@ namespace TestNetRewarder this.bufferSpan = bufferSpan; } - public void Add(IChainStateRequest request) + public void Add(RequestEvent requestEvent) { - requests.Add(request); + requestEvents.Add(requestEvent); } public void Update() { var now = DateTime.UtcNow; - requests.RemoveAll(r => (now - r.FinishedUtc) > bufferSpan); + requestEvents.RemoveAll(r => (now - r.Request.FinishedUtc) > bufferSpan); } public MarketAverage? GetAverage() { - if (requests.Count == 0) return null; + if (requestEvents.Count == 0) return null; return new MarketAverage { - NumberOfFinished = requests.Count, + NumberOfFinished = requestEvents.Count, TimeRangeSeconds = (int)bufferSpan.TotalSeconds, Price = Average(s => s.Request.Ask.Reward), Duration = Average(s => s.Request.Ask.Duration), @@ -54,10 +54,10 @@ namespace TestNetRewarder private float Average(Func getValue) { var sum = 0.0f; - float count = requests.Count; - foreach (var r in requests) + float count = requestEvents.Count; + foreach (var r in requestEvents) { - sum += getValue(r); + sum += getValue(r.Request); } if (count < 1.0f) return 0.0f; diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index a850e322..981c51c5 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -32,28 +32,28 @@ namespace TestNetRewarder return buffers.Select(b => b.GetAverage()).Where(a => a != null).Cast().ToArray(); } - public void OnNewRequest(IChainStateRequest request) + public void OnNewRequest(RequestEvent requestEvent) { } - public void OnRequestFinished(IChainStateRequest request) + public void OnRequestFinished(RequestEvent requestEvent) { - foreach (var b in buffers) b.Add(request); + foreach (var b in buffers) b.Add(requestEvent); } - public void OnRequestFulfilled(IChainStateRequest request) + public void OnRequestFulfilled(RequestEvent requestEvent) { } - public void OnRequestCancelled(IChainStateRequest request) + public void OnRequestCancelled(RequestEvent requestEvent) { } - public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { } - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { } diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs index 0132f040..c01e0835 100644 --- a/Tools/TestNetRewarder/RewardCheck.cs +++ b/Tools/TestNetRewarder/RewardCheck.cs @@ -22,40 +22,40 @@ namespace TestNetRewarder this.giver = giver; } - public void OnNewRequest(IChainStateRequest request) + public void OnNewRequest(RequestEvent requestEvent) { - if (MeetsRequirements(CheckType.ClientPostedContract, request)) + if (MeetsRequirements(CheckType.ClientPostedContract, requestEvent)) { - GiveReward(reward, request.Client); + GiveReward(reward, requestEvent.Request.Client); } } - public void OnRequestCancelled(IChainStateRequest request) + public void OnRequestCancelled(RequestEvent requestEvent) { } - public void OnRequestFinished(IChainStateRequest request) + public void OnRequestFinished(RequestEvent requestEvent) { - if (MeetsRequirements(CheckType.HostFinishedSlot, request)) + if (MeetsRequirements(CheckType.HostFinishedSlot, requestEvent)) { - foreach (var host in request.Hosts.GetHosts()) + foreach (var host in requestEvent.Request.Hosts.GetHosts()) { GiveReward(reward, host); } } } - public void OnRequestFulfilled(IChainStateRequest request) + public void OnRequestFulfilled(RequestEvent requestEvent) { - if (MeetsRequirements(CheckType.ClientStartedContract, request)) + if (MeetsRequirements(CheckType.ClientStartedContract, requestEvent)) { - GiveReward(reward, request.Client); + GiveReward(reward, requestEvent.Request.Client); } } - public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { - if (MeetsRequirements(CheckType.HostFilledSlot, request)) + if (MeetsRequirements(CheckType.HostFilledSlot, requestEvent)) { if (host != null) { @@ -64,7 +64,7 @@ namespace TestNetRewarder } } - public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { } @@ -73,12 +73,12 @@ namespace TestNetRewarder giver.Give(reward, receiver); } - private bool MeetsRequirements(CheckType type, IChainStateRequest request) + private bool MeetsRequirements(CheckType type, RequestEvent requestEvent) { return reward.CheckConfig.Type == type && - MeetsDurationRequirement(request) && - MeetsSizeRequirement(request); + MeetsDurationRequirement(requestEvent.Request) && + MeetsSizeRequirement(requestEvent.Request); } private bool MeetsSizeRequirement(IChainStateRequest r)