From 89f1f74ffad8936cc2e63ce5c467638d87de0b54 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 9 Apr 2025 14:39:35 +0200 Subject: [PATCH 01/51] Fix for exceptionally long error messages in admin channel on checkCID fail --- Tools/BiblioTech/CodexCidChecker.cs | 61 ++++---------------- Tools/BiblioTech/Commands/CheckCidCommand.cs | 22 ++++++- 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/Tools/BiblioTech/CodexCidChecker.cs b/Tools/BiblioTech/CodexCidChecker.cs index 15728d0d..dd3b30c7 100644 --- a/Tools/BiblioTech/CodexCidChecker.cs +++ b/Tools/BiblioTech/CodexCidChecker.cs @@ -33,7 +33,7 @@ namespace BiblioTech { if (string.IsNullOrEmpty(config.CodexEndpoint)) { - return new CheckResponse(false, "Codex CID checker is not (yet) available.", ""); + return new CheckResponse(false, "Codex CID checker is not (yet) available."); } try @@ -41,14 +41,10 @@ namespace BiblioTech checkMutex.WaitOne(); var codex = GetCodex(); var nodeCheck = CheckCodex(codex); - if (!nodeCheck) return new CheckResponse(false, "Codex node is not available. Cannot perform check.", $"Codex node at '{config.CodexEndpoint}' did not respond correctly to debug/info."); + if (!nodeCheck) return new CheckResponse(false, "Codex node is not available. Cannot perform check."); return PerformCheck(codex, cid); } - catch (Exception ex) - { - return new CheckResponse(false, "Internal server error", ex.ToString()); - } finally { checkMutex.ReleaseMutex(); @@ -62,9 +58,9 @@ namespace BiblioTech var manifest = codex.DownloadManifestOnly(new ContentId(cid)); return SuccessMessage(manifest); } - catch (Exception ex) + catch { - return UnexpectedException(ex); + return FailedMessage(); } } @@ -74,58 +70,27 @@ namespace BiblioTech { return FormatResponse( success: true, - title: $"Success: '{content.Cid}'", - error: "", + title: $"Success", + $"cid: '{content.Cid}'", $"size: {content.Manifest.OriginalBytes} bytes", $"blockSize: {content.Manifest.BlockSize} bytes", $"protected: {content.Manifest.Protected}" ); } - private CheckResponse UnexpectedException(Exception ex) + private CheckResponse FailedMessage() { - return FormatResponse( - success: false, - title: "Unexpected error", - error: ex.ToString(), - content: "Details will be sent to the bot-admin channel." - ); - } + var msg = "Could not download content."; - private CheckResponse UnexpectedReturnCode(string response) - { - var msg = "Unexpected return code. Response: " + response; return FormatResponse( success: false, - title: "Unexpected return code", - error: msg, - content: msg - ); - } - - private CheckResponse FailedToFetch(string response) - { - var msg = "Failed to download content. Response: " + response; - return FormatResponse( - success: false, - title: "Could not download content", - error: msg, + title: "Failed", msg, $"Connection trouble? See 'https://docs.codex.storage/learn/troubleshoot'" ); } - private CheckResponse CidFormatInvalid(string response) - { - return FormatResponse( - success: false, - title: "Invalid format", - error: "", - content: "Provided CID is not formatted correctly." - ); - } - - private CheckResponse FormatResponse(bool success, string title, string error, params string[] content) + private CheckResponse FormatResponse(bool success, string title, params string[] content) { var msg = string.Join(nl, new string[] @@ -140,7 +105,7 @@ namespace BiblioTech }) ) + nl + nl; - return new CheckResponse(success, msg, error); + return new CheckResponse(success, msg); } #endregion @@ -190,15 +155,13 @@ namespace BiblioTech public class CheckResponse { - public CheckResponse(bool success, string message, string error) + public CheckResponse(bool success, string message) { Success = success; Message = message; - Error = error; } public bool Success { get; } public string Message { get; } - public string Error { get; } } } diff --git a/Tools/BiblioTech/Commands/CheckCidCommand.cs b/Tools/BiblioTech/Commands/CheckCidCommand.cs index 1e77ce26..1c5d6138 100644 --- a/Tools/BiblioTech/Commands/CheckCidCommand.cs +++ b/Tools/BiblioTech/Commands/CheckCidCommand.cs @@ -1,5 +1,6 @@ using BiblioTech.Options; using Discord; +using Discord.WebSocket; namespace BiblioTech.Commands { @@ -33,18 +34,35 @@ namespace BiblioTech.Commands return; } - var response = checker.PerformCheck(cid); - await Program.AdminChecker.SendInAdminChannel($"User {Mention(user)} used '/{Name}' for cid '{cid}'. Lookup-success: {response.Success}. Message: '{response.Message}' Error: '{response.Error}'"); + try + { + await PerformCheck(context, user, cid); + } + catch (Exception ex) + { + await RespondeWithError(context, ex); + } + } + private async Task PerformCheck(CommandContext context, SocketUser user, string cid) + { + var response = checker.PerformCheck(cid); if (response.Success) { await CheckAltruisticRole(context, user, cid, response.Message); return; } + await Program.AdminChecker.SendInAdminChannel($"User {Mention(user)} used '/{Name}' for cid '{cid}'. Lookup-success: {response.Success}. Message: '{response.Message}'"); await context.Followup(response.Message); } + private async Task RespondeWithError(CommandContext context, Exception ex) + { + await Program.AdminChecker.SendInAdminChannel("Exception during CheckCidCommand: " + ex); + await context.Followup("I'm sorry to report something has gone wrong in an unexpected way. Error details are already posted in the admin channel."); + } + private async Task CheckAltruisticRole(CommandContext context, IUser user, string cid, string responseMessage) { if (cidStorage.TryAddCid(cid, user.Id)) From 7ccdbd3c268a5921799784218bef48a1faed05f0 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 9 Apr 2025 15:25:31 +0200 Subject: [PATCH 02/51] wip --- Framework/WebUtils/Http.cs | 9 - Framework/WebUtils/HttpFactory.cs | 24 ++- Tools/BiblioTech/CodexCidChecker.cs | 167 ------------------- Tools/BiblioTech/CodexTwoWayChecker.cs | 59 +++++++ Tools/BiblioTech/Commands/CheckCidCommand.cs | 4 +- Tools/BiblioTech/Program.cs | 2 +- 6 files changed, 84 insertions(+), 181 deletions(-) delete mode 100644 Tools/BiblioTech/CodexCidChecker.cs create mode 100644 Tools/BiblioTech/CodexTwoWayChecker.cs diff --git a/Framework/WebUtils/Http.cs b/Framework/WebUtils/Http.cs index 7c9a91ad..e4931996 100644 --- a/Framework/WebUtils/Http.cs +++ b/Framework/WebUtils/Http.cs @@ -20,11 +20,6 @@ namespace WebUtils private readonly Action onClientCreated; private readonly string id; - internal Http(string id, ILog log, IWebCallTimeSet timeSet) - : this(id, log, timeSet, DoNothing) - { - } - internal Http(string id, ILog log, IWebCallTimeSet timeSet, Action onClientCreated) { this.id = id; @@ -89,9 +84,5 @@ namespace WebUtils onClientCreated(client); return client; } - - private static void DoNothing(HttpClient client) - { - } } } diff --git a/Framework/WebUtils/HttpFactory.cs b/Framework/WebUtils/HttpFactory.cs index 4d5d7c4c..8120527c 100644 --- a/Framework/WebUtils/HttpFactory.cs +++ b/Framework/WebUtils/HttpFactory.cs @@ -13,16 +13,28 @@ namespace WebUtils { private readonly ILog log; private readonly IWebCallTimeSet defaultTimeSet; + private readonly Action factoryOnClientCreated; public HttpFactory(ILog log) : this (log, new DefaultWebCallTimeSet()) { } + public HttpFactory(ILog log, Action onClientCreated) + : this(log, new DefaultWebCallTimeSet(), onClientCreated) + { + } + public HttpFactory(ILog log, IWebCallTimeSet defaultTimeSet) + : this(log, defaultTimeSet, DoNothing) + { + } + + public HttpFactory(ILog log, IWebCallTimeSet defaultTimeSet, Action onClientCreated) { this.log = log; this.defaultTimeSet = defaultTimeSet; + this.factoryOnClientCreated = onClientCreated; } public IHttp CreateHttp(string id, Action onClientCreated) @@ -32,12 +44,20 @@ namespace WebUtils public IHttp CreateHttp(string id, Action onClientCreated, IWebCallTimeSet ts) { - return new Http(id, log, ts, onClientCreated); + return new Http(id, log, ts, (c) => + { + factoryOnClientCreated(c); + onClientCreated(c); + }); } public IHttp CreateHttp(string id) { - return new Http(id, log, defaultTimeSet); + return new Http(id, log, defaultTimeSet, factoryOnClientCreated); + } + + private static void DoNothing(HttpClient client) + { } } } diff --git a/Tools/BiblioTech/CodexCidChecker.cs b/Tools/BiblioTech/CodexCidChecker.cs deleted file mode 100644 index dd3b30c7..00000000 --- a/Tools/BiblioTech/CodexCidChecker.cs +++ /dev/null @@ -1,167 +0,0 @@ -using CodexClient; -using Logging; -using Utils; - -namespace BiblioTech -{ - public class CodexCidChecker - { - private static readonly string nl = Environment.NewLine; - private readonly Configuration config; - private readonly ILog log; - private readonly Mutex checkMutex = new Mutex(); - private readonly CodexNodeFactory factory; - private ICodexNode? currentCodexNode; - - public CodexCidChecker(Configuration config, ILog log) - { - this.config = config; - this.log = log; - - factory = new CodexNodeFactory(log, dataDir: config.DataPath); - - if (!string.IsNullOrEmpty(config.CodexEndpointAuth) && config.CodexEndpointAuth.Contains(":")) - { - throw new Exception("Todo: codexnodefactory httpfactory support basicauth!"); - //var tokens = config.CodexEndpointAuth.Split(':'); - //if (tokens.Length != 2) throw new Exception("Expected ':' in CodexEndpointAuth parameter."); - //client.SetBasicAuthentication(tokens[0], tokens[1]); - } - } - - public CheckResponse PerformCheck(string cid) - { - if (string.IsNullOrEmpty(config.CodexEndpoint)) - { - return new CheckResponse(false, "Codex CID checker is not (yet) available."); - } - - try - { - checkMutex.WaitOne(); - var codex = GetCodex(); - var nodeCheck = CheckCodex(codex); - if (!nodeCheck) return new CheckResponse(false, "Codex node is not available. Cannot perform check."); - - return PerformCheck(codex, cid); - } - finally - { - checkMutex.ReleaseMutex(); - } - } - - private CheckResponse PerformCheck(ICodexNode codex, string cid) - { - try - { - var manifest = codex.DownloadManifestOnly(new ContentId(cid)); - return SuccessMessage(manifest); - } - catch - { - return FailedMessage(); - } - } - - #region Response formatting - - private CheckResponse SuccessMessage(LocalDataset content) - { - return FormatResponse( - success: true, - title: $"Success", - $"cid: '{content.Cid}'", - $"size: {content.Manifest.OriginalBytes} bytes", - $"blockSize: {content.Manifest.BlockSize} bytes", - $"protected: {content.Manifest.Protected}" - ); - } - - private CheckResponse FailedMessage() - { - var msg = "Could not download content."; - - return FormatResponse( - success: false, - title: "Failed", - msg, - $"Connection trouble? See 'https://docs.codex.storage/learn/troubleshoot'" - ); - } - - private CheckResponse FormatResponse(bool success, string title, params string[] content) - { - var msg = string.Join(nl, - new string[] - { - title, - "```" - } - .Concat(content) - .Concat(new string[] - { - "```" - }) - ) + nl + nl; - - return new CheckResponse(success, msg); - } - - #endregion - - #region Codex Node API - - private ICodexNode GetCodex() - { - if (currentCodexNode == null) currentCodexNode = CreateCodex(); - return currentCodexNode; - } - - private bool CheckCodex(ICodexNode node) - { - try - { - var info = node.GetDebugInfo(); - if (info == null || string.IsNullOrEmpty(info.Id)) return false; - return true; - } - catch (Exception e) - { - log.Error(e.ToString()); - return false; - } - } - - private ICodexNode CreateCodex() - { - var endpoint = config.CodexEndpoint; - var splitIndex = endpoint.LastIndexOf(':'); - var host = endpoint.Substring(0, splitIndex); - var port = Convert.ToInt32(endpoint.Substring(splitIndex + 1)); - - var address = new Address( - logName: $"cdx@{host}:{port}", - host: host, - port: port - ); - - var instance = CodexInstance.CreateFromApiEndpoint("ac", address); - return factory.CreateCodexNode(instance); - } - - #endregion - } - - public class CheckResponse - { - public CheckResponse(bool success, string message) - { - Success = success; - Message = message; - } - - public bool Success { get; } - public string Message { get; } - } -} diff --git a/Tools/BiblioTech/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexTwoWayChecker.cs new file mode 100644 index 00000000..8aa27f5b --- /dev/null +++ b/Tools/BiblioTech/CodexTwoWayChecker.cs @@ -0,0 +1,59 @@ +using CodexClient; +using IdentityModel.Client; +using Logging; +using Utils; +using WebUtils; + +namespace BiblioTech +{ + public class CodexTwoWayChecker + { + private static readonly string nl = Environment.NewLine; + private readonly Configuration config; + private readonly ILog log; + private readonly CodexNodeFactory factory; + private ICodexNode? currentCodexNode; + + public CodexTwoWayChecker(Configuration config, ILog log) + { + this.config = config; + this.log = log; + + var httpFactory = CreateHttpFactory(); + + factory = new CodexNodeFactory(log, httpFactory, dataDir: config.DataPath); + } + + // down check: + // generate unique data + // upload to cloud node + // give CID to user to download + // user inputs unique data into command to clear this check + + // up check: + // generate unique data + // create file and send it to user via discord api + // user uploads and gives CID via command + // download manifest: file is not larger than expected + // download file: contents is unique data -> clear this check + + // both checks: altruistic role + + private HttpFactory CreateHttpFactory() + { + if (string.IsNullOrEmpty(config.CodexEndpointAuth) && config.CodexEndpointAuth.Contains(":")) + { + return new HttpFactory(log); + } + + var tokens = config.CodexEndpointAuth.Split(':'); + if (tokens.Length != 2) throw new Exception("Expected ':' in CodexEndpointAuth parameter."); + + return new HttpFactory(log, onClientCreated: client => + { + client.SetBasicAuthentication(tokens[0], tokens[1]); + }); + } + + } +} diff --git a/Tools/BiblioTech/Commands/CheckCidCommand.cs b/Tools/BiblioTech/Commands/CheckCidCommand.cs index 1c5d6138..f19a33f5 100644 --- a/Tools/BiblioTech/Commands/CheckCidCommand.cs +++ b/Tools/BiblioTech/Commands/CheckCidCommand.cs @@ -10,10 +10,10 @@ namespace BiblioTech.Commands name: "cid", description: "Codex Content-Identifier", isRequired: true); - private readonly CodexCidChecker checker; + private readonly CodexTwoWayChecker checker; private readonly CidStorage cidStorage; - public CheckCidCommand(CodexCidChecker checker) + public CheckCidCommand(CodexTwoWayChecker checker) { this.checker = checker; this.cidStorage = new CidStorage(Path.Combine(Program.Config.DataPath, "valid_cids.txt")); diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 46dee29c..ffc4f86d 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -80,7 +80,7 @@ namespace BiblioTech client = new DiscordSocketClient(); client.Log += ClientLog; - var checker = new CodexCidChecker(Config, Log); + var checker = new CodexTwoWayChecker(Config, Log); var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); From 9ccc4c559cf025d5603786b04f2eb332892d228c Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 14:54:49 +0200 Subject: [PATCH 03/51] sets up upload and download checking commands --- Framework/Utils/RandomUtils.cs | 18 +- .../Modes/FolderStore/FolderSaver.cs | 16 +- Tools/BiblioTech/CodexChecking/CheckRepo.cs | 78 +++++++ .../CodexChecking/CodexTwoWayChecker.cs | 209 ++++++++++++++++++ .../BiblioTech/CodexChecking/CodexWrapper.cs | 85 +++++++ Tools/BiblioTech/CodexTwoWayChecker.cs | 59 ----- Tools/BiblioTech/Commands/CheckCidCommand.cs | 129 ----------- .../Commands/CheckDownloadCommand.cs | 53 +++++ .../Commands/CheckResponseHandler.cs | 66 ++++++ .../BiblioTech/Commands/CheckUploadCommand.cs | 53 +++++ Tools/BiblioTech/Configuration.cs | 1 + Tools/BiblioTech/Options/CommandContext.cs | 10 + Tools/BiblioTech/Program.cs | 8 +- Tools/BiblioTech/RandomBusyMessage.cs | 4 +- 14 files changed, 583 insertions(+), 206 deletions(-) create mode 100644 Tools/BiblioTech/CodexChecking/CheckRepo.cs create mode 100644 Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs create mode 100644 Tools/BiblioTech/CodexChecking/CodexWrapper.cs delete mode 100644 Tools/BiblioTech/CodexTwoWayChecker.cs delete mode 100644 Tools/BiblioTech/Commands/CheckCidCommand.cs create mode 100644 Tools/BiblioTech/Commands/CheckDownloadCommand.cs create mode 100644 Tools/BiblioTech/Commands/CheckResponseHandler.cs create mode 100644 Tools/BiblioTech/Commands/CheckUploadCommand.cs diff --git a/Framework/Utils/RandomUtils.cs b/Framework/Utils/RandomUtils.cs index f4f28dd4..b5d2580b 100644 --- a/Framework/Utils/RandomUtils.cs +++ b/Framework/Utils/RandomUtils.cs @@ -34,10 +34,26 @@ var source = items.ToList(); while (source.Any()) { - result.Add(RandomUtils.PickOneRandom(source)); + result.Add(PickOneRandom(source)); } return result.ToArray(); } } + + public static string GenerateRandomString(long requiredLength) + { + lock (@lock) + { + var result = ""; + while (result.Length < requiredLength) + { + var bytes = new byte[1024]; + random.NextBytes(bytes); + result += string.Join("", bytes.Select(b => b.ToString())); + } + + return result; + } + } } } diff --git a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs index ed358ccc..77251080 100644 --- a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs +++ b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs @@ -1,4 +1,5 @@ using Logging; +using Utils; namespace AutoClient.Modes.FolderStore { @@ -126,24 +127,11 @@ namespace AutoClient.Modes.FolderStore if (info.Length < min) { var required = Math.Max(1024, min - info.Length); - status.Padding = paddingMessage + GenerateRandomString(required); + status.Padding = paddingMessage + RandomUtils.GenerateRandomString(required); statusFile.Save(status); } } - private string GenerateRandomString(long required) - { - var result = ""; - while (result.Length < required) - { - var bytes = new byte[1024]; - random.NextBytes(bytes); - result += string.Join("", bytes.Select(b => b.ToString())); - } - - return result; - } - private FileSaver CreateFileSaver(string folderFile, FileStatus entry) { var fixedLength = entry.Filename.PadRight(35); diff --git a/Tools/BiblioTech/CodexChecking/CheckRepo.cs b/Tools/BiblioTech/CodexChecking/CheckRepo.cs new file mode 100644 index 00000000..a8b2860b --- /dev/null +++ b/Tools/BiblioTech/CodexChecking/CheckRepo.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; + +namespace BiblioTech.CodexChecking +{ + public class CheckRepo + { + private const string modelFilename = "model.json"; + private readonly Configuration config; + private readonly object _lock = new object(); + private CheckRepoModel? model = null; + + public CheckRepo(Configuration config) + { + this.config = config; + } + + public CheckReport GetOrCreate(ulong userId) + { + lock (_lock) + { + if (model == null) LoadModel(); + + var existing = model.Reports.SingleOrDefault(r => r.UserId == userId); + if (existing == null) + { + var newEntry = new CheckReport + { + UserId = userId, + }; + model.Reports.Add(newEntry); + SaveChanges(); + return newEntry; + } + return existing; + } + } + + public void SaveChanges() + { + File.WriteAllText(GetModelFilepath(), JsonConvert.SerializeObject(model, Formatting.Indented)); + } + + private void LoadModel() + { + if (!File.Exists(GetModelFilepath())) + { + model = new CheckRepoModel(); + SaveChanges(); + return; + } + + model = JsonConvert.DeserializeObject(File.ReadAllText(GetModelFilepath())); + } + + private string GetModelFilepath() + { + return Path.Combine(config.ChecksDataPath, modelFilename); + } + } + + public class CheckRepoModel + { + public List Reports { get; set; } = new List(); + } + + public class CheckReport + { + public ulong UserId { get; set; } + public TransferCheck UploadCheck { get; set; } = new TransferCheck(); + public TransferCheck DownloadCheck { get; set; } = new TransferCheck(); + } + + public class TransferCheck + { + public DateTime CompletedUtc { get; set; } = DateTime.MinValue; + public string UniqueData { get; set; } = string.Empty; + } +} diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs new file mode 100644 index 00000000..8dadc97b --- /dev/null +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -0,0 +1,209 @@ +using CodexClient; +using FileUtils; +using Logging; +using Utils; + +namespace BiblioTech.CodexChecking +{ + public interface ICheckResponseHandler + { + Task CheckNotStarted(); + Task NowCompleted(); + Task GiveRoleReward(); + + Task InvalidData(); + Task CouldNotDownloadCid(); + Task GiveCidToUser(string cid); + Task GiveDataFileToUser(string fileContent); + } + + public class CodexTwoWayChecker + { + private readonly ILog log; + private readonly Configuration config; + private readonly CheckRepo repo; + private readonly CodexWrapper codexWrapper; + + public CodexTwoWayChecker(ILog log, Configuration config, CheckRepo repo, CodexWrapper codexWrapper) + { + this.log = log; + this.config = config; + this.repo = repo; + this.codexWrapper = codexWrapper; + } + + public async Task StartDownloadCheck(ICheckResponseHandler handler, ulong userId) + { + var check = repo.GetOrCreate(userId).DownloadCheck; + if (string.IsNullOrEmpty(check.UniqueData)) + { + check.UniqueData = GenerateUniqueData(); + repo.SaveChanges(); + } + + var cid = UploadData(check.UniqueData); + await handler.GiveCidToUser(cid); + } + + public async Task VerifyDownloadCheck(ICheckResponseHandler handler, ulong userId, string receivedData) + { + var check = repo.GetOrCreate(userId).DownloadCheck; + if (string.IsNullOrEmpty(check.UniqueData)) + { + await handler.CheckNotStarted(); + return; + } + + if (string.IsNullOrEmpty(receivedData) || receivedData != check.UniqueData) + { + await handler.InvalidData(); + return; + } + + CheckNowCompleted(handler, check, userId); + } + + public async Task StartUploadCheck(ICheckResponseHandler handler, ulong userId) + { + var check = repo.GetOrCreate(userId).UploadCheck; + if (string.IsNullOrEmpty(check.UniqueData)) + { + check.UniqueData = GenerateUniqueData(); + repo.SaveChanges(); + } + + await handler.GiveDataFileToUser(check.UniqueData); + } + + public async Task VerifyUploadCheck(ICheckResponseHandler handler, ulong userId, string receivedCid) + { + var check = repo.GetOrCreate(userId).UploadCheck; + if (string.IsNullOrEmpty(receivedCid)) + { + await handler.InvalidData(); + return; + } + + var manifest = GetManifest(receivedCid); + if (manifest == null) + { + await handler.CouldNotDownloadCid(); + return; + } + + if (IsManifestLengthCompatible(check, manifest)) + { + if (IsContentCorrect(check, receivedCid)) + { + CheckNowCompleted(handler, check, userId); + return; + } + } + + await handler.InvalidData(); + } + + private string GenerateUniqueData() + { + return $"'{RandomBusyMessage.Get()}'{RandomUtils.GenerateRandomString(12)}"; + } + + private string UploadData(string uniqueData) + { + var filePath = Path.Combine(config.ChecksDataPath, Guid.NewGuid().ToString()); + + try + { + File.WriteAllText(filePath, uniqueData); + var file = new TrackedFile(log, filePath, "checkData"); + + return codexWrapper.OnCodex(node => + { + return node.UploadFile(file).Id; + }); + } + catch (Exception ex) + { + log.Error("Exception when uploading data: " + ex); + throw; + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + } + } + + private Manifest? GetManifest(string receivedCid) + { + try + { + return codexWrapper.OnCodex(node => + { + return node.DownloadManifestOnly(new ContentId(receivedCid)).Manifest; + }); + } + catch + { + return null; + } + } + + private bool IsManifestLengthCompatible(TransferCheck check, Manifest manifest) + { + var dataLength = check.UniqueData.Length; + var manifestLength = manifest.OriginalBytes.SizeInBytes; + + return + manifestLength > (dataLength - 1) && + manifestLength < (dataLength + 1); + } + + private bool IsContentCorrect(TransferCheck check, string receivedCid) + { + try + { + return codexWrapper.OnCodex(node => + { + var file = node.DownloadContent(new ContentId(receivedCid)); + if (file == null) return false; + try + { + var content = File.ReadAllText(file.Filename).Trim(); + return content == check.UniqueData; + } + finally + { + if (File.Exists(file.Filename)) File.Delete(file.Filename); + } + }); + } + catch + { + return false; + } + } + + private void CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId) + { + if (check.CompletedUtc != DateTime.MinValue) return; + + check.CompletedUtc = DateTime.UtcNow; + repo.SaveChanges(); + + handler.NowCompleted(); + CheckUserForRoleRewards(handler, userId); + } + + private void CheckUserForRoleRewards(ICheckResponseHandler handler, ulong userId) + { + var check = repo.GetOrCreate(userId); + + if ( + check.UploadCheck.CompletedUtc != DateTime.MinValue && + check.DownloadCheck.CompletedUtc != DateTime.MinValue) + { + handler.GiveRoleReward(); + } + } + } +} diff --git a/Tools/BiblioTech/CodexChecking/CodexWrapper.cs b/Tools/BiblioTech/CodexChecking/CodexWrapper.cs new file mode 100644 index 00000000..a8cbe785 --- /dev/null +++ b/Tools/BiblioTech/CodexChecking/CodexWrapper.cs @@ -0,0 +1,85 @@ +using CodexClient; +using IdentityModel.Client; +using Logging; +using Utils; +using WebUtils; + +namespace BiblioTech.CodexChecking +{ + public class CodexWrapper + { + private readonly CodexNodeFactory factory; + private readonly ILog log; + private readonly Configuration config; + private readonly object codexLock = new object(); + private ICodexNode? currentCodexNode; + + public CodexWrapper(ILog log, Configuration config) + { + this.log = log; + this.config = config; + + var httpFactory = CreateHttpFactory(); + factory = new CodexNodeFactory(log, httpFactory, dataDir: config.DataPath); + } + + public void OnCodex(Action action) + { + lock (codexLock) + { + action(Get()); + } + } + + public T OnCodex(Func func) + { + lock (codexLock) + { + return func(Get()); + } + } + + private ICodexNode Get() + { + if (currentCodexNode == null) + { + currentCodexNode = CreateCodex(); + } + + return currentCodexNode; + } + + private ICodexNode CreateCodex() + { + var endpoint = config.CodexEndpoint; + var splitIndex = endpoint.LastIndexOf(':'); + var host = endpoint.Substring(0, splitIndex); + var port = Convert.ToInt32(endpoint.Substring(splitIndex + 1)); + + var address = new Address( + logName: $"cdx@{host}:{port}", + host: host, + port: port + ); + + var instance = CodexInstance.CreateFromApiEndpoint("ac", address); + return factory.CreateCodexNode(instance); + } + + private HttpFactory CreateHttpFactory() + { + if (string.IsNullOrEmpty(config.CodexEndpointAuth) && config.CodexEndpointAuth.Contains(":")) + { + return new HttpFactory(log); + } + + var tokens = config.CodexEndpointAuth.Split(':'); + if (tokens.Length != 2) throw new Exception("Expected ':' in CodexEndpointAuth parameter."); + + return new HttpFactory(log, onClientCreated: client => + { + client.SetBasicAuthentication(tokens[0], tokens[1]); + }); + } + } +} diff --git a/Tools/BiblioTech/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexTwoWayChecker.cs deleted file mode 100644 index 8aa27f5b..00000000 --- a/Tools/BiblioTech/CodexTwoWayChecker.cs +++ /dev/null @@ -1,59 +0,0 @@ -using CodexClient; -using IdentityModel.Client; -using Logging; -using Utils; -using WebUtils; - -namespace BiblioTech -{ - public class CodexTwoWayChecker - { - private static readonly string nl = Environment.NewLine; - private readonly Configuration config; - private readonly ILog log; - private readonly CodexNodeFactory factory; - private ICodexNode? currentCodexNode; - - public CodexTwoWayChecker(Configuration config, ILog log) - { - this.config = config; - this.log = log; - - var httpFactory = CreateHttpFactory(); - - factory = new CodexNodeFactory(log, httpFactory, dataDir: config.DataPath); - } - - // down check: - // generate unique data - // upload to cloud node - // give CID to user to download - // user inputs unique data into command to clear this check - - // up check: - // generate unique data - // create file and send it to user via discord api - // user uploads and gives CID via command - // download manifest: file is not larger than expected - // download file: contents is unique data -> clear this check - - // both checks: altruistic role - - private HttpFactory CreateHttpFactory() - { - if (string.IsNullOrEmpty(config.CodexEndpointAuth) && config.CodexEndpointAuth.Contains(":")) - { - return new HttpFactory(log); - } - - var tokens = config.CodexEndpointAuth.Split(':'); - if (tokens.Length != 2) throw new Exception("Expected ':' in CodexEndpointAuth parameter."); - - return new HttpFactory(log, onClientCreated: client => - { - client.SetBasicAuthentication(tokens[0], tokens[1]); - }); - } - - } -} diff --git a/Tools/BiblioTech/Commands/CheckCidCommand.cs b/Tools/BiblioTech/Commands/CheckCidCommand.cs deleted file mode 100644 index f19a33f5..00000000 --- a/Tools/BiblioTech/Commands/CheckCidCommand.cs +++ /dev/null @@ -1,129 +0,0 @@ -using BiblioTech.Options; -using Discord; -using Discord.WebSocket; - -namespace BiblioTech.Commands -{ - public class CheckCidCommand : BaseCommand - { - private readonly StringOption cidOption = new StringOption( - name: "cid", - description: "Codex Content-Identifier", - isRequired: true); - private readonly CodexTwoWayChecker checker; - private readonly CidStorage cidStorage; - - public CheckCidCommand(CodexTwoWayChecker checker) - { - this.checker = checker; - this.cidStorage = new CidStorage(Path.Combine(Program.Config.DataPath, "valid_cids.txt")); - } - - public override string Name => "check"; - public override string StartingMessage => RandomBusyMessage.Get(); - public override string Description => "Checks if content is available in the testnet."; - public override CommandOption[] Options => new[] { cidOption }; - - protected override async Task Invoke(CommandContext context) - { - var user = context.Command.User; - var cid = await cidOption.Parse(context); - if (string.IsNullOrEmpty(cid)) - { - await context.Followup("Option 'cid' was not received."); - return; - } - - try - { - await PerformCheck(context, user, cid); - } - catch (Exception ex) - { - await RespondeWithError(context, ex); - } - } - - private async Task PerformCheck(CommandContext context, SocketUser user, string cid) - { - var response = checker.PerformCheck(cid); - if (response.Success) - { - await CheckAltruisticRole(context, user, cid, response.Message); - return; - } - - await Program.AdminChecker.SendInAdminChannel($"User {Mention(user)} used '/{Name}' for cid '{cid}'. Lookup-success: {response.Success}. Message: '{response.Message}'"); - await context.Followup(response.Message); - } - - private async Task RespondeWithError(CommandContext context, Exception ex) - { - await Program.AdminChecker.SendInAdminChannel("Exception during CheckCidCommand: " + ex); - await context.Followup("I'm sorry to report something has gone wrong in an unexpected way. Error details are already posted in the admin channel."); - } - - private async Task CheckAltruisticRole(CommandContext context, IUser user, string cid, string responseMessage) - { - if (cidStorage.TryAddCid(cid, user.Id)) - { - if (await GiveAltruisticRole(context, user, responseMessage)) - { - return; - } - } - else - { - await context.Followup($"{responseMessage}\n\nThis CID has already been used by another user. No role will be granted."); - return; - } - - await context.Followup(responseMessage); - } - - private async Task GiveAltruisticRole(CommandContext context, IUser user, string responseMessage) - { - try - { - await Program.RoleDriver.GiveAltruisticRole(user); - await context.Followup($"{responseMessage}\n\nCongratulations! You've been granted the Altruistic Mode role for checking a valid CID!"); - return true; - } - catch (Exception ex) - { - await Program.AdminChecker.SendInAdminChannel($"Failed to grant Altruistic Mode role to user {Mention(user)}: {ex.Message}"); - return false; - } - } - } - - public class CidStorage - { - private readonly string filePath; - private static readonly object _lock = new object(); - - public CidStorage(string filePath) - { - this.filePath = filePath; - if (!File.Exists(filePath)) - { - File.WriteAllText(filePath, string.Empty); - } - } - - public bool TryAddCid(string cid, ulong userId) - { - lock (_lock) - { - var existingEntries = File.ReadAllLines(filePath); - if (existingEntries.Any(line => line.Split(',')[0] == cid)) - { - return false; - } - - File.AppendAllLines(filePath, new[] { $"{cid},{userId}" }); - return true; - } - } - } -} diff --git a/Tools/BiblioTech/Commands/CheckDownloadCommand.cs b/Tools/BiblioTech/Commands/CheckDownloadCommand.cs new file mode 100644 index 00000000..32cff02d --- /dev/null +++ b/Tools/BiblioTech/Commands/CheckDownloadCommand.cs @@ -0,0 +1,53 @@ +using BiblioTech.CodexChecking; +using BiblioTech.Options; + +namespace BiblioTech.Commands +{ + public class CheckDownloadCommand : BaseCommand + { + private readonly CodexTwoWayChecker checker; + + private readonly StringOption contentOption = new StringOption( + name: "content", + description: "Content of the downloaded file", + isRequired: false); + + public CheckDownloadCommand(CodexTwoWayChecker checker) + { + this.checker = checker; + } + + public override string Name => "checkdownload"; + public override string StartingMessage => RandomBusyMessage.Get(); + public override string Description => "Checks the download connectivity of your Codex node."; + public override CommandOption[] Options => [contentOption]; + + protected override async Task Invoke(CommandContext context) + { + var user = context.Command.User; + var content = await contentOption.Parse(context); + try + { + var handler = new CheckResponseHandler(context, user); + if (string.IsNullOrEmpty(content)) + { + await checker.StartDownloadCheck(handler, user.Id); + } + else + { + await checker.VerifyDownloadCheck(handler, user.Id, content); + } + } + catch (Exception ex) + { + await RespondWithError(context, ex); + } + } + + private async Task RespondWithError(CommandContext context, Exception ex) + { + await Program.AdminChecker.SendInAdminChannel("Exception during CheckDownloadCommand: " + ex); + await context.Followup("I'm sorry to report something has gone wrong in an unexpected way. Error details are already posted in the admin channel."); + } + } +} diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs new file mode 100644 index 00000000..be6dd2be --- /dev/null +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -0,0 +1,66 @@ +using BiblioTech.CodexChecking; +using BiblioTech.Options; +using Discord; + +namespace BiblioTech.Commands +{ + public class CheckResponseHandler : ICheckResponseHandler + { + private CommandContext context; + private readonly IUser user; + + public CheckResponseHandler(CommandContext context, IUser user) + { + this.context = context; + this.user = user; + } + + public async Task CheckNotStarted() + { + await context.Followup("Run this command without any arguments first, to begin the check process."); + } + + public async Task CouldNotDownloadCid() + { + await context.Followup("Could not download the CID."); + } + + public async Task GiveCidToUser(string cid) + { + await context.Followup("Please download this CID using your Codex node. " + + "Then provide the content of the downloaded file as argument to this command. " + + $"`{cid}`"); + } + + public async Task GiveDataFileToUser(string fileContent) + { + await context.Followup("Please download the attached file. Upload it to your Codex node, " + + "then provide the CID as argument to this command."); + + await context.SendFile(fileContent); + } + + public async Task GiveRoleReward() + { + try + { + await Program.RoleDriver.GiveAltruisticRole(user); + await context.Followup($"Congratulations! You've been granted the Altruistic Mode role!"); + } + catch (Exception ex) + { + await Program.AdminChecker.SendInAdminChannel($"Failed to grant Altruistic Mode role to user <@{user.Id}>: {ex.Message}"); + } + } + + public async Task InvalidData() + { + await context.Followup("The received data didn't match. Check has failed."); + } + + public async Task NowCompleted() + { + await context.Followup("Successfully completed the check!"); + } + } +} diff --git a/Tools/BiblioTech/Commands/CheckUploadCommand.cs b/Tools/BiblioTech/Commands/CheckUploadCommand.cs new file mode 100644 index 00000000..b1589b5c --- /dev/null +++ b/Tools/BiblioTech/Commands/CheckUploadCommand.cs @@ -0,0 +1,53 @@ +using BiblioTech.CodexChecking; +using BiblioTech.Options; + +namespace BiblioTech.Commands +{ + public class CheckUploadCommand : BaseCommand + { + private readonly CodexTwoWayChecker checker; + + private readonly StringOption cidOption = new StringOption( + name: "cid", + description: "Codex Content-Identifier", + isRequired: false); + + public CheckUploadCommand(CodexTwoWayChecker checker) + { + this.checker = checker; + } + + public override string Name => "checkupload"; + public override string StartingMessage => RandomBusyMessage.Get(); + public override string Description => "Checks the upload connectivity of your Codex node."; + public override CommandOption[] Options => [cidOption]; + + protected override async Task Invoke(CommandContext context) + { + var user = context.Command.User; + var cid = await cidOption.Parse(context); + try + { + var handler = new CheckResponseHandler(context, user); + if (string.IsNullOrEmpty(cid)) + { + await checker.StartUploadCheck(handler, user.Id); + } + else + { + await checker.VerifyUploadCheck(handler, user.Id, cid); + } + } + catch (Exception ex) + { + await RespondWithError(context, ex); + } + } + + private async Task RespondWithError(CommandContext context, Exception ex) + { + await Program.AdminChecker.SendInAdminChannel("Exception during CheckUploadCommand: " + ex); + await context.Followup("I'm sorry to report something has gone wrong in an unexpected way. Error details are already posted in the admin channel."); + } + } +} diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 342fbb08..b732e090 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -49,6 +49,7 @@ namespace BiblioTech public string EndpointsPath => Path.Combine(DataPath, "endpoints"); public string UserDataPath => Path.Combine(DataPath, "users"); + public string ChecksDataPath => Path.Combine(DataPath, "checks"); public string LogPath => Path.Combine(DataPath, "logs"); public bool DebugNoDiscord => NoDiscord == 1; } diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index 42a065ae..884ef8b7 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -49,6 +49,16 @@ namespace BiblioTech.Options } } + public async Task SendFile(string fileContent) + { + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(fileContent); + stream.Position = 0; + + await Command.RespondWithFileAsync(stream, "CheckFile.txt", ephemeral: true); + } + private string FormatChunk(string[] chunk) { return string.Join(Environment.NewLine, chunk); diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index ffc4f86d..b3d62338 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,4 +1,5 @@ using ArgsUniform; +using BiblioTech.CodexChecking; using BiblioTech.Commands; using BiblioTech.Rewards; using Discord; @@ -80,7 +81,9 @@ namespace BiblioTech client = new DiscordSocketClient(); client.Log += ClientLog; - var checker = new CodexTwoWayChecker(Config, Log); + var checkRepo = new CheckRepo(Config); + var codexWrapper = new CodexWrapper(Log, Config); + var checker = new CodexTwoWayChecker(Log, Config, checkRepo, codexWrapper); var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); @@ -90,7 +93,8 @@ namespace BiblioTech sprCommand, associateCommand, notifyCommand, - new CheckCidCommand(checker), + new CheckUploadCommand(checker), + new CheckDownloadCommand(checker), new AdminCommand(sprCommand, replacement) ); diff --git a/Tools/BiblioTech/RandomBusyMessage.cs b/Tools/BiblioTech/RandomBusyMessage.cs index 290c9273..8f0477a7 100644 --- a/Tools/BiblioTech/RandomBusyMessage.cs +++ b/Tools/BiblioTech/RandomBusyMessage.cs @@ -14,7 +14,9 @@ "Analyzing the wavelengths...", "Charging the flux-capacitor...", "Jumping to hyperspace...", - "Computing the ultimate answer..." + "Computing the ultimate answer...", + "Turning it off and on again...", + "Compiling from sources..." }; public static string Get() From 6e6b9a6bfe603dd199eaf9ad0cae72ebc24b35d5 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 15:08:50 +0200 Subject: [PATCH 04/51] fixes auth string parsing --- Tools/BiblioTech/CodexChecking/CodexWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/BiblioTech/CodexChecking/CodexWrapper.cs b/Tools/BiblioTech/CodexChecking/CodexWrapper.cs index a8cbe785..3c295d7f 100644 --- a/Tools/BiblioTech/CodexChecking/CodexWrapper.cs +++ b/Tools/BiblioTech/CodexChecking/CodexWrapper.cs @@ -68,7 +68,7 @@ namespace BiblioTech.CodexChecking private HttpFactory CreateHttpFactory() { - if (string.IsNullOrEmpty(config.CodexEndpointAuth) && config.CodexEndpointAuth.Contains(":")) + if (string.IsNullOrEmpty(config.CodexEndpointAuth) || !config.CodexEndpointAuth.Contains(":")) { return new HttpFactory(log); } From 8326578006ce91076f81ab8bf97333457cc941ea Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 15:18:29 +0200 Subject: [PATCH 05/51] forgot to ensure checkdata path --- Tools/BiblioTech/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index b3d62338..af2c9ca3 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -34,6 +34,7 @@ namespace BiblioTech EnsurePath(Config.DataPath); EnsurePath(Config.UserDataPath); EnsurePath(Config.EndpointsPath); + EnsurePath(Config.ChecksDataPath); return new Program().MainAsync(args); } From 2ea5bf1c5d8127ae320b5badd817265bb429ac1a Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 17:49:26 +0200 Subject: [PATCH 06/51] attempt to fix discord file sending --- Tools/BiblioTech/Options/CommandContext.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index 884ef8b7..8f350c89 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -51,12 +51,19 @@ namespace BiblioTech.Options public async Task SendFile(string fileContent) { - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); writer.Write(fileContent); - stream.Position = 0; await Command.RespondWithFileAsync(stream, "CheckFile.txt", ephemeral: true); + + // Detached task for cleaning up the stream resources. + _ = Task.Run(() => + { + Thread.Sleep(TimeSpan.FromSeconds(30)); + writer.Dispose(); + stream.Dispose(); + }); } private string FormatChunk(string[] chunk) From 99f7e25f52994f0faaf7c51efdea1ac9c60790ce Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 18:04:06 +0200 Subject: [PATCH 07/51] still trying to fix the discord file send --- Framework/Utils/RandomUtils.cs | 3 ++- Tools/BiblioTech/Commands/CheckResponseHandler.cs | 4 +--- Tools/BiblioTech/Options/CommandContext.cs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Framework/Utils/RandomUtils.cs b/Framework/Utils/RandomUtils.cs index b5d2580b..f3792123 100644 --- a/Framework/Utils/RandomUtils.cs +++ b/Framework/Utils/RandomUtils.cs @@ -47,7 +47,8 @@ var result = ""; while (result.Length < requiredLength) { - var bytes = new byte[1024]; + var len = Math.Min(1024, requiredLength - result.Length); + var bytes = new byte[len]; random.NextBytes(bytes); result += string.Join("", bytes.Select(b => b.ToString())); } diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index be6dd2be..9a32a7c3 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -34,10 +34,8 @@ namespace BiblioTech.Commands public async Task GiveDataFileToUser(string fileContent) { - await context.Followup("Please download the attached file. Upload it to your Codex node, " + + await context.SendFile(fileContent, "Please download the attached file. Upload it to your Codex node, " + "then provide the CID as argument to this command."); - - await context.SendFile(fileContent); } public async Task GiveRoleReward() diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index 8f350c89..73ca344d 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -49,13 +49,13 @@ namespace BiblioTech.Options } } - public async Task SendFile(string fileContent) + public async Task SendFile(string fileContent, string message) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(fileContent); - await Command.RespondWithFileAsync(stream, "CheckFile.txt", ephemeral: true); + await Command.RespondWithFileAsync(stream, "CheckFile.txt", text: message, ephemeral: true); // Detached task for cleaning up the stream resources. _ = Task.Run(() => From 9540e97e2d3edf43ac0f4a139fb126083e04bd67 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 10 Apr 2025 18:11:02 +0200 Subject: [PATCH 08/51] still trying to fix it --- Tools/BiblioTech/Options/CommandContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index 73ca344d..c454be0f 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -55,7 +55,7 @@ namespace BiblioTech.Options var writer = new StreamWriter(stream); writer.Write(fileContent); - await Command.RespondWithFileAsync(stream, "CheckFile.txt", text: message, ephemeral: true); + await Command.FollowupWithFileAsync(stream, "CheckFile.txt", text: message, ephemeral: true); // Detached task for cleaning up the stream resources. _ = Task.Run(() => From fc9aa0726c3cebe82fa21170ee43a111de24c2f4 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 08:32:18 +0200 Subject: [PATCH 09/51] Adds debug messages for upload check --- Framework/Utils/RandomUtils.cs | 5 +++-- .../CodexChecking/CodexTwoWayChecker.cs | 22 ++++++++++++------- .../Commands/CheckResponseHandler.cs | 5 +++++ Tools/BiblioTech/Options/StringOption.cs | 3 ++- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Framework/Utils/RandomUtils.cs b/Framework/Utils/RandomUtils.cs index f3792123..9f127167 100644 --- a/Framework/Utils/RandomUtils.cs +++ b/Framework/Utils/RandomUtils.cs @@ -47,13 +47,14 @@ var result = ""; while (result.Length < requiredLength) { - var len = Math.Min(1024, requiredLength - result.Length); + var remaining = requiredLength - result.Length; + var len = Math.Min(1024, remaining); var bytes = new byte[len]; random.NextBytes(bytes); result += string.Join("", bytes.Select(b => b.ToString())); } - return result; + return result.Substring(0, Convert.ToInt32(requiredLength)); } } } diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index 8dadc97b..216ab7f0 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -15,6 +15,8 @@ namespace BiblioTech.CodexChecking Task CouldNotDownloadCid(); Task GiveCidToUser(string cid); Task GiveDataFileToUser(string fileContent); + + Task ToAdminChannel(string msg); } public class CodexTwoWayChecker @@ -91,9 +93,9 @@ namespace BiblioTech.CodexChecking return; } - if (IsManifestLengthCompatible(check, manifest)) + if (await IsManifestLengthCompatible(handler, check, manifest)) { - if (IsContentCorrect(check, receivedCid)) + if (await IsContentCorrect(handler, check, receivedCid)) { CheckNowCompleted(handler, check, userId); return; @@ -148,34 +150,38 @@ namespace BiblioTech.CodexChecking } } - private bool IsManifestLengthCompatible(TransferCheck check, Manifest manifest) + private async Task IsManifestLengthCompatible(ICheckResponseHandler handler, TransferCheck check, Manifest manifest) { var dataLength = check.UniqueData.Length; var manifestLength = manifest.OriginalBytes.SizeInBytes; + await handler.ToAdminChannel($"Debug:dataLength={dataLength},manifestLength={manifestLength}"); + return manifestLength > (dataLength - 1) && manifestLength < (dataLength + 1); } - private bool IsContentCorrect(TransferCheck check, string receivedCid) + private async Task IsContentCorrect(ICheckResponseHandler handler, TransferCheck check, string receivedCid) { try { - return codexWrapper.OnCodex(node => + var content = codexWrapper.OnCodex(node => { var file = node.DownloadContent(new ContentId(receivedCid)); - if (file == null) return false; + if (file == null) return string.Empty; try { - var content = File.ReadAllText(file.Filename).Trim(); - return content == check.UniqueData; + return File.ReadAllText(file.Filename).Trim(); } finally { if (File.Exists(file.Filename)) File.Delete(file.Filename); } }); + + await handler.ToAdminChannel($"Debug:content=`{content}`,check=`{check.UniqueData}`"); + return content == check.UniqueData; } catch { diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index 9a32a7c3..8777003e 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -60,5 +60,10 @@ namespace BiblioTech.Commands { await context.Followup("Successfully completed the check!"); } + + public async Task ToAdminChannel(string msg) + { + await Program.AdminChecker.SendInAdminChannel(msg); + } } } diff --git a/Tools/BiblioTech/Options/StringOption.cs b/Tools/BiblioTech/Options/StringOption.cs index 15e04c9a..047bf15a 100644 --- a/Tools/BiblioTech/Options/StringOption.cs +++ b/Tools/BiblioTech/Options/StringOption.cs @@ -12,11 +12,12 @@ namespace BiblioTech.Options public async Task Parse(CommandContext context) { var strData = context.Options.SingleOrDefault(o => o.Name == Name); - if (strData == null) + if (strData == null && IsRequired) { await context.Followup("String option not received."); return null; } + if (strData == null) return null; return strData.Value as string; } } From a9cba76de506b27ac97d445c6eb156882e093171 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 08:58:39 +0200 Subject: [PATCH 10/51] debugging check commands. --- .../FrameworkTests/Utils/RandomUtilsTests.cs | 19 ++++++++++++ .../CodexChecking/CodexTwoWayChecker.cs | 29 ++++++++++++------- .../Commands/CheckDownloadCommand.cs | 5 ++++ .../Commands/CheckResponseHandler.cs | 3 +- 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 Tests/FrameworkTests/Utils/RandomUtilsTests.cs diff --git a/Tests/FrameworkTests/Utils/RandomUtilsTests.cs b/Tests/FrameworkTests/Utils/RandomUtilsTests.cs new file mode 100644 index 00000000..3859f0d3 --- /dev/null +++ b/Tests/FrameworkTests/Utils/RandomUtilsTests.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using Utils; + +namespace FrameworkTests.Utils +{ + [TestFixture] + public class RandomUtilsTests + { + [Test] + [Combinatorial] + public void TestRandomStringLength( + [Values(1, 5, 10, 1023, 1024, 1025, 2222)] int length) + { + var str = RandomUtils.GenerateRandomString(length); + + Assert.That(str.Length, Is.EqualTo(length)); + } + } +} diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index 216ab7f0..4381198a 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -8,7 +8,7 @@ namespace BiblioTech.CodexChecking public interface ICheckResponseHandler { Task CheckNotStarted(); - Task NowCompleted(); + Task NowCompleted(ulong userId, string checkName); Task GiveRoleReward(); Task InvalidData(); @@ -56,13 +56,14 @@ namespace BiblioTech.CodexChecking return; } + Log($"Verifying for downloadCheck: received: '{receivedData}' check: '{check.UniqueData}'"); if (string.IsNullOrEmpty(receivedData) || receivedData != check.UniqueData) { await handler.InvalidData(); return; } - CheckNowCompleted(handler, check, userId); + await CheckNowCompleted(handler, check, userId, "DownloadCheck"); } public async Task StartUploadCheck(ICheckResponseHandler handler, ulong userId) @@ -93,11 +94,12 @@ namespace BiblioTech.CodexChecking return; } - if (await IsManifestLengthCompatible(handler, check, manifest)) + await IsManifestLengthCompatible(handler, check, manifest); + if (true) // debugging, always pass the length check { if (await IsContentCorrect(handler, check, receivedCid)) { - CheckNowCompleted(handler, check, userId); + await CheckNowCompleted(handler, check, userId, "UploadCheck"); return; } } @@ -107,7 +109,7 @@ namespace BiblioTech.CodexChecking private string GenerateUniqueData() { - return $"'{RandomBusyMessage.Get()}'{RandomUtils.GenerateRandomString(12)}"; + return $"{RandomBusyMessage.Get().Substring(5)}{RandomUtils.GenerateRandomString(12)}"; } private string UploadData(string uniqueData) @@ -155,6 +157,7 @@ namespace BiblioTech.CodexChecking var dataLength = check.UniqueData.Length; var manifestLength = manifest.OriginalBytes.SizeInBytes; + Log($"Checking manifest length: dataLength={dataLength},manifestLength={manifestLength}"); await handler.ToAdminChannel($"Debug:dataLength={dataLength},manifestLength={manifestLength}"); return @@ -180,6 +183,7 @@ namespace BiblioTech.CodexChecking } }); + Log($"Checking content: content={content},check={check.UniqueData}"); await handler.ToAdminChannel($"Debug:content=`{content}`,check=`{check.UniqueData}`"); return content == check.UniqueData; } @@ -189,18 +193,18 @@ namespace BiblioTech.CodexChecking } } - private void CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId) + private async Task CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId, string checkName) { if (check.CompletedUtc != DateTime.MinValue) return; check.CompletedUtc = DateTime.UtcNow; repo.SaveChanges(); - handler.NowCompleted(); - CheckUserForRoleRewards(handler, userId); + await handler.NowCompleted(userId, checkName); + await CheckUserForRoleRewards(handler, userId); } - private void CheckUserForRoleRewards(ICheckResponseHandler handler, ulong userId) + private async Task CheckUserForRoleRewards(ICheckResponseHandler handler, ulong userId) { var check = repo.GetOrCreate(userId); @@ -208,8 +212,13 @@ namespace BiblioTech.CodexChecking check.UploadCheck.CompletedUtc != DateTime.MinValue && check.DownloadCheck.CompletedUtc != DateTime.MinValue) { - handler.GiveRoleReward(); + await handler.GiveRoleReward(); } } + + private void Log(string msg) + { + log.Log(msg); + } } } diff --git a/Tools/BiblioTech/Commands/CheckDownloadCommand.cs b/Tools/BiblioTech/Commands/CheckDownloadCommand.cs index 32cff02d..0e1aa563 100644 --- a/Tools/BiblioTech/Commands/CheckDownloadCommand.cs +++ b/Tools/BiblioTech/Commands/CheckDownloadCommand.cs @@ -35,6 +35,11 @@ namespace BiblioTech.Commands } else { + if (content.Length > 1024) + { + await context.Followup("Provided content is too long!"); + return; + } await checker.VerifyDownloadCheck(handler, user.Id, content); } } diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index 8777003e..c3cff1b1 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -56,9 +56,10 @@ namespace BiblioTech.Commands await context.Followup("The received data didn't match. Check has failed."); } - public async Task NowCompleted() + public async Task NowCompleted(ulong userId, string checkName) { await context.Followup("Successfully completed the check!"); + await Program.AdminChecker.SendInAdminChannel($"User <@{userId}> has completed check: {checkName}"); } public async Task ToAdminChannel(string msg) From 1b1e1020498a6ec9326b593f5897ae5cc2b0ce57 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 09:11:30 +0200 Subject: [PATCH 11/51] Replacing streams with temp file on discord file upload --- Tools/BiblioTech/Options/CommandContext.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index c454be0f..fbd45b36 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -51,18 +51,18 @@ namespace BiblioTech.Options public async Task SendFile(string fileContent, string message) { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(fileContent); + if (fileContent.Length < 1) throw new Exception("File content is empty."); - await Command.FollowupWithFileAsync(stream, "CheckFile.txt", text: message, ephemeral: true); + var filename = Guid.NewGuid().ToString() + ".tmp"; + File.WriteAllText(filename, fileContent); + + await Command.FollowupWithFileAsync(filename, "Codex_UploadCheckFile.txt", text: message, ephemeral: true); // Detached task for cleaning up the stream resources. _ = Task.Run(() => { - Thread.Sleep(TimeSpan.FromSeconds(30)); - writer.Dispose(); - stream.Dispose(); + Thread.Sleep(TimeSpan.FromMinutes(2)); + File.Delete(filename); }); } From edc091e2c9404fabacdd315a663454defac1a5f6 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 14:52:55 +0200 Subject: [PATCH 12/51] fixes issue where checks dont end if they were previously completed --- Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index 4381198a..f54bea55 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -195,12 +195,12 @@ namespace BiblioTech.CodexChecking private async Task CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId, string checkName) { - if (check.CompletedUtc != DateTime.MinValue) return; + await handler.NowCompleted(userId, checkName); + if (check.CompletedUtc != DateTime.MinValue) return; check.CompletedUtc = DateTime.UtcNow; repo.SaveChanges(); - await handler.NowCompleted(userId, checkName); await CheckUserForRoleRewards(handler, userId); } From c88b9d920e94c04405010ae7d60997fcf751f293 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 15:01:20 +0200 Subject: [PATCH 13/51] removes debug messages in admin channel --- Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index f54bea55..5f37cfb2 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -94,10 +94,9 @@ namespace BiblioTech.CodexChecking return; } - await IsManifestLengthCompatible(handler, check, manifest); - if (true) // debugging, always pass the length check + if (IsManifestLengthCompatible(handler, check, manifest)) { - if (await IsContentCorrect(handler, check, receivedCid)) + if (IsContentCorrect(handler, check, receivedCid)) { await CheckNowCompleted(handler, check, userId, "UploadCheck"); return; @@ -152,20 +151,19 @@ namespace BiblioTech.CodexChecking } } - private async Task IsManifestLengthCompatible(ICheckResponseHandler handler, TransferCheck check, Manifest manifest) + private bool IsManifestLengthCompatible(ICheckResponseHandler handler, TransferCheck check, Manifest manifest) { var dataLength = check.UniqueData.Length; var manifestLength = manifest.OriginalBytes.SizeInBytes; Log($"Checking manifest length: dataLength={dataLength},manifestLength={manifestLength}"); - await handler.ToAdminChannel($"Debug:dataLength={dataLength},manifestLength={manifestLength}"); return manifestLength > (dataLength - 1) && manifestLength < (dataLength + 1); } - private async Task IsContentCorrect(ICheckResponseHandler handler, TransferCheck check, string receivedCid) + private bool IsContentCorrect(ICheckResponseHandler handler, TransferCheck check, string receivedCid) { try { @@ -184,7 +182,6 @@ namespace BiblioTech.CodexChecking }); Log($"Checking content: content={content},check={check.UniqueData}"); - await handler.ToAdminChannel($"Debug:content=`{content}`,check=`{check.UniqueData}`"); return content == check.UniqueData; } catch From 112c1f37c18c6ae949fbfe0607b733b814ca58d8 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 11 Apr 2025 15:19:03 +0200 Subject: [PATCH 14/51] Making the instruction messages more catchy --- .../Commands/CheckResponseHandler.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index c3cff1b1..8d20e5bf 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -1,4 +1,5 @@ -using BiblioTech.CodexChecking; +using System.Linq; +using BiblioTech.CodexChecking; using BiblioTech.Options; using Discord; @@ -27,15 +28,28 @@ namespace BiblioTech.Commands public async Task GiveCidToUser(string cid) { - await context.Followup("Please download this CID using your Codex node. " + - "Then provide the content of the downloaded file as argument to this command. " + - $"`{cid}`"); + await context.Followup( + FormatCatchyMessage("[💾] Please download this CID using your Codex node.", + $"👉 `{cid}`.", + "👉 Then provide the *content of the downloaded file* as argument to this command.")); } public async Task GiveDataFileToUser(string fileContent) { - await context.SendFile(fileContent, "Please download the attached file. Upload it to your Codex node, " + - "then provide the CID as argument to this command."); + await context.SendFile(fileContent, + FormatCatchyMessage("[💿] Please download the attached file.", + "👉 Upload it to your Codex node.", + "👉 Then provide the *CID* as argument to this command.")); + } + + private string FormatCatchyMessage(string title, params string[] content) + { + var entries = new List(); + entries.Add(title); + entries.Add("```"); + entries.AddRange(content); + entries.Add("```"); + return string.Join(Environment.NewLine, entries.ToArray()); } public async Task GiveRoleReward() From 9e4e56205a1e987d594e3bf97d41f625ea095095 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 16 Apr 2025 15:17:40 +0200 Subject: [PATCH 15/51] Cleans up a lot of old reward system code. Adds periodic role check for p2p participant role --- Framework/DiscordRewards/CheckConfig.cs | 21 --- ...veRewardsCommand.cs => EventsAndErrors.cs} | 11 +- Framework/DiscordRewards/RewardConfig.cs | 18 -- Framework/DiscordRewards/RewardRepo.cs | 53 ------ .../UtilityTests/DiscordBotTests.cs | 8 +- Tools/BiblioTech/AdminChecker.cs | 1 - .../CodexChecking/ActiveP2pRoleRemover.cs | 66 +++++++ .../CodexChecking/CodexTwoWayChecker.cs | 4 +- Tools/BiblioTech/CommandHandler.cs | 27 ++- .../Commands/CheckResponseHandler.cs | 6 +- Tools/BiblioTech/Configuration.cs | 34 +++- Tools/BiblioTech/LoggingRoleDriver.cs | 29 ++- Tools/BiblioTech/Program.cs | 5 +- Tools/BiblioTech/Rewards/RewardContext.cs | 102 ----------- Tools/BiblioTech/Rewards/RewardController.cs | 16 +- Tools/BiblioTech/Rewards/RoleDriver.cs | 168 ++++-------------- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 115 ++++++++++++ Tools/BiblioTech/UserRepo.cs | 7 + Tools/TestNetRewarder/BotClient.cs | 2 +- Tools/TestNetRewarder/Processor.cs | 9 +- Tools/TestNetRewarder/RequestBuilder.cs | 29 +-- Tools/TestNetRewarder/RewardCheck.cs | 113 ------------ Tools/TestNetRewarder/RewardChecker.cs | 17 -- 23 files changed, 332 insertions(+), 529 deletions(-) delete mode 100644 Framework/DiscordRewards/CheckConfig.cs rename Framework/DiscordRewards/{GiveRewardsCommand.cs => EventsAndErrors.cs} (53%) delete mode 100644 Framework/DiscordRewards/RewardConfig.cs delete mode 100644 Framework/DiscordRewards/RewardRepo.cs create mode 100644 Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs delete mode 100644 Tools/BiblioTech/Rewards/RewardContext.cs create mode 100644 Tools/BiblioTech/Rewards/RoleModifyContext.cs delete mode 100644 Tools/TestNetRewarder/RewardCheck.cs delete mode 100644 Tools/TestNetRewarder/RewardChecker.cs diff --git a/Framework/DiscordRewards/CheckConfig.cs b/Framework/DiscordRewards/CheckConfig.cs deleted file mode 100644 index 34425cea..00000000 --- a/Framework/DiscordRewards/CheckConfig.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Utils; - -namespace DiscordRewards -{ - public class CheckConfig - { - public CheckType Type { get; set; } - public ulong MinNumberOfHosts { get; set; } - public ByteSize MinSlotSize { get; set; } = 0.Bytes(); - public TimeSpan MinDuration { get; set; } = TimeSpan.Zero; - } - - public enum CheckType - { - Uninitialized, - HostFilledSlot, - HostFinishedSlot, - ClientPostedContract, - ClientStartedContract, - } -} diff --git a/Framework/DiscordRewards/GiveRewardsCommand.cs b/Framework/DiscordRewards/EventsAndErrors.cs similarity index 53% rename from Framework/DiscordRewards/GiveRewardsCommand.cs rename to Framework/DiscordRewards/EventsAndErrors.cs index 3aae088b..53e4b3cc 100644 --- a/Framework/DiscordRewards/GiveRewardsCommand.cs +++ b/Framework/DiscordRewards/EventsAndErrors.cs @@ -1,23 +1,16 @@ namespace DiscordRewards { - public class GiveRewardsCommand + public class EventsAndErrors { - public RewardUsersCommand[] Rewards { get; set; } = Array.Empty(); public ChainEventMessage[] EventsOverview { get; set; } = Array.Empty(); public string[] Errors { get; set; } = Array.Empty(); public bool HasAny() { - return Rewards.Any() || EventsOverview.Any(); + return Errors.Length > 0 || EventsOverview.Length > 0; } } - public class RewardUsersCommand - { - public ulong RewardId { get; set; } - public string[] UserAddresses { get; set; } = Array.Empty(); - } - public class ChainEventMessage { public ulong BlockNumber { get; set; } diff --git a/Framework/DiscordRewards/RewardConfig.cs b/Framework/DiscordRewards/RewardConfig.cs deleted file mode 100644 index dda0dfe1..00000000 --- a/Framework/DiscordRewards/RewardConfig.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace DiscordRewards -{ - public class RewardConfig - { - public const string UsernameTag = ""; - - public RewardConfig(ulong roleId, string message, CheckConfig checkConfig) - { - RoleId = roleId; - Message = message; - CheckConfig = checkConfig; - } - - public ulong RoleId { get; } - public string Message { get; } - public CheckConfig CheckConfig { get; } - } -} diff --git a/Framework/DiscordRewards/RewardRepo.cs b/Framework/DiscordRewards/RewardRepo.cs deleted file mode 100644 index 1c97caf9..00000000 --- a/Framework/DiscordRewards/RewardRepo.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace DiscordRewards -{ - public class RewardRepo - { - private static string Tag => RewardConfig.UsernameTag; - - public RewardConfig[] Rewards { get; } = new RewardConfig[0]; - - // Example configuration, from test server: - //{ - // // Filled any slot - // new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig - // { - // Type = CheckType.HostFilledSlot - // }), - - // // Finished any slot - // new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig - // { - // 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.HostFinishedSlot, - // MinSlotSize = 10.MB(), - // MinDuration = TimeSpan.FromMinutes(5.0), - // }), - - // // Posted any contract - // new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig - // { - // 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.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/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs b/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs index abebac0c..22819e1b 100644 --- a/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs @@ -88,7 +88,7 @@ namespace ExperimentalTests.UtilityTests $"Event '{msg}' did not occure correct number of times."); } - private void OnCommand(string timestamp, GiveRewardsCommand call) + private void OnCommand(string timestamp, EventsAndErrors call) { Log($""); foreach (var e in call.EventsOverview) @@ -276,7 +276,7 @@ namespace ExperimentalTests.UtilityTests 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)); } @@ -286,14 +286,14 @@ namespace ExperimentalTests.UtilityTests monitor.Stop(); } - private void ParseLine(string line, Action onCommand) + private void ParseLine(string line, Action onCommand) { try { var timestamp = line.Substring(0, 30); var json = line.Substring(31); - var cmd = JsonConvert.DeserializeObject(json); + var cmd = JsonConvert.DeserializeObject(json); if (cmd != null) { onCommand(timestamp, cmd); diff --git a/Tools/BiblioTech/AdminChecker.cs b/Tools/BiblioTech/AdminChecker.cs index 532220de..96f89691 100644 --- a/Tools/BiblioTech/AdminChecker.cs +++ b/Tools/BiblioTech/AdminChecker.cs @@ -1,7 +1,6 @@ using BiblioTech.Options; using Discord; using Discord.WebSocket; -using Org.BouncyCastle.Utilities; namespace BiblioTech { diff --git a/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs b/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs new file mode 100644 index 00000000..4a448792 --- /dev/null +++ b/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs @@ -0,0 +1,66 @@ +using Discord; +using Logging; +using System.Threading.Tasks; + +namespace BiblioTech.CodexChecking +{ + public class ActiveP2pRoleRemover + { + private readonly Configuration config; + private readonly ILog log; + private readonly CheckRepo repo; + + public ActiveP2pRoleRemover(Configuration config, ILog log, CheckRepo repo) + { + this.config = config; + this.log = log; + this.repo = repo; + } + + public void Start() + { + if (config.ActiveP2pRoleDurationMinutes > 0) + { + Task.Run(Worker); + } + } + + private void Worker() + { + var loopDelay = TimeSpan.FromMinutes(config.ActiveP2pRoleDurationMinutes) / 60; + var min = TimeSpan.FromMinutes(10.0); + if (loopDelay < min) loopDelay = min; + + try + { + while (true) + { + Thread.Sleep(loopDelay); + CheckP2pRoleRemoval(); + } + } + catch (Exception ex) + { + log.Error($"Exception in {nameof(ActiveP2pRoleRemover)}: {ex}"); + Environment.Exit(1); + } + } + + private void CheckP2pRoleRemoval() + { + var expiryMoment = DateTime.UtcNow - TimeSpan.FromMinutes(config.ActiveP2pRoleDurationMinutes); + + Program.RoleDriver.IterateRemoveActiveP2pParticipants(p => ShouldRemoveRole(p, expiryMoment)); + } + + private bool ShouldRemoveRole(IUser user, DateTime expiryMoment) + { + var report = repo.GetOrCreate(user.Id); + + if (report.UploadCheck.CompletedUtc > expiryMoment) return false; + if (report.DownloadCheck.CompletedUtc > expiryMoment) return false; + + return true; + } + } +} diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index 5f37cfb2..c5b9361a 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -194,7 +194,6 @@ namespace BiblioTech.CodexChecking { await handler.NowCompleted(userId, checkName); - if (check.CompletedUtc != DateTime.MinValue) return; check.CompletedUtc = DateTime.UtcNow; repo.SaveChanges(); @@ -205,8 +204,7 @@ namespace BiblioTech.CodexChecking { var check = repo.GetOrCreate(userId); - if ( - check.UploadCheck.CompletedUtc != DateTime.MinValue && + if (check.UploadCheck.CompletedUtc != DateTime.MinValue && check.DownloadCheck.CompletedUtc != DateTime.MinValue) { await handler.GiveRoleReward(); diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index b6f2f7ca..8c888ea7 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -4,6 +4,9 @@ using Discord; using Newtonsoft.Json; using BiblioTech.Rewards; using Logging; +using BiblioTech.CodexChecking; +using Nethereum.Model; +using static Org.BouncyCastle.Math.EC.ECCurve; namespace BiblioTech { @@ -11,13 +14,15 @@ namespace BiblioTech { private readonly DiscordSocketClient client; private readonly CustomReplacement replacement; + private readonly ActiveP2pRoleRemover roleRemover; private readonly BaseCommand[] commands; private readonly ILog log; - public CommandHandler(ILog log, DiscordSocketClient client, CustomReplacement replacement, params BaseCommand[] commands) + public CommandHandler(ILog log, DiscordSocketClient client, CustomReplacement replacement, ActiveP2pRoleRemover roleRemover, params BaseCommand[] commands) { this.client = client; this.replacement = replacement; + this.roleRemover = roleRemover; this.commands = commands; this.log = log; client.Ready += Client_Ready; @@ -30,10 +35,14 @@ namespace BiblioTech Program.AdminChecker.SetGuild(guild); 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, log, replacement); + var adminChannel = GetChannel(guild, Program.Config.AdminChannelId); + if (adminChannel == null) throw new Exception("No admin message channel"); + var chainEventsChannel = GetChannel(guild, Program.Config.ChainEventsChannelId); + var rewardsChannel = GetChannel(guild, Program.Config.RewardsChannelId); + + Program.AdminChecker.SetAdminChannel(adminChannel); + Program.RoleDriver = new RoleDriver(client, Program.UserRepo, log, rewardsChannel); + Program.EventsSender = new ChainEventsSender(log, replacement, chainEventsChannel); var builders = commands.Select(c => { @@ -65,6 +74,8 @@ namespace BiblioTech { log.Log($"{cmd.Name} ({cmd.Description}) [{DescribOptions(cmd.Options)}]"); } + + roleRemover.Start(); } catch (HttpException exception) { @@ -75,6 +86,12 @@ namespace BiblioTech log.Log("Initialized."); } + private SocketTextChannel? GetChannel(SocketGuild guild, ulong id) + { + if (id == 0) return null; + return guild.TextChannels.SingleOrDefault(c => c.Id == id); + } + private string DescribOptions(IReadOnlyCollection options) { return string.Join(",", options.Select(DescribeOption).ToArray()); diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index 8d20e5bf..ca9dedd4 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -56,7 +56,11 @@ namespace BiblioTech.Commands { try { - await Program.RoleDriver.GiveAltruisticRole(user); + await Program.RoleDriver.RunRoleGiver(async r => + { + await r.GiveAltruisticRole(user); + await r.GiveActiveP2pParticipant(user); + }); await context.Followup($"Congratulations! You've been granted the Altruistic Mode role!"); } catch (Exception ex) diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index b732e090..a8187379 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -26,9 +26,6 @@ namespace BiblioTech [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("altruistic-role-id", "ar", "ALTRUISTICROLE", true, "ID of the Discord server role for Altruistic Mode.")] - public ulong AltruisticRoleId { get; set; } - [Uniform("reward-api-port", "rp", "REWARDAPIPORT", true, "TCP listen port for the reward API.")] public int RewardApiPort { get; set; } = 31080; @@ -47,6 +44,37 @@ namespace BiblioTech [Uniform("codex-endpoint-auth", "cea", "CODEXENDPOINTAUTH", false, "Codex endpoint basic auth. Colon separated username and password. (default: empty, no auth used.)")] public string CodexEndpointAuth { get; set; } = ""; + #region Role Rewards + + /// + /// Awarded when both checkupload and checkdownload have been completed. + /// + [Uniform("altruistic-role-id", "ar", "ALTRUISTICROLE", true, "ID of the Discord server role for Altruistic Mode.")] + public ulong AltruisticRoleId { get; set; } + + /// + /// Awarded as long as either checkupload or checkdownload were completed within the last ActiveP2pRoleDuration minutes. + /// + [Uniform("active-p2p-role-id", "apri", "ACTIVEP2PROLEID", false, "ID of discord server role for active p2p participants.")] + public ulong ActiveP2pParticipantRoleId { get; set; } + + [Uniform("active-p2p-role-duration", "aprd", "ACTIVEP2PROLEDURATION", false, "Duration in minutes for the active p2p participant role from the last successful check command.")] + public int ActiveP2pRoleDurationMinutes { get; set; } + + /// + /// Awarded as long as the user is hosting at least 1 slot. + /// + [Uniform("active-host-role-id", "ahri", "ACTIVEHOSTROLEID", false, "Id of discord server role for active slot hosters.")] + public ulong ActiveHostRoleId { get; set; } + + /// + /// Awarded as long as the user has at least 1 active storage purchase contract. + /// + [Uniform("active-client-role-id", "acri", "ACTIVECLIENTROLEID", false, "Id of discord server role for users with at least 1 active purchase contract.")] + public ulong ActiveClientRoleId { get; set; } + + #endregion + public string EndpointsPath => Path.Combine(DataPath, "endpoints"); public string UserDataPath => Path.Combine(DataPath, "users"); public string ChecksDataPath => Path.Combine(DataPath, "checks"); diff --git a/Tools/BiblioTech/LoggingRoleDriver.cs b/Tools/BiblioTech/LoggingRoleDriver.cs index f5435523..a5631271 100644 --- a/Tools/BiblioTech/LoggingRoleDriver.cs +++ b/Tools/BiblioTech/LoggingRoleDriver.cs @@ -15,18 +15,37 @@ namespace BiblioTech this.log = log; } - public async Task GiveAltruisticRole(IUser user) + public async Task RunRoleGiver(Func action) { await Task.CompletedTask; - - log.Log($"Give altruistic role to {user.Id}"); + await action(new LoggingRoleGiver(log)); } - public async Task GiveRewards(GiveRewardsCommand rewards) + public async Task IterateRemoveActiveP2pParticipants(Func predicate) { await Task.CompletedTask; + } - log.Log(JsonConvert.SerializeObject(rewards, Formatting.None)); + private class LoggingRoleGiver : IRoleGiver + { + private readonly ILog log; + + public LoggingRoleGiver(ILog log) + { + this.log = log; + } + + public async Task GiveActiveP2pParticipant(IUser user) + { + log.Log($"Giving ActiveP2p role to " + user.Id); + await Task.CompletedTask; + } + + public async Task GiveAltruisticRole(IUser user) + { + log.Log($"Giving Altruistic role to " + user.Id); + await Task.CompletedTask; + } } } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index af2c9ca3..df1ce6bc 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -5,6 +5,7 @@ using BiblioTech.Rewards; using Discord; using Discord.WebSocket; using Logging; +using Nethereum.Model; namespace BiblioTech { @@ -17,6 +18,7 @@ namespace BiblioTech public static UserRepo UserRepo { get; } = new UserRepo(); public static AdminChecker AdminChecker { get; private set; } = null!; public static IDiscordRoleDriver RoleDriver { get; set; } = null!; + public static ChainEventsSender EventsSender { get; set; } = null!; public static ILog Log { get; private set; } = null!; public static Task Main(string[] args) @@ -88,7 +90,8 @@ namespace BiblioTech var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); - var handler = new CommandHandler(Log, client, replacement, + var roleRemover = new ActiveP2pRoleRemover(Config, Log, checkRepo); + var handler = new CommandHandler(Log, client, replacement, roleRemover, new GetBalanceCommand(associateCommand), new MintCommand(associateCommand), sprCommand, diff --git a/Tools/BiblioTech/Rewards/RewardContext.cs b/Tools/BiblioTech/Rewards/RewardContext.cs deleted file mode 100644 index d05d5f6d..00000000 --- a/Tools/BiblioTech/Rewards/RewardContext.cs +++ /dev/null @@ -1,102 +0,0 @@ -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/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 3a20a084..7cbed562 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -4,10 +4,20 @@ using Microsoft.AspNetCore.Mvc; namespace BiblioTech.Rewards { + /// + /// We like callbacks in this interface because we're trying to batch role-modifying operations, + /// So that we're not poking the server lots of times very quickly. + /// public interface IDiscordRoleDriver { - Task GiveRewards(GiveRewardsCommand rewards); + Task RunRoleGiver(Func action); + Task IterateRemoveActiveP2pParticipants(Func predicate); + } + + public interface IRoleGiver + { Task GiveAltruisticRole(IUser user); + Task GiveActiveP2pParticipant(IUser user); } [Route("api/[controller]")] @@ -21,11 +31,11 @@ namespace BiblioTech.Rewards } [HttpPost] - public async Task Give(GiveRewardsCommand cmd) + public async Task Give(EventsAndErrors cmd) { try { - await Program.RoleDriver.GiveRewards(cmd); + await Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors); } catch (Exception ex) { diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 1964da8e..c716c011 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 k8s.KubeConfigModels; using Logging; using Newtonsoft.Json; using Utils; @@ -10,145 +11,46 @@ namespace BiblioTech.Rewards public class RoleDriver : IDiscordRoleDriver { private readonly DiscordSocketClient client; + private readonly UserRepo userRepo; private readonly ILog log; private readonly SocketTextChannel? rewardsChannel; - private readonly ChainEventsSender eventsSender; - private readonly RewardRepo repo = new RewardRepo(); - public RoleDriver(DiscordSocketClient client, ILog log, CustomReplacement replacement) + public RoleDriver(DiscordSocketClient client, UserRepo userRepo, ILog log, SocketTextChannel? rewardsChannel) { this.client = client; + this.userRepo = userRepo; this.log = log; - rewardsChannel = GetChannel(Program.Config.RewardsChannelId); - eventsSender = new ChainEventsSender(log, replacement, GetChannel(Program.Config.ChainEventsChannelId)); + this.rewardsChannel = rewardsChannel; } - public async Task GiveRewards(GiveRewardsCommand rewards) + public async Task RunRoleGiver(Func action) { - log.Log($"Processing rewards command: '{JsonConvert.SerializeObject(rewards)}'"); + var context = await OpenRoleModifyContext(); + var mapper = new RoleMapper(context); + await action(mapper); + } - if (rewards.Rewards.Any()) + public async Task IterateRemoveActiveP2pParticipants(Func shouldRemove) + { + var context = await OpenRoleModifyContext(); + foreach (var user in context.Users) { - await ProcessRewards(rewards); - } - - await eventsSender.ProcessChainEvents(rewards.EventsOverview, rewards.Errors); - } - - public async Task GiveAltruisticRole(IUser user) - { - var guild = GetGuild(); - var role = guild.Roles.SingleOrDefault(r => r.Id == Program.Config.AltruisticRoleId); - if (role == null) return; - - var guildUser = guild.Users.SingleOrDefault(u => u.Id == user.Id); - if (guildUser == null) return; - - await guildUser.AddRoleAsync(role); - } - - private async Task ProcessRewards(GiveRewardsCommand rewards) - { - 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)); - } - catch (Exception ex) - { - log.Error("Failed to process rewards: " + ex); - } - } - - private SocketTextChannel? GetChannel(ulong id) - { - if (id == 0) return null; - return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id); - } - - private async Task> LoadAllUsers(SocketGuild guild) - { - log.Log("Loading all users.."); - var result = new Dictionary(); - var users = guild.GetUsersAsync(); - await foreach (var ulist in users) - { - foreach (var u in ulist) + if (user.RoleIds.Any(r => r == Program.Config.ActiveP2pParticipantRoleId)) { - result.Add(u.Id, u); - //var roleIds = string.Join(",", u.RoleIds.Select(r => r.ToString()).ToArray()); - //log.Log($" > {u.Id}({u.DisplayName}) has [{roleIds}]"); - } - } - return result; - } - - private Dictionary LookUpAllRoles(SocketGuild guild, GiveRewardsCommand rewards) - { - var result = new Dictionary(); - foreach (var r in rewards.Rewards) - { - if (!result.ContainsKey(r.RewardId)) - { - var rewardConfig = repo.Rewards.SingleOrDefault(rr => rr.RoleId == r.RewardId); - if (rewardConfig == null) + // This user has the role. Should it be removed? + if (shouldRemove(user)) { - log.Log($"No Reward is configured for id '{r.RewardId}'."); - } - else - { - var socketRole = guild.GetRole(r.RewardId); - if (socketRole == null) - { - log.Log($"Guild Role by id '{r.RewardId}' not found."); - } - else - { - result.Add(r.RewardId, new RoleReward(socketRole, rewardConfig)); - } + await context.RemoveRole(user, Program.Config.ActiveP2pParticipantRoleId); } } } - - return result; } - private UserReward[] LookUpUsers(GiveRewardsCommand rewards) + private async Task OpenRoleModifyContext() { - return rewards.Rewards.Select(LookUpUserData).ToArray(); - } - - private UserReward LookUpUserData(RewardUsersCommand command) - { - return new UserReward(command, - command.UserAddresses - .Select(LookUpUserDataForAddress) - .Where(d => d != null) - .Cast() - .ToArray()); - } - - private UserData? LookUpUserDataForAddress(string address) - { - try - { - var userData = Program.UserRepo.GetUserDataForAddress(new 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) - { - log.Error("Error during UserData lookup: " + ex); - return null; - } + var context = new RoleModifyContext(GetGuild(), userRepo, log, rewardsChannel); + await context.Initialize(); + return context; } private SocketGuild GetGuild() @@ -163,27 +65,23 @@ namespace BiblioTech.Rewards } } - public class RoleReward + public class RoleMapper : IRoleGiver { - public RoleReward(SocketRole socketRole, RewardConfig reward) + private readonly RoleModifyContext context; + + public RoleMapper(RoleModifyContext context) { - SocketRole = socketRole; - Reward = reward; + this.context = context; } - public SocketRole SocketRole { get; } - public RewardConfig Reward { get; } - } - - public class UserReward - { - public UserReward(RewardUsersCommand rewardCommand, UserData[] users) + public async Task GiveActiveP2pParticipant(IUser user) { - RewardCommand = rewardCommand; - Users = users; + await context.GiveRole(user, Program.Config.ActiveP2pParticipantRoleId); } - public RewardUsersCommand RewardCommand { get; } - public UserData[] Users { get; } + public async Task GiveAltruisticRole(IUser user) + { + await context.GiveRole(user, Program.Config.AltruisticRoleId); + } } } diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs new file mode 100644 index 00000000..124d687d --- /dev/null +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -0,0 +1,115 @@ +using Discord.WebSocket; +using Discord; +using DiscordRewards; +using Nethereum.Model; +using Logging; + +namespace BiblioTech.Rewards +{ + public class RoleModifyContext + { + private Dictionary users = new(); + private Dictionary roles = new(); + private readonly SocketGuild guild; + private readonly UserRepo userRepo; + private readonly ILog log; + private readonly SocketTextChannel? rewardsChannel; + + public RoleModifyContext(SocketGuild guild, UserRepo userRepo, ILog log, SocketTextChannel? rewardsChannel) + { + this.guild = guild; + this.userRepo = userRepo; + this.log = log; + this.rewardsChannel = rewardsChannel; + } + + public async Task Initialize() + { + this.users = await LoadAllUsers(guild); + this.roles = LoadAllRoles(guild); + } + + public IGuildUser[] Users => users.Values.ToArray(); + + public async Task GiveRole(IUser user, ulong roleId) + { + var role = GetRole(roleId); + var guildUser = GetUser(user.Id); + if (role == null) return; + if (guildUser == null) return; + + await guildUser.AddRoleAsync(role); + await Program.AdminChecker.SendInAdminChannel($"Added role '{role.Name}' for user <@{user.Id}>."); + + await SendNotification(guildUser, role); + } + + public async Task RemoveRole(IUser user, ulong roleId) + { + var role = GetRole(roleId); + var guildUser = GetUser(user.Id); + if (role == null) return; + if (guildUser == null) return; + + await guildUser.RemoveRoleAsync(role); + await Program.AdminChecker.SendInAdminChannel($"Removed role '{role.Name}' for user <@{user.Id}>."); + } + + private SocketRole? GetRole(ulong roleId) + { + if (roles.ContainsKey(roleId)) return roles[roleId]; + return null; + } + + private IGuildUser? GetUser(ulong userId) + { + if (users.ContainsKey(userId)) return users[userId]; + return null; + } + + private async Task> LoadAllUsers(SocketGuild guild) + { + log.Log("Loading all users.."); + var result = new Dictionary(); + var users = guild.GetUsersAsync(); + await foreach (var ulist in users) + { + foreach (var u in ulist) + { + result.Add(u.Id, u); + } + } + return result; + } + + private Dictionary LoadAllRoles(SocketGuild guild) + { + var result = new Dictionary(); + var roles = guild.Roles.ToArray(); + foreach (var role in roles) + { + result.Add(role.Id, role); + } + return result; + } + + private async Task SendNotification(IGuildUser user, SocketRole role) + { + try + { + var userData = userRepo.GetUserById(user.Id); + if (userData == null) return; + + if (userData.NotificationsEnabled && rewardsChannel != null) + { + var msg = $"<@{user.Id}> has received '{role.Name}'."; + await rewardsChannel.SendMessageAsync(msg); + } + } + catch (Exception ex) + { + log.Error($"Failed to notify user '{user.DisplayName}' about role '{role.Name}': {ex}"); + } + } + } +} diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index d1a766ab..b6c02500 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -41,6 +41,13 @@ namespace BiblioTech return cache.Values.ToArray(); } + public UserData? GetUserById(ulong id) + { + if (cache.Count == 0) LoadAllUserData(); + if (cache.ContainsKey(id)) return cache[id]; + return null; + } + public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction? eth, Transaction? tokens) { lock (repoLock) diff --git a/Tools/TestNetRewarder/BotClient.cs b/Tools/TestNetRewarder/BotClient.cs index 6a5c5759..c5ddd9a0 100644 --- a/Tools/TestNetRewarder/BotClient.cs +++ b/Tools/TestNetRewarder/BotClient.cs @@ -21,7 +21,7 @@ namespace TestNetRewarder return result == "Pong"; } - public async Task SendRewards(GiveRewardsCommand command) + public async Task SendRewards(EventsAndErrors command) { if (command == null) return false; var result = await HttpPostJson(command); diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 2b49ef11..7377a7da 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -8,7 +8,6 @@ namespace TestNetRewarder public class Processor : ITimeSegmentHandler { private readonly RequestBuilder builder; - private readonly RewardChecker rewardChecker; private readonly EventsFormatter eventsFormatter; private readonly ChainState chainState; private readonly Configuration config; @@ -24,15 +23,9 @@ namespace TestNetRewarder lastPeriodUpdateUtc = DateTime.UtcNow; builder = new RequestBuilder(); - rewardChecker = new RewardChecker(builder); eventsFormatter = new EventsFormatter(config); - var handler = new ChainStateChangeHandlerMux( - rewardChecker.Handler, - eventsFormatter - ); - - chainState = new ChainState(log, contracts, handler, config.HistoryStartUtc, + chainState = new ChainState(log, contracts, eventsFormatter, config.HistoryStartUtc, doProofPeriodMonitoring: config.ShowProofPeriodReports > 0); } diff --git a/Tools/TestNetRewarder/RequestBuilder.cs b/Tools/TestNetRewarder/RequestBuilder.cs index b1f641b8..adc68032 100644 --- a/Tools/TestNetRewarder/RequestBuilder.cs +++ b/Tools/TestNetRewarder/RequestBuilder.cs @@ -3,38 +3,15 @@ using Utils; namespace TestNetRewarder { - public class RequestBuilder : IRewardGiver + public class RequestBuilder { - private readonly Dictionary> rewards = new Dictionary>(); - - public void Give(RewardConfig reward, EthAddress receiver) + public EventsAndErrors Build(ChainEventMessage[] lines, string[] errors) { - if (rewards.ContainsKey(reward.RoleId)) + return new EventsAndErrors { - rewards[reward.RoleId].Add(receiver); - } - else - { - rewards.Add(reward.RoleId, new List { receiver }); - } - } - - public GiveRewardsCommand Build(ChainEventMessage[] lines, string[] errors) - { - var result = new GiveRewardsCommand - { - Rewards = rewards.Select(p => new RewardUsersCommand - { - RewardId = p.Key, - UserAddresses = p.Value.Select(v => v.Address).ToArray() - }).ToArray(), EventsOverview = lines, Errors = errors }; - - rewards.Clear(); - - return result; } } } diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs deleted file mode 100644 index 2f947d8d..00000000 --- a/Tools/TestNetRewarder/RewardCheck.cs +++ /dev/null @@ -1,113 +0,0 @@ -using BlockchainUtils; -using CodexContractsPlugin.ChainMonitor; -using DiscordRewards; -using System.Numerics; -using Utils; - -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(RequestEvent requestEvent) - { - if (MeetsRequirements(CheckType.ClientPostedContract, requestEvent)) - { - GiveReward(reward, requestEvent.Request.Client); - } - } - - public void OnRequestCancelled(RequestEvent requestEvent) - { - } - - public void OnRequestFailed(RequestEvent requestEvent) - { - } - - public void OnRequestFinished(RequestEvent requestEvent) - { - if (MeetsRequirements(CheckType.HostFinishedSlot, requestEvent)) - { - foreach (var host in requestEvent.Request.Hosts.GetHosts()) - { - GiveReward(reward, host); - } - } - } - - public void OnRequestFulfilled(RequestEvent requestEvent) - { - if (MeetsRequirements(CheckType.ClientStartedContract, requestEvent)) - { - GiveReward(reward, requestEvent.Request.Client); - } - } - - public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) - { - if (MeetsRequirements(CheckType.HostFilledSlot, requestEvent)) - { - if (host != null) - { - GiveReward(reward, host); - } - } - } - - public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) - { - } - - public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) - { - } - - public void OnError(string msg) - { - } - - public void OnProofSubmitted(BlockTimeEntry block, string id) - { - } - - private void GiveReward(RewardConfig reward, EthAddress receiver) - { - giver.Give(reward, receiver); - } - - private bool MeetsRequirements(CheckType type, RequestEvent requestEvent) - { - return - reward.CheckConfig.Type == type && - MeetsDurationRequirement(requestEvent.Request) && - MeetsSizeRequirement(requestEvent.Request); - } - - private bool MeetsSizeRequirement(IChainStateRequest r) - { - var slotSize = r.Request.Ask.SlotSize; - ulong min = Convert.ToUInt64(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 deleted file mode 100644 index f8e500cc..00000000 --- a/Tools/TestNetRewarder/RewardChecker.cs +++ /dev/null @@ -1,17 +0,0 @@ -using CodexContractsPlugin.ChainMonitor; -using DiscordRewards; - -namespace TestNetRewarder -{ - public class RewardChecker - { - public RewardChecker(IRewardGiver giver) - { - var repo = new RewardRepo(); - var checks = repo.Rewards.Select(r => new RewardCheck(r, giver)).ToArray(); - Handler = new ChainStateChangeHandlerMux(checks); - } - - public IChainStateChangeHandler Handler { get; } - } -} From 80cf288b9a117861b93959145517844c2e64326c Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 16 Apr 2025 15:39:30 +0200 Subject: [PATCH 16/51] More cleanup --- .../UtilityTests/DiscordBotTests.cs | 370 ------------------ Tools/BiblioTech/Commands/AdminCommand.cs | 86 +--- Tools/BiblioTech/Commands/SprCommand.cs | 48 --- .../Commands/UserAssociateCommand.cs | 4 +- Tools/BiblioTech/Program.cs | 4 +- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 2 +- Tools/BiblioTech/UserRepo.cs | 24 +- 7 files changed, 16 insertions(+), 522 deletions(-) delete mode 100644 Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs delete mode 100644 Tools/BiblioTech/Commands/SprCommand.cs diff --git a/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs b/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs deleted file mode 100644 index 22819e1b..00000000 --- a/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs +++ /dev/null @@ -1,370 +0,0 @@ -using CodexClient; -using CodexContractsPlugin; -using CodexDiscordBotPlugin; -using CodexPlugin; -using CodexTests; -using Core; -using DiscordRewards; -using DistTestCore; -using GethPlugin; -using KubernetesWorkflow.Types; -using Logging; -using Newtonsoft.Json; -using NUnit.Framework; -using Utils; - -namespace ExperimentalTests.UtilityTests -{ - [TestFixture] - public class DiscordBotTests : AutoBootstrapDistTest - { - private readonly RewardRepo repo = new RewardRepo(); - private readonly TestToken hostInitialBalance = 3000000.TstWei(); - private readonly TestToken clientInitialBalance = 1000000000.TstWei(); - private readonly EthAccount clientAccount = EthAccountGenerator.GenerateNew(); - 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(); - - [Test] - [DontDownloadLogs] - [Ignore("Used to debug testnet bots.")] - public void BotRewardTest() - { - var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth")); - var contracts = Ci.StartCodexContracts(geth); - var gethInfo = CreateGethInfo(geth, contracts); - - var botContainer = StartDiscordBot(gethInfo); - var rewarderContainer = StartRewarderBot(gethInfo, botContainer); - - StartHosts(geth, contracts); - var client = StartClient(geth, contracts); - - var apiCalls = new RewardApiCalls(GetTestLog(), Ci, botContainer); - apiCalls.Start(OnCommand); - - var purchaseContract = ClientPurchasesStorage(client); - purchaseContract.WaitForStorageContractStarted(); - purchaseContract.WaitForStorageContractFinished(); - - // todo: removed from codexclient: - //contracts.WaitUntilNextPeriod(); - //contracts.WaitUntilNextPeriod(); - - //var blocks = 3; - //Log($"Waiting {blocks} blocks for nodes to process payouts..."); - //Thread.Sleep(GethContainerRecipe.BlockInterval * blocks); - - Thread.Sleep(rewarderInterval * 3); - - apiCalls.Stop(); - - 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) - { - var seen = rewardsSeen.Any(s => r.RoleId == s); - - 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 AssertEventOccurance(string msg, int expectedCount) - { - Assert.That(receivedEvents.Count(e => e.Message.Contains(msg)), Is.EqualTo(expectedCount), - $"Event '{msg}' did not occure correct number of times."); - } - - private void OnCommand(string timestamp, EventsAndErrors call) - { - Log($""); - foreach (var e in call.EventsOverview) - { - Assert.That(receivedEvents.All(r => r.BlockNumber < e.BlockNumber), "Received event out of order."); - } - - receivedEvents.AddRange(call.EventsOverview); - foreach (var e in call.EventsOverview) - { - Log("\tEvent: " + e); - } - 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); - Log("\tReward: " + user + ": " + reward.Message); - } - } - Log($""); - } - - private IStoragePurchaseContract ClientPurchasesStorage(ICodexNode client) - { - var testFile = GenerateTestFile(GetMinFileSize()); - var contentId = client.UploadFile(testFile); - var purchase = new StoragePurchaseRequest(contentId) - { - PricePerBytePerSecond = 2.TstWei(), - CollateralPerByte = 10.TstWei(), - MinRequiredNumberOfNodes = GetNumberOfRequiredHosts(), - NodeFailureTolerance = 2, - ProofProbability = 5, - Duration = GetMinRequiredRequestDuration(), - Expiry = GetMinRequiredRequestDuration() - TimeSpan.FromMinutes(1) - }; - - return client.Marketplace.RequestStorage(purchase); - } - - private ICodexNode StartClient(IGethNode geth, ICodexContracts contracts) - { - 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 RunningPod StartRewarderBot(DiscordBotGethInfo gethInfo, RunningContainer botContainer) - { - return 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)), - 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: "discord-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(IGethNode geth, ICodexContracts contracts) - { - var hosts = StartCodex(GetNumberOfLiveHosts(), s => s - .WithName("Host") - .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) - { - ContractClock = CodexLogLevel.Trace, - }) - .WithStorageQuota(Mult(GetMinFileSizePlus(50), GetNumberOfLiveHosts())) - .EnableMarketplace(geth, contracts, m => m - .WithInitial(10.Eth(), hostInitialBalance) - .AsStorageNode() - .AsValidator())); - - var availability = new StorageAvailability( - totalSpace: Mult(GetMinFileSize(), GetNumberOfLiveHosts()), - maxDuration: TimeSpan.FromMinutes(30), - minPricePerBytePerSecond: 1.TstWei(), - totalCollateral: hostInitialBalance - ); - - foreach (var host in hosts) - { - hostAccounts.Add(host.EthAccount); - host.Marketplace.MakeStorageAvailable(availability); - } - } - - private int GetNumberOfLiveHosts() - { - return Convert.ToInt32(GetNumberOfRequiredHosts()) + 3; - } - - 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); - } - - 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 + 1024) * minNumHosts; - return new ByteSize(Convert.ToInt64(minFileSize)); - } - - private uint GetNumberOfRequiredHosts() - { - 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"; - try - { - var index = hostAccounts.FindIndex(a => a.EthAddress.Address == address); - return "Host" + index; - } - catch - { - return "UNKNOWN"; - } - } - - public class RewardApiCalls - { - private readonly ContainerFileMonitor monitor; - - public RewardApiCalls(ILog log, CoreInterface ci, RunningContainer botContainer) - { - monitor = new ContainerFileMonitor(log, ci, botContainer, "/app/datapath/logs/discordbot.log"); - } - - public void Start(Action 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); - var json = line.Substring(31); - - var cmd = JsonConvert.DeserializeObject(json); - if (cmd != null) - { - onCommand(timestamp, cmd); - } - } - catch - { - } - } - } - - public class ContainerFileMonitor - { - private readonly ILog log; - 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(ILog log, CoreInterface ci, RunningContainer botContainer, string filePath) - { - this.log = log; - this.ci = ci; - this.botContainer = botContainer; - this.filePath = filePath; - } - - public void Start(Action onNewLine) - { - this.onNewLine = onNewLine; - worker = Task.Run(Worker); - } - - public void Stop() - { - cts.Cancel(); - worker.Wait(); - } - - // did any container crash? that's why it repeats? - - - 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", filePath); - var lines = botLog.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) - { - // log.Log("line: " + line); - - if (!seenLines.Contains(line)) - { - seenLines.Add(line); - onNewLine(line); - } - } - } - } - } -} diff --git a/Tools/BiblioTech/Commands/AdminCommand.cs b/Tools/BiblioTech/Commands/AdminCommand.cs index 45b3fc29..f9877181 100644 --- a/Tools/BiblioTech/Commands/AdminCommand.cs +++ b/Tools/BiblioTech/Commands/AdminCommand.cs @@ -8,16 +8,10 @@ namespace BiblioTech.Commands private readonly ClearUserAssociationCommand clearCommand = new ClearUserAssociationCommand(); private readonly ReportCommand reportCommand = new ReportCommand(); private readonly WhoIsCommand whoIsCommand = new WhoIsCommand(); - private readonly AddSprCommand addSprCommand; - private readonly ClearSprsCommand clearSprsCommand; - private readonly GetSprCommand getSprCommand; private readonly LogReplaceCommand logReplaceCommand; - public AdminCommand(SprCommand sprCommand, CustomReplacement replacement) + public AdminCommand(CustomReplacement replacement) { - addSprCommand = new AddSprCommand(sprCommand); - clearSprsCommand = new ClearSprsCommand(sprCommand); - getSprCommand = new GetSprCommand(sprCommand); logReplaceCommand = new LogReplaceCommand(replacement); } @@ -30,9 +24,6 @@ namespace BiblioTech.Commands clearCommand, reportCommand, whoIsCommand, - addSprCommand, - clearSprsCommand, - getSprCommand, logReplaceCommand }; @@ -53,9 +44,6 @@ namespace BiblioTech.Commands await clearCommand.CommandHandler(context); await reportCommand.CommandHandler(context); await whoIsCommand.CommandHandler(context); - await addSprCommand.CommandHandler(context); - await clearSprsCommand.CommandHandler(context); - await getSprCommand.CommandHandler(context); await logReplaceCommand.CommandHandler(context); } @@ -144,78 +132,6 @@ namespace BiblioTech.Commands } } - public class AddSprCommand : SubCommandOption - { - private readonly SprCommand sprCommand; - private readonly StringOption stringOption = new StringOption("spr", "Codex SPR", true); - - public AddSprCommand(SprCommand sprCommand) - : base(name: "addspr", - description: "Adds a Codex SPR, to be given to users with '/boot'.") - { - this.sprCommand = sprCommand; - } - - public override CommandOption[] Options => new[] { stringOption }; - - protected override async Task onSubCommand(CommandContext context) - { - var spr = await stringOption.Parse(context); - - if (!string.IsNullOrEmpty(spr) ) - { - sprCommand.Add(spr); - await context.Followup("A-OK!"); - } - else - { - await context.Followup("SPR is null or empty."); - } - } - } - - public class ClearSprsCommand : SubCommandOption - { - private readonly SprCommand sprCommand; - private readonly StringOption stringOption = new StringOption("areyousure", "set to 'true' if you are.", true); - - public ClearSprsCommand(SprCommand sprCommand) - : base(name: "clearsprs", - description: "Clears all Codex SPRs in the bot. Users won't be able to use '/boot' till new ones are added.") - { - this.sprCommand = sprCommand; - } - - public override CommandOption[] Options => new[] { stringOption }; - - protected override async Task onSubCommand(CommandContext context) - { - var areyousure = await stringOption.Parse(context); - - if (areyousure != "true") return; - - sprCommand.Clear(); - await context.Followup("Cleared all SPRs."); - } - } - - public class GetSprCommand : SubCommandOption - { - private readonly SprCommand sprCommand; - - public GetSprCommand(SprCommand sprCommand) - : base(name: "getsprs", - description: "Shows all Codex SPRs in the bot.") - { - this.sprCommand = sprCommand; - } - - protected override async Task onSubCommand(CommandContext context) - { - await context.Followup("SPRs: " + string.Join(", ", sprCommand.Get().Select(s => $"'{s}'"))); - } - } - public class LogReplaceCommand : SubCommandOption { private readonly CustomReplacement replacement; diff --git a/Tools/BiblioTech/Commands/SprCommand.cs b/Tools/BiblioTech/Commands/SprCommand.cs deleted file mode 100644 index 4b3235de..00000000 --- a/Tools/BiblioTech/Commands/SprCommand.cs +++ /dev/null @@ -1,48 +0,0 @@ -using BiblioTech.Options; - -namespace BiblioTech.Commands -{ - public class SprCommand : BaseCommand - { - private readonly Random random = new Random(); - private readonly List knownSprs = new List(); - - public override string Name => "boot"; - public override string StartingMessage => RandomBusyMessage.Get(); - public override string Description => "Gets an SPR. (Signed peer record, used for bootstrapping.)"; - - protected override async Task Invoke(CommandContext context) - { - await ReplyWithRandomSpr(context); - } - - public void Add(string spr) - { - if (knownSprs.Contains(spr)) return; - knownSprs.Add(spr); - } - - public void Clear() - { - knownSprs.Clear(); - } - - public string[] Get() - { - return knownSprs.ToArray(); - } - - private async Task ReplyWithRandomSpr(CommandContext context) - { - if (!knownSprs.Any()) - { - await context.Followup("I'm sorry, no SPRs are available... :c"); - return; - } - - var i = random.Next(0, knownSprs.Count); - var spr = knownSprs[i]; - await context.Followup($"Your SPR: `{spr}`"); - } - } -} diff --git a/Tools/BiblioTech/Commands/UserAssociateCommand.cs b/Tools/BiblioTech/Commands/UserAssociateCommand.cs index 61d55fb9..dbe7dced 100644 --- a/Tools/BiblioTech/Commands/UserAssociateCommand.cs +++ b/Tools/BiblioTech/Commands/UserAssociateCommand.cs @@ -69,8 +69,8 @@ namespace BiblioTech.Commands { await context.Followup(new string[] { - "Done! Thank you for joining the test net!", - "By default, the bot will @-mention you with test-net related notifications.", + "Done! Thank you for joining!", + "By default, the bot will @-mention you with discord role notifications.", $"You can enable/disable this behavior with the '/{notifyCommand.Name}' command." }); diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index df1ce6bc..0a86a8aa 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -89,17 +89,15 @@ namespace BiblioTech var checker = new CodexTwoWayChecker(Log, Config, checkRepo, codexWrapper); var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); - var sprCommand = new SprCommand(); var roleRemover = new ActiveP2pRoleRemover(Config, Log, checkRepo); var handler = new CommandHandler(Log, client, replacement, roleRemover, new GetBalanceCommand(associateCommand), new MintCommand(associateCommand), - sprCommand, associateCommand, notifyCommand, new CheckUploadCommand(checker), new CheckDownloadCommand(checker), - new AdminCommand(sprCommand, replacement) + new AdminCommand(replacement) ); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs index 124d687d..f65d4cf2 100644 --- a/Tools/BiblioTech/Rewards/RoleModifyContext.cs +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -97,7 +97,7 @@ namespace BiblioTech.Rewards { try { - var userData = userRepo.GetUserById(user.Id); + var userData = userRepo.GetUser(user); if (userData == null) return; if (userData.NotificationsEnabled && rewardsChannel != null) diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index b6c02500..07831dd6 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -41,11 +41,10 @@ namespace BiblioTech return cache.Values.ToArray(); } - public UserData? GetUserById(ulong id) + public UserData GetUser(IUser user) { if (cache.Count == 0) LoadAllUserData(); - if (cache.ContainsKey(id)) return cache[id]; - return null; + return GetOrCreate(user); } public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction? eth, Transaction? tokens) @@ -75,10 +74,10 @@ namespace BiblioTech lock (repoLock) { - var userData = GetUserData(user); + var userData = GetUserDataMaybe(user); if (userData == null) { - result.Add("User has not joined the test net."); + result.Add("User has not interacted with bot."); } else { @@ -107,19 +106,19 @@ namespace BiblioTech public string[] GetUserReport(IUser user) { - var userData = GetUserData(user); + var userData = GetUserDataMaybe(user); if (userData == null) return new[] { "User has not joined the test net." }; return userData.CreateOverview(); } public string[] GetUserReport(EthAddress ethAddress) { - var userData = GetUserDataForAddress(ethAddress); + var userData = GetUserDataForAddressMaybe(ethAddress); if (userData == null) return new[] { "No user is using this eth address." }; return userData.CreateOverview(); } - public UserData? GetUserDataForAddress(EthAddress? address) + public UserData? GetUserDataForAddressMaybe(EthAddress? address) { if (address == null) return null; @@ -144,7 +143,7 @@ namespace BiblioTech private SetAddressResponse SetUserAddress(IUser user, EthAddress? address) { - if (GetUserDataForAddress(address) != null) + if (GetUserDataForAddressMaybe(address) != null) { return SetAddressResponse.AddressAlreadyInUse; } @@ -159,13 +158,12 @@ namespace BiblioTech private void SetUserNotification(IUser user, bool notifyEnabled) { - var userData = GetUserData(user); - if (userData == null) return; + var userData = GetOrCreate(user); userData.NotificationsEnabled = notifyEnabled; SaveUserData(userData); } - private UserData? GetUserData(IUser user) + private UserData? GetUserDataMaybe(IUser user) { if (cache.ContainsKey(user.Id)) { @@ -184,7 +182,7 @@ namespace BiblioTech private UserData GetOrCreate(IUser user) { - var userData = GetUserData(user); + var userData = GetUserDataMaybe(user); if (userData == null) { return CreateAndSaveNewUserData(user); From a2c8c18c5cda6f391f9d9913342f0f09291ed329 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 16 Apr 2025 16:25:31 +0200 Subject: [PATCH 17/51] wip --- Framework/DiscordRewards/EventsAndErrors.cs | 13 +++- Tools/BiblioTech/CommandHandler.cs | 1 + Tools/BiblioTech/Program.cs | 1 + .../Rewards/ChainActivityHandler.cs | 60 +++++++++++++++++++ Tools/TestNetRewarder/Processor.cs | 6 +- Tools/TestNetRewarder/RequestBuilder.cs | 44 +++++++++++++- 6 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/ChainActivityHandler.cs diff --git a/Framework/DiscordRewards/EventsAndErrors.cs b/Framework/DiscordRewards/EventsAndErrors.cs index 53e4b3cc..e26568c0 100644 --- a/Framework/DiscordRewards/EventsAndErrors.cs +++ b/Framework/DiscordRewards/EventsAndErrors.cs @@ -4,10 +4,15 @@ { public ChainEventMessage[] EventsOverview { get; set; } = Array.Empty(); public string[] Errors { get; set; } = Array.Empty(); + public ActiveChainAddresses ActiveChainAddresses { get; set; } = new ActiveChainAddresses(); public bool HasAny() { - return Errors.Length > 0 || EventsOverview.Length > 0; + return + Errors.Length > 0 || + EventsOverview.Length > 0 || + ActiveChainAddresses.Hosts.Length > 0 || + ActiveChainAddresses.Clients.Length > 0; } } @@ -16,4 +21,10 @@ public ulong BlockNumber { get; set; } public string Message { get; set; } = string.Empty; } + + public class ActiveChainAddresses + { + public string[] Hosts { get; set; } = Array.Empty(); + public string[] Clients { get; set; } = Array.Empty(); + } } diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 8c888ea7..5a7a83e7 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -42,6 +42,7 @@ namespace BiblioTech Program.AdminChecker.SetAdminChannel(adminChannel); Program.RoleDriver = new RoleDriver(client, Program.UserRepo, log, rewardsChannel); + Program.ChainActivityHandler = new ChainActivityHandler(log); Program.EventsSender = new ChainEventsSender(log, replacement, chainEventsChannel); var builders = commands.Select(c => diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 0a86a8aa..cb3b13e5 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -18,6 +18,7 @@ namespace BiblioTech public static UserRepo UserRepo { get; } = new UserRepo(); public static AdminChecker AdminChecker { get; private set; } = null!; public static IDiscordRoleDriver RoleDriver { get; set; } = null!; + public static ChainActivityHandler ChainActivityHandler { get; set; } = null!; public static ChainEventsSender EventsSender { get; set; } = null!; public static ILog Log { get; private set; } = null!; diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs new file mode 100644 index 00000000..2025725a --- /dev/null +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -0,0 +1,60 @@ +using DiscordRewards; +using Logging; + +namespace BiblioTech.Rewards +{ + public class ChainActivityHandler + { + private readonly ILog log; + private readonly UserRepo repo; + + public ChainActivityHandler(ILog log, UserRepo repo) + { + this.log = log; + this.repo = repo; + } + + public async Task Process(ActiveChainAddresses activeChainAddresses) + { + var activeUserIds = ConvertToUserIds(activeChainAddresses); + if (!activeUserIds.HasAny()) return; + + todo call role driver to add roles to new activeIds or remove them. + } + + private ActiveUserIds ConvertToUserIds(ActiveChainAddresses activeChainAddresses) + { + return new ActiveUserIds + { + Hosts = Map(activeChainAddresses.Hosts), + Clients = Map(activeChainAddresses.Clients) + }; + } + + private ulong[] Map(string[] ethAddresses) + { + var result = new List(); + foreach (var ethAddress in ethAddresses) + { + var userMaybe = repo.GetUserDataForAddressMaybe(new Utils.EthAddress(ethAddress)); + if (userMaybe != null) + { + result.Add(userMaybe.DiscordId); + } + } + + return result.ToArray(); + } + + private class ActiveUserIds + { + public ulong[] Hosts { get; set; } = Array.Empty(); + public ulong[] Clients { get; set; } = Array.Empty(); + + public bool HasAny() + { + return Hosts.Any() || Clients.Any(); + } + } + } +} diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index 7377a7da..b018d377 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -32,7 +32,7 @@ namespace TestNetRewarder public async Task Initialize() { var events = eventsFormatter.GetInitializationEvents(config); - var request = builder.Build(events, Array.Empty()); + var request = builder.Build(chainState, events, Array.Empty()); if (request.HasAny()) { await client.SendRewards(request); @@ -49,7 +49,7 @@ namespace TestNetRewarder if (numberOfChainEvents == 0) return TimeSegmentResponse.Underload; if (numberOfChainEvents > 10) return TimeSegmentResponse.Overload; - if (duration > TimeSpan.FromSeconds(1)) return TimeSegmentResponse.Overload; + if (duration > TimeSpan.FromSeconds(3)) return TimeSegmentResponse.Overload; return TimeSegmentResponse.OK; } catch (Exception ex) @@ -69,7 +69,7 @@ namespace TestNetRewarder var events = eventsFormatter.GetEvents(); var errors = eventsFormatter.GetErrors(); - var request = builder.Build(events, errors); + var request = builder.Build(chainState, events, errors); if (request.HasAny()) { await client.SendRewards(request); diff --git a/Tools/TestNetRewarder/RequestBuilder.cs b/Tools/TestNetRewarder/RequestBuilder.cs index adc68032..5b859269 100644 --- a/Tools/TestNetRewarder/RequestBuilder.cs +++ b/Tools/TestNetRewarder/RequestBuilder.cs @@ -1,17 +1,55 @@ -using DiscordRewards; +using CodexContractsPlugin.ChainMonitor; +using DiscordRewards; using Utils; namespace TestNetRewarder { public class RequestBuilder { - public EventsAndErrors Build(ChainEventMessage[] lines, string[] errors) + public EventsAndErrors Build(ChainState chainState, ChainEventMessage[] lines, string[] errors) { + var activeChainAddresses = CollectActiveAddresses(chainState); + return new EventsAndErrors { EventsOverview = lines, - Errors = errors + Errors = errors, + ActiveChainAddresses = activeChainAddresses }; } + + private ActiveChainAddresses CollectActiveAddresses(ChainState chainState) + { + var hosts = new List(); + var clients = new List(); + + foreach (var request in chainState.Requests) + { + CollectAddresses(request, hosts, clients); + } + + return new ActiveChainAddresses + { + Hosts = hosts.ToArray(), + Clients = clients.ToArray() + }; + } + + private void CollectAddresses(IChainStateRequest request, List hosts, List clients) + { + if (request.State != CodexContractsPlugin.RequestState.Started) return; + + AddIfNew(clients, request.Client); + foreach (var host in request.Hosts.GetHosts()) + { + AddIfNew(hosts, host); + } + } + + private void AddIfNew(List list, EthAddress address) + { + var addr = address.Address; + if (!list.Contains(addr)) list.Add(addr); + } } } From 0eaaa625f10bbbc5879401f536f41664ca2b63f8 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 09:35:13 +0200 Subject: [PATCH 18/51] Implements giving and removing of activehost and activeclient roles --- .../CodexChecking/ActiveP2pRoleRemover.cs | 16 +++- Tools/BiblioTech/CommandHandler.cs | 2 +- .../Commands/CheckResponseHandler.cs | 4 +- Tools/BiblioTech/LoggingRoleDriver.cs | 45 ++++++++- .../Rewards/ChainActivityHandler.cs | 94 +++++++++++++++++-- Tools/BiblioTech/Rewards/RewardController.cs | 12 ++- Tools/BiblioTech/Rewards/RoleDriver.cs | 49 ++++++++-- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 12 +-- 8 files changed, 197 insertions(+), 37 deletions(-) diff --git a/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs b/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs index 4a448792..2d8ec602 100644 --- a/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs +++ b/Tools/BiblioTech/CodexChecking/ActiveP2pRoleRemover.cs @@ -1,4 +1,5 @@ -using Discord; +using BiblioTech.Rewards; +using Discord; using Logging; using System.Threading.Tasks; @@ -50,7 +51,18 @@ namespace BiblioTech.CodexChecking { var expiryMoment = DateTime.UtcNow - TimeSpan.FromMinutes(config.ActiveP2pRoleDurationMinutes); - Program.RoleDriver.IterateRemoveActiveP2pParticipants(p => ShouldRemoveRole(p, expiryMoment)); + Program.RoleDriver.IterateUsersWithRoles( + (g, u, r) => OnUserWithRole(g, u, r, expiryMoment), + Program.Config.ActiveP2pParticipantRoleId); + } + + private async Task OnUserWithRole(IRoleGiver giver, IUser user, ulong roleId, DateTime expiryMoment) + { + var report = repo.GetOrCreate(user.Id); + if (report.UploadCheck.CompletedUtc > expiryMoment) return; + if (report.DownloadCheck.CompletedUtc > expiryMoment) return; + + await giver.RemoveActiveP2pParticipant(user.Id); } private bool ShouldRemoveRole(IUser user, DateTime expiryMoment) diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 5a7a83e7..822d4446 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -42,7 +42,7 @@ namespace BiblioTech Program.AdminChecker.SetAdminChannel(adminChannel); Program.RoleDriver = new RoleDriver(client, Program.UserRepo, log, rewardsChannel); - Program.ChainActivityHandler = new ChainActivityHandler(log); + Program.ChainActivityHandler = new ChainActivityHandler(log, Program.UserRepo); Program.EventsSender = new ChainEventsSender(log, replacement, chainEventsChannel); var builders = commands.Select(c => diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index ca9dedd4..5428e86b 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -58,8 +58,8 @@ namespace BiblioTech.Commands { await Program.RoleDriver.RunRoleGiver(async r => { - await r.GiveAltruisticRole(user); - await r.GiveActiveP2pParticipant(user); + await r.GiveAltruisticRole(user.Id); + await r.GiveActiveP2pParticipant(user.Id); }); await context.Followup($"Congratulations! You've been granted the Altruistic Mode role!"); } diff --git a/Tools/BiblioTech/LoggingRoleDriver.cs b/Tools/BiblioTech/LoggingRoleDriver.cs index a5631271..b1759af9 100644 --- a/Tools/BiblioTech/LoggingRoleDriver.cs +++ b/Tools/BiblioTech/LoggingRoleDriver.cs @@ -21,7 +21,12 @@ namespace BiblioTech await action(new LoggingRoleGiver(log)); } - public async Task IterateRemoveActiveP2pParticipants(Func predicate) + public async Task IterateUsersWithRoles(Func onUserWithRole, params ulong[] rolesToIterate) + { + await Task.CompletedTask; + } + + public async Task IterateUsersWithRoles(Func onUserWithRole, Func whenDone, params ulong[] rolesToIterate) { await Task.CompletedTask; } @@ -35,15 +40,45 @@ namespace BiblioTech this.log = log; } - public async Task GiveActiveP2pParticipant(IUser user) + public async Task GiveActiveClient(ulong userId) { - log.Log($"Giving ActiveP2p role to " + user.Id); + log.Log($"Giving ActiveClient role to " + userId); await Task.CompletedTask; } - public async Task GiveAltruisticRole(IUser user) + public async Task GiveActiveHost(ulong userId) { - log.Log($"Giving Altruistic role to " + user.Id); + log.Log($"Giving ActiveHost role to " + userId); + await Task.CompletedTask; + } + + public async Task GiveActiveP2pParticipant(ulong userId) + { + log.Log($"Giving ActiveP2p role to " + userId); + await Task.CompletedTask; + } + + public async Task RemoveActiveP2pParticipant(ulong userId) + { + log.Log($"Removing ActiveP2p role from " + userId); + await Task.CompletedTask; + } + + public async Task GiveAltruisticRole(ulong userId) + { + log.Log($"Giving Altruistic role to " + userId); + await Task.CompletedTask; + } + + public async Task RemoveActiveClient(ulong userId) + { + log.Log($"Removing ActiveClient role from " + userId); + await Task.CompletedTask; + } + + public async Task RemoveActiveHost(ulong userId) + { + log.Log($"Removing ActiveHost role from " + userId); await Task.CompletedTask; } } diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index 2025725a..a3bb17a8 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -1,4 +1,5 @@ -using DiscordRewards; +using Discord; +using DiscordRewards; using Logging; namespace BiblioTech.Rewards @@ -7,6 +8,7 @@ namespace BiblioTech.Rewards { private readonly ILog log; private readonly UserRepo repo; + private ActiveUserIds? previousIds = null; public ChainActivityHandler(ILog log, UserRepo repo) { @@ -18,17 +20,85 @@ namespace BiblioTech.Rewards { var activeUserIds = ConvertToUserIds(activeChainAddresses); if (!activeUserIds.HasAny()) return; + + if (!HasChanged(activeUserIds)) return; - todo call role driver to add roles to new activeIds or remove them. + await GiveAndRemoveRoles(activeUserIds); + } + + private async Task GiveAndRemoveRoles(ActiveUserIds activeUserIds) + { + await Program.RoleDriver.IterateUsersWithRoles( + (g, u, r) => OnUserWithRole(g, u, r, activeUserIds), + whenDone: g => GiveRolesToRemaining(g, activeUserIds), + Program.Config.ActiveClientRoleId, + Program.Config.ActiveHostRoleId); + } + + private async Task OnUserWithRole(IRoleGiver giver, IUser user, ulong roleId, ActiveUserIds activeIds) + { + if (roleId == Program.Config.ActiveClientRoleId) + { + await CheckUserWithRole(user, activeIds.Clients, giver.RemoveActiveClient); + } + else if (roleId == Program.Config.ActiveHostRoleId) + { + await CheckUserWithRole(user, activeIds.Hosts, giver.RemoveActiveHost); + } + else + { + throw new Exception("Unknown roleId received!"); + } + } + + private async Task CheckUserWithRole(IUser user, List activeUsers, Func removeActiveRole) + { + if (ShouldUserHaveRole(user, activeUsers)) + { + activeUsers.Remove(user.Id); + } + else + { + await removeActiveRole(user.Id); + } + } + + private bool ShouldUserHaveRole(IUser user, List activeUsers) + { + return activeUsers.Any(id => id == user.Id); + } + + private async Task GiveRolesToRemaining(IRoleGiver giver, ActiveUserIds ids) + { + foreach (var client in ids.Clients) await giver.GiveActiveClient(client); + foreach (var host in ids.Hosts) await giver.GiveActiveHost(host); + } + + private bool HasChanged(ActiveUserIds activeUserIds) + { + if (previousIds == null) + { + previousIds = activeUserIds; + return true; + } + + if (!IsEquivalent(previousIds.Hosts, activeUserIds.Hosts)) return true; + if (!IsEquivalent(previousIds.Clients, activeUserIds.Clients)) return true; + return false; + } + + private static bool IsEquivalent(IEnumerable a, IEnumerable b) + { + return a.SequenceEqual(b); } private ActiveUserIds ConvertToUserIds(ActiveChainAddresses activeChainAddresses) { return new ActiveUserIds - { - Hosts = Map(activeChainAddresses.Hosts), - Clients = Map(activeChainAddresses.Clients) - }; + ( + hosts: Map(activeChainAddresses.Hosts), + clients: Map(activeChainAddresses.Clients) + ); } private ulong[] Map(string[] ethAddresses) @@ -43,13 +113,19 @@ namespace BiblioTech.Rewards } } - return result.ToArray(); + return result.Order().ToArray(); } private class ActiveUserIds { - public ulong[] Hosts { get; set; } = Array.Empty(); - public ulong[] Clients { get; set; } = Array.Empty(); + public ActiveUserIds(IEnumerable hosts, IEnumerable clients) + { + Hosts = hosts.ToList(); + Clients = clients.ToList(); + } + + public List Hosts { get; } + public List Clients { get; } public bool HasAny() { diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 7cbed562..7a4b8aa3 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -11,13 +11,19 @@ namespace BiblioTech.Rewards public interface IDiscordRoleDriver { Task RunRoleGiver(Func action); - Task IterateRemoveActiveP2pParticipants(Func predicate); + Task IterateUsersWithRoles(Func onUserWithRole, params ulong[] rolesToIterate); + Task IterateUsersWithRoles(Func onUserWithRole, Func whenDone, params ulong[] rolesToIterate); } public interface IRoleGiver { - Task GiveAltruisticRole(IUser user); - Task GiveActiveP2pParticipant(IUser user); + Task GiveAltruisticRole(ulong userId); + Task GiveActiveP2pParticipant(ulong userId); + Task RemoveActiveP2pParticipant(ulong userId); + Task GiveActiveHost(ulong userId); + Task RemoveActiveHost(ulong userId); + Task GiveActiveClient(ulong userId); + Task RemoveActiveClient(ulong userId); } [Route("api/[controller]")] diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index c716c011..41b0e44c 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -30,20 +30,26 @@ namespace BiblioTech.Rewards await action(mapper); } - public async Task IterateRemoveActiveP2pParticipants(Func shouldRemove) + public async Task IterateUsersWithRoles(Func onUserWithRole, params ulong[] rolesToIterate) + { + await IterateUsersWithRoles(onUserWithRole, g => Task.CompletedTask, rolesToIterate); + } + + public async Task IterateUsersWithRoles(Func onUserWithRole, Func whenDone, params ulong[] rolesToIterate) { var context = await OpenRoleModifyContext(); + var mapper = new RoleMapper(context); foreach (var user in context.Users) { - if (user.RoleIds.Any(r => r == Program.Config.ActiveP2pParticipantRoleId)) + foreach (var role in rolesToIterate) { - // This user has the role. Should it be removed? - if (shouldRemove(user)) + if (user.RoleIds.Contains(role)) { - await context.RemoveRole(user, Program.Config.ActiveP2pParticipantRoleId); + await onUserWithRole(mapper, user, role); } } } + await whenDone(mapper); } private async Task OpenRoleModifyContext() @@ -74,14 +80,39 @@ namespace BiblioTech.Rewards this.context = context; } - public async Task GiveActiveP2pParticipant(IUser user) + public async Task GiveActiveClient(ulong userId) { - await context.GiveRole(user, Program.Config.ActiveP2pParticipantRoleId); + await context.GiveRole(userId, Program.Config.ActiveClientRoleId); } - public async Task GiveAltruisticRole(IUser user) + public async Task GiveActiveHost(ulong userId) { - await context.GiveRole(user, Program.Config.AltruisticRoleId); + await context.GiveRole(userId, Program.Config.ActiveHostRoleId); + } + + public async Task GiveActiveP2pParticipant(ulong userId) + { + await context.GiveRole(userId, Program.Config.ActiveP2pParticipantRoleId); + } + + public async Task RemoveActiveP2pParticipant(ulong userId) + { + await context.RemoveRole(userId, Program.Config.ActiveP2pParticipantRoleId); + } + + public async Task GiveAltruisticRole(ulong userId) + { + await context.GiveRole(userId, Program.Config.AltruisticRoleId); + } + + public async Task RemoveActiveClient(ulong userId) + { + await context.RemoveRole(userId, Program.Config.ActiveClientRoleId); + } + + public async Task RemoveActiveHost(ulong userId) + { + await context.RemoveRole(userId, Program.Config.ActiveHostRoleId); } } } diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs index f65d4cf2..58914f5e 100644 --- a/Tools/BiblioTech/Rewards/RoleModifyContext.cs +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -31,28 +31,28 @@ namespace BiblioTech.Rewards public IGuildUser[] Users => users.Values.ToArray(); - public async Task GiveRole(IUser user, ulong roleId) + public async Task GiveRole(ulong userId, ulong roleId) { var role = GetRole(roleId); - var guildUser = GetUser(user.Id); + var guildUser = GetUser(userId); if (role == null) return; if (guildUser == null) return; await guildUser.AddRoleAsync(role); - await Program.AdminChecker.SendInAdminChannel($"Added role '{role.Name}' for user <@{user.Id}>."); + await Program.AdminChecker.SendInAdminChannel($"Added role '{role.Name}' for user <@{userId}>."); await SendNotification(guildUser, role); } - public async Task RemoveRole(IUser user, ulong roleId) + public async Task RemoveRole(ulong userId, ulong roleId) { var role = GetRole(roleId); - var guildUser = GetUser(user.Id); + var guildUser = GetUser(userId); if (role == null) return; if (guildUser == null) return; await guildUser.RemoveRoleAsync(role); - await Program.AdminChecker.SendInAdminChannel($"Removed role '{role.Name}' for user <@{user.Id}>."); + await Program.AdminChecker.SendInAdminChannel($"Removed role '{role.Name}' for user <@{userId}>."); } private SocketRole? GetRole(ulong roleId) From e4d99932ddb6590525ec47420bbeaf4c43dbd507 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 13:53:21 +0200 Subject: [PATCH 19/51] Adds logging to debug active chain roles --- Framework/DiscordRewards/EventsAndErrors.cs | 13 +++++++-- .../Rewards/ChainActivityHandler.cs | 28 +++++++++++++++++-- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 7 +++++ Tools/TestNetRewarder/Program.cs | 1 + 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Framework/DiscordRewards/EventsAndErrors.cs b/Framework/DiscordRewards/EventsAndErrors.cs index e26568c0..638299c6 100644 --- a/Framework/DiscordRewards/EventsAndErrors.cs +++ b/Framework/DiscordRewards/EventsAndErrors.cs @@ -11,8 +11,7 @@ return Errors.Length > 0 || EventsOverview.Length > 0 || - ActiveChainAddresses.Hosts.Length > 0 || - ActiveChainAddresses.Clients.Length > 0; + ActiveChainAddresses.HasAny(); } } @@ -26,5 +25,15 @@ { public string[] Hosts { get; set; } = Array.Empty(); public string[] Clients { get; set; } = Array.Empty(); + + public bool HasAny() + { + return Hosts.Length > 0 || Clients.Length > 0; + } + + public override string ToString() + { + return "Hosts:" + string.Join(",", Hosts) + "Clients:" + string.Join(",", Clients); + } } } diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index a3bb17a8..1541bde8 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -18,10 +18,24 @@ namespace BiblioTech.Rewards public async Task Process(ActiveChainAddresses activeChainAddresses) { + if (!activeChainAddresses.HasAny()) + { + Log("Received empty activeChainAddresses."); + return; + } + var activeUserIds = ConvertToUserIds(activeChainAddresses); - if (!activeUserIds.HasAny()) return; + if (!activeUserIds.HasAny()) + { + Log("Empty userIds after lookup of addresses: " + activeChainAddresses); + return; + } - if (!HasChanged(activeUserIds)) return; + if (!HasChanged(activeUserIds)) + { + Log("Active userIds has not changed: " + activeUserIds); + return; + } await GiveAndRemoveRoles(activeUserIds); } @@ -116,6 +130,11 @@ namespace BiblioTech.Rewards return result.Order().ToArray(); } + private void Log(string msg) + { + log.Log(msg); + } + private class ActiveUserIds { public ActiveUserIds(IEnumerable hosts, IEnumerable clients) @@ -131,6 +150,11 @@ namespace BiblioTech.Rewards { return Hosts.Any() || Clients.Any(); } + + public override string ToString() + { + return "Hosts:" + string.Join(",", Hosts) + "Clients:" + string.Join(",", Clients); + } } } } diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs index 58914f5e..3fbc5132 100644 --- a/Tools/BiblioTech/Rewards/RoleModifyContext.cs +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -33,6 +33,7 @@ namespace BiblioTech.Rewards public async Task GiveRole(ulong userId, ulong roleId) { + Log($"Giving role {roleId} to user {userId}"); var role = GetRole(roleId); var guildUser = GetUser(userId); if (role == null) return; @@ -46,6 +47,7 @@ namespace BiblioTech.Rewards public async Task RemoveRole(ulong userId, ulong roleId) { + Log($"Removing role {roleId} from user {userId}"); var role = GetRole(roleId); var guildUser = GetUser(userId); if (role == null) return; @@ -67,6 +69,11 @@ namespace BiblioTech.Rewards return null; } + private void Log(string msg) + { + log.Log(msg); + } + private async Task> LoadAllUsers(SocketGuild guild) { log.Log("Loading all users.."); diff --git a/Tools/TestNetRewarder/Program.cs b/Tools/TestNetRewarder/Program.cs index ab21f61c..e3ed24e7 100644 --- a/Tools/TestNetRewarder/Program.cs +++ b/Tools/TestNetRewarder/Program.cs @@ -45,6 +45,7 @@ namespace TestNetRewarder Log.Log("Starting TestNet Rewarder..."); var segmenter = new TimeSegmenter(Log, Config.Interval, Config.HistoryStartUtc, processor); + await EnsureBotOnline(); await processor.Initialize(); while (!CancellationToken.IsCancellationRequested) From abe03abff6b1888ed752404de1beb793fd0b06c4 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 14:05:10 +0200 Subject: [PATCH 20/51] Sometimes you just forget to call the function --- .../Rewards/ChainActivityHandler.cs | 23 ++++--------------- Tools/BiblioTech/Rewards/RewardController.cs | 10 ++++++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index 1541bde8..66fe5e63 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -16,27 +16,12 @@ namespace BiblioTech.Rewards this.repo = repo; } - public async Task Process(ActiveChainAddresses activeChainAddresses) + public async Task ProcessChainActivity(ActiveChainAddresses activeChainAddresses) { - if (!activeChainAddresses.HasAny()) - { - Log("Received empty activeChainAddresses."); - return; - } - + if (!activeChainAddresses.HasAny()) return; var activeUserIds = ConvertToUserIds(activeChainAddresses); - if (!activeUserIds.HasAny()) - { - Log("Empty userIds after lookup of addresses: " + activeChainAddresses); - return; - } - - if (!HasChanged(activeUserIds)) - { - Log("Active userIds has not changed: " + activeUserIds); - return; - } - + if (!activeUserIds.HasAny()) return; + if (!HasChanged(activeUserIds)) return; await GiveAndRemoveRoles(activeUserIds); } diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 7a4b8aa3..93e9b008 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -38,16 +38,22 @@ namespace BiblioTech.Rewards [HttpPost] public async Task Give(EventsAndErrors cmd) + { + await Safe(() => Program.ChainActivityHandler.ProcessChainActivity(cmd.ActiveChainAddresses)); + await Safe(() => Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors)); + return "OK"; + } + + private async Task Safe(Func action) { try { - await Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors); + await action(); } catch (Exception ex) { Program.Log.Error("Exception: " + ex); } - return "OK"; } } } From b118f7d103d458e46f1e4bd51b55ab26e71c0f2d Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 14:19:52 +0200 Subject: [PATCH 21/51] debugging bot api call --- Tools/BiblioTech/Program.cs | 14 ++++++++++++++ Tools/BiblioTech/Rewards/RewardController.cs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index cb3b13e5..2e0427e6 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -4,8 +4,10 @@ using BiblioTech.Commands; using BiblioTech.Rewards; using Discord; using Discord.WebSocket; +using DiscordRewards; using Logging; using Nethereum.Model; +using Newtonsoft.Json; namespace BiblioTech { @@ -42,6 +44,18 @@ namespace BiblioTech return new Program().MainAsync(args); } + public static void Write(EventsAndErrors cmd) + { + if (Log == null) return; + + if (cmd == null) + { + Log.Log("cmd is null!"); + return; + } + Log.Log(JsonConvert.SerializeObject(cmd)); + } + public async Task MainAsync(string[] args) { Log.Log("Starting Codex Discord Bot..."); diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 93e9b008..1879c6e7 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -39,6 +39,8 @@ namespace BiblioTech.Rewards [HttpPost] public async Task Give(EventsAndErrors cmd) { + Program.Write(cmd); + await Safe(() => Program.ChainActivityHandler.ProcessChainActivity(cmd.ActiveChainAddresses)); await Safe(() => Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors)); return "OK"; From 2b7b8161b3879e8445c598cff9a7d9826813484d Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 14:55:32 +0200 Subject: [PATCH 22/51] Fixes issue where api calls are missed due to initialized components --- Tools/BiblioTech/CallDispatcher.cs | 66 +++++++++++++++++++ Tools/BiblioTech/CommandHandler.cs | 1 + Tools/BiblioTech/Program.cs | 17 +---- .../Rewards/ChainActivityHandler.cs | 21 +++++- Tools/BiblioTech/Rewards/RewardController.cs | 25 +++---- 5 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 Tools/BiblioTech/CallDispatcher.cs diff --git a/Tools/BiblioTech/CallDispatcher.cs b/Tools/BiblioTech/CallDispatcher.cs new file mode 100644 index 00000000..b24ea830 --- /dev/null +++ b/Tools/BiblioTech/CallDispatcher.cs @@ -0,0 +1,66 @@ +using Logging; + +namespace BiblioTech +{ + public class CallDispatcher + { + private readonly ILog log; + private readonly object _lock = new object(); + private readonly List queue = new List(); + private readonly AutoResetEvent autoResetEvent = new AutoResetEvent(false); + + public CallDispatcher(ILog log) + { + this.log = log; + } + + public void Add(Action call) + { + lock (_lock) + { + queue.Add(call); + autoResetEvent.Set(); + if (queue.Count > 100) + { + log.Error("Queue overflow!"); + queue.Clear(); + } + } + } + + public void Start() + { + Task.Run(() => + { + while (true) + { + try + { + Worker(); + } + catch (Exception ex) + { + log.Error("Exception in CallDispatcher: " + ex); + } + } + }); + } + + private void Worker() + { + autoResetEvent.WaitOne(); + var tasks = Array.Empty(); + + lock (_lock) + { + tasks = queue.ToArray(); + queue.Clear(); + } + + foreach (var task in tasks) + { + task(); + } + } + } +} diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 822d4446..10e1e24b 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -84,6 +84,7 @@ namespace BiblioTech log.Error(json); throw; } + Program.Dispatcher.Start(); log.Log("Initialized."); } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 2e0427e6..0ce84bde 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -16,6 +16,7 @@ namespace BiblioTech private DiscordSocketClient client = null!; private CustomReplacement replacement = null!; + public static CallDispatcher Dispatcher { get; private set; } = null!; public static Configuration Config { get; private set; } = null!; public static UserRepo UserRepo { get; } = new UserRepo(); public static AdminChecker AdminChecker { get; private set; } = null!; @@ -26,8 +27,6 @@ namespace BiblioTech public static Task Main(string[] args) { - Log = new ConsoleLog(); - var uniformArgs = new ArgsUniform(PrintHelp, args); Config = uniformArgs.Parse(); @@ -36,6 +35,8 @@ namespace BiblioTech new ConsoleLog() ); + Dispatcher = new CallDispatcher(Log); + EnsurePath(Config.DataPath); EnsurePath(Config.UserDataPath); EnsurePath(Config.EndpointsPath); @@ -44,18 +45,6 @@ namespace BiblioTech return new Program().MainAsync(args); } - public static void Write(EventsAndErrors cmd) - { - if (Log == null) return; - - if (cmd == null) - { - Log.Log("cmd is null!"); - return; - } - Log.Log(JsonConvert.SerializeObject(cmd)); - } - public async Task MainAsync(string[] args) { Log.Log("Starting Codex Discord Bot..."); diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index 66fe5e63..64f08e38 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -18,10 +18,25 @@ namespace BiblioTech.Rewards public async Task ProcessChainActivity(ActiveChainAddresses activeChainAddresses) { - if (!activeChainAddresses.HasAny()) return; + if (!activeChainAddresses.HasAny()) + { + Log("Received empty activeChainAddresses."); + return; + } + var activeUserIds = ConvertToUserIds(activeChainAddresses); - if (!activeUserIds.HasAny()) return; - if (!HasChanged(activeUserIds)) return; + if (!activeUserIds.HasAny()) + { + Log("Empty userIds after lookup of addresses: " + activeChainAddresses); + return; + } + + if (!HasChanged(activeUserIds)) + { + Log("Active userIds has not changed: " + activeUserIds); + return; + } + await GiveAndRemoveRoles(activeUserIds); } diff --git a/Tools/BiblioTech/Rewards/RewardController.cs b/Tools/BiblioTech/Rewards/RewardController.cs index 1879c6e7..43a01ad5 100644 --- a/Tools/BiblioTech/Rewards/RewardController.cs +++ b/Tools/BiblioTech/Rewards/RewardController.cs @@ -39,23 +39,18 @@ namespace BiblioTech.Rewards [HttpPost] public async Task Give(EventsAndErrors cmd) { - Program.Write(cmd); + Program.Dispatcher.Add(() => + { + Program.ChainActivityHandler.ProcessChainActivity(cmd.ActiveChainAddresses).Wait(); + }); - await Safe(() => Program.ChainActivityHandler.ProcessChainActivity(cmd.ActiveChainAddresses)); - await Safe(() => Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors)); + Program.Dispatcher.Add(() => + { + Program.EventsSender.ProcessChainEvents(cmd.EventsOverview, cmd.Errors).Wait(); + }); + + await Task.CompletedTask; return "OK"; } - - private async Task Safe(Func action) - { - try - { - await action(); - } - catch (Exception ex) - { - Program.Log.Error("Exception: " + ex); - } - } } } From 9a58da26aa14065f53ce3b2e2a6bea81137a8fec Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 15:14:59 +0200 Subject: [PATCH 23/51] cleanup log messages --- .../Rewards/ChainActivityHandler.cs | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index 64f08e38..c190e7b7 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -18,24 +18,10 @@ namespace BiblioTech.Rewards public async Task ProcessChainActivity(ActiveChainAddresses activeChainAddresses) { - if (!activeChainAddresses.HasAny()) - { - Log("Received empty activeChainAddresses."); - return; - } - + if (!activeChainAddresses.HasAny()) return; var activeUserIds = ConvertToUserIds(activeChainAddresses); - if (!activeUserIds.HasAny()) - { - Log("Empty userIds after lookup of addresses: " + activeChainAddresses); - return; - } - - if (!HasChanged(activeUserIds)) - { - Log("Active userIds has not changed: " + activeUserIds); - return; - } + if (!activeUserIds.HasAny()) return; + if (!HasChanged(activeUserIds)) return; await GiveAndRemoveRoles(activeUserIds); } From cd17b0c887817cfb757de137bcf0902974b58681 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 15:26:57 +0200 Subject: [PATCH 24/51] Speeds up looking for users by ethAddress. Reduces load on discord server. --- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 13 ++++++++++--- Tools/BiblioTech/UserRepo.cs | 19 ++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs index 3fbc5132..9b38dcea 100644 --- a/Tools/BiblioTech/Rewards/RoleModifyContext.cs +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -10,6 +10,8 @@ namespace BiblioTech.Rewards { private Dictionary users = new(); private Dictionary roles = new(); + private DateTime lastLoad = DateTime.MinValue; + private readonly SocketGuild guild; private readonly UserRepo userRepo; private readonly ILog log; @@ -25,8 +27,14 @@ namespace BiblioTech.Rewards public async Task Initialize() { - this.users = await LoadAllUsers(guild); - this.roles = LoadAllRoles(guild); + var span = DateTime.UtcNow - lastLoad; + if (span > TimeSpan.FromMinutes(10)) + { + lastLoad = DateTime.UtcNow; + log.Log("Loading all users and roles..."); + this.users = await LoadAllUsers(guild); + this.roles = LoadAllRoles(guild); + } } public IGuildUser[] Users => users.Values.ToArray(); @@ -76,7 +84,6 @@ namespace BiblioTech.Rewards private async Task> LoadAllUsers(SocketGuild guild) { - log.Log("Loading all users.."); var result = new Dictionary(); var users = guild.GetUsersAsync(); await foreach (var ulist in users) diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index 07831dd6..701fdfe9 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -122,20 +122,17 @@ namespace BiblioTech { if (address == null) return null; - // If this becomes a performance problem, switch to in-memory cached list. - var files = Directory.GetFiles(Program.Config.UserDataPath); - foreach (var file in files) + var lower = address.Address.ToLowerInvariant(); + if (string.IsNullOrEmpty(lower)) return null; + + if (cache.Count == 0) LoadAllUserData(); + foreach (var item in cache.Values) { - try + if (item.CurrentAddress != null && + item.CurrentAddress.Address.ToLowerInvariant() == lower) { - var user = JsonConvert.DeserializeObject(File.ReadAllText(file))!; - if (user.CurrentAddress != null && - user.CurrentAddress.Address == address.Address) - { - return user; - } + return item; } - catch { } } return null; From 16b5ee4fd1a3c24d7e88d2ddcfda0b2b78d245ad Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 15:29:40 +0200 Subject: [PATCH 25/51] makes logreplace command automatically add lowercap versions of from-string --- Tools/BiblioTech/Rewards/CustomReplacement.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Tools/BiblioTech/Rewards/CustomReplacement.cs b/Tools/BiblioTech/Rewards/CustomReplacement.cs index dbd8eaf3..912aa789 100644 --- a/Tools/BiblioTech/Rewards/CustomReplacement.cs +++ b/Tools/BiblioTech/Rewards/CustomReplacement.cs @@ -28,14 +28,14 @@ namespace BiblioTech.Rewards public void Add(string from, string to) { - if (replacements.ContainsKey(from)) + AddOrUpdate(from, to); + + var lower = from.ToLowerInvariant(); + if (lower != from) { - replacements[from] = to; - } - else - { - replacements.Add(from, to); + AddOrUpdate(lower, to); } + Save(); } @@ -55,6 +55,18 @@ namespace BiblioTech.Rewards return result; } + private void AddOrUpdate(string from, string to) + { + if (replacements.ContainsKey(from)) + { + replacements[from] = to; + } + else + { + replacements.Add(from, to); + } + } + private void Save() { ReplaceJson[] replaces = replacements.Select(pair => From abdc7a0ab15d32676c00847b0057a749ecf16bd4 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 15:51:20 +0200 Subject: [PATCH 26/51] block and lock for loading of discord users --- Tools/BiblioTech/Rewards/RoleDriver.cs | 8 ++++---- Tools/BiblioTech/Rewards/RoleModifyContext.cs | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 41b0e44c..1cc9f3f2 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -25,7 +25,7 @@ namespace BiblioTech.Rewards public async Task RunRoleGiver(Func action) { - var context = await OpenRoleModifyContext(); + var context = OpenRoleModifyContext(); var mapper = new RoleMapper(context); await action(mapper); } @@ -37,7 +37,7 @@ namespace BiblioTech.Rewards public async Task IterateUsersWithRoles(Func onUserWithRole, Func whenDone, params ulong[] rolesToIterate) { - var context = await OpenRoleModifyContext(); + var context = OpenRoleModifyContext(); var mapper = new RoleMapper(context); foreach (var user in context.Users) { @@ -52,10 +52,10 @@ namespace BiblioTech.Rewards await whenDone(mapper); } - private async Task OpenRoleModifyContext() + private RoleModifyContext OpenRoleModifyContext() { var context = new RoleModifyContext(GetGuild(), userRepo, log, rewardsChannel); - await context.Initialize(); + context.Initialize(); return context; } diff --git a/Tools/BiblioTech/Rewards/RoleModifyContext.cs b/Tools/BiblioTech/Rewards/RoleModifyContext.cs index 9b38dcea..6fd4fb48 100644 --- a/Tools/BiblioTech/Rewards/RoleModifyContext.cs +++ b/Tools/BiblioTech/Rewards/RoleModifyContext.cs @@ -11,6 +11,7 @@ namespace BiblioTech.Rewards private Dictionary users = new(); private Dictionary roles = new(); private DateTime lastLoad = DateTime.MinValue; + private readonly object _lock = new object(); private readonly SocketGuild guild; private readonly UserRepo userRepo; @@ -25,15 +26,20 @@ namespace BiblioTech.Rewards this.rewardsChannel = rewardsChannel; } - public async Task Initialize() + public void Initialize() { - var span = DateTime.UtcNow - lastLoad; - if (span > TimeSpan.FromMinutes(10)) + lock (_lock) { - lastLoad = DateTime.UtcNow; - log.Log("Loading all users and roles..."); - this.users = await LoadAllUsers(guild); - this.roles = LoadAllRoles(guild); + var span = DateTime.UtcNow - lastLoad; + if (span > TimeSpan.FromMinutes(10)) + { + lastLoad = DateTime.UtcNow; + log.Log("Loading all users and roles..."); + var task = LoadAllUsers(guild); + task.Wait(); + this.users = task.Result; + this.roles = LoadAllRoles(guild); + } } } From e8ef65d641749a495b48cf909bdb419a6d8dde40 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 17 Apr 2025 16:06:34 +0200 Subject: [PATCH 27/51] Allows processing of empty chain addresses so roles can be cleaned up --- Framework/DiscordRewards/EventsAndErrors.cs | 5 ----- Tools/BiblioTech/Rewards/ChainActivityHandler.cs | 12 ------------ Tools/TestNetRewarder/Processor.cs | 3 +-- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/Framework/DiscordRewards/EventsAndErrors.cs b/Framework/DiscordRewards/EventsAndErrors.cs index 638299c6..7e18f07f 100644 --- a/Framework/DiscordRewards/EventsAndErrors.cs +++ b/Framework/DiscordRewards/EventsAndErrors.cs @@ -30,10 +30,5 @@ { return Hosts.Length > 0 || Clients.Length > 0; } - - public override string ToString() - { - return "Hosts:" + string.Join(",", Hosts) + "Clients:" + string.Join(",", Clients); - } } } diff --git a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs index c190e7b7..d35d9fe9 100644 --- a/Tools/BiblioTech/Rewards/ChainActivityHandler.cs +++ b/Tools/BiblioTech/Rewards/ChainActivityHandler.cs @@ -18,9 +18,7 @@ namespace BiblioTech.Rewards public async Task ProcessChainActivity(ActiveChainAddresses activeChainAddresses) { - if (!activeChainAddresses.HasAny()) return; var activeUserIds = ConvertToUserIds(activeChainAddresses); - if (!activeUserIds.HasAny()) return; if (!HasChanged(activeUserIds)) return; await GiveAndRemoveRoles(activeUserIds); @@ -131,16 +129,6 @@ namespace BiblioTech.Rewards public List Hosts { get; } public List Clients { get; } - - public bool HasAny() - { - return Hosts.Any() || Clients.Any(); - } - - public override string ToString() - { - return "Hosts:" + string.Join(",", Hosts) + "Clients:" + string.Join(",", Clients); - } } } } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index b018d377..eed26962 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -47,8 +47,7 @@ namespace TestNetRewarder var numberOfChainEvents = await ProcessEvents(timeRange); var duration = sw.Elapsed; - if (numberOfChainEvents == 0) return TimeSegmentResponse.Underload; - if (numberOfChainEvents > 10) return TimeSegmentResponse.Overload; + if (duration > TimeSpan.FromSeconds(1)) return TimeSegmentResponse.Underload; if (duration > TimeSpan.FromSeconds(3)) return TimeSegmentResponse.Overload; return TimeSegmentResponse.OK; } From 147fc42e9b121267c9e8f699ff34f49e1b3ed3b1 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 18 Apr 2025 10:02:52 +0200 Subject: [PATCH 28/51] Support for names of parameterized testfixtures --- Tests/DistTestCore/NameUtils.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/DistTestCore/NameUtils.cs b/Tests/DistTestCore/NameUtils.cs index 5e1f8a9f..55489db9 100644 --- a/Tests/DistTestCore/NameUtils.cs +++ b/Tests/DistTestCore/NameUtils.cs @@ -25,6 +25,7 @@ namespace DistTestCore var test = TestContext.CurrentContext.Test; if (test.ClassName!.Contains("AdhocContext")) return "none"; var className = test.ClassName!.Substring(test.ClassName.LastIndexOf('.') + 1); + className += FormatArguments(test); return className.Replace('.', '-'); } @@ -54,7 +55,7 @@ namespace DistTestCore private static string FormatArguments(TestContext.TestAdapter test) { - if (test.Arguments == null || !test.Arguments.Any()) return ""; + if (test.Arguments == null || test.Arguments.Length == 0) return ""; return $"[{string.Join(',', test.Arguments.Select(FormatArgument).ToArray())}]"; } @@ -69,6 +70,8 @@ namespace DistTestCore private static string ReplaceInvalidCharacters(string name) { return name + .Replace("codexstorage/nim-codex:", "") + .Replace("-dist-tests", "") .Replace(":", "_") .Replace("/", "_") .Replace("\\", "_"); @@ -84,9 +87,9 @@ namespace DistTestCore private static string GetFixtureName(string name, DateTime start) { - var className = GetRawFixtureName(); - if (!string.IsNullOrEmpty(name)) className = name; - return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{className.Replace('.', '-')}"; + var fixtureName = GetRawFixtureName(); + if (!string.IsNullOrEmpty(name)) fixtureName = name; + return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{fixtureName.Replace('.', '-')}"; } private static string Pad(int n) From df8a0eed8ac2a8788cbb583e58edade165355748 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 18 Apr 2025 15:47:44 +0200 Subject: [PATCH 29/51] Uses label of docker image to find compatible contracts images. --- Framework/Core/PluginManager.cs | 13 ++- Framework/Core/ProjectPlugin.cs | 6 + .../CodexContractsContainerRecipe.cs | 10 +- .../CodexContractsPlugin.cs | 17 ++- .../CodexContractsStarter.cs | 6 +- .../CoreInterfaceExtensions.cs | 5 + .../CodexContractsPlugin/VersionRegistry.cs | 103 ++++++++++++++++++ .../CodexDiscordBotPlugin.cs | 4 + .../CodexPlugin/CodexContainerRecipe.cs | 17 +-- .../CodexPlugin/CodexDockerImage.cs | 20 ++++ ProjectPlugins/CodexPlugin/CodexPlugin.cs | 12 +- .../CodexPlugin/ContainerCodexStarter.cs | 5 +- .../CodexPlugin/LocalCodexBuilder.cs | 4 +- .../DeployAndRunPlugin/DeployAndRunPlugin.cs | 4 + ProjectPlugins/GethPlugin/GethPlugin.cs | 4 + ProjectPlugins/MetricsPlugin/MetricsPlugin.cs | 4 + Tests/CodexContinuousTests/NodeRunner.cs | 2 +- 17 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs create mode 100644 ProjectPlugins/CodexPlugin/CodexDockerImage.cs diff --git a/Framework/Core/PluginManager.cs b/Framework/Core/PluginManager.cs index 27b08fe3..c8bac789 100644 --- a/Framework/Core/PluginManager.cs +++ b/Framework/Core/PluginManager.cs @@ -1,6 +1,6 @@ namespace Core { - internal class PluginManager + internal class PluginManager : IPluginAccess { private readonly List pairs = new List(); @@ -14,6 +14,7 @@ ApplyLogPrefix(plugin, tools); } + AwakePlugins(); } internal void AnnouncePlugins() @@ -43,7 +44,7 @@ } } - internal T GetPlugin() where T : IProjectPlugin + public T GetPlugin() where T : IProjectPlugin { return (T)pairs.Single(p => p.Plugin.GetType() == typeof(T)).Plugin; } @@ -55,6 +56,14 @@ return plugin; } + private void AwakePlugins() + { + foreach (var p in pairs) + { + p.Plugin.Awake(this); + } + } + private void ApplyLogPrefix(IProjectPlugin plugin, PluginTools tools) { if (plugin is IHasLogPrefix hasLogPrefix) diff --git a/Framework/Core/ProjectPlugin.cs b/Framework/Core/ProjectPlugin.cs index 72f3c16a..a777ef80 100644 --- a/Framework/Core/ProjectPlugin.cs +++ b/Framework/Core/ProjectPlugin.cs @@ -4,6 +4,7 @@ namespace Core { public interface IProjectPlugin { + void Awake(IPluginAccess access); void Announce(); void Decommission(); } @@ -18,6 +19,11 @@ namespace Core void AddMetadata(IAddMetadata metadata); } + public interface IPluginAccess + { + T GetPlugin() where T : IProjectPlugin; + } + public static class ProjectPlugin { /// diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs index 66adf592..e09a0ee7 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs @@ -6,13 +6,17 @@ namespace CodexContractsPlugin { public class CodexContractsContainerRecipe : ContainerRecipeFactory { - public static string DockerImage { get; } = "codexstorage/codex-contracts-eth:latest-dist-tests"; - public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json"; public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json"; + private readonly VersionRegistry versionRegistry; public override string AppName => "codex-contracts"; - public override string Image => DockerImage; + public override string Image => versionRegistry.GetContractsDockerImage(); + + public CodexContractsContainerRecipe(VersionRegistry versionRegistry) + { + this.versionRegistry = versionRegistry; + } protected override void Initialize(StartupConfig startupConfig) { diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs index 416b17aa..6e02280d 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs @@ -7,15 +7,23 @@ namespace CodexContractsPlugin { private readonly IPluginTools tools; private readonly CodexContractsStarter starter; + private readonly VersionRegistry versionRegistry; + private readonly CodexContractsContainerRecipe recipe; public CodexContractsPlugin(IPluginTools tools) { this.tools = tools; - starter = new CodexContractsStarter(tools); + versionRegistry = new VersionRegistry(tools.GetLog()); + recipe = new CodexContractsContainerRecipe(versionRegistry); + starter = new CodexContractsStarter(tools, recipe); } public string LogPrefix => "(CodexContracts) "; + public void Awake(IPluginAccess access) + { + } + public void Announce() { tools.GetLog().Log($"Loaded Codex-Marketplace SmartContracts"); @@ -23,7 +31,7 @@ namespace CodexContractsPlugin public void AddMetadata(IAddMetadata metadata) { - metadata.Add("codexcontractsid", CodexContractsContainerRecipe.DockerImage); + metadata.Add("codexcontractsid", recipe.Image); } public void Decommission() @@ -40,5 +48,10 @@ namespace CodexContractsPlugin deployment = SerializeGate.Gate(deployment); return starter.Wrap(gethNode, deployment); } + + public void SetCodexDockerImageProvider(ICodexDockerImageProvider provider) + { + versionRegistry.SetProvider(provider); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index be74a3b2..5ecc22a9 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -12,10 +12,12 @@ namespace CodexContractsPlugin public class CodexContractsStarter { private readonly IPluginTools tools; + private readonly CodexContractsContainerRecipe recipe; - public CodexContractsStarter(IPluginTools tools) + public CodexContractsStarter(IPluginTools tools, CodexContractsContainerRecipe recipe) { this.tools = tools; + this.recipe = recipe; } public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode) @@ -26,7 +28,7 @@ namespace CodexContractsPlugin var startupConfig = CreateStartupConfig(gethNode); startupConfig.NameOverride = "codex-contracts"; - var containers = workflow.Start(1, new CodexContractsContainerRecipe(), startupConfig).WaitForOnline(); + var containers = workflow.Start(1, recipe, 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/CodexContractsPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs index 7d68a860..ea123bc9 100644 --- a/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs @@ -21,6 +21,11 @@ namespace CodexContractsPlugin return WrapCodexContractsDeployment(ci, gethNode, deployment); } + public static void SetCodexDockerImageProvider(this CoreInterface ci, ICodexDockerImageProvider provider) + { + Plugin(ci).SetCodexDockerImageProvider(provider); + } + private static CodexContractsPlugin Plugin(CoreInterface ci) { return ci.GetPlugin(); diff --git a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs new file mode 100644 index 00000000..9ed4e8b4 --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs @@ -0,0 +1,103 @@ +using System.Diagnostics; +using Logging; + +namespace CodexContractsPlugin +{ + public interface ICodexDockerImageProvider + { + string GetCodexDockerImage(); + } + + public class VersionRegistry + { + private ICodexDockerImageProvider provider = new ExceptionProvider(); + private readonly Dictionary cache = new Dictionary(); + private readonly ILog log; + + public VersionRegistry(ILog log) + { + this.log = log; + } + + public void SetProvider(ICodexDockerImageProvider provider) + { + this.provider = provider; + } + + public string GetContractsDockerImage() + { + try + { + var codexImage = provider.GetCodexDockerImage(); + return GetContractsDockerImage(codexImage); + } + catch (Exception exc) + { + throw new Exception("Failed to get contracts docker image.", exc); + } + } + + private string GetContractsDockerImage(string codexImage) + { + if (cache.TryGetValue(codexImage, out string? value)) + { + return value; + } + var result = GetContractsImage(codexImage); + cache.Add(codexImage, result); + return result; + } + + private string GetContractsImage(string codexImage) + { + var inspectResult = InspectCodexImage(codexImage); + var image = ParseCodexContractsImageName(inspectResult); + log.Log($"From codex docker image '{codexImage}', determined codex-contracts docker image: '{image}'"); + return image; + } + + private string InspectCodexImage(string img) + { + Execute("docker", $"pull {img}"); + return Execute("docker", $"inspect {img}"); + } + + private string ParseCodexContractsImageName(string inspectResult) + { + // It is a nice json structure. But we only need this one line. + // "storage.codex.nim-codex.blockchain-image": "codexstorage/codex-contracts-eth:sha-0bf1385-dist-tests" + var lines = inspectResult.Split('\n', StringSplitOptions.RemoveEmptyEntries); + var line = lines.Single(l => l.Contains("storage.codex.nim-codex.blockchain-image")); + var tokens = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + return tokens.Last().Replace("\"", "").Trim(); + } + + private string Execute(string cmd, string args) + { + var startInfo = new ProcessStartInfo( + fileName: cmd, + arguments: args + ); + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + + var process = Process.Start(startInfo); + if (process == null || process.HasExited) + { + throw new Exception("Failed to start: " + cmd + args); + } + + process.WaitForExit(); + return process.StandardOutput.ReadToEnd(); + } + } + + internal class ExceptionProvider : ICodexDockerImageProvider + { + public string GetCodexDockerImage() + { + throw new InvalidOperationException("CodexContractsPlugin has not yet received a CodexDockerImageProvider " + + "and so cannot select a compatible contracts docker image."); + } + } +} diff --git a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs index 556ef152..e1354806 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/CodexDiscordBotPlugin.cs @@ -17,6 +17,10 @@ namespace CodexDiscordBotPlugin public string LogPrefix => "(DiscordBot) "; + public void Awake(IPluginAccess access) + { + } + public void Announce() { tools.GetLog().Log($"Codex DiscordBot (BiblioTech) loaded."); diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 9dfcf2cf..6a0b76e7 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,6 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; @@ -16,11 +15,15 @@ namespace CodexPlugin // Used by tests for time-constraint assertions. public static readonly TimeSpan MaxUploadTimePerMegabyte = TimeSpan.FromSeconds(2.0); public static readonly TimeSpan MaxDownloadTimePerMegabyte = TimeSpan.FromSeconds(2.0); + private readonly CodexDockerImage codexDockerImage; public override string AppName => "codex"; - public override string Image => GetDockerImage(); + public override string Image => codexDockerImage.GetCodexDockerImage(); - public static string DockerImageOverride { get; set; } = string.Empty; + public CodexContainerRecipe(CodexDockerImage codexDockerImage) + { + this.codexDockerImage = codexDockerImage; + } protected override void Initialize(StartupConfig startupConfig) { @@ -163,13 +166,5 @@ namespace CodexPlugin // Default Codex quota: 8 Gb, using +20% to be safe. return 8.GB().Multiply(1.2); } - - private string GetDockerImage() - { - var image = Environment.GetEnvironmentVariable("CODEXDOCKERIMAGE"); - if (!string.IsNullOrEmpty(image)) return image; - if (!string.IsNullOrEmpty(DockerImageOverride)) return DockerImageOverride; - return DefaultDockerImage; - } } } diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs new file mode 100644 index 00000000..f86470db --- /dev/null +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -0,0 +1,20 @@ +using CodexContractsPlugin; + +namespace CodexPlugin +{ + public class CodexDockerImage : ICodexDockerImageProvider + { + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-c9a5ef8-dist-tests"; + //"codexstorage/nim-codex:latest-dist-tests"; + + public static string Override { get; set; } = string.Empty; + + public string GetCodexDockerImage() + { + var image = Environment.GetEnvironmentVariable("CODEXDOCKERIMAGE"); + if (!string.IsNullOrEmpty(image)) return image; + if (!string.IsNullOrEmpty(Override)) return Override; + return DefaultDockerImage; + } + } +} diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index 02fbeaa6..277b6101 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -13,12 +13,15 @@ namespace CodexPlugin private readonly CodexLogLevel defaultLogLevel = CodexLogLevel.Trace; private readonly CodexHooksFactory hooksFactory = new CodexHooksFactory(); private readonly ProcessControlMap processControlMap = new ProcessControlMap(); + private readonly CodexDockerImage codexDockerImage = new CodexDockerImage(); + private readonly CodexContainerRecipe recipe; private readonly CodexWrapper codexWrapper; public CodexPlugin(IPluginTools tools) { this.tools = tools; + recipe = new CodexContainerRecipe(codexDockerImage); codexStarter = CreateCodexStarter(); codexWrapper = new CodexWrapper(tools, processControlMap, hooksFactory); } @@ -28,7 +31,7 @@ namespace CodexPlugin if (UseContainers) { Log("Using Containerized Codex instances"); - return new ContainerCodexStarter(tools, processControlMap); + return new ContainerCodexStarter(tools, recipe, processControlMap); } Log("Using Binary Codex instances"); @@ -37,8 +40,15 @@ namespace CodexPlugin public string LogPrefix => "(Codex) "; + public void Awake(IPluginAccess access) + { + access.GetPlugin().SetCodexDockerImageProvider(codexDockerImage); + } + public void Announce() { + // give codex docker image to contracts plugin. + Log($"Loaded with Codex ID: '{codexWrapper.GetCodexId()}' - Revision: {codexWrapper.GetCodexRevision()}"); } diff --git a/ProjectPlugins/CodexPlugin/ContainerCodexStarter.cs b/ProjectPlugins/CodexPlugin/ContainerCodexStarter.cs index 0627bc1f..4103fb8c 100644 --- a/ProjectPlugins/CodexPlugin/ContainerCodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/ContainerCodexStarter.cs @@ -10,12 +10,13 @@ namespace CodexPlugin { private readonly IPluginTools pluginTools; private readonly ProcessControlMap processControlMap; - private readonly CodexContainerRecipe recipe = new CodexContainerRecipe(); + private readonly CodexContainerRecipe recipe; private readonly ApiChecker apiChecker; - public ContainerCodexStarter(IPluginTools pluginTools, ProcessControlMap processControlMap) + public ContainerCodexStarter(IPluginTools pluginTools, CodexContainerRecipe recipe, ProcessControlMap processControlMap) { this.pluginTools = pluginTools; + this.recipe = recipe; this.processControlMap = processControlMap; apiChecker = new ApiChecker(pluginTools); } diff --git a/ProjectPlugins/CodexPlugin/LocalCodexBuilder.cs b/ProjectPlugins/CodexPlugin/LocalCodexBuilder.cs index 9f1b76c9..90bc1cb1 100644 --- a/ProjectPlugins/CodexPlugin/LocalCodexBuilder.cs +++ b/ProjectPlugins/CodexPlugin/LocalCodexBuilder.cs @@ -40,7 +40,7 @@ namespace CodexNetDeployer Log($"Codex docker image will be built in path '{repoPath}'."); Log("Please note this can take several minutes. If you're not trying to use a Codex image with local code changes,"); Log("Consider using the default test image or consider setting the 'CODEXDOCKERIMAGE' environment variable to use an already built image."); - CodexContainerRecipe.DockerImageOverride = $"Using docker image locally built in path '{repoPath}'."; + CodexDockerImage.Override = $"Using docker image locally built in path '{repoPath}'."; } public void Build() @@ -62,7 +62,7 @@ namespace CodexNetDeployer Docker("push", customImage); - CodexContainerRecipe.DockerImageOverride = customImage; + CodexDockerImage.Override = customImage; Log("Image pushed. Good to go!"); } diff --git a/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs b/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs index c290c0d5..e1c60c24 100644 --- a/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs +++ b/ProjectPlugins/DeployAndRunPlugin/DeployAndRunPlugin.cs @@ -13,6 +13,10 @@ namespace DeployAndRunPlugin this.tools = tools; } + public void Awake(IPluginAccess access) + { + } + public void Announce() { tools.GetLog().Log("Deploy-and-Run plugin loaded."); diff --git a/ProjectPlugins/GethPlugin/GethPlugin.cs b/ProjectPlugins/GethPlugin/GethPlugin.cs index 07249eca..76f92314 100644 --- a/ProjectPlugins/GethPlugin/GethPlugin.cs +++ b/ProjectPlugins/GethPlugin/GethPlugin.cs @@ -16,6 +16,10 @@ namespace GethPlugin public string LogPrefix => "(Geth) "; + public void Awake(IPluginAccess access) + { + } + public void Announce() { tools.GetLog().Log($"Loaded Geth plugin."); diff --git a/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs b/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs index a796ba06..75e27eca 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsPlugin.cs @@ -18,6 +18,10 @@ namespace MetricsPlugin public string LogPrefix => "(Metrics) "; + public void Awake(IPluginAccess access) + { + } + public void Announce() { tools.GetLog().Log($"Prometheus plugin loaded with '{starter.GetPrometheusId()}'."); diff --git a/Tests/CodexContinuousTests/NodeRunner.cs b/Tests/CodexContinuousTests/NodeRunner.cs index f2b3c64f..ae18f7e9 100644 --- a/Tests/CodexContinuousTests/NodeRunner.cs +++ b/Tests/CodexContinuousTests/NodeRunner.cs @@ -33,7 +33,7 @@ namespace ContinuousTests var entryPoint = CreateEntryPoint(); // We have to be sure that the transient node we start is using the same image as whatever's already in the deployed network. // Therefore, we use the image of the bootstrap node. - CodexContainerRecipe.DockerImageOverride = bootstrapNode.GetImageName(); + CodexDockerImage.Override = bootstrapNode.GetImageName(); try { From 9f10c9e7bb7aa97f72cae41dd5bbc1e27ed1a404 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 09:41:56 +0200 Subject: [PATCH 30/51] Updates marketplace. Fixes locking issue in codex-disttest base --- .../CodexContractsPlugin/Marketplace/Marketplace.cs | 2 +- Tests/ExperimentalTests/CodexDistTest.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs index e46bbdd9..ae309f1f 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 = "0x60c060405234801561001057600080fd5b50604051614f0a380380614f0a83398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516146e5610825600039600081816104bf01528181610f7001528181612019015281816125d30152818161268301528181612812015281816128c20152612cec01526000818161355601526137e601526146e56000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138b3565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138b3565b6105c1565b6040516101f091906138e2565b61022c610227366004613911565b6106e4565b005b61022c61023c366004613978565b610877565b61022c61024f3660046139bb565b610948565b61022c610262366004613978565b610dfa565b61028a6102753660046138b3565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138b3565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138b3565b611046565b6102f16102ec3660046138b3565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138b3565b611072565b61031e6110d1565b6040516101f091906139fb565b6103576103393660046138b3565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613ad9565b61022c6103923660046138b3565b61126f565b61031e61127c565b61022c6103ad366004613b61565b61129b565b61022c6103c03660046138b3565b6117e1565b6103d86103d33660046138b3565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138b3565b61186f565b6040516101f09190613c90565b61041b6104163660046138b3565b611b51565b6040516101f09190613ccb565b6103d86104363660046138b3565b611c1f565b61022c610449366004613cdf565b611c32565b6103d861045c366004613978565b6120b3565b61022c61046f366004613d04565b61211f565b6101dc6104823660046138b3565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138b3565b612298565b6040516101f09190613d32565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b90506000816004811115610505576105056138cc565b148061052257506001816004811115610520576105206138cc565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b6002816004811115610562576105626138cc565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124ae565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d6138cc565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff166004811115610683576106836138cc565b14806106a457506000815460ff1660048111156106a2576106a26138cc565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff166006811115610709576107096138cc565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c6138cc565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e76138cc565b03610801576107fc82600101548787876124c4565b61086f565b6005816006811115610815576108156138cc565b0361082a576107fc826001015487878761270d565b600381600681111561083e5761083e6138cc565b0361084d576107fc3387612956565b6001816006811115610861576108616138cc565b0361086f5761086f86612978565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bd0565b60008181526020819052604090209091506108de9033612c15565b50600154600082815260208190526040902060ff909116906108ff90612c2a565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bd0565b60008181526020819052604090209091506109fe9033612c34565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e6138cc565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac36138cc565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b78838761211f565b60028101805460019190600090610b999084906001600160401b0316613d5b565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c56565b816001016000828254610bf19190613d7a565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612c85565b90506006610c7786611b51565b6006811115610c8857610c886138cc565b03610cbb57600b54606490610ca09060ff1683613d8d565b610caa9190613dba565b610cb49082613d7a565b9150610cbf565b8091505b610cc93383612ca4565b8160136000016000828254610cde9190613dce565b9091555050600384018190556004840154610d02906001600160a01b031686612d78565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d826138cc565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e166138cc565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612d9a565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612c85565b610f049190613d8d565b610f0e9190613dba565b600b54909150600090606490610f2e906301000000900460ff1684613d8d565b610f389190613dba565b90508060136001016000828254610f4f9190613dce565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613de1565b610ff157610ff1613e03565b818460030160008282546110059190613d7a565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f86612978565b600061105982611054612fb1565b612fbc565b92915050565b60006110598261106d612fb1565b612fd0565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e19565b60018301546110bf9161ffff1690613d8d565b6110c99190613dba565b949350505050565b336000908152600a602052604090206060906110f3906110f090613062565b90565b905090565b61110061380b565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e33565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e33565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f090613062565b60006112ae6112a983613fc4565b61306f565b9050336112be60208401846140cd565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611346610140830161012084016140ea565b6001600160401b0316158061138d575061136660e0830160c084016140ea565b6001600160401b0316611381610140840161012085016140ea565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a08301608084016140ea565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a08301608084016140ea565b6001600160401b0316611440610100840160e085016140ea565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c084016140ea565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b610100830183614107565b6115a59080614127565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c085016140ea565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142c7565b5061166a905060e0830160c084016140ea565b6116749042613d5b565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c1610140830161012084016140ea565b6116cb9042613d5b565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c90611726908401846140cd565b8261309f565b600061173f61173a84613fc4565b6130c1565b600083815260116020526040812060010182905560138054929350839290919061176a908490613dce565b9091555061177a90503382612ca4565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b03169061447b565b60405180910390a1505050565b806000808281526012602052604090205460ff166006811115611806576118066138cc565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fb1565b6130fd565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f260405180604001604052806138746040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff166006811115611917576119176138cc565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c660405180604001604052806138746040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e33565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e33565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c6138cc565b03611bab575060049392505050565b6002816004811115611bbf57611bbf6138cc565b03611bce575060059392505050565b6003816004811115611be257611be26138cc565b03611bf1575060029392505050565b6004816004811115611c0557611c056138cc565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fb1565b6131b5565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd6138cc565b14158015611ced57506004816004811115611cea57611cea6138cc565b14155b8015611d0b57506003816004811115611d0857611d086138cc565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d946138cc565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c56565b6002830154611e1491906001600160401b0316613d8d565b826001016000828254611e279190613dce565b90915550611fbf9050565b6004816004811115611e4657611e466138cc565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e33565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e33565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130c1565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b0316876131ec565b600182015460148054829190600090611fee908490613dce565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613de1565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bd0565b90506120cc8161320e565b80156120f55750600154600082815260208190526040902060ff909116906120f390612c2a565b105b8015612116575060008181526020819052604090206121149083612c34565b155b95945050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661216657604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121ba576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181015460009081526010602052604080822081516003808252608082019093529092918160200160208202803683370190505090506122026121fd87611046565b61323b565b8160008151811061221557612215614528565b6020908102919091010152600682015461222e9061324c565b8160018151811061224157612241614528565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b03168160028151811061228157612281614528565b60200260200101818152505061086f868683613258565b61230d6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661234457604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c082015292810192909252825180840184526005820180549394929392850192829082906123f890613e33565b80601f016020809104026020016040519081016040528092919081815260200182805461242490613e33565b80156124715780601f1061244657610100808354040283529160200191612471565b820191906000526020600020905b81548152906001019060200180831161245457829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008183106124bd57816105ba565b5090919050565b60008481526010602052604090205484906001600160a01b03166124fb57604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff1916600317825588855260129093529220815461253d906001600160a01b0316896131ec565b6004810154612555906001600160a01b031688612956565b6002810154600090612571908a906001600160401b0316612c56565b60038301549091506125838183613dce565b60148054600090612595908490613dce565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561261c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126409190613de1565b61265d57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126f09190613de1565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661274457604051635eeb253d60e11b815260040160405180910390fd5b6000848152601260205260409020600481015461276a906001600160a01b031686612956565b60028101546000906127b09088906001600160401b03166127ab826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f6565b60038301549091506127c28183613dce565b601480546000906127d4908490613dce565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561285b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061287f9190613de1565b61289c57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561290b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061292f9190613de1565b61294c57604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134d5565b600081815260126020908152604080832060018101548085526011909352922060028301546129b19083906001600160401b0316612c56565b8160010160008282546129c49190613dce565b909155505060048301546129e1906001600160a01b031685612956565b60008481526020819052604081209081816129fc8282613881565b5050845460ff1916600617855550506002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a649084906001600160401b031661453e565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b17916001600160401b03908116911661453e565b60048301546001600160401b039182169250600160c01b90041681118015612b5457506001835460ff166004811115612b5257612b526138cc565b145b1561086f57825460ff19166004178355612b6f60014261453e565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612bf79291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134e1565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f6565b600081608001516001600160401b031682604001516110599190613d8d565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5b9190613de1565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a602052604090206109439082613530565b6000612da58261353c565b6001600160401b03169050428110612de9576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e0690600160401b90046001600160401b031682613dce565b4210612e3e576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612e9a576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612ea483836131b5565b612eda576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f37576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612f8891859116613d5b565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f34261354f565b60006105ba612fcb8484612fd0565b61357b565b600080612fdf6101004361455d565b6002549091506000906101009061300e9071010000000000000000000000000000000000900460ff1686614571565b6130189190614593565b6001600160401b0316905060006130316101008761455d565b90506000610100826130438587613dce565b61304d9190613dce565b613057919061455d565b979650505050505050565b606060006105ba836135d5565b6000816040516020016130829190613d32565b604051602081830303815290604052805190602001209050919050565b6001600160a01b03821660009081526009602052604090206109439082613530565b60006130d08260200151613631565b602083015160a08101516060909101516130ea9190614571565b6001600160401b03166110599190613d8d565b600080600061310b85611b51565b60008681526005602052604081205491925090613130906001600160401b031661354f565b90506001826006811115613146576131466138cc565b14158061315a57506131588582613650565b155b1561316d576000809350935050506131ae565b6131778686612fd0565b925060006131848461357b565b9050600061319188611072565b90508015806131a757506131a5818361455d565b155b9550505050505b9250929050565b60008060006131c485856130fd565b90925090508180156121165750600254600160801b900460ff90811691161015949350505050565b6001600160a01b038216600090815260096020526040902061094390826134d5565b60008060008381526012602052604090205460ff166006811115613234576132346138cc565b1492915050565b600060ff198216816110c982613666565b6000806105ba83613666565b60008381526007602052604081209061326f612fb1565b6001600160401b0316815260208101919091526040016000205460ff16156132c3576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d9161330d9186918691016145c1565b602060405180830381865afa15801561332a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061334e9190613de1565b613384576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083815260076020526040812060019161339d612fb1565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613449576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b890613631565b6134c2858561453e565b6001600160401b03166121169190613d8d565b60006105ba83836136d8565b600081815260018301602052604081205461352857508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134e1565b600061105961354a836137d2565b6137df565b60006110597f00000000000000000000000000000000000000000000000000000000000000008361466b565b60008060ff831661358d600143613d7a565b6135979190613d7a565b40905060008190036135ab576135ab613e03565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561362557602002820191906000526020600020905b815481526020019060010190808311613611575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613d8d565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136a6908290613d8d565b83901b7fff0000000000000000000000000000000000000000000000000000000000000016919091179060010161368c565b600081815260018301602052604081205480156137c15760006136fc600183613d7a565b855490915060009061371090600190613d7a565b905081811461377557600086600001828154811061373057613730614528565b906000526020600020015490508087600001848154811061375357613753614528565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378657613786614699565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b6000611059826001613d5b565b60006110597f000000000000000000000000000000000000000000000000000000000000000083614571565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b508054600082559060005260206000209081019061127991905b808211156138af576000815560010161389b565b5090565b6000602082840312156138c557600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106138f6576138f66138cc565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561392657600080fd5b833592506020840135613938816138fc565b91506040840135613948816138fc565b809150509250925092565b6001600160401b038116811461127957600080fd5b803561397381613953565b919050565b6000806040838503121561398b57600080fd5b82359150602083013561399d81613953565b809150509250929050565b600061010082840312156106de57600080fd5b600080600061014084860312156139d157600080fd5b8335925060208401356139e381613953565b91506139f285604086016139a8565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a33578351835260209384019390920191600101613a15565b509095945050505050565b6000815180845260005b81811015613a6457602081850181015186830182015201613a48565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a3e565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b2d610100840182613a84565b90506040840151613b4460c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613b7357600080fd5b81356001600160401b03811115613b8957600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613bb16040850182613a3e565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c5d610160850182613b9c565b90506060830151613c7a6101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613cac6060840182613bc5565b90506001600160401b0360208501511660408401528091505092915050565b60208101600783106138f6576138f66138cc565b60008060408385031215613cf257600080fd5b82359150602083013561399d816138fc565b6000806101208385031215613d1857600080fd5b82359150613d2984602085016139a8565b90509250929050565b6020815260006105ba6020830184613bc5565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d45565b8181038181111561105957611059613d45565b808202811582820484141761105957611059613d45565b634e487b7160e01b600052601260045260246000fd5b600082613dc957613dc9613da4565b500490565b8082018082111561105957611059613d45565b600060208284031215613df357600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d45565b600181811c90821680613e4757607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613e9f57613e9f613e67565b60405290565b60405160a081016001600160401b0381118282101715613e9f57613e9f613e67565b60405160e081016001600160401b0381118282101715613e9f57613e9f613e67565b604051601f8201601f191681016001600160401b0381118282101715613f1157613f11613e67565b604052919050565b600060408284031215613f2b57600080fd5b613f33613e7d565b905081356001600160401b03811115613f4b57600080fd5b8201601f81018413613f5c57600080fd5b80356001600160401b03811115613f7557613f75613e67565b613f88601f8201601f1916602001613ee9565b818152856020838501011115613f9d57600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b6000813603610160811215613fd857600080fd5b613fe0613ea5565b8335613feb816138fc565b815260e0601f1983011215613fff57600080fd5b614007613ec7565b6020858101358252604080870135918301919091526060860135908201529150608084013561403581613953565b606083015260a084013561404881613953565b608083015260c084013561405b81613953565b60a083015260e084013561406e81613953565b60c08301526020810191909152610100830135906001600160401b0382111561409657600080fd5b6140a236838601613f19565b60408201526140b46101208501613968565b6060820152610140939093013560808401525090919050565b6000602082840312156140df57600080fd5b81356105ba816138fc565b6000602082840312156140fc57600080fd5b81356105ba81613953565b60008235603e1983360301811261411d57600080fd5b9190910192915050565b6000808335601e1984360301811261413e57600080fd5b8301803591506001600160401b0382111561415857600080fd5b6020019150368190038213156131ae57600080fd5b6000813561105981613953565b601f82111561094357806000526020600020601f840160051c810160208510156141a15750805b601f840160051c820191505b818110156141c157600081556001016141ad565b5050505050565b8135601e198336030181126141dc57600080fd5b820180356001600160401b03811180156141f557600080fd5b81360360208401131561420757600080fd5b6000905061421f826142198654613e33565b8661417a565b80601f8311600181146142545782841561423c5750848201602001355b600019600386901b1c1916600185901b1786556142b3565b600086815260209020601f19851690845b8281101561428757602085890181013583559485019460019092019101614265565b50858210156142a75760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b81356142d2816138fc565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039190911617815560208201356001820155604082013560028201556060820135600382015560048101608083013561432a81613953565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561435381613953565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143c761439260c0850161416d565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b61441f6143d660e0850161416d565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061443a614431610100840184614107565b600583016141c8565b61446b61444a610120840161416d565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144b381613953565b6001600160401b03811660808401525060808401356144d181613953565b6001600160401b03811660a08401525060a08401356144ef81613953565b6001600160401b03811660c08401525061450b60c08501613968565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d45565b60008261456c5761456c613da4565b500690565b6001600160401b0381811683821602908116908181146137cb576137cb613d45565b60006001600160401b038316806145ac576145ac613da4565b806001600160401b0384160691505092915050565b8235815260208084013590820152600061012082016145f0604084016040870180358252602090810135910152565b61460a608084016080870180358252602090810135910152565b61462460c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b8181101561465f578351835260209384019390920191600101614641565b50909695505050505050565b60006001600160401b0383168061468457614684613da4565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206e940e8cb5c54ce6f6606e3b8f89e55d51c501d4017adbc3ac7b30357327730d64736f6c634300081c0033"; + public static string BYTECODE = "0x60c060405234801561001057600080fd5b50604051614f04380380614f0483398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516146df610825600039600081816104bf01528181610f7001528181612019015281816125cd0152818161267d0152818161280c015281816128bc0152612ce601526000818161355001526137e001526146df6000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138ad565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138ad565b6105c1565b6040516101f091906138dc565b61022c61022736600461390b565b6106e4565b005b61022c61023c366004613972565b610877565b61022c61024f3660046139b5565b610948565b61022c610262366004613972565b610dfa565b61028a6102753660046138ad565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138ad565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138ad565b611046565b6102f16102ec3660046138ad565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138ad565b611072565b61031e6110d1565b6040516101f091906139f5565b6103576103393660046138ad565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613ad3565b61022c6103923660046138ad565b61126f565b61031e61127c565b61022c6103ad366004613b5b565b61129b565b61022c6103c03660046138ad565b6117e1565b6103d86103d33660046138ad565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138ad565b61186f565b6040516101f09190613c8a565b61041b6104163660046138ad565b611b51565b6040516101f09190613cc5565b6103d86104363660046138ad565b611c1f565b61022c610449366004613cd9565b611c32565b6103d861045c366004613972565b6120b3565b61022c61046f366004613cfe565b61211f565b6101dc6104823660046138ad565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138ad565b612298565b6040516101f09190613d2c565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b90506000816004811115610505576105056138c6565b148061052257506001816004811115610520576105206138c6565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b6002816004811115610562576105626138c6565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124ae565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d6138c6565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff166004811115610683576106836138c6565b14806106a457506000815460ff1660048111156106a2576106a26138c6565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff166006811115610709576107096138c6565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c6138c6565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e76138c6565b03610801576107fc82600101548787876124be565b61086f565b6005816006811115610815576108156138c6565b0361082a576107fc8260010154878787612707565b600381600681111561083e5761083e6138c6565b0361084d576107fc3387612950565b6001816006811115610861576108616138c6565b0361086f5761086f86612972565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bca565b60008181526020819052604090209091506108de9033612c0f565b50600154600082815260208190526040902060ff909116906108ff90612c24565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bca565b60008181526020819052604090209091506109fe9033612c2e565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e6138c6565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac36138c6565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b78838761211f565b60028101805460019190600090610b999084906001600160401b0316613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c50565b816001016000828254610bf19190613d74565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612c7f565b90506006610c7786611b51565b6006811115610c8857610c886138c6565b03610cbb57600b54606490610ca09060ff1683613d87565b610caa9190613db4565b610cb49082613d74565b9150610cbf565b8091505b610cc93383612c9e565b8160136000016000828254610cde9190613dc8565b9091555050600384018190556004840154610d02906001600160a01b031686612d72565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d826138c6565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e166138c6565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612d94565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612c7f565b610f049190613d87565b610f0e9190613db4565b600b54909150600090606490610f2e906301000000900460ff1684613d87565b610f389190613db4565b90508060136001016000828254610f4f9190613dc8565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613ddb565b610ff157610ff1613dfd565b818460030160008282546110059190613d74565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f86612972565b600061105982611054612fab565b612fb6565b92915050565b60006110598261106d612fab565b612fca565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e13565b60018301546110bf9161ffff1690613d87565b6110c99190613db4565b949350505050565b336000908152600a602052604090206060906110f3906110f09061305c565b90565b905090565b611100613805565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e2d565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e2d565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f09061305c565b60006112ae6112a983613fbe565b613069565b9050336112be60208401846140c7565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611346610140830161012084016140e4565b6001600160401b0316158061138d575061136660e0830160c084016140e4565b6001600160401b0316611381610140840161012085016140e4565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a08301608084016140e4565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a08301608084016140e4565b6001600160401b0316611440610100840160e085016140e4565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c084016140e4565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b610100830183614101565b6115a59080614121565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c085016140e4565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142c1565b5061166a905060e0830160c084016140e4565b6116749042613d55565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c1610140830161012084016140e4565b6116cb9042613d55565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c90611726908401846140c7565b82613099565b600061173f61173a84613fbe565b6130bb565b600083815260116020526040812060010182905560138054929350839290919061176a908490613dc8565b9091555061177a90503382612c9e565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b031690614475565b60405180910390a1505050565b806000808281526012602052604090205460ff166006811115611806576118066138c6565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fab565b6130f7565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f2604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff166006811115611917576119176138c6565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c6604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e2d565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c6138c6565b03611bab575060049392505050565b6002816004811115611bbf57611bbf6138c6565b03611bce575060059392505050565b6003816004811115611be257611be26138c6565b03611bf1575060029392505050565b6004816004811115611c0557611c056138c6565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fab565b6131af565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd6138c6565b14158015611ced57506004816004811115611cea57611cea6138c6565b14155b8015611d0b57506003816004811115611d0857611d086138c6565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d946138c6565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c50565b6002830154611e1491906001600160401b0316613d87565b826001016000828254611e279190613dc8565b90915550611fbf9050565b6004816004811115611e4657611e466138c6565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e2d565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130bb565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b0316876131e6565b600182015460148054829190600090611fee908490613dc8565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613ddb565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bca565b90506120cc81613208565b80156120f55750600154600082815260208190526040902060ff909116906120f390612c24565b105b8015612116575060008181526020819052604090206121149083612c2e565b155b95945050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661216657604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121ba576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181015460009081526010602052604080822081516003808252608082019093529092918160200160208202803683370190505090506122026121fd87611046565b613235565b8160008151811061221557612215614522565b6020908102919091010152600682015461222e90613246565b8160018151811061224157612241614522565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b03168160028151811061228157612281614522565b60200260200101818152505061086f868683613252565b61230d6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661234457604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c082015292810192909252825180840184526005820180549394929392850192829082906123f890613e2d565b80601f016020809104026020016040519081016040528092919081815260200182805461242490613e2d565b80156124715780601f1061244657610100808354040283529160200191612471565b820191906000526020600020905b81548152906001019060200180831161245457829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008282188284100282186105ba565b60008481526010602052604090205484906001600160a01b03166124f557604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff19166003178255888552601290935292208154612537906001600160a01b0316896131e6565b600481015461254f906001600160a01b031688612950565b600281015460009061256b908a906001600160401b0316612c50565b600383015490915061257d8183613dc8565b6014805460009061258f908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061263a9190613ddb565b61265757604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126ea9190613ddb565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661273e57604051635eeb253d60e11b815260040160405180910390fd5b60008481526012602052604090206004810154612764906001600160a01b031686612950565b60028101546000906127aa9088906001600160401b03166127a5826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f0565b60038301549091506127bc8183613dc8565b601480546000906127ce908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612855573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128799190613ddb565b61289657604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612905573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129299190613ddb565b61294657604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134cf565b600081815260126020908152604080832060018101548085526011909352922060028301546129ab9083906001600160401b0316612c50565b8160010160008282546129be9190613dc8565b909155505060048301546129db906001600160a01b031685612950565b60008481526020819052604081209081816129f6828261387b565b5050845460ff1916600617855550506002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a5e9084906001600160401b0316614538565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b11916001600160401b039081169116614538565b60048301546001600160401b039182169250600160c01b90041681118015612b4e57506001835460ff166004811115612b4c57612b4c6138c6565b145b1561086f57825460ff19166004178355612b69600142614538565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612bf19291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134db565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f0565b600081608001516001600160401b031682604001516110599190613d87565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d31573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d559190613ddb565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a60205260409020610943908261352a565b6000612d9f82613536565b6001600160401b03169050428110612de3576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e0090600160401b90046001600160401b031682613dc8565b4210612e38576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612e94576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612e9e83836131af565b612ed4576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f31576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612f8291859116613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f342613549565b60006105ba612fc58484612fca565b613575565b600080612fd961010043614557565b600254909150600090610100906130089071010000000000000000000000000000000000900460ff168661456b565b613012919061458d565b6001600160401b03169050600061302b61010087614557565b905060006101008261303d8587613dc8565b6130479190613dc8565b6130519190614557565b979650505050505050565b606060006105ba836135cf565b60008160405160200161307c9190613d2c565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600960205260409020610943908261352a565b60006130ca826020015161362b565b602083015160a08101516060909101516130e4919061456b565b6001600160401b03166110599190613d87565b600080600061310585611b51565b6000868152600560205260408120549192509061312a906001600160401b0316613549565b90506001826006811115613140576131406138c6565b1415806131545750613152858261364a565b155b15613167576000809350935050506131a8565b6131718686612fca565b9250600061317e84613575565b9050600061318b88611072565b90508015806131a1575061319f8183614557565b155b9550505050505b9250929050565b60008060006131be85856130f7565b90925090508180156121165750600254600160801b900460ff90811691161015949350505050565b6001600160a01b038216600090815260096020526040902061094390826134cf565b60008060008381526012602052604090205460ff16600681111561322e5761322e6138c6565b1492915050565b600060ff198216816110c982613660565b6000806105ba83613660565b600083815260076020526040812090613269612fab565b6001600160401b0316815260208101919091526040016000205460ff16156132bd576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d916133079186918691016145bb565b602060405180830381865afa158015613324573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133489190613ddb565b61337e576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600760205260408120600191613397612fab565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613443576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b29061362b565b6134bc8585614538565b6001600160401b03166121169190613d87565b60006105ba83836136d2565b600081815260018301602052604081205461352257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134db565b6000611059613544836137cc565b6137d9565b60006110597f000000000000000000000000000000000000000000000000000000000000000083614665565b60008060ff8316613587600143613d74565b6135919190613d74565b40905060008190036135a5576135a5613dfd565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561361f57602002820191906000526020600020905b81548152602001906001019080831161360b575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613d87565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136a0908290613d87565b83901b7fff00000000000000000000000000000000000000000000000000000000000000169190911790600101613686565b600081815260018301602052604081205480156137bb5760006136f6600183613d74565b855490915060009061370a90600190613d74565b905080821461376f57600086600001828154811061372a5761372a614522565b906000526020600020015490508087600001848154811061374d5761374d614522565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378057613780614693565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b6000611059826001613d55565b60006110597f00000000000000000000000000000000000000000000000000000000000000008361456b565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b508054600082559060005260206000209081019061127991905b808211156138a95760008155600101613895565b5090565b6000602082840312156138bf57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106138f0576138f06138c6565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561392057600080fd5b833592506020840135613932816138f6565b91506040840135613942816138f6565b809150509250925092565b6001600160401b038116811461127957600080fd5b803561396d8161394d565b919050565b6000806040838503121561398557600080fd5b8235915060208301356139978161394d565b809150509250929050565b600061010082840312156106de57600080fd5b600080600061014084860312156139cb57600080fd5b8335925060208401356139dd8161394d565b91506139ec85604086016139a2565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a2d578351835260209384019390920191600101613a0f565b509095945050505050565b6000815180845260005b81811015613a5e57602081850181015186830182015201613a42565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a38565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b27610100840182613a7e565b90506040840151613b3e60c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613b6d57600080fd5b81356001600160401b03811115613b8357600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613bab6040850182613a38565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c57610160850182613b96565b90506060830151613c746101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613ca66060840182613bbf565b90506001600160401b0360208501511660408401528091505092915050565b60208101600783106138f0576138f06138c6565b60008060408385031215613cec57600080fd5b823591506020830135613997816138f6565b6000806101208385031215613d1257600080fd5b82359150613d2384602085016139a2565b90509250929050565b6020815260006105ba6020830184613bbf565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d3f565b8181038181111561105957611059613d3f565b808202811582820484141761105957611059613d3f565b634e487b7160e01b600052601260045260246000fd5b600082613dc357613dc3613d9e565b500490565b8082018082111561105957611059613d3f565b600060208284031215613ded57600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d3f565b600181811c90821680613e4157607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613e9957613e99613e61565b60405290565b60405160a081016001600160401b0381118282101715613e9957613e99613e61565b60405160e081016001600160401b0381118282101715613e9957613e99613e61565b604051601f8201601f191681016001600160401b0381118282101715613f0b57613f0b613e61565b604052919050565b600060408284031215613f2557600080fd5b613f2d613e77565b905081356001600160401b03811115613f4557600080fd5b8201601f81018413613f5657600080fd5b80356001600160401b03811115613f6f57613f6f613e61565b613f82601f8201601f1916602001613ee3565b818152856020838501011115613f9757600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b6000813603610160811215613fd257600080fd5b613fda613e9f565b8335613fe5816138f6565b815260e0601f1983011215613ff957600080fd5b614001613ec1565b6020858101358252604080870135918301919091526060860135908201529150608084013561402f8161394d565b606083015260a08401356140428161394d565b608083015260c08401356140558161394d565b60a083015260e08401356140688161394d565b60c08301526020810191909152610100830135906001600160401b0382111561409057600080fd5b61409c36838601613f13565b60408201526140ae6101208501613962565b6060820152610140939093013560808401525090919050565b6000602082840312156140d957600080fd5b81356105ba816138f6565b6000602082840312156140f657600080fd5b81356105ba8161394d565b60008235603e1983360301811261411757600080fd5b9190910192915050565b6000808335601e1984360301811261413857600080fd5b8301803591506001600160401b0382111561415257600080fd5b6020019150368190038213156131a857600080fd5b600081356110598161394d565b601f82111561094357806000526020600020601f840160051c8101602085101561419b5750805b601f840160051c820191505b818110156141bb57600081556001016141a7565b5050505050565b8135601e198336030181126141d657600080fd5b820180356001600160401b03811180156141ef57600080fd5b81360360208401131561420157600080fd5b60009050614219826142138654613e2d565b86614174565b80601f83116001811461424e578284156142365750848201602001355b600019600386901b1c1916600185901b1786556142ad565b600086815260209020601f19851690845b828110156142815760208589018101358355948501946001909201910161425f565b50858210156142a15760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b81356142cc816138f6565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03919091161781556020820135600182015560408201356002820155606082013560038201556004810160808301356143248161394d565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561434d8161394d565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143c161438c60c08501614167565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b6144196143d060e08501614167565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061443461442b610100840184614101565b600583016141c2565b6144656144446101208401614167565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144ad8161394d565b6001600160401b03811660808401525060808401356144cb8161394d565b6001600160401b03811660a08401525060a08401356144e98161394d565b6001600160401b03811660c08401525061450560c08501613962565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d3f565b60008261456657614566613d9e565b500690565b6001600160401b0381811683821602908116908181146137c5576137c5613d3f565b60006001600160401b038316806145a6576145a6613d9e565b806001600160401b0384160691505092915050565b8235815260208084013590820152600061012082016145ea604084016040870180358252602090810135910152565b614604608084016080870180358252602090810135910152565b61461e60c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b8181101561465957835183526020938401939092019160010161463b565b50909695505050505050565b60006001600160401b0383168061467e5761467e613d9e565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206b0b03ebd7dbbc3b4661bc03194b4eeec33ef1f5a894e376e036a4d4e8a6908f64736f6c634300081c0033"; public MarketplaceDeploymentBase() : base(BYTECODE) { } public MarketplaceDeploymentBase(string byteCode) : base(byteCode) { } [Parameter("tuple", "config", 1)] diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index 4cc9c707..058d5bfd 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -134,8 +134,11 @@ namespace CodexTests if (!result.Success) { - var codexNodes = nodes[lifecycle]; - foreach (var node in codexNodes) node.DownloadLog(); + lock (_lock) + { + var codexNodes = nodes[lifecycle]; + foreach (var node in codexNodes) node.DownloadLog(); + } } } From c9fe841bb5f5653b347d66a0f83a33ca634db6e3 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 09:59:53 +0200 Subject: [PATCH 31/51] Fixes issue with frozen docker commands --- .../CodexContractsPlugin/VersionRegistry.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs index 9ed4e8b4..f52a38a8 100644 --- a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs +++ b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs @@ -11,7 +11,8 @@ namespace CodexContractsPlugin public class VersionRegistry { private ICodexDockerImageProvider provider = new ExceptionProvider(); - private readonly Dictionary cache = new Dictionary(); + private static readonly Dictionary cache = new Dictionary(); + private static readonly object cacheLock = new object(); private readonly ILog log; public VersionRegistry(ILog log) @@ -39,13 +40,16 @@ namespace CodexContractsPlugin private string GetContractsDockerImage(string codexImage) { - if (cache.TryGetValue(codexImage, out string? value)) + lock (cacheLock) { - return value; + if (cache.TryGetValue(codexImage, out string? value)) + { + return value; + } + var result = GetContractsImage(codexImage); + cache.Add(codexImage, result); + return result; } - var result = GetContractsImage(codexImage); - cache.Add(codexImage, result); - return result; } private string GetContractsImage(string codexImage) @@ -82,14 +86,32 @@ namespace CodexContractsPlugin startInfo.RedirectStandardError = true; var process = Process.Start(startInfo); - if (process == null || process.HasExited) + if (process == null) { throw new Exception("Failed to start: " + cmd + args); } + KillAfterTimeout(process); process.WaitForExit(); return process.StandardOutput.ReadToEnd(); } + + private void KillAfterTimeout(Process process) + { + // There's a known issue that some docker commands on some platforms + // will fail to stop on their own. This has been known since 2019 and it's not fixed. + // So we will issue a kill to the process ourselves if it exceeds a timeout. + + Task.Run(() => + { + Thread.Sleep(TimeSpan.FromSeconds(30.0)); + + if (process != null && !process.HasExited) + { + process.Kill(); + } + }); + } } internal class ExceptionProvider : ICodexDockerImageProvider From 7844252957f699f17e5afa16134f2f62957e4608 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 10:17:46 +0200 Subject: [PATCH 32/51] Updates marketplace contract again --- ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs index ae309f1f..e46bbdd9 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 = "0x60c060405234801561001057600080fd5b50604051614f04380380614f0483398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516146df610825600039600081816104bf01528181610f7001528181612019015281816125cd0152818161267d0152818161280c015281816128bc0152612ce601526000818161355001526137e001526146df6000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138ad565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138ad565b6105c1565b6040516101f091906138dc565b61022c61022736600461390b565b6106e4565b005b61022c61023c366004613972565b610877565b61022c61024f3660046139b5565b610948565b61022c610262366004613972565b610dfa565b61028a6102753660046138ad565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138ad565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138ad565b611046565b6102f16102ec3660046138ad565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138ad565b611072565b61031e6110d1565b6040516101f091906139f5565b6103576103393660046138ad565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613ad3565b61022c6103923660046138ad565b61126f565b61031e61127c565b61022c6103ad366004613b5b565b61129b565b61022c6103c03660046138ad565b6117e1565b6103d86103d33660046138ad565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138ad565b61186f565b6040516101f09190613c8a565b61041b6104163660046138ad565b611b51565b6040516101f09190613cc5565b6103d86104363660046138ad565b611c1f565b61022c610449366004613cd9565b611c32565b6103d861045c366004613972565b6120b3565b61022c61046f366004613cfe565b61211f565b6101dc6104823660046138ad565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138ad565b612298565b6040516101f09190613d2c565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b90506000816004811115610505576105056138c6565b148061052257506001816004811115610520576105206138c6565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b6002816004811115610562576105626138c6565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124ae565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d6138c6565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff166004811115610683576106836138c6565b14806106a457506000815460ff1660048111156106a2576106a26138c6565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff166006811115610709576107096138c6565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c6138c6565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e76138c6565b03610801576107fc82600101548787876124be565b61086f565b6005816006811115610815576108156138c6565b0361082a576107fc8260010154878787612707565b600381600681111561083e5761083e6138c6565b0361084d576107fc3387612950565b6001816006811115610861576108616138c6565b0361086f5761086f86612972565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bca565b60008181526020819052604090209091506108de9033612c0f565b50600154600082815260208190526040902060ff909116906108ff90612c24565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bca565b60008181526020819052604090209091506109fe9033612c2e565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e6138c6565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac36138c6565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b78838761211f565b60028101805460019190600090610b999084906001600160401b0316613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c50565b816001016000828254610bf19190613d74565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612c7f565b90506006610c7786611b51565b6006811115610c8857610c886138c6565b03610cbb57600b54606490610ca09060ff1683613d87565b610caa9190613db4565b610cb49082613d74565b9150610cbf565b8091505b610cc93383612c9e565b8160136000016000828254610cde9190613dc8565b9091555050600384018190556004840154610d02906001600160a01b031686612d72565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d826138c6565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e166138c6565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612d94565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612c7f565b610f049190613d87565b610f0e9190613db4565b600b54909150600090606490610f2e906301000000900460ff1684613d87565b610f389190613db4565b90508060136001016000828254610f4f9190613dc8565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613ddb565b610ff157610ff1613dfd565b818460030160008282546110059190613d74565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f86612972565b600061105982611054612fab565b612fb6565b92915050565b60006110598261106d612fab565b612fca565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e13565b60018301546110bf9161ffff1690613d87565b6110c99190613db4565b949350505050565b336000908152600a602052604090206060906110f3906110f09061305c565b90565b905090565b611100613805565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e2d565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e2d565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f09061305c565b60006112ae6112a983613fbe565b613069565b9050336112be60208401846140c7565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611346610140830161012084016140e4565b6001600160401b0316158061138d575061136660e0830160c084016140e4565b6001600160401b0316611381610140840161012085016140e4565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a08301608084016140e4565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a08301608084016140e4565b6001600160401b0316611440610100840160e085016140e4565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c084016140e4565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b610100830183614101565b6115a59080614121565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c085016140e4565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142c1565b5061166a905060e0830160c084016140e4565b6116749042613d55565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c1610140830161012084016140e4565b6116cb9042613d55565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c90611726908401846140c7565b82613099565b600061173f61173a84613fbe565b6130bb565b600083815260116020526040812060010182905560138054929350839290919061176a908490613dc8565b9091555061177a90503382612c9e565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b031690614475565b60405180910390a1505050565b806000808281526012602052604090205460ff166006811115611806576118066138c6565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fab565b6130f7565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f2604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff166006811115611917576119176138c6565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c6604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e2d565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c6138c6565b03611bab575060049392505050565b6002816004811115611bbf57611bbf6138c6565b03611bce575060059392505050565b6003816004811115611be257611be26138c6565b03611bf1575060029392505050565b6004816004811115611c0557611c056138c6565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fab565b6131af565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd6138c6565b14158015611ced57506004816004811115611cea57611cea6138c6565b14155b8015611d0b57506003816004811115611d0857611d086138c6565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d946138c6565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c50565b6002830154611e1491906001600160401b0316613d87565b826001016000828254611e279190613dc8565b90915550611fbf9050565b6004816004811115611e4657611e466138c6565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e2d565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130bb565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b0316876131e6565b600182015460148054829190600090611fee908490613dc8565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613ddb565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bca565b90506120cc81613208565b80156120f55750600154600082815260208190526040902060ff909116906120f390612c24565b105b8015612116575060008181526020819052604090206121149083612c2e565b155b95945050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661216657604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121ba576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181015460009081526010602052604080822081516003808252608082019093529092918160200160208202803683370190505090506122026121fd87611046565b613235565b8160008151811061221557612215614522565b6020908102919091010152600682015461222e90613246565b8160018151811061224157612241614522565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b03168160028151811061228157612281614522565b60200260200101818152505061086f868683613252565b61230d6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661234457604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c082015292810192909252825180840184526005820180549394929392850192829082906123f890613e2d565b80601f016020809104026020016040519081016040528092919081815260200182805461242490613e2d565b80156124715780601f1061244657610100808354040283529160200191612471565b820191906000526020600020905b81548152906001019060200180831161245457829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008282188284100282186105ba565b60008481526010602052604090205484906001600160a01b03166124f557604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff19166003178255888552601290935292208154612537906001600160a01b0316896131e6565b600481015461254f906001600160a01b031688612950565b600281015460009061256b908a906001600160401b0316612c50565b600383015490915061257d8183613dc8565b6014805460009061258f908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061263a9190613ddb565b61265757604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126ea9190613ddb565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661273e57604051635eeb253d60e11b815260040160405180910390fd5b60008481526012602052604090206004810154612764906001600160a01b031686612950565b60028101546000906127aa9088906001600160401b03166127a5826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f0565b60038301549091506127bc8183613dc8565b601480546000906127ce908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612855573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128799190613ddb565b61289657604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612905573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129299190613ddb565b61294657604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134cf565b600081815260126020908152604080832060018101548085526011909352922060028301546129ab9083906001600160401b0316612c50565b8160010160008282546129be9190613dc8565b909155505060048301546129db906001600160a01b031685612950565b60008481526020819052604081209081816129f6828261387b565b5050845460ff1916600617855550506002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a5e9084906001600160401b0316614538565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b11916001600160401b039081169116614538565b60048301546001600160401b039182169250600160c01b90041681118015612b4e57506001835460ff166004811115612b4c57612b4c6138c6565b145b1561086f57825460ff19166004178355612b69600142614538565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612bf19291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134db565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f0565b600081608001516001600160401b031682604001516110599190613d87565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d31573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d559190613ddb565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a60205260409020610943908261352a565b6000612d9f82613536565b6001600160401b03169050428110612de3576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e0090600160401b90046001600160401b031682613dc8565b4210612e38576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612e94576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612e9e83836131af565b612ed4576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f31576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612f8291859116613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f342613549565b60006105ba612fc58484612fca565b613575565b600080612fd961010043614557565b600254909150600090610100906130089071010000000000000000000000000000000000900460ff168661456b565b613012919061458d565b6001600160401b03169050600061302b61010087614557565b905060006101008261303d8587613dc8565b6130479190613dc8565b6130519190614557565b979650505050505050565b606060006105ba836135cf565b60008160405160200161307c9190613d2c565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600960205260409020610943908261352a565b60006130ca826020015161362b565b602083015160a08101516060909101516130e4919061456b565b6001600160401b03166110599190613d87565b600080600061310585611b51565b6000868152600560205260408120549192509061312a906001600160401b0316613549565b90506001826006811115613140576131406138c6565b1415806131545750613152858261364a565b155b15613167576000809350935050506131a8565b6131718686612fca565b9250600061317e84613575565b9050600061318b88611072565b90508015806131a1575061319f8183614557565b155b9550505050505b9250929050565b60008060006131be85856130f7565b90925090508180156121165750600254600160801b900460ff90811691161015949350505050565b6001600160a01b038216600090815260096020526040902061094390826134cf565b60008060008381526012602052604090205460ff16600681111561322e5761322e6138c6565b1492915050565b600060ff198216816110c982613660565b6000806105ba83613660565b600083815260076020526040812090613269612fab565b6001600160401b0316815260208101919091526040016000205460ff16156132bd576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d916133079186918691016145bb565b602060405180830381865afa158015613324573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133489190613ddb565b61337e576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600760205260408120600191613397612fab565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613443576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b29061362b565b6134bc8585614538565b6001600160401b03166121169190613d87565b60006105ba83836136d2565b600081815260018301602052604081205461352257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134db565b6000611059613544836137cc565b6137d9565b60006110597f000000000000000000000000000000000000000000000000000000000000000083614665565b60008060ff8316613587600143613d74565b6135919190613d74565b40905060008190036135a5576135a5613dfd565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561361f57602002820191906000526020600020905b81548152602001906001019080831161360b575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613d87565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136a0908290613d87565b83901b7fff00000000000000000000000000000000000000000000000000000000000000169190911790600101613686565b600081815260018301602052604081205480156137bb5760006136f6600183613d74565b855490915060009061370a90600190613d74565b905080821461376f57600086600001828154811061372a5761372a614522565b906000526020600020015490508087600001848154811061374d5761374d614522565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378057613780614693565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b6000611059826001613d55565b60006110597f00000000000000000000000000000000000000000000000000000000000000008361456b565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b508054600082559060005260206000209081019061127991905b808211156138a95760008155600101613895565b5090565b6000602082840312156138bf57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106138f0576138f06138c6565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561392057600080fd5b833592506020840135613932816138f6565b91506040840135613942816138f6565b809150509250925092565b6001600160401b038116811461127957600080fd5b803561396d8161394d565b919050565b6000806040838503121561398557600080fd5b8235915060208301356139978161394d565b809150509250929050565b600061010082840312156106de57600080fd5b600080600061014084860312156139cb57600080fd5b8335925060208401356139dd8161394d565b91506139ec85604086016139a2565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a2d578351835260209384019390920191600101613a0f565b509095945050505050565b6000815180845260005b81811015613a5e57602081850181015186830182015201613a42565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a38565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b27610100840182613a7e565b90506040840151613b3e60c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613b6d57600080fd5b81356001600160401b03811115613b8357600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613bab6040850182613a38565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c57610160850182613b96565b90506060830151613c746101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613ca66060840182613bbf565b90506001600160401b0360208501511660408401528091505092915050565b60208101600783106138f0576138f06138c6565b60008060408385031215613cec57600080fd5b823591506020830135613997816138f6565b6000806101208385031215613d1257600080fd5b82359150613d2384602085016139a2565b90509250929050565b6020815260006105ba6020830184613bbf565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d3f565b8181038181111561105957611059613d3f565b808202811582820484141761105957611059613d3f565b634e487b7160e01b600052601260045260246000fd5b600082613dc357613dc3613d9e565b500490565b8082018082111561105957611059613d3f565b600060208284031215613ded57600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d3f565b600181811c90821680613e4157607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613e9957613e99613e61565b60405290565b60405160a081016001600160401b0381118282101715613e9957613e99613e61565b60405160e081016001600160401b0381118282101715613e9957613e99613e61565b604051601f8201601f191681016001600160401b0381118282101715613f0b57613f0b613e61565b604052919050565b600060408284031215613f2557600080fd5b613f2d613e77565b905081356001600160401b03811115613f4557600080fd5b8201601f81018413613f5657600080fd5b80356001600160401b03811115613f6f57613f6f613e61565b613f82601f8201601f1916602001613ee3565b818152856020838501011115613f9757600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b6000813603610160811215613fd257600080fd5b613fda613e9f565b8335613fe5816138f6565b815260e0601f1983011215613ff957600080fd5b614001613ec1565b6020858101358252604080870135918301919091526060860135908201529150608084013561402f8161394d565b606083015260a08401356140428161394d565b608083015260c08401356140558161394d565b60a083015260e08401356140688161394d565b60c08301526020810191909152610100830135906001600160401b0382111561409057600080fd5b61409c36838601613f13565b60408201526140ae6101208501613962565b6060820152610140939093013560808401525090919050565b6000602082840312156140d957600080fd5b81356105ba816138f6565b6000602082840312156140f657600080fd5b81356105ba8161394d565b60008235603e1983360301811261411757600080fd5b9190910192915050565b6000808335601e1984360301811261413857600080fd5b8301803591506001600160401b0382111561415257600080fd5b6020019150368190038213156131a857600080fd5b600081356110598161394d565b601f82111561094357806000526020600020601f840160051c8101602085101561419b5750805b601f840160051c820191505b818110156141bb57600081556001016141a7565b5050505050565b8135601e198336030181126141d657600080fd5b820180356001600160401b03811180156141ef57600080fd5b81360360208401131561420157600080fd5b60009050614219826142138654613e2d565b86614174565b80601f83116001811461424e578284156142365750848201602001355b600019600386901b1c1916600185901b1786556142ad565b600086815260209020601f19851690845b828110156142815760208589018101358355948501946001909201910161425f565b50858210156142a15760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b81356142cc816138f6565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03919091161781556020820135600182015560408201356002820155606082013560038201556004810160808301356143248161394d565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561434d8161394d565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143c161438c60c08501614167565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b6144196143d060e08501614167565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061443461442b610100840184614101565b600583016141c2565b6144656144446101208401614167565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144ad8161394d565b6001600160401b03811660808401525060808401356144cb8161394d565b6001600160401b03811660a08401525060a08401356144e98161394d565b6001600160401b03811660c08401525061450560c08501613962565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d3f565b60008261456657614566613d9e565b500690565b6001600160401b0381811683821602908116908181146137c5576137c5613d3f565b60006001600160401b038316806145a6576145a6613d9e565b806001600160401b0384160691505092915050565b8235815260208084013590820152600061012082016145ea604084016040870180358252602090810135910152565b614604608084016080870180358252602090810135910152565b61461e60c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b8181101561465957835183526020938401939092019160010161463b565b50909695505050505050565b60006001600160401b0383168061467e5761467e613d9e565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206b0b03ebd7dbbc3b4661bc03194b4eeec33ef1f5a894e376e036a4d4e8a6908f64736f6c634300081c0033"; + public static string BYTECODE = "0x60c060405234801561001057600080fd5b50604051614f0a380380614f0a83398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516146e5610825600039600081816104bf01528181610f7001528181612019015281816125d30152818161268301528181612812015281816128c20152612cec01526000818161355601526137e601526146e56000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138b3565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138b3565b6105c1565b6040516101f091906138e2565b61022c610227366004613911565b6106e4565b005b61022c61023c366004613978565b610877565b61022c61024f3660046139bb565b610948565b61022c610262366004613978565b610dfa565b61028a6102753660046138b3565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138b3565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138b3565b611046565b6102f16102ec3660046138b3565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138b3565b611072565b61031e6110d1565b6040516101f091906139fb565b6103576103393660046138b3565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613ad9565b61022c6103923660046138b3565b61126f565b61031e61127c565b61022c6103ad366004613b61565b61129b565b61022c6103c03660046138b3565b6117e1565b6103d86103d33660046138b3565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138b3565b61186f565b6040516101f09190613c90565b61041b6104163660046138b3565b611b51565b6040516101f09190613ccb565b6103d86104363660046138b3565b611c1f565b61022c610449366004613cdf565b611c32565b6103d861045c366004613978565b6120b3565b61022c61046f366004613d04565b61211f565b6101dc6104823660046138b3565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138b3565b612298565b6040516101f09190613d32565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b90506000816004811115610505576105056138cc565b148061052257506001816004811115610520576105206138cc565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b6002816004811115610562576105626138cc565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124ae565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d6138cc565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff166004811115610683576106836138cc565b14806106a457506000815460ff1660048111156106a2576106a26138cc565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff166006811115610709576107096138cc565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c6138cc565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e76138cc565b03610801576107fc82600101548787876124c4565b61086f565b6005816006811115610815576108156138cc565b0361082a576107fc826001015487878761270d565b600381600681111561083e5761083e6138cc565b0361084d576107fc3387612956565b6001816006811115610861576108616138cc565b0361086f5761086f86612978565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bd0565b60008181526020819052604090209091506108de9033612c15565b50600154600082815260208190526040902060ff909116906108ff90612c2a565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bd0565b60008181526020819052604090209091506109fe9033612c34565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e6138cc565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac36138cc565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b78838761211f565b60028101805460019190600090610b999084906001600160401b0316613d5b565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c56565b816001016000828254610bf19190613d7a565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612c85565b90506006610c7786611b51565b6006811115610c8857610c886138cc565b03610cbb57600b54606490610ca09060ff1683613d8d565b610caa9190613dba565b610cb49082613d7a565b9150610cbf565b8091505b610cc93383612ca4565b8160136000016000828254610cde9190613dce565b9091555050600384018190556004840154610d02906001600160a01b031686612d78565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d826138cc565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e166138cc565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612d9a565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612c85565b610f049190613d8d565b610f0e9190613dba565b600b54909150600090606490610f2e906301000000900460ff1684613d8d565b610f389190613dba565b90508060136001016000828254610f4f9190613dce565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613de1565b610ff157610ff1613e03565b818460030160008282546110059190613d7a565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f86612978565b600061105982611054612fb1565b612fbc565b92915050565b60006110598261106d612fb1565b612fd0565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e19565b60018301546110bf9161ffff1690613d8d565b6110c99190613dba565b949350505050565b336000908152600a602052604090206060906110f3906110f090613062565b90565b905090565b61110061380b565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e33565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e33565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f090613062565b60006112ae6112a983613fc4565b61306f565b9050336112be60208401846140cd565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611346610140830161012084016140ea565b6001600160401b0316158061138d575061136660e0830160c084016140ea565b6001600160401b0316611381610140840161012085016140ea565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a08301608084016140ea565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a08301608084016140ea565b6001600160401b0316611440610100840160e085016140ea565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c084016140ea565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b610100830183614107565b6115a59080614127565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c085016140ea565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142c7565b5061166a905060e0830160c084016140ea565b6116749042613d5b565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c1610140830161012084016140ea565b6116cb9042613d5b565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c90611726908401846140cd565b8261309f565b600061173f61173a84613fc4565b6130c1565b600083815260116020526040812060010182905560138054929350839290919061176a908490613dce565b9091555061177a90503382612ca4565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b03169061447b565b60405180910390a1505050565b806000808281526012602052604090205460ff166006811115611806576118066138cc565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fb1565b6130fd565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f260405180604001604052806138746040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff166006811115611917576119176138cc565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c660405180604001604052806138746040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e33565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e33565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c6138cc565b03611bab575060049392505050565b6002816004811115611bbf57611bbf6138cc565b03611bce575060059392505050565b6003816004811115611be257611be26138cc565b03611bf1575060029392505050565b6004816004811115611c0557611c056138cc565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fb1565b6131b5565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd6138cc565b14158015611ced57506004816004811115611cea57611cea6138cc565b14155b8015611d0b57506003816004811115611d0857611d086138cc565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d946138cc565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c56565b6002830154611e1491906001600160401b0316613d8d565b826001016000828254611e279190613dce565b90915550611fbf9050565b6004816004811115611e4657611e466138cc565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e33565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e33565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130c1565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b0316876131ec565b600182015460148054829190600090611fee908490613dce565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613de1565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bd0565b90506120cc8161320e565b80156120f55750600154600082815260208190526040902060ff909116906120f390612c2a565b105b8015612116575060008181526020819052604090206121149083612c34565b155b95945050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661216657604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121ba576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181015460009081526010602052604080822081516003808252608082019093529092918160200160208202803683370190505090506122026121fd87611046565b61323b565b8160008151811061221557612215614528565b6020908102919091010152600682015461222e9061324c565b8160018151811061224157612241614528565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b03168160028151811061228157612281614528565b60200260200101818152505061086f868683613258565b61230d6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661234457604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c082015292810192909252825180840184526005820180549394929392850192829082906123f890613e33565b80601f016020809104026020016040519081016040528092919081815260200182805461242490613e33565b80156124715780601f1061244657610100808354040283529160200191612471565b820191906000526020600020905b81548152906001019060200180831161245457829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008183106124bd57816105ba565b5090919050565b60008481526010602052604090205484906001600160a01b03166124fb57604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff1916600317825588855260129093529220815461253d906001600160a01b0316896131ec565b6004810154612555906001600160a01b031688612956565b6002810154600090612571908a906001600160401b0316612c56565b60038301549091506125838183613dce565b60148054600090612595908490613dce565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561261c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126409190613de1565b61265d57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126f09190613de1565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661274457604051635eeb253d60e11b815260040160405180910390fd5b6000848152601260205260409020600481015461276a906001600160a01b031686612956565b60028101546000906127b09088906001600160401b03166127ab826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f6565b60038301549091506127c28183613dce565b601480546000906127d4908490613dce565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561285b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061287f9190613de1565b61289c57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561290b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061292f9190613de1565b61294c57604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134d5565b600081815260126020908152604080832060018101548085526011909352922060028301546129b19083906001600160401b0316612c56565b8160010160008282546129c49190613dce565b909155505060048301546129e1906001600160a01b031685612956565b60008481526020819052604081209081816129fc8282613881565b5050845460ff1916600617855550506002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a649084906001600160401b031661453e565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b17916001600160401b03908116911661453e565b60048301546001600160401b039182169250600160c01b90041681118015612b5457506001835460ff166004811115612b5257612b526138cc565b145b1561086f57825460ff19166004178355612b6f60014261453e565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612bf79291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134e1565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f6565b600081608001516001600160401b031682604001516110599190613d8d565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5b9190613de1565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a602052604090206109439082613530565b6000612da58261353c565b6001600160401b03169050428110612de9576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e0690600160401b90046001600160401b031682613dce565b4210612e3e576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612e9a576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612ea483836131b5565b612eda576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f37576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612f8891859116613d5b565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f34261354f565b60006105ba612fcb8484612fd0565b61357b565b600080612fdf6101004361455d565b6002549091506000906101009061300e9071010000000000000000000000000000000000900460ff1686614571565b6130189190614593565b6001600160401b0316905060006130316101008761455d565b90506000610100826130438587613dce565b61304d9190613dce565b613057919061455d565b979650505050505050565b606060006105ba836135d5565b6000816040516020016130829190613d32565b604051602081830303815290604052805190602001209050919050565b6001600160a01b03821660009081526009602052604090206109439082613530565b60006130d08260200151613631565b602083015160a08101516060909101516130ea9190614571565b6001600160401b03166110599190613d8d565b600080600061310b85611b51565b60008681526005602052604081205491925090613130906001600160401b031661354f565b90506001826006811115613146576131466138cc565b14158061315a57506131588582613650565b155b1561316d576000809350935050506131ae565b6131778686612fd0565b925060006131848461357b565b9050600061319188611072565b90508015806131a757506131a5818361455d565b155b9550505050505b9250929050565b60008060006131c485856130fd565b90925090508180156121165750600254600160801b900460ff90811691161015949350505050565b6001600160a01b038216600090815260096020526040902061094390826134d5565b60008060008381526012602052604090205460ff166006811115613234576132346138cc565b1492915050565b600060ff198216816110c982613666565b6000806105ba83613666565b60008381526007602052604081209061326f612fb1565b6001600160401b0316815260208101919091526040016000205460ff16156132c3576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d9161330d9186918691016145c1565b602060405180830381865afa15801561332a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061334e9190613de1565b613384576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083815260076020526040812060019161339d612fb1565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613449576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b890613631565b6134c2858561453e565b6001600160401b03166121169190613d8d565b60006105ba83836136d8565b600081815260018301602052604081205461352857508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134e1565b600061105961354a836137d2565b6137df565b60006110597f00000000000000000000000000000000000000000000000000000000000000008361466b565b60008060ff831661358d600143613d7a565b6135979190613d7a565b40905060008190036135ab576135ab613e03565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561362557602002820191906000526020600020905b815481526020019060010190808311613611575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613d8d565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136a6908290613d8d565b83901b7fff0000000000000000000000000000000000000000000000000000000000000016919091179060010161368c565b600081815260018301602052604081205480156137c15760006136fc600183613d7a565b855490915060009061371090600190613d7a565b905081811461377557600086600001828154811061373057613730614528565b906000526020600020015490508087600001848154811061375357613753614528565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378657613786614699565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b6000611059826001613d5b565b60006110597f000000000000000000000000000000000000000000000000000000000000000083614571565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b508054600082559060005260206000209081019061127991905b808211156138af576000815560010161389b565b5090565b6000602082840312156138c557600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106138f6576138f66138cc565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561392657600080fd5b833592506020840135613938816138fc565b91506040840135613948816138fc565b809150509250925092565b6001600160401b038116811461127957600080fd5b803561397381613953565b919050565b6000806040838503121561398b57600080fd5b82359150602083013561399d81613953565b809150509250929050565b600061010082840312156106de57600080fd5b600080600061014084860312156139d157600080fd5b8335925060208401356139e381613953565b91506139f285604086016139a8565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a33578351835260209384019390920191600101613a15565b509095945050505050565b6000815180845260005b81811015613a6457602081850181015186830182015201613a48565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a3e565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b2d610100840182613a84565b90506040840151613b4460c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613b7357600080fd5b81356001600160401b03811115613b8957600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613bb16040850182613a3e565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c5d610160850182613b9c565b90506060830151613c7a6101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613cac6060840182613bc5565b90506001600160401b0360208501511660408401528091505092915050565b60208101600783106138f6576138f66138cc565b60008060408385031215613cf257600080fd5b82359150602083013561399d816138fc565b6000806101208385031215613d1857600080fd5b82359150613d2984602085016139a8565b90509250929050565b6020815260006105ba6020830184613bc5565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d45565b8181038181111561105957611059613d45565b808202811582820484141761105957611059613d45565b634e487b7160e01b600052601260045260246000fd5b600082613dc957613dc9613da4565b500490565b8082018082111561105957611059613d45565b600060208284031215613df357600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d45565b600181811c90821680613e4757607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613e9f57613e9f613e67565b60405290565b60405160a081016001600160401b0381118282101715613e9f57613e9f613e67565b60405160e081016001600160401b0381118282101715613e9f57613e9f613e67565b604051601f8201601f191681016001600160401b0381118282101715613f1157613f11613e67565b604052919050565b600060408284031215613f2b57600080fd5b613f33613e7d565b905081356001600160401b03811115613f4b57600080fd5b8201601f81018413613f5c57600080fd5b80356001600160401b03811115613f7557613f75613e67565b613f88601f8201601f1916602001613ee9565b818152856020838501011115613f9d57600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b6000813603610160811215613fd857600080fd5b613fe0613ea5565b8335613feb816138fc565b815260e0601f1983011215613fff57600080fd5b614007613ec7565b6020858101358252604080870135918301919091526060860135908201529150608084013561403581613953565b606083015260a084013561404881613953565b608083015260c084013561405b81613953565b60a083015260e084013561406e81613953565b60c08301526020810191909152610100830135906001600160401b0382111561409657600080fd5b6140a236838601613f19565b60408201526140b46101208501613968565b6060820152610140939093013560808401525090919050565b6000602082840312156140df57600080fd5b81356105ba816138fc565b6000602082840312156140fc57600080fd5b81356105ba81613953565b60008235603e1983360301811261411d57600080fd5b9190910192915050565b6000808335601e1984360301811261413e57600080fd5b8301803591506001600160401b0382111561415857600080fd5b6020019150368190038213156131ae57600080fd5b6000813561105981613953565b601f82111561094357806000526020600020601f840160051c810160208510156141a15750805b601f840160051c820191505b818110156141c157600081556001016141ad565b5050505050565b8135601e198336030181126141dc57600080fd5b820180356001600160401b03811180156141f557600080fd5b81360360208401131561420757600080fd5b6000905061421f826142198654613e33565b8661417a565b80601f8311600181146142545782841561423c5750848201602001355b600019600386901b1c1916600185901b1786556142b3565b600086815260209020601f19851690845b8281101561428757602085890181013583559485019460019092019101614265565b50858210156142a75760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b81356142d2816138fc565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039190911617815560208201356001820155604082013560028201556060820135600382015560048101608083013561432a81613953565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561435381613953565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143c761439260c0850161416d565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b61441f6143d660e0850161416d565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061443a614431610100840184614107565b600583016141c8565b61446b61444a610120840161416d565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144b381613953565b6001600160401b03811660808401525060808401356144d181613953565b6001600160401b03811660a08401525060a08401356144ef81613953565b6001600160401b03811660c08401525061450b60c08501613968565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d45565b60008261456c5761456c613da4565b500690565b6001600160401b0381811683821602908116908181146137cb576137cb613d45565b60006001600160401b038316806145ac576145ac613da4565b806001600160401b0384160691505092915050565b8235815260208084013590820152600061012082016145f0604084016040870180358252602090810135910152565b61460a608084016080870180358252602090810135910152565b61462460c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b8181101561465f578351835260209384019390920191600101614641565b50909695505050505050565b60006001600160401b0383168061468457614684613da4565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206e940e8cb5c54ce6f6606e3b8f89e55d51c501d4017adbc3ac7b30357327730d64736f6c634300081c0033"; public MarketplaceDeploymentBase() : base(BYTECODE) { } public MarketplaceDeploymentBase(string byteCode) : base(byteCode) { } [Parameter("tuple", "config", 1)] From 61401a83db95c72f2c9faf0e284f59b584c5579a Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 10:18:41 +0200 Subject: [PATCH 33/51] sets codex image back to latest --- ProjectPlugins/CodexPlugin/CodexDockerImage.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index f86470db..5ed36c92 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -4,8 +4,7 @@ namespace CodexPlugin { public class CodexDockerImage : ICodexDockerImageProvider { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-c9a5ef8-dist-tests"; - //"codexstorage/nim-codex:latest-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests"; public static string Override { get; set; } = string.Empty; From 02ef655a40dcd5014f89fd6a1876346697c7b2a7 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 12:25:37 +0200 Subject: [PATCH 34/51] Enables console output for release tests. --- Framework/Logging/BaseLog.cs | 10 ++- Framework/Logging/ConsoleLog.cs | 2 +- Framework/Logging/FileLog.cs | 2 +- Framework/Logging/LogPrefixer.cs | 11 ++- Framework/Logging/LogSplitter.cs | 10 +++ Framework/Logging/NullLog.cs | 6 +- .../ContinuousTestRunner.cs | 2 +- Tests/CodexContinuousTests/SingleTestRun.cs | 8 +- Tests/CodexContinuousTests/StartupChecker.cs | 4 +- Tests/DistTestCore/DistTest.cs | 24 +++--- Tests/DistTestCore/Logs/BaseTestLog.cs | 74 ++++++++++++++++--- Tests/DistTestCore/Logs/FixtureLog.cs | 22 +++--- Tests/DistTestCore/Logs/TestLog.cs | 22 +++--- Tests/ExperimentalTests/CodexDistTest.cs | 6 +- 14 files changed, 138 insertions(+), 65 deletions(-) diff --git a/Framework/Logging/BaseLog.cs b/Framework/Logging/BaseLog.cs index 2c79fd39..56ff66ec 100644 --- a/Framework/Logging/BaseLog.cs +++ b/Framework/Logging/BaseLog.cs @@ -7,8 +7,10 @@ namespace Logging void Log(string message); void Debug(string message = "", int skipFrames = 0); void Error(string message); + void Raw(string message); void AddStringReplace(string from, string to); LogFile CreateSubfile(string addName, string ext = "log"); + string GetFullName(); } public abstract class BaseLog : ILog @@ -28,7 +30,8 @@ namespace Logging } protected bool IsDebug { get; private set; } - protected abstract string GetFullName(); + + public abstract string GetFullName(); public LogFile LogFile { @@ -60,6 +63,11 @@ namespace Logging Log(msg); } + public void Raw(string message) + { + LogFile.WriteRaw(message); + } + public virtual void AddStringReplace(string from, string to) { if (string.IsNullOrWhiteSpace(from)) return; diff --git a/Framework/Logging/ConsoleLog.cs b/Framework/Logging/ConsoleLog.cs index ab67a400..2604e64f 100644 --- a/Framework/Logging/ConsoleLog.cs +++ b/Framework/Logging/ConsoleLog.cs @@ -2,7 +2,7 @@ { public class ConsoleLog : BaseLog { - protected override string GetFullName() + public override string GetFullName() { return "CONSOLE"; } diff --git a/Framework/Logging/FileLog.cs b/Framework/Logging/FileLog.cs index cc5e9952..86bc1985 100644 --- a/Framework/Logging/FileLog.cs +++ b/Framework/Logging/FileLog.cs @@ -9,7 +9,7 @@ public string FullFilename { get; } - protected override string GetFullName() + public override string GetFullName() { return FullFilename; } diff --git a/Framework/Logging/LogPrefixer.cs b/Framework/Logging/LogPrefixer.cs index 6fe6033b..f0f303d6 100644 --- a/Framework/Logging/LogPrefixer.cs +++ b/Framework/Logging/LogPrefixer.cs @@ -17,7 +17,6 @@ public string Prefix { get; set; } = string.Empty; - public LogFile CreateSubfile(string addName, string ext = "log") { return backingLog.CreateSubfile(addName, ext); @@ -42,5 +41,15 @@ { backingLog.AddStringReplace(from, to); } + + public void Raw(string message) + { + backingLog.Raw(message); + } + + public string GetFullName() + { + return backingLog.GetFullName(); + } } } diff --git a/Framework/Logging/LogSplitter.cs b/Framework/Logging/LogSplitter.cs index 563f5812..8de462c9 100644 --- a/Framework/Logging/LogSplitter.cs +++ b/Framework/Logging/LogSplitter.cs @@ -29,11 +29,21 @@ OnAll(l => l.Error(message)); } + public string GetFullName() + { + return targetLogs.First().GetFullName(); + } + public void Log(string message) { OnAll(l => l.Log(message)); } + public void Raw(string message) + { + OnAll(l => l.Raw(message)); + } + private void OnAll(Action action) { foreach (var t in targetLogs) action(t); diff --git a/Framework/Logging/NullLog.cs b/Framework/Logging/NullLog.cs index a9cf8d2d..f8094f32 100644 --- a/Framework/Logging/NullLog.cs +++ b/Framework/Logging/NullLog.cs @@ -2,11 +2,9 @@ { public class NullLog : BaseLog { - public string FullFilename { get; set; } = "NULL"; - - protected override string GetFullName() + public override string GetFullName() { - return FullFilename; + return "NULL"; } public override void Log(string message) diff --git a/Tests/CodexContinuousTests/ContinuousTestRunner.cs b/Tests/CodexContinuousTests/ContinuousTestRunner.cs index fde65aa3..edec4e43 100644 --- a/Tests/CodexContinuousTests/ContinuousTestRunner.cs +++ b/Tests/CodexContinuousTests/ContinuousTestRunner.cs @@ -27,7 +27,7 @@ namespace ContinuousTests var startTime = DateTime.UtcNow; var overviewLog = new LogSplitter( - new FixtureLog(logConfig, startTime, config.CodexDeployment.Id, "Overview"), + FixtureLog.Create(logConfig, startTime, config.CodexDeployment.Id, "Overview"), new ConsoleLog() ); var statusLog = new StatusLog(logConfig, startTime, "continuous-tests", config.CodexDeployment.Id, diff --git a/Tests/CodexContinuousTests/SingleTestRun.cs b/Tests/CodexContinuousTests/SingleTestRun.cs index 0606c8e4..93870406 100644 --- a/Tests/CodexContinuousTests/SingleTestRun.cs +++ b/Tests/CodexContinuousTests/SingleTestRun.cs @@ -37,7 +37,7 @@ namespace ContinuousTests this.handle = handle; this.cancelToken = cancelToken; testName = handle.Test.GetType().Name; - fixtureLog = new FixtureLog(new LogConfig(config.LogPath), DateTime.UtcNow, deployId, testName); + fixtureLog = FixtureLog.Create(new LogConfig(config.LogPath), DateTime.UtcNow, deployId, testName); entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, config.CodexDeployment.Metadata.KubeNamespace, fixtureLog); ApplyLogReplacements(fixtureLog, startupChecker); @@ -81,17 +81,11 @@ namespace ContinuousTests OverviewLog($" > Test passed. ({Time.FormatDuration(duration)})"); UpdateStatusLogPassed(testStart, duration); - if (!config.KeepPassedTestLogs) - { - fixtureLog.Delete(); - } - resultHandler(true); } catch (Exception ex) { fixtureLog.Error("Test run failed with exception: " + ex); - fixtureLog.MarkAsFailed(); UpdateStatusLogFailed(testStart, duration, ex.ToString()); DownloadContainerLogs(testStart); diff --git a/Tests/CodexContinuousTests/StartupChecker.cs b/Tests/CodexContinuousTests/StartupChecker.cs index 0914b3d1..2febf14e 100644 --- a/Tests/CodexContinuousTests/StartupChecker.cs +++ b/Tests/CodexContinuousTests/StartupChecker.cs @@ -22,7 +22,7 @@ namespace ContinuousTests public void Check() { - var log = new FixtureLog(new LogConfig(config.LogPath), DateTime.UtcNow, config.CodexDeployment.Id, + var log = FixtureLog.Create(new LogConfig(config.LogPath), DateTime.UtcNow, config.CodexDeployment.Id, "StartupChecks"); log.Log("Starting continuous test run..."); IncludeDeploymentConfiguration(log); @@ -90,7 +90,7 @@ namespace ContinuousTests } } - private void CheckCodexNodes(BaseLog log, Configuration config) + private void CheckCodexNodes(ILog log, Configuration config) { throw new NotImplementedException(); diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 691f19ed..534eb74a 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -33,7 +33,7 @@ namespace DistTestCore var logConfig = configuration.GetLogConfig(); var startTime = DateTime.UtcNow; - fixtureLog = new FixtureLog(logConfig, startTime, deployId); + fixtureLog = FixtureLog.Create(logConfig, startTime, deployId); statusLog = new StatusLog(logConfig, startTime, "dist-tests", deployId); globalEntryPoint = new EntryPoint(fixtureLog, configuration.GetK8sConfiguration(new DefaultK8sTimeSet(), TestNamespacePrefix), configuration.GetFileManagerFolder()); @@ -44,7 +44,7 @@ namespace DistTestCore [OneTimeSetUp] public void GlobalSetup() { - fixtureLog.Log($"Distributed Tests are starting..."); + fixtureLog.Log($"Starting..."); globalEntryPoint.Announce(); // Previous test run may have been interrupted. @@ -87,7 +87,15 @@ namespace DistTestCore } else { - CreateNewTestLifecycle(); + try + { + CreateNewTestLifecycle(); + } + catch (Exception ex) + { + fixtureLog.Error("Setup failed: " + ex); + GlobalTestFailure.HasFailed = true; + } } } @@ -236,11 +244,6 @@ namespace DistTestCore Log(result.Message); Log($"{result.StackTrace}"); } - - if (result.Outcome.Status == TestStatus.Failed) - { - log.MarkAsFailed(); - } } private IWebCallTimeSet GetWebCallTimeSet() @@ -296,11 +299,6 @@ namespace DistTestCore private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) { var testStatus = TestContext.CurrentContext.Result.Outcome.Status; - if (testStatus == TestStatus.Failed) - { - fixtureLog.MarkAsFailed(); - } - if (ShouldDownloadAllLogs(testStatus)) { lifecycle.Log.Log("Downloading all container logs..."); diff --git a/Tests/DistTestCore/Logs/BaseTestLog.cs b/Tests/DistTestCore/Logs/BaseTestLog.cs index 22485545..e670e0c1 100644 --- a/Tests/DistTestCore/Logs/BaseTestLog.cs +++ b/Tests/DistTestCore/Logs/BaseTestLog.cs @@ -2,27 +2,83 @@ namespace DistTestCore.Logs { - public abstract class BaseTestLog : BaseLog + public abstract class BaseTestLog : ILog { - private bool hasFailed; - private readonly string deployId; + private readonly ILog backingLog; - protected BaseTestLog(string deployId) + protected BaseTestLog(ILog backingLog, string deployId) { - this.deployId = deployId; + this.backingLog = backingLog; + + DeployId = deployId; + } + + public string DeployId { get; } + + public void AddStringReplace(string from, string to) + { + backingLog.AddStringReplace(from, to); + } + + public LogFile CreateSubfile(string addName, string ext = "log") + { + return backingLog.CreateSubfile(addName, ext); + } + + public void Debug(string message = "", int skipFrames = 0) + { + backingLog.Debug(message, skipFrames); + } + + public void Error(string message) + { + backingLog.Error(message); + } + + public string GetFullName() + { + return backingLog.GetFullName(); + } + + public void Log(string message) + { + backingLog.Log(message); + } + + public void Raw(string message) + { + backingLog.Raw(message); } public void WriteLogTag() { var category = NameUtils.GetCategoryName(); var name = NameUtils.GetTestMethodName(); - LogFile.WriteRaw($"{deployId} {category} {name}"); + backingLog.Raw($"{DeployId} {category} {name}"); } - public void MarkAsFailed() + protected static ILog CreateMainLog(string fullName, string name) { - if (hasFailed) return; - hasFailed = true; + ILog log = new FileLog(fullName); + log = ApplyConsoleOutput(log); + return log; + } + + private static ILog ApplyConsoleOutput(ILog log) + { + // If we're running as a release test, we'll split the log output + // to the console as well. + + var testType = Environment.GetEnvironmentVariable("TEST_TYPE"); + if (string.IsNullOrEmpty(testType) || testType.ToLowerInvariant() != "release-tests") + { + return log; + } + + return new LogSplitter( + log, + new ConsoleLog() + ); } } } diff --git a/Tests/DistTestCore/Logs/FixtureLog.cs b/Tests/DistTestCore/Logs/FixtureLog.cs index 6559f81d..9d3c77d3 100644 --- a/Tests/DistTestCore/Logs/FixtureLog.cs +++ b/Tests/DistTestCore/Logs/FixtureLog.cs @@ -4,28 +4,26 @@ namespace DistTestCore.Logs { public class FixtureLog : BaseTestLog { - private readonly string fullName; + private readonly ILog backingLog; private readonly string deployId; - public FixtureLog(LogConfig config, DateTime start, string deployId, string name = "") : base(deployId) + public FixtureLog(ILog backingLog, string deployId) + : base(backingLog, deployId) { + this.backingLog = backingLog; this.deployId = deployId; - fullName = NameUtils.GetFixtureFullName(config, start, name); } public TestLog CreateTestLog(string name = "") { - return new TestLog(fullName, deployId, name); + return TestLog.Create(this, name); } - public void DeleteFolder() + public static FixtureLog Create(LogConfig config, DateTime start, string deployId, string name = "") { - Directory.Delete(fullName, true); - } - - protected override string GetFullName() - { - return fullName; + var fullName = NameUtils.GetFixtureFullName(config, start, name); + var log = CreateMainLog(fullName, name); + return new FixtureLog(log, deployId); } } -} \ No newline at end of file +} diff --git a/Tests/DistTestCore/Logs/TestLog.cs b/Tests/DistTestCore/Logs/TestLog.cs index 1f598b65..0dca1464 100644 --- a/Tests/DistTestCore/Logs/TestLog.cs +++ b/Tests/DistTestCore/Logs/TestLog.cs @@ -1,20 +1,22 @@ -namespace DistTestCore.Logs +using Logging; +using System.Xml.Linq; + +namespace DistTestCore.Logs { public class TestLog : BaseTestLog { - private readonly string fullName; - - public TestLog(string folder, string deployId, string name = "") : base(deployId) + public TestLog(ILog backingLog, string methodName, string deployId, string name = "") + : base(backingLog, deployId) { - var methodName = NameUtils.GetTestMethodName(name); - fullName = Path.Combine(folder, methodName); - - Log($"*** Begin: {methodName}"); + backingLog.Log($"*** Begin: {methodName}"); } - protected override string GetFullName() + public static TestLog Create(FixtureLog parentLog, string name = "") { - return fullName; + var methodName = NameUtils.GetTestMethodName(name); + var fullName = Path.Combine(parentLog.GetFullName(), methodName); + var backingLog = CreateMainLog(fullName, name); + return new TestLog(backingLog, methodName, parentLog.DeployId); } } } diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index 058d5bfd..d27216d3 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -301,7 +301,7 @@ namespace CodexTests Stopwatch.Measure(lifecycle.Log, $"Transcript.Finalize: {outputFilepath}", () => { - writer.IncludeFile(lifecycle.Log.LogFile.Filename); + writer.IncludeFile(lifecycle.Log.GetFullName()); writer.Finalize(outputFilepath); }); } @@ -313,9 +313,9 @@ namespace CodexTests private string GetOutputFullPath(TestLifecycle lifecycle, CreateTranscriptAttribute attr) { - var outputPath = Path.GetDirectoryName(lifecycle.Log.LogFile.Filename); + var outputPath = Path.GetDirectoryName(lifecycle.Log.GetFullName()); if (outputPath == null) throw new Exception("Logfile path is null"); - var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.LogFile.Filename); + var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.GetFullName()); if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); var outputFile = Path.Combine(outputPath, filename + "_" + attr.OutputFilename); if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; From 07b57007e841a50b227755e84b799f99853f63a1 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 12:32:50 +0200 Subject: [PATCH 35/51] Attempt to set up docker to be used for pull and inspect --- docker/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 07a9123b..e7730028 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,6 +2,9 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 COPY --chmod=0755 docker/docker-entrypoint.sh / +RUN apt-get update +RUN apt-get install docker -y + ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["dotnet", "test"] From ff4693ad4dfd72a3dbd35a705b135d259fbf4aca Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 22 Apr 2025 12:52:49 +0200 Subject: [PATCH 36/51] Revert "Attempt to set up docker to be used for pull and inspect" This reverts commit 07b57007e841a50b227755e84b799f99853f63a1. --- docker/Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e7730028..07a9123b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,9 +2,6 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 COPY --chmod=0755 docker/docker-entrypoint.sh / -RUN apt-get update -RUN apt-get install docker -y - ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["dotnet", "test"] From 6e45c638e7fc56779068da743d173bfac8bf0e09 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 22 Apr 2025 17:11:34 +0200 Subject: [PATCH 37/51] Switches from docker image label to debug-info version field --- ProjectPlugins/CodexClient/CodexTypes.cs | 1 + ProjectPlugins/CodexClient/Mapper.cs | 5 +- .../CodexContractsContainerRecipe.cs | 16 ++- .../CodexContractsPlugin.cs | 17 +-- .../CodexContractsPlugin.csproj | 1 + .../CodexContractsStarter.cs | 12 +- .../CoreInterfaceExtensions.cs | 16 +-- .../CodexContractsPlugin/VersionRegistry.cs | 125 ------------------ .../CodexPlugin/CodexDockerImage.cs | 6 +- ProjectPlugins/CodexPlugin/CodexPlugin.cs | 1 - .../DataTests/DataExpiryTest.cs | 3 +- .../MarketplaceAutoBootstrapDistTest.cs | 5 +- .../AutoBootstrapDistTest.cs | 21 +-- .../BasicTests/MarketplaceTests.cs | 2 +- .../FullyConnectedDownloadTests.cs | 2 +- .../PeerDiscoveryTests/PeerDiscoveryTests.cs | 2 +- Tools/CodexNetDeployer/Deployer.cs | 6 +- 17 files changed, 60 insertions(+), 181 deletions(-) delete mode 100644 ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs diff --git a/ProjectPlugins/CodexClient/CodexTypes.cs b/ProjectPlugins/CodexClient/CodexTypes.cs index 9085aa2c..f9a0d9d2 100644 --- a/ProjectPlugins/CodexClient/CodexTypes.cs +++ b/ProjectPlugins/CodexClient/CodexTypes.cs @@ -17,6 +17,7 @@ namespace CodexClient { public string Version { get; set; } = string.Empty; public string Revision { get; set; } = string.Empty; + public string Contracts { get; set; } = string.Empty; public bool IsValid() { diff --git a/ProjectPlugins/CodexClient/Mapper.cs b/ProjectPlugins/CodexClient/Mapper.cs index 43bb15d0..47b1215c 100644 --- a/ProjectPlugins/CodexClient/Mapper.cs +++ b/ProjectPlugins/CodexClient/Mapper.cs @@ -165,10 +165,13 @@ namespace CodexClient private DebugInfoVersion Map(CodexOpenApi.CodexVersion obj) { + throw new Exception("waiting for new codex image with contracts revision"); + return new DebugInfoVersion { Version = obj.Version, - Revision = obj.Revision + Revision = obj.Revision, + Contracts = "aaa" }; } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs index e09a0ee7..b46f3c96 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs @@ -1,4 +1,5 @@ -using GethPlugin; +using CodexClient; +using GethPlugin; using KubernetesWorkflow; using KubernetesWorkflow.Recipe; @@ -8,14 +9,14 @@ namespace CodexContractsPlugin { public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json"; public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json"; - private readonly VersionRegistry versionRegistry; + private readonly DebugInfoVersion versionInfo; public override string AppName => "codex-contracts"; - public override string Image => versionRegistry.GetContractsDockerImage(); + public override string Image => GetContractsDockerImage(); - public CodexContractsContainerRecipe(VersionRegistry versionRegistry) + public CodexContractsContainerRecipe(DebugInfoVersion versionInfo) { - this.versionRegistry = versionRegistry; + this.versionInfo = versionInfo; } protected override void Initialize(StartupConfig startupConfig) @@ -30,5 +31,10 @@ namespace CodexContractsPlugin AddEnvVar("HARDHAT_NETWORK", "codexdisttestnetwork"); AddEnvVar("KEEP_ALIVE", "1"); } + + private string GetContractsDockerImage() + { + return $"codexstorage/codex-contracts-eth:sha-{versionInfo.Contracts}-dist-tests"; + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs index 6e02280d..1d122d3b 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs @@ -7,15 +7,11 @@ namespace CodexContractsPlugin { private readonly IPluginTools tools; private readonly CodexContractsStarter starter; - private readonly VersionRegistry versionRegistry; - private readonly CodexContractsContainerRecipe recipe; public CodexContractsPlugin(IPluginTools tools) { this.tools = tools; - versionRegistry = new VersionRegistry(tools.GetLog()); - recipe = new CodexContractsContainerRecipe(versionRegistry); - starter = new CodexContractsStarter(tools, recipe); + starter = new CodexContractsStarter(tools); } public string LogPrefix => "(CodexContracts) "; @@ -31,16 +27,16 @@ namespace CodexContractsPlugin public void AddMetadata(IAddMetadata metadata) { - metadata.Add("codexcontractsid", recipe.Image); + metadata.Add("codexcontractsid", "dynamic"); } public void Decommission() { } - public CodexContractsDeployment DeployContracts(CoreInterface ci, IGethNode gethNode) + public CodexContractsDeployment DeployContracts(CoreInterface ci, IGethNode gethNode, CodexClient.DebugInfoVersion versionInfo) { - return starter.Deploy(ci, gethNode); + return starter.Deploy(ci, gethNode, versionInfo); } public ICodexContracts WrapDeploy(IGethNode gethNode, CodexContractsDeployment deployment) @@ -48,10 +44,5 @@ namespace CodexContractsPlugin deployment = SerializeGate.Gate(deployment); return starter.Wrap(gethNode, deployment); } - - public void SetCodexDockerImageProvider(ICodexDockerImageProvider provider) - { - versionRegistry.SetProvider(provider); - } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj index 24f87068..34f6cad1 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj @@ -13,6 +13,7 @@ + diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index 5ecc22a9..c7e900be 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -1,4 +1,5 @@ -using CodexContractsPlugin.Marketplace; +using CodexClient; +using CodexContractsPlugin.Marketplace; using Core; using GethPlugin; using KubernetesWorkflow; @@ -12,15 +13,13 @@ namespace CodexContractsPlugin public class CodexContractsStarter { private readonly IPluginTools tools; - private readonly CodexContractsContainerRecipe recipe; - public CodexContractsStarter(IPluginTools tools, CodexContractsContainerRecipe recipe) + public CodexContractsStarter(IPluginTools tools) { this.tools = tools; - this.recipe = recipe; } - public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode) + public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { Log("Starting Codex SmartContracts container..."); @@ -28,6 +27,9 @@ namespace CodexContractsPlugin var startupConfig = CreateStartupConfig(gethNode); startupConfig.NameOverride = "codex-contracts"; + var recipe = new CodexContractsContainerRecipe(versionInfo); + Log($"Using image: {recipe.Image}"); + var containers = workflow.Start(1, recipe, 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/CodexContractsPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs index ea123bc9..d07e25f7 100644 --- a/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs @@ -1,13 +1,14 @@ -using Core; +using CodexClient; +using Core; using GethPlugin; namespace CodexContractsPlugin { public static class CoreInterfaceExtensions { - public static CodexContractsDeployment DeployCodexContracts(this CoreInterface ci, IGethNode gethNode) + public static CodexContractsDeployment DeployCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { - return Plugin(ci).DeployContracts(ci, gethNode); + return Plugin(ci).DeployContracts(ci, gethNode, versionInfo); } public static ICodexContracts WrapCodexContractsDeployment(this CoreInterface ci, IGethNode gethNode, CodexContractsDeployment deployment) @@ -15,17 +16,12 @@ namespace CodexContractsPlugin return Plugin(ci).WrapDeploy(gethNode, deployment); } - public static ICodexContracts StartCodexContracts(this CoreInterface ci, IGethNode gethNode) + public static ICodexContracts StartCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { - var deployment = DeployCodexContracts(ci, gethNode); + var deployment = DeployCodexContracts(ci, gethNode, versionInfo); return WrapCodexContractsDeployment(ci, gethNode, deployment); } - public static void SetCodexDockerImageProvider(this CoreInterface ci, ICodexDockerImageProvider provider) - { - Plugin(ci).SetCodexDockerImageProvider(provider); - } - private static CodexContractsPlugin Plugin(CoreInterface ci) { return ci.GetPlugin(); diff --git a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs deleted file mode 100644 index f52a38a8..00000000 --- a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Diagnostics; -using Logging; - -namespace CodexContractsPlugin -{ - public interface ICodexDockerImageProvider - { - string GetCodexDockerImage(); - } - - public class VersionRegistry - { - private ICodexDockerImageProvider provider = new ExceptionProvider(); - private static readonly Dictionary cache = new Dictionary(); - private static readonly object cacheLock = new object(); - private readonly ILog log; - - public VersionRegistry(ILog log) - { - this.log = log; - } - - public void SetProvider(ICodexDockerImageProvider provider) - { - this.provider = provider; - } - - public string GetContractsDockerImage() - { - try - { - var codexImage = provider.GetCodexDockerImage(); - return GetContractsDockerImage(codexImage); - } - catch (Exception exc) - { - throw new Exception("Failed to get contracts docker image.", exc); - } - } - - private string GetContractsDockerImage(string codexImage) - { - lock (cacheLock) - { - if (cache.TryGetValue(codexImage, out string? value)) - { - return value; - } - var result = GetContractsImage(codexImage); - cache.Add(codexImage, result); - return result; - } - } - - private string GetContractsImage(string codexImage) - { - var inspectResult = InspectCodexImage(codexImage); - var image = ParseCodexContractsImageName(inspectResult); - log.Log($"From codex docker image '{codexImage}', determined codex-contracts docker image: '{image}'"); - return image; - } - - private string InspectCodexImage(string img) - { - Execute("docker", $"pull {img}"); - return Execute("docker", $"inspect {img}"); - } - - private string ParseCodexContractsImageName(string inspectResult) - { - // It is a nice json structure. But we only need this one line. - // "storage.codex.nim-codex.blockchain-image": "codexstorage/codex-contracts-eth:sha-0bf1385-dist-tests" - var lines = inspectResult.Split('\n', StringSplitOptions.RemoveEmptyEntries); - var line = lines.Single(l => l.Contains("storage.codex.nim-codex.blockchain-image")); - var tokens = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - return tokens.Last().Replace("\"", "").Trim(); - } - - private string Execute(string cmd, string args) - { - var startInfo = new ProcessStartInfo( - fileName: cmd, - arguments: args - ); - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - - var process = Process.Start(startInfo); - if (process == null) - { - throw new Exception("Failed to start: " + cmd + args); - } - KillAfterTimeout(process); - - process.WaitForExit(); - return process.StandardOutput.ReadToEnd(); - } - - private void KillAfterTimeout(Process process) - { - // There's a known issue that some docker commands on some platforms - // will fail to stop on their own. This has been known since 2019 and it's not fixed. - // So we will issue a kill to the process ourselves if it exceeds a timeout. - - Task.Run(() => - { - Thread.Sleep(TimeSpan.FromSeconds(30.0)); - - if (process != null && !process.HasExited) - { - process.Kill(); - } - }); - } - } - - internal class ExceptionProvider : ICodexDockerImageProvider - { - public string GetCodexDockerImage() - { - throw new InvalidOperationException("CodexContractsPlugin has not yet received a CodexDockerImageProvider " + - "and so cannot select a compatible contracts docker image."); - } - } -} diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index 5ed36c92..bbb09c0b 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -1,8 +1,6 @@ -using CodexContractsPlugin; - -namespace CodexPlugin +namespace CodexPlugin { - public class CodexDockerImage : ICodexDockerImageProvider + public class CodexDockerImage { private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests"; diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index 277b6101..0c8f4510 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -42,7 +42,6 @@ namespace CodexPlugin public void Awake(IPluginAccess access) { - access.GetPlugin().SetCodexDockerImageProvider(codexDockerImage); } public void Announce() diff --git a/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs b/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs index 263a9e8b..4b9139e6 100644 --- a/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs +++ b/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs @@ -50,8 +50,9 @@ namespace CodexReleaseTests.DataTests var blockTtl = TimeSpan.FromMinutes(1.0); var interval = TimeSpan.FromSeconds(10.0); + var bootstrapNode = StartCodex(); var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, bootstrapNode.Version); var node = StartCodex(s => s .EnableMarketplace(geth, contracts, m => m.WithInitial(100.Eth(), 100.Tst())) .WithBlockTTL(blockTtl) diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index e6711e34..a5ec0885 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -6,7 +6,6 @@ using CodexTests; using DistTestCore; using GethPlugin; using Nethereum.Hex.HexConvertors.Extensions; -using NUnit.Framework; using Utils; namespace CodexReleaseTests.MarketTests @@ -21,14 +20,14 @@ namespace CodexReleaseTests.MarketTests { base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); } protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) { - base.LifecycleStop(lifecycle, result); handles.Remove(lifecycle); + base.LifecycleStop(lifecycle, result); } protected IGethNode GetGeth() diff --git a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs index d39c31ec..d8ac6805 100644 --- a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs +++ b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs @@ -1,37 +1,40 @@ using CodexClient; using CodexPlugin; using DistTestCore; -using NUnit.Framework; namespace CodexTests { public class AutoBootstrapDistTest : CodexDistTest { private readonly Dictionary bootstrapNodes = new Dictionary(); + private bool isBooting = false; - [SetUp] - public void SetUpBootstrapNode() + protected override void LifecycleStart(TestLifecycle tl) { - var tl = Get(); + base.LifecycleStart(tl); if (!bootstrapNodes.ContainsKey(tl)) { + isBooting = true; bootstrapNodes.Add(tl, StartCodex(s => s.WithName("BOOTSTRAP_" + tl.TestNamespace))); + isBooting = false; } } - [TearDown] - public void TearDownBootstrapNode() + protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) { - bootstrapNodes.Remove(Get()); + bootstrapNodes.Remove(lifecycle); + base.LifecycleStop(lifecycle, result); } protected override void OnCodexSetup(ICodexSetup setup) { + if (isBooting) return; + var node = BootstrapNode; if (node != null) setup.WithBootstrapNode(node); } - protected ICodexNode? BootstrapNode + protected ICodexNode BootstrapNode { get { @@ -40,7 +43,7 @@ namespace CodexTests { return node; } - return null; + throw new InvalidOperationException("Bootstrap node not yet started."); } } } diff --git a/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs index f8ef52d5..af213342 100644 --- a/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs +++ b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs @@ -31,7 +31,7 @@ namespace ExperimentalTests.BasicTests ); var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth")); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var numberOfHosts = 5; var hosts = StartCodex(numberOfHosts, s => s diff --git a/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs index ec128154..d0acc991 100644 --- a/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs +++ b/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs @@ -21,7 +21,7 @@ namespace ExperimentalTests.DownloadConnectivityTests public void MarketplaceDoesNotInterfereWithPeerDownload() { var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); diff --git a/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 901504c7..60a9be9a 100644 --- a/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -31,7 +31,7 @@ namespace ExperimentalTests.PeerDiscoveryTests public void MarketplaceDoesNotInterfereWithPeerDiscovery() { var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 753a1371..0d78962e 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -64,8 +64,12 @@ namespace CodexNetDeployer var gethDeployment = DeployGeth(ci); var gethNode = ci.WrapGethDeployment(gethDeployment, new BlockCache()); + var bootNode = ci.StartCodexNode(); + var versionInfo = bootNode.GetDebugInfo().Version; + bootNode.Stop(waitTillStopped: true); + Log("Geth started. Deploying Codex contracts..."); - var contractsDeployment = ci.DeployCodexContracts(gethNode); + var contractsDeployment = ci.DeployCodexContracts(gethNode, versionInfo); var contracts = ci.WrapCodexContractsDeployment(gethNode, contractsDeployment); Log("Codex contracts deployed."); From 9da87709c5f19e6172484fc87dc7694cb9df5030 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 22 Apr 2025 19:49:03 +0200 Subject: [PATCH 38/51] Implements using debug-info contracts version in codex-contracts plugin --- ProjectPlugins/CodexClient/Mapper.cs | 4 +--- ProjectPlugins/CodexClient/openapi.yaml | 3 +++ ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ProjectPlugins/CodexClient/Mapper.cs b/ProjectPlugins/CodexClient/Mapper.cs index 47b1215c..16c156bd 100644 --- a/ProjectPlugins/CodexClient/Mapper.cs +++ b/ProjectPlugins/CodexClient/Mapper.cs @@ -165,13 +165,11 @@ namespace CodexClient private DebugInfoVersion Map(CodexOpenApi.CodexVersion obj) { - throw new Exception("waiting for new codex image with contracts revision"); - return new DebugInfoVersion { Version = obj.Version, Revision = obj.Revision, - Contracts = "aaa" + Contracts = obj.Contracts }; } diff --git a/ProjectPlugins/CodexClient/openapi.yaml b/ProjectPlugins/CodexClient/openapi.yaml index fd66648e..d59cbcfa 100644 --- a/ProjectPlugins/CodexClient/openapi.yaml +++ b/ProjectPlugins/CodexClient/openapi.yaml @@ -124,6 +124,9 @@ components: revision: type: string example: 0c647d8 + contracts: + type: string + example: 0b537c7 PeersTable: type: object diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 656c6588..4b190b34 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -10,7 +10,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "1A-F7-DF-C3-E1-C6-98-FF-32-20-21-9B-26-40-B0-51-08-35-C2-E7-DB-41-49-93-60-A9-CE-47-B5-AD-3D-A3"; + private const string OpenApiYamlHash = "06-B9-41-E8-C8-6C-DE-01-86-83-F3-9A-E4-AC-E7-30-D9-E6-64-60-E0-21-81-9E-4E-C5-93-77-2C-71-79-14"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; From c630d4c81ea2b0a5fb57abd05e7ebcc78074c391 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 23 Apr 2025 09:04:23 +0200 Subject: [PATCH 39/51] Asks user to set their eth address after passing a check --- .../CodexChecking/CodexTwoWayChecker.cs | 4 ++-- .../Commands/CheckResponseHandler.cs | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index c5b9361a..83175803 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -8,7 +8,7 @@ namespace BiblioTech.CodexChecking public interface ICheckResponseHandler { Task CheckNotStarted(); - Task NowCompleted(ulong userId, string checkName); + Task NowCompleted(string checkName); Task GiveRoleReward(); Task InvalidData(); @@ -192,7 +192,7 @@ namespace BiblioTech.CodexChecking private async Task CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId, string checkName) { - await handler.NowCompleted(userId, checkName); + await handler.NowCompleted(checkName); check.CompletedUtc = DateTime.UtcNow; repo.SaveChanges(); diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index 5428e86b..f7b1c4f0 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -74,10 +74,24 @@ namespace BiblioTech.Commands await context.Followup("The received data didn't match. Check has failed."); } - public async Task NowCompleted(ulong userId, string checkName) + public async Task NowCompleted(string checkName) { - await context.Followup("Successfully completed the check!"); - await Program.AdminChecker.SendInAdminChannel($"User <@{userId}> has completed check: {checkName}"); + // check if eth address is known for user. + var data = Program.UserRepo.GetUser(user); + if (data.CurrentAddress == null) + { + await context.Followup($"Successfully completed the check!{Environment.NewLine}" + + $"You haven't yet set your ethereum address. Consider using '/set' to set it.{Environment.NewLine}" + + $"(You can find your address in the 'eth.address' file of your Codex node.)"); + + await Program.AdminChecker.SendInAdminChannel($"User <@{user.Id}> has completed check: {checkName}" + + $" - EthAddress not set for user. User was reminded."); + } + else + { + await context.Followup("Successfully completed the check!"); + await Program.AdminChecker.SendInAdminChannel($"User <@{user.Id}> has completed check: {checkName}"); + } } public async Task ToAdminChannel(string msg) From 9446671b186d5dce78735ce684f5b20e94bb67b5 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 23 Apr 2025 09:07:19 +0200 Subject: [PATCH 40/51] Regenerates unique data for new checks --- .../BiblioTech/CodexChecking/CodexTwoWayChecker.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index 83175803..ac83ccd9 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -37,7 +37,7 @@ namespace BiblioTech.CodexChecking public async Task StartDownloadCheck(ICheckResponseHandler handler, ulong userId) { var check = repo.GetOrCreate(userId).DownloadCheck; - if (string.IsNullOrEmpty(check.UniqueData)) + if (IsUniqueDataStale(check)) { check.UniqueData = GenerateUniqueData(); repo.SaveChanges(); @@ -69,7 +69,7 @@ namespace BiblioTech.CodexChecking public async Task StartUploadCheck(ICheckResponseHandler handler, ulong userId) { var check = repo.GetOrCreate(userId).UploadCheck; - if (string.IsNullOrEmpty(check.UniqueData)) + if (IsUniqueDataStale(check)) { check.UniqueData = GenerateUniqueData(); repo.SaveChanges(); @@ -111,6 +111,15 @@ namespace BiblioTech.CodexChecking return $"{RandomBusyMessage.Get().Substring(5)}{RandomUtils.GenerateRandomString(12)}"; } + private bool IsUniqueDataStale(TransferCheck check) + { + var expiry = DateTime.UtcNow - TimeSpan.FromMinutes(10.0); + + return + string.IsNullOrEmpty(check.UniqueData) || + check.CompletedUtc < expiry; + } + private string UploadData(string uniqueData) { var filePath = Path.Combine(config.ChecksDataPath, Guid.NewGuid().ToString()); From 7eb3bbd1deeb4cb6bd7d95561b4ef1294d0696a2 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 23 Apr 2025 10:22:23 +0200 Subject: [PATCH 41/51] Setup example in lifecycelytest --- Tests/CodexReleaseTests/Parallelism.cs | 2 +- Tests/FrameworkTests/LifecycelyTest.cs | 226 +++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 Tests/FrameworkTests/LifecycelyTest.cs diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index a1b26c73..f6e68cc0 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; [assembly: LevelOfParallelism(1)] -namespace CodexReleaseTests.DataTests +namespace CodexReleaseTests { } diff --git a/Tests/FrameworkTests/LifecycelyTest.cs b/Tests/FrameworkTests/LifecycelyTest.cs new file mode 100644 index 00000000..ef90a48c --- /dev/null +++ b/Tests/FrameworkTests/LifecycelyTest.cs @@ -0,0 +1,226 @@ +using NUnit.Framework; + +namespace FrameworkTests +{ + [Parallelizable(ParallelScope.All)] + [TestFixture(10)] + [TestFixture(20)] + [TestFixture(30)] + public class LifecycelyTest + { + public LifecycelyTest(int num) + { + Log("ctor", GetCurrentTestName(), num); + this.num = num; + } + + [SetUp] + public void Setup() + { + Log(nameof(Setup), GetCurrentTestName()); + } + + [TearDown] + public void TearDown() + { + Log(nameof(TearDown), GetCurrentTestName()); + } + + [Test] + public void A() + { + Log(nameof(A), "Run"); + SleepRandom(); + Log(nameof(A), "Finish"); + } + + [Test] + public void B() + { + Log(nameof(B), "Run"); + SleepRandom(); + Log(nameof(B), "Finish"); + } + + [Test] + public void C() + { + Log(nameof(C), "Run"); + SleepRandom(); + Log(nameof(C), "Finish"); + } + + [Test] + [Combinatorial] + public void Multi( + [Values(1, 2, 3)] int num) + { + Log(nameof(Multi), "Run", num); + SleepRandom(); + Log(nameof(Multi), "Finish", num); + } + + + + + + + + + + + + + private static readonly Random r = new Random(); + private readonly int num; + + private void SleepRandom() + { + Thread.Sleep(TimeSpan.FromSeconds(5.0)); + Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); + } + + private void Log(string scope, string msg) + { + ALog.Log($"{num} {scope} {msg}"); + } + + private void Log(string scope, string msg, int num) + { + ALog.Log($"{this.num} {scope} {msg} {num}"); + } + + private string GetCurrentTestName() + { + return $"[{TestContext.CurrentContext.Test.Name}]"; + } + } + + + + + public class ALog + { + private static readonly object _lock = new object(); + + public static void Log(string msg) + { + lock (_lock) + { + File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); + } + } + } + + + + + + + + + + + + public interface ITestLifecycleComponent + { + void Start(); + void Stop(string results); + } + + + public class Base + { + private readonly Dictionary> anyFields = new(); + + public void Setup() + { + var testId = 23; + + var fields = new Dictionary(); + anyFields.Add(testId, fields); + YieldFields(field => + { + fields.Add(field.GetType(), field); + }); + + foreach (var field in fields.Values) + { + field.Start(); + } + } + + public void TearDown() + { + var testId = 23; + + // foreach stop + + anyFields.Remove(testId); + } + + public T Get() + { + int testId = 123; + var fields = anyFields[testId]; + var type = typeof(T); + var result = fields[type]; + return (T)result; + } + + public BaseFields GetBaseField() + { + return Get(); + } + + protected virtual void YieldFields(Action giveField) + { + giveField(new BaseFields()); + } + } + + public class Mid : Base + { + protected override void YieldFields(Action giveField) + { + base.YieldFields(giveField); + giveField(new MidFields()); + } + + public MidFields GetMid() + { + return Get(); + } + } + + public class Top : Mid + { + protected override void YieldFields(Action giveField) + { + base.YieldFields(giveField); + giveField(new TopFields()); + } + + public TopFields GetTop() + { + return Get(); + } + } + + public class BaseFields : ITestLifecycleComponent + { + public string EntryPoint { get; set; } = string.Empty; + public string Log { get; set; } = string.Empty; + } + + public class MidFields : ITestLifecycleComponent + { + public string Nodes { get; set; } = string.Empty; + } + + public class TopFields : ITestLifecycleComponent + { + public string Geth { get; set; } = string.Empty; + public string Contracts { get; set; } = string.Empty; + } +} From 2dfdfac2bb52e4ff919ee5f1b3986a839d71b0a2 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 23 Apr 2025 14:18:11 +0200 Subject: [PATCH 42/51] while better the new plan is still a mess --- .../OverwatchSupport/CodexTranscriptWriter.cs | 15 +- .../CodexTranscriptWriterConfig.cs | 4 +- .../MarketplaceAutoBootstrapDistTest.cs | 39 +- Tests/DistTestCore/DistTest.cs | 124 ++----- .../DistTestLifecycleComponents.cs | 97 +++++ Tests/DistTestCore/Logs/FixtureLog.cs | 13 +- Tests/DistTestCore/Logs/TestLog.cs | 2 - Tests/DistTestCore/TestLifecycle.cs | 30 +- .../AutoBootstrapDistTest.cs | 58 +-- Tests/ExperimentalTests/CodexDistTest.cs | 215 ++++------- .../CodexLogTrackerProvider.cs | 73 ++++ Tests/FrameworkTests/LifecycelyTest.cs | 334 +++++++++--------- Tests/FrameworkTests/Parallelism.cs | 6 + 13 files changed, 549 insertions(+), 461 deletions(-) create mode 100644 Tests/DistTestCore/DistTestLifecycleComponents.cs create mode 100644 Tests/ExperimentalTests/CodexLogTrackerProvider.cs create mode 100644 Tests/FrameworkTests/Parallelism.cs diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs index afd8d1a2..53ac1496 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs @@ -23,16 +23,27 @@ namespace CodexPlugin.OverwatchSupport converter = new CodexLogConverter(writer, config, identityMap); } - public void Finalize(string outputFilepath) + public void Finalize() { log.Log("Finalizing Codex transcript..."); writer.AddHeader(CodexHeaderKey, CreateCodexHeader()); - writer.Write(outputFilepath); + writer.Write(GetOutputFullPath()); log.Log("Done"); } + private string GetOutputFullPath() + { + var outputPath = Path.GetDirectoryName(log.GetFullName()); + if (outputPath == null) throw new Exception("Logfile path is null"); + var filename = Path.GetFileNameWithoutExtension(log.GetFullName()); + if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); + var outputFile = Path.Combine(outputPath, filename + "_" + config.OutputFilename); + if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; + return outputFile; + } + public ICodexNodeHooks CreateHooks(string nodeName) { nodeName = Str.Between(nodeName, "'", "'"); diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs index 247494c8..8c5e7bf0 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs @@ -2,11 +2,13 @@ { public class CodexTranscriptWriterConfig { - public CodexTranscriptWriterConfig(bool includeBlockReceivedEvents) + public CodexTranscriptWriterConfig(string outputFilename, bool includeBlockReceivedEvents) { + OutputFilename = outputFilename; IncludeBlockReceivedEvents = includeBlockReceivedEvents; } + public string OutputFilename { get; } public bool IncludeBlockReceivedEvents { get; } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index a5ec0885..f6e8a9ca 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -10,18 +10,41 @@ using Utils; namespace CodexReleaseTests.MarketTests { + public class MarketplaceTestComponent : ILifecycleComponent + { + public IGethNode Geth { get; } + public ICodexContracts Contracts { get; } + + public void Start(ILifecycleComponentAccess access) + { + throw new NotImplementedException(); + } + + public void Stop(ILifecycleComponentAccess access, DistTestResult result) + { + throw new NotImplementedException(); + } + } + public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest { - private readonly Dictionary handles = new Dictionary(); protected const int StartingBalanceTST = 1000; protected const int StartingBalanceEth = 10; + protected override void CreateComponents(ILifecycleComponentCollector collector) + { + base.CreateComponents(collector); + + + collector.AddComponent(new MarketplaceTestComponent()); + } + protected override void LifecycleStart(TestLifecycle lifecycle) { base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); - handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); + handles.Add(lifecycle, new MarketplaceTestComponent(geth, contracts)); } protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) @@ -323,17 +346,5 @@ namespace CodexReleaseTests.MarketTests public SlotFilledEventDTO SlotFilledEvent { get; } public ICodexNode Host { get; } } - - private class MarketplaceHandle - { - public MarketplaceHandle(IGethNode geth, ICodexContracts contracts) - { - Geth = geth; - Contracts = contracts; - } - - public IGethNode Geth { get; } - public ICodexContracts Contracts { get; } - } } } diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 534eb74a..9a969532 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -19,9 +19,8 @@ namespace DistTestCore private readonly Assembly[] testAssemblies; private readonly FixtureLog fixtureLog; private readonly StatusLog statusLog; - private readonly object lifecycleLock = new object(); private readonly EntryPoint globalEntryPoint; - private readonly Dictionary lifecycles = new Dictionary(); + private readonly DistTestLifecycleComponents lifecycleComponents = new DistTestLifecycleComponents(); private readonly string deployId; public DistTest() @@ -89,7 +88,12 @@ namespace DistTestCore { try { - CreateNewTestLifecycle(); + var testName = GetCurrentTestName(); + fixtureLog.WriteLogTag(); + Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => + { + lifecycleComponents.Setup(testName, CreateComponents); + }); } catch (Exception ex) { @@ -104,7 +108,7 @@ namespace DistTestCore { try { - DisposeTestLifecycle(); + Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", DisposeTestLifecycle); } catch (Exception ex) { @@ -171,80 +175,49 @@ namespace DistTestCore { } - protected virtual void LifecycleStart(TestLifecycle lifecycle) + protected virtual void CreateComponents(ILifecycleComponentCollector collector) + { + var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); + var lifecycle = new TestLifecycle( + fixtureLog.CreateTestLog(), + configuration, + GetWebCallTimeSet(), + GetK8sTimeSet(), + testNamespace, + GetCurrentTestName(), + deployId, + ShouldWaitForCleanup()); + + collector.AddComponent(lifecycle); + } + + protected virtual void DestroyComponents(TestLifecycle lifecycle, DistTestResult testResult) { } - protected virtual void LifecycleStop(TestLifecycle lifecycle, DistTestResult testResult) + public T Get() where T : ILifecycleComponent { + return lifecycleComponents.Get(GetCurrentTestName()); } - protected virtual void CollectStatusLogData(TestLifecycle lifecycle, Dictionary data) + private TestLifecycle Get() { - } - - protected TestLifecycle Get() - { - lock (lifecycleLock) - { - return lifecycles[GetCurrentTestName()]; - } - } - - private void CreateNewTestLifecycle() - { - var testName = GetCurrentTestName(); - fixtureLog.WriteLogTag(); - Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => - { - lock (lifecycleLock) - { - var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); - var lifecycle = new TestLifecycle( - fixtureLog.CreateTestLog(), - configuration, - GetWebCallTimeSet(), - GetK8sTimeSet(), - testNamespace, - deployId, - ShouldWaitForCleanup()); - lifecycles.Add(testName, lifecycle); - LifecycleStart(lifecycle); - } - }); + return Get(); } private void DisposeTestLifecycle() { + var testName = GetCurrentTestName(); + var results = GetTestResult(); var lifecycle = Get(); - var testResult = GetTestResult(); var testDuration = lifecycle.GetTestDuration(); var data = lifecycle.GetPluginMetadata(); - CollectStatusLogData(lifecycle, data); - fixtureLog.Log($"{GetCurrentTestName()} = {testResult} ({testDuration})"); - statusLog.ConcludeTest(testResult, testDuration, data); - Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", () => - { - WriteEndTestLog(lifecycle.Log); + fixtureLog.Log($"{GetCurrentTestName()} = {results} ({testDuration})"); + statusLog.ConcludeTest(results, testDuration, data); - IncludeLogsOnTestFailure(lifecycle); - LifecycleStop(lifecycle, testResult); - lifecycle.DeleteAllResources(); - lifecycles.Remove(GetCurrentTestName()); - }); + lifecycleComponents.TearDown(testName, results); } - private void WriteEndTestLog(TestLog log) - { - var result = TestContext.CurrentContext.Result; - - Log($"*** Finished: {GetCurrentTestName()} = {result.Outcome.Status}"); - if (!string.IsNullOrEmpty(result.Message)) - { - Log(result.Message); - Log($"{result.StackTrace}"); - } - } private IWebCallTimeSet GetWebCallTimeSet() { @@ -296,28 +269,6 @@ namespace DistTestCore .ToArray(); } - private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) - { - var testStatus = TestContext.CurrentContext.Result.Outcome.Status; - if (ShouldDownloadAllLogs(testStatus)) - { - lifecycle.Log.Log("Downloading all container logs..."); - lifecycle.DownloadAllLogs(); - } - } - - private bool ShouldDownloadAllLogs(TestStatus testStatus) - { - if (configuration.AlwaysDownloadContainerLogs) return true; - if (!IsDownloadingLogsEnabled()) return false; - if (testStatus == TestStatus.Failed) - { - return true; - } - - return false; - } - private string GetCurrentTestName() { return $"[{TestContext.CurrentContext.Test.Name}]"; @@ -328,7 +279,8 @@ namespace DistTestCore var success = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; var status = TestContext.CurrentContext.Result.Outcome.Status.ToString(); var result = TestContext.CurrentContext.Result.Message; - return new DistTestResult(success, status, result ?? string.Empty); + var trace = TestContext.CurrentContext.Result.StackTrace; + return new DistTestResult(success, status, result ?? string.Empty, trace ?? string.Empty); } private bool IsDownloadingLogsEnabled() @@ -339,16 +291,18 @@ namespace DistTestCore public class DistTestResult { - public DistTestResult(bool success, string status, string result) + public DistTestResult(bool success, string status, string result, string trace) { Success = success; Status = status; Result = result; + Trace = trace; } public bool Success { get; } public string Status { get; } public string Result { get; } + public string Trace { get; } public override string ToString() { diff --git a/Tests/DistTestCore/DistTestLifecycleComponents.cs b/Tests/DistTestCore/DistTestLifecycleComponents.cs new file mode 100644 index 00000000..203a891c --- /dev/null +++ b/Tests/DistTestCore/DistTestLifecycleComponents.cs @@ -0,0 +1,97 @@ +namespace DistTestCore +{ + public interface ILifecycleComponent + { + void Start(ILifecycleComponentAccess access); + void Stop(ILifecycleComponentAccess access, DistTestResult result); + } + + public interface ILifecycleComponentCollector + { + void AddComponent(ILifecycleComponent component); + } + + public interface ILifecycleComponentAccess + { + T Get() where T : ILifecycleComponent; + } + + public class DistTestLifecycleComponents + { + private readonly object _lock = new object(); + private readonly Dictionary> components = new(); + + public void Setup(string testName, Action initializer) + { + var newComponents = new Dictionary(); + lock (_lock) + { + components.Add(testName, newComponents); + var collector = new Collector(newComponents); + initializer(collector); + } + + var access = new ScopedAccess(this, testName); + foreach (var component in newComponents.Values) + { + component.Start(access); + } + } + + public T Get(string testName) where T : ILifecycleComponent + { + var type = typeof(T); + lock (_lock) + { + return (T)components[testName][type]; + } + } + + public void TearDown(string testName, DistTestResult result) + { + var access = new ScopedAccess(this, testName); + var closingComponents = components[testName]; + foreach (var component in closingComponents.Values) + { + component.Stop(access, result); + } + + lock (_lock) + { + components.Remove(testName); + } + } + + private class Collector : ILifecycleComponentCollector + { + private readonly Dictionary components; + + public Collector(Dictionary components) + { + this.components = components; + } + + public void AddComponent(ILifecycleComponent component) + { + components.Add(component.GetType(), component); + } + } + + private class ScopedAccess : ILifecycleComponentAccess + { + private readonly DistTestLifecycleComponents parent; + private readonly string testName; + + public ScopedAccess(DistTestLifecycleComponents parent, string testName) + { + this.parent = parent; + this.testName = testName; + } + + public T Get() where T : ILifecycleComponent + { + return parent.Get(testName); + } + } + } +} diff --git a/Tests/DistTestCore/Logs/FixtureLog.cs b/Tests/DistTestCore/Logs/FixtureLog.cs index 9d3c77d3..f90147d8 100644 --- a/Tests/DistTestCore/Logs/FixtureLog.cs +++ b/Tests/DistTestCore/Logs/FixtureLog.cs @@ -4,26 +4,25 @@ namespace DistTestCore.Logs { public class FixtureLog : BaseTestLog { - private readonly ILog backingLog; - private readonly string deployId; - public FixtureLog(ILog backingLog, string deployId) : base(backingLog, deployId) { - this.backingLog = backingLog; - this.deployId = deployId; } public TestLog CreateTestLog(string name = "") { - return TestLog.Create(this, name); + var result = TestLog.Create(this, name); + result.Log(NameUtils.GetRawFixtureName()); + return result; } public static FixtureLog Create(LogConfig config, DateTime start, string deployId, string name = "") { var fullName = NameUtils.GetFixtureFullName(config, start, name); var log = CreateMainLog(fullName, name); - return new FixtureLog(log, deployId); + var result = new FixtureLog(log, deployId); + result.Log(NameUtils.GetRawFixtureName()); + return result; } } } diff --git a/Tests/DistTestCore/Logs/TestLog.cs b/Tests/DistTestCore/Logs/TestLog.cs index 0dca1464..6a334138 100644 --- a/Tests/DistTestCore/Logs/TestLog.cs +++ b/Tests/DistTestCore/Logs/TestLog.cs @@ -1,5 +1,4 @@ using Logging; -using System.Xml.Linq; namespace DistTestCore.Logs { @@ -8,7 +7,6 @@ namespace DistTestCore.Logs public TestLog(ILog backingLog, string methodName, string deployId, string name = "") : base(backingLog, deployId) { - backingLog.Log($"*** Begin: {methodName}"); } public static TestLog Create(FixtureLog parentLog, string name = "") diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 3d642d20..550b6151 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -10,22 +10,24 @@ using WebUtils; namespace DistTestCore { - public class TestLifecycle : IK8sHooks + public class TestLifecycle : IK8sHooks, ILifecycleComponent { private const string TestsType = "dist-tests"; private readonly EntryPoint entryPoint; private readonly Dictionary metadata; private readonly List runningContainers = new(); + private readonly string testName; private readonly string deployId; private readonly List stoppedContainerLogs = new List(); - public TestLifecycle(TestLog log, Configuration configuration, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8sTimeSet, string testNamespace, string deployId, bool waitForCleanup) + public TestLifecycle(TestLog log, Configuration configuration, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8sTimeSet, string testNamespace, string testName, string deployId, bool waitForCleanup) { Log = log; Configuration = configuration; WebCallTimeSet = webCallTimeSet; K8STimeSet = k8sTimeSet; TestNamespace = testNamespace; + this.testName = testName; TestStart = DateTime.UtcNow; entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(k8sTimeSet, this, testNamespace), configuration.GetFileManagerFolder(), webCallTimeSet, k8sTimeSet); @@ -36,6 +38,28 @@ namespace DistTestCore log.WriteLogTag(); } + public void Start(ILifecycleComponentAccess access) + { + Log.Log($"*** Begin: {testName}"); + } + + public void Stop(ILifecycleComponentAccess access, DistTestResult result) + { + Log.Log($"*** Finished: {testName} = {result.Status}"); + if (!string.IsNullOrEmpty(result.Result)) + { + Log.Log(result.Result); + Log.Log($"{result.Trace}"); + } + + if (!result.Success) + { + DownloadAllLogs(); + } + + DeleteAllResources(); + } + public DateTime TestStart { get; } public TestLog Log { get; } public Configuration Configuration { get; } @@ -45,7 +69,7 @@ namespace DistTestCore public bool WaitForCleanup { get; } public CoreInterface CoreInterface { get; } - public void DeleteAllResources() + private void DeleteAllResources() { entryPoint.Decommission( deleteKubernetesResources: true, diff --git a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs index d8ac6805..a2477597 100644 --- a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs +++ b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs @@ -4,46 +4,56 @@ using DistTestCore; namespace CodexTests { - public class AutoBootstrapDistTest : CodexDistTest + public class AutoBootstrapComponent : ILifecycleComponent { - private readonly Dictionary bootstrapNodes = new Dictionary(); - private bool isBooting = false; + public ICodexNode? BootstrapNode { get; private set; } = null; - protected override void LifecycleStart(TestLifecycle tl) + public void Start(ILifecycleComponentAccess access) { - base.LifecycleStart(tl); - if (!bootstrapNodes.ContainsKey(tl)) - { - isBooting = true; - bootstrapNodes.Add(tl, StartCodex(s => s.WithName("BOOTSTRAP_" + tl.TestNamespace))); - isBooting = false; - } + if (BootstrapNode != null) return; + + var tl = access.Get(); + var ci = tl.CoreInterface; + var testNamespace = tl.TestNamespace; + + BootstrapNode = ci.StartCodexNode(s => s.WithName("BOOTSTRAP_" + testNamespace)); } - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + public void ApplyBootstrapNode(ICodexSetup setup) { - bootstrapNodes.Remove(lifecycle); - base.LifecycleStop(lifecycle, result); + if (BootstrapNode == null) return; + + setup.WithBootstrapNode(BootstrapNode); + } + + public void Stop(ILifecycleComponentAccess access, DistTestResult result) + { + if (BootstrapNode == null) return; + BootstrapNode.Stop(waitTillStopped: false); + } + } + + public class AutoBootstrapDistTest : CodexDistTest + { + + protected override void CreateComponents(ILifecycleComponentCollector collector) + { + base.CreateComponents(collector); + collector.AddComponent(new AutoBootstrapComponent()); } protected override void OnCodexSetup(ICodexSetup setup) { - if (isBooting) return; - - var node = BootstrapNode; - if (node != null) setup.WithBootstrapNode(node); + Get().ApplyBootstrapNode(setup); } protected ICodexNode BootstrapNode { get { - var tl = Get(); - if (bootstrapNodes.TryGetValue(tl, out var node)) - { - return node; - } - throw new InvalidOperationException("Bootstrap node not yet started."); + var bn = Get().BootstrapNode; + if (bn == null) throw new InvalidOperationException("BootstrapNode accessed before initialized."); + return bn; } } } diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index d27216d3..40e711fc 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -1,6 +1,5 @@ using BlockchainUtils; using CodexClient; -using CodexClient.Hooks; using CodexContractsPlugin; using CodexNetDeployer; using CodexPlugin; @@ -17,86 +16,74 @@ using Newtonsoft.Json; using NUnit.Framework; using NUnit.Framework.Constraints; using OverwatchTranscript; -using Utils; namespace CodexTests { - public class CodexLogTrackerProvider : ICodexHooksProvider + public class CodexDistTestComponents : ILifecycleComponent { - private readonly Action addNode; + private readonly object nodesLock = new object(); - public CodexLogTrackerProvider(Action addNode) + public CodexDistTestComponents(CodexTranscriptWriter? writer) { - this.addNode = addNode; + Writer = writer; } - // See TestLifecycle.cs DownloadAllLogs() - public ICodexNodeHooks CreateHooks(string nodeName) + public CodexTranscriptWriter? Writer { get; } + public BlockCache Cache { get; } = new(); + public List Nodes { get; } = new(); + + public void Start(ILifecycleComponentAccess access) { - return new CodexLogTracker(addNode); + var ci = access.Get().CoreInterface; + ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => + { + lock (nodesLock) + { + Nodes.Add(n); + } + })); } - public class CodexLogTracker : ICodexNodeHooks + public void Stop(ILifecycleComponentAccess access, DistTestResult result) { - private readonly Action addNode; + var tl = access.Get(); + var log = tl.Log; + var logFiles = tl.DownloadAllLogs(); - public CodexLogTracker(Action addNode) + TeardownTranscript(log, logFiles, result); + + // todo: on not success: go to nodes and dl logs? + // or fix disttest failure log download so we can always have logs even for non-codexes? + } + + private void TeardownTranscript(TestLog log, IDownloadedLog[] logFiles, DistTestResult result) + { + if (Writer == null) return; + + Writer.AddResult(result.Success, result.Result); + + try { - this.addNode = addNode; + Stopwatch.Measure(log, "Transcript.ProcessLogs", () => + { + Writer.ProcessLogs(logFiles); + }); + + Stopwatch.Measure(log, $"Transcript.Finalize", () => + { + Writer.IncludeFile(log.GetFullName()); + Writer.Finalize(); + }); } - - public void OnFileDownloaded(ByteSize size, ContentId cid) - { - } - - public void OnFileDownloading(ContentId cid) - { - } - - public void OnFileUploaded(string uid, ByteSize size, ContentId cid) - { - } - - public void OnFileUploading(string uid, ByteSize size) - { - } - - public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) - { - addNode(node); - } - - public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) - { - } - - public void OnNodeStopping() - { - } - - public void OnStorageAvailabilityCreated(StorageAvailability response) - { - } - - public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) - { - } - - public void OnStorageContractUpdated(StoragePurchase purchaseStatus) + catch (Exception ex) { + log.Error("Failure during transcript teardown: " + ex); } } } public class CodexDistTest : DistTest { - private static readonly object _lock = new object(); - private static readonly Dictionary writers = new Dictionary(); - private static readonly Dictionary blockCaches = new Dictionary(); - - // this entire structure is not good and needs to be destroyed at the earliest convenience: - private static readonly Dictionary> nodes = new Dictionary>(); - public CodexDistTest() { ProjectPlugin.Load(); @@ -112,34 +99,12 @@ namespace CodexTests localBuilder.Build(); } - protected override void LifecycleStart(TestLifecycle lifecycle) + protected override void CreateComponents(ILifecycleComponentCollector collector) { - base.LifecycleStart(lifecycle); - SetupTranscript(lifecycle); - - Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => - { - lock (_lock) - { - if (!nodes.ContainsKey(lifecycle)) nodes.Add(lifecycle, new List()); - nodes[lifecycle].Add(n); - } - })); - } - - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) - { - base.LifecycleStop(lifecycle, result); - TeardownTranscript(lifecycle, result); - - if (!result.Success) - { - lock (_lock) - { - var codexNodes = nodes[lifecycle]; - foreach (var node in codexNodes) node.DownloadLog(); - } - } + base.CreateComponents(collector); + collector.AddComponent(new CodexDistTestComponents( + SetupTranscript() + )); } public ICodexNode StartCodex() @@ -173,6 +138,11 @@ namespace CodexTests return Ci.StartGethNode(GetBlockCache(), setup); } + private BlockCache GetBlockCache() + { + return Get().Cache; + } + public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers() { return new PeerConnectionTestHelpers(GetTestLog()); @@ -180,7 +150,7 @@ namespace CodexTests public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers() { - return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); + return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); } public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "") @@ -258,81 +228,20 @@ namespace CodexTests return null; } - private void SetupTranscript(TestLifecycle lifecycle) + private CodexTranscriptWriter? SetupTranscript() { var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; + if (attr == null) return null; var config = new CodexTranscriptWriterConfig( + attr.OutputFilename, attr.IncludeBlockReceivedEvents ); - var log = new LogPrefixer(lifecycle.Log, "(Transcript) "); + var log = new LogPrefixer(GetTestLog(), "(Transcript) "); var writer = new CodexTranscriptWriter(log, config, Transcript.NewWriter(log)); Ci.AddCodexHooksProvider(writer); - lock (_lock) - { - writers.Add(lifecycle, writer); - } - } - - private void TeardownTranscript(TestLifecycle lifecycle, DistTestResult result) - { - var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; - - var outputFilepath = GetOutputFullPath(lifecycle, attr); - - CodexTranscriptWriter writer = null!; - lock (_lock) - { - writer = writers[lifecycle]; - writers.Remove(lifecycle); - } - - writer.AddResult(result.Success, result.Result); - - try - { - Stopwatch.Measure(lifecycle.Log, "Transcript.ProcessLogs", () => - { - writer.ProcessLogs(lifecycle.DownloadAllLogs()); - }); - - Stopwatch.Measure(lifecycle.Log, $"Transcript.Finalize: {outputFilepath}", () => - { - writer.IncludeFile(lifecycle.Log.GetFullName()); - writer.Finalize(outputFilepath); - }); - } - catch (Exception ex) - { - lifecycle.Log.Error("Failure during transcript teardown: " + ex); - } - } - - private string GetOutputFullPath(TestLifecycle lifecycle, CreateTranscriptAttribute attr) - { - var outputPath = Path.GetDirectoryName(lifecycle.Log.GetFullName()); - if (outputPath == null) throw new Exception("Logfile path is null"); - var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.GetFullName()); - if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); - var outputFile = Path.Combine(outputPath, filename + "_" + attr.OutputFilename); - if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; - return outputFile; - } - - private BlockCache GetBlockCache() - { - var lifecycle = Get(); - lock (_lock) - { - if (!blockCaches.ContainsKey(lifecycle)) - { - blockCaches[lifecycle] = new BlockCache(); - } - } - return blockCaches[lifecycle]; + return writer; } } diff --git a/Tests/ExperimentalTests/CodexLogTrackerProvider.cs b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs new file mode 100644 index 00000000..1b2fdd66 --- /dev/null +++ b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs @@ -0,0 +1,73 @@ +using CodexClient; +using CodexClient.Hooks; +using Utils; + +namespace CodexTests +{ + public class CodexLogTrackerProvider : ICodexHooksProvider + { + private readonly Action addNode; + + public CodexLogTrackerProvider(Action addNode) + { + this.addNode = addNode; + } + + // See TestLifecycle.cs DownloadAllLogs() + public ICodexNodeHooks CreateHooks(string nodeName) + { + return new CodexLogTracker(addNode); + } + + public class CodexLogTracker : ICodexNodeHooks + { + private readonly Action addNode; + + public CodexLogTracker(Action addNode) + { + this.addNode = addNode; + } + + public void OnFileDownloaded(ByteSize size, ContentId cid) + { + } + + public void OnFileDownloading(ContentId cid) + { + } + + public void OnFileUploaded(string uid, ByteSize size, ContentId cid) + { + } + + public void OnFileUploading(string uid, ByteSize size) + { + } + + public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) + { + addNode(node); + } + + public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) + { + } + + public void OnNodeStopping() + { + } + + public void OnStorageAvailabilityCreated(StorageAvailability response) + { + } + + public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) + { + } + + public void OnStorageContractUpdated(StoragePurchase purchaseStatus) + { + } + } + } +} diff --git a/Tests/FrameworkTests/LifecycelyTest.cs b/Tests/FrameworkTests/LifecycelyTest.cs index ef90a48c..6c11e14f 100644 --- a/Tests/FrameworkTests/LifecycelyTest.cs +++ b/Tests/FrameworkTests/LifecycelyTest.cs @@ -1,64 +1,64 @@ -using NUnit.Framework; +//using NUnit.Framework; -namespace FrameworkTests -{ - [Parallelizable(ParallelScope.All)] - [TestFixture(10)] - [TestFixture(20)] - [TestFixture(30)] - public class LifecycelyTest - { - public LifecycelyTest(int num) - { - Log("ctor", GetCurrentTestName(), num); - this.num = num; - } +//namespace FrameworkTests +//{ +// [Parallelizable(ParallelScope.All)] +// [TestFixture(10)] +// [TestFixture(20)] +// [TestFixture(30)] +// public class LifecycelyTest +// { +// public LifecycelyTest(int num) +// { +// Log("ctor", GetCurrentTestName(), num); +// this.num = num; +// } - [SetUp] - public void Setup() - { - Log(nameof(Setup), GetCurrentTestName()); - } +// [SetUp] +// public void Setup() +// { +// Log(nameof(Setup), GetCurrentTestName()); +// } - [TearDown] - public void TearDown() - { - Log(nameof(TearDown), GetCurrentTestName()); - } +// [TearDown] +// public void TearDown() +// { +// Log(nameof(TearDown), GetCurrentTestName()); +// } - [Test] - public void A() - { - Log(nameof(A), "Run"); - SleepRandom(); - Log(nameof(A), "Finish"); - } +// [Test] +// public void A() +// { +// Log(nameof(A), "Run"); +// SleepRandom(); +// Log(nameof(A), "Finish"); +// } - [Test] - public void B() - { - Log(nameof(B), "Run"); - SleepRandom(); - Log(nameof(B), "Finish"); - } +// [Test] +// public void B() +// { +// Log(nameof(B), "Run"); +// SleepRandom(); +// Log(nameof(B), "Finish"); +// } - [Test] - public void C() - { - Log(nameof(C), "Run"); - SleepRandom(); - Log(nameof(C), "Finish"); - } +// [Test] +// public void C() +// { +// Log(nameof(C), "Run"); +// SleepRandom(); +// Log(nameof(C), "Finish"); +// } - [Test] - [Combinatorial] - public void Multi( - [Values(1, 2, 3)] int num) - { - Log(nameof(Multi), "Run", num); - SleepRandom(); - Log(nameof(Multi), "Finish", num); - } +// [Test] +// [Combinatorial] +// public void Multi( +// [Values(1, 2, 3)] int num) +// { +// Log(nameof(Multi), "Run", num); +// SleepRandom(); +// Log(nameof(Multi), "Finish", num); +// } @@ -71,46 +71,46 @@ namespace FrameworkTests - private static readonly Random r = new Random(); - private readonly int num; +// private static readonly Random r = new Random(); +// private readonly int num; - private void SleepRandom() - { - Thread.Sleep(TimeSpan.FromSeconds(5.0)); - Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); - } +// private void SleepRandom() +// { +// Thread.Sleep(TimeSpan.FromSeconds(5.0)); +// Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); +// } - private void Log(string scope, string msg) - { - ALog.Log($"{num} {scope} {msg}"); - } +// private void Log(string scope, string msg) +// { +// ALog.Log($"{num} {scope} {msg}"); +// } - private void Log(string scope, string msg, int num) - { - ALog.Log($"{this.num} {scope} {msg} {num}"); - } +// private void Log(string scope, string msg, int num) +// { +// ALog.Log($"{this.num} {scope} {msg} {num}"); +// } - private string GetCurrentTestName() - { - return $"[{TestContext.CurrentContext.Test.Name}]"; - } - } +// private string GetCurrentTestName() +// { +// return $"[{TestContext.CurrentContext.Test.Name}]"; +// } +// } - public class ALog - { - private static readonly object _lock = new object(); +// public class ALog +// { +// private static readonly object _lock = new object(); - public static void Log(string msg) - { - lock (_lock) - { - File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); - } - } - } +// public static void Log(string msg) +// { +// lock (_lock) +// { +// File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); +// } +// } +// } @@ -122,105 +122,99 @@ namespace FrameworkTests - public interface ITestLifecycleComponent - { - void Start(); - void Stop(string results); - } +// public class Base +// { +// private readonly Dictionary> anyFields = new(); - public class Base - { - private readonly Dictionary> anyFields = new(); +// public void Setup() +// { +// var testId = 23; - public void Setup() - { - var testId = 23; +// var fields = new Dictionary(); +// anyFields.Add(testId, fields); +// YieldFields(field => +// { +// fields.Add(field.GetType(), field); +// }); - var fields = new Dictionary(); - anyFields.Add(testId, fields); - YieldFields(field => - { - fields.Add(field.GetType(), field); - }); +// foreach (var field in fields.Values) +// { +// field.Start(); +// } +// } - foreach (var field in fields.Values) - { - field.Start(); - } - } +// public void TearDown() +// { +// var testId = 23; - public void TearDown() - { - var testId = 23; +// // foreach stop - // foreach stop +// anyFields.Remove(testId); +// } - anyFields.Remove(testId); - } +// public T Get() +// { +// int testId = 123; +// var fields = anyFields[testId]; +// var type = typeof(T); +// var result = fields[type]; +// return (T)result; +// } - public T Get() - { - int testId = 123; - var fields = anyFields[testId]; - var type = typeof(T); - var result = fields[type]; - return (T)result; - } +// public BaseFields GetBaseField() +// { +// return Get(); +// } - public BaseFields GetBaseField() - { - return Get(); - } +// protected virtual void YieldFields(Action giveField) +// { +// giveField(new BaseFields()); +// } +// } - protected virtual void YieldFields(Action giveField) - { - giveField(new BaseFields()); - } - } +// public class Mid : Base +// { +// protected override void YieldFields(Action giveField) +// { +// base.YieldFields(giveField); +// giveField(new MidFields()); +// } - public class Mid : Base - { - protected override void YieldFields(Action giveField) - { - base.YieldFields(giveField); - giveField(new MidFields()); - } +// public MidFields GetMid() +// { +// return Get(); +// } +// } - public MidFields GetMid() - { - return Get(); - } - } +// public class Top : Mid +// { +// protected override void YieldFields(Action giveField) +// { +// base.YieldFields(giveField); +// giveField(new TopFields()); +// } - public class Top : Mid - { - protected override void YieldFields(Action giveField) - { - base.YieldFields(giveField); - giveField(new TopFields()); - } +// public TopFields GetTop() +// { +// return Get(); +// } +// } - public TopFields GetTop() - { - return Get(); - } - } +// public class BaseFields : ITestLifecycleComponent +// { +// public string EntryPoint { get; set; } = string.Empty; +// public string Log { get; set; } = string.Empty; +// } - public class BaseFields : ITestLifecycleComponent - { - public string EntryPoint { get; set; } = string.Empty; - public string Log { get; set; } = string.Empty; - } +// public class MidFields : ITestLifecycleComponent +// { +// public string Nodes { get; set; } = string.Empty; +// } - public class MidFields : ITestLifecycleComponent - { - public string Nodes { get; set; } = string.Empty; - } - - public class TopFields : ITestLifecycleComponent - { - public string Geth { get; set; } = string.Empty; - public string Contracts { get; set; } = string.Empty; - } -} +// public class TopFields : ITestLifecycleComponent +// { +// public string Geth { get; set; } = string.Empty; +// public string Contracts { get; set; } = string.Empty; +// } +//} diff --git a/Tests/FrameworkTests/Parallelism.cs b/Tests/FrameworkTests/Parallelism.cs new file mode 100644 index 00000000..8a877f41 --- /dev/null +++ b/Tests/FrameworkTests/Parallelism.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +[assembly: LevelOfParallelism(100)] +namespace FrameworkTests +{ +} From a1c5736e1234e6f200a3273e53f14aee04d108ab Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 23 Apr 2025 16:16:05 +0200 Subject: [PATCH 43/51] Log messages for successful asserts --- .../MarketTests/MarketplaceAutoBootstrapDistTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index a5ec0885..e5ad35a4 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -194,6 +194,8 @@ namespace CodexReleaseTests.MarketTests var expectedBalance = StartingBalanceTST.Tst() - GetContractFinalCost(pricePerBytePerSecond, contract, hosts); AssertTstBalance(client, expectedBalance, "Client balance incorrect."); + + Log($"Client has paid for contract. Balance: {expectedBalance}"); } protected void AssertHostsWerePaidForContract(TestToken pricePerBytePerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts) @@ -213,7 +215,9 @@ namespace CodexReleaseTests.MarketTests foreach (var pair in expectedBalances) { - AssertTstBalance(pair.Key, pair.Value, "Host was not paid for storage."); + AssertTstBalance(pair.Key, pair.Value, $"Host {pair.Key} was not paid for storage."); + + Log($"Host {pair.Key} was paid for storage. Balance: {pair.Value}"); } } From 373f7826bf5c87d6b53cb7b0d8478bded7ab7ff9 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 25 Apr 2025 07:21:07 +0200 Subject: [PATCH 44/51] todo: try fixture lifecycle attribute --- Tests/FrameworkTests/LifecycelyTest.cs | 329 ++++++++++++------------- 1 file changed, 164 insertions(+), 165 deletions(-) diff --git a/Tests/FrameworkTests/LifecycelyTest.cs b/Tests/FrameworkTests/LifecycelyTest.cs index 6c11e14f..e420a911 100644 --- a/Tests/FrameworkTests/LifecycelyTest.cs +++ b/Tests/FrameworkTests/LifecycelyTest.cs @@ -1,64 +1,65 @@ -//using NUnit.Framework; +using NUnit.Framework; -//namespace FrameworkTests -//{ -// [Parallelizable(ParallelScope.All)] -// [TestFixture(10)] -// [TestFixture(20)] -// [TestFixture(30)] -// public class LifecycelyTest -// { -// public LifecycelyTest(int num) -// { -// Log("ctor", GetCurrentTestName(), num); -// this.num = num; -// } +namespace FrameworkTests +{ + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + [TestFixture(10)] + [TestFixture(20)] + [TestFixture(30)] + public class LifecycelyTest + { + public LifecycelyTest(int num) + { + Log("ctor", GetCurrentTestName(), num); + this.num = num; + } -// [SetUp] -// public void Setup() -// { -// Log(nameof(Setup), GetCurrentTestName()); -// } + [SetUp] + public void Setup() + { + Log(nameof(Setup), GetCurrentTestName()); + } -// [TearDown] -// public void TearDown() -// { -// Log(nameof(TearDown), GetCurrentTestName()); -// } + [TearDown] + public void TearDown() + { + Log(nameof(TearDown), GetCurrentTestName()); + } -// [Test] -// public void A() -// { -// Log(nameof(A), "Run"); -// SleepRandom(); -// Log(nameof(A), "Finish"); -// } + //[Test] + //public void A() + //{ + // Log(nameof(A), "Run"); + // SleepRandom(); + // Log(nameof(A), "Finish"); + //} -// [Test] -// public void B() -// { -// Log(nameof(B), "Run"); -// SleepRandom(); -// Log(nameof(B), "Finish"); -// } + //[Test] + //public void B() + //{ + // Log(nameof(B), "Run"); + // SleepRandom(); + // Log(nameof(B), "Finish"); + //} -// [Test] -// public void C() -// { -// Log(nameof(C), "Run"); -// SleepRandom(); -// Log(nameof(C), "Finish"); -// } + //[Test] + //public void C() + //{ + // Log(nameof(C), "Run"); + // SleepRandom(); + // Log(nameof(C), "Finish"); + //} -// [Test] -// [Combinatorial] -// public void Multi( -// [Values(1, 2, 3)] int num) -// { -// Log(nameof(Multi), "Run", num); -// SleepRandom(); -// Log(nameof(Multi), "Finish", num); -// } + [Test] + [Combinatorial] + public void Multi( + [Values(1, 2, 3)] int num) + { + Log(nameof(Multi), "Run", num); + SleepRandom(); + Log(nameof(Multi), "Finish", num); + } @@ -71,150 +72,148 @@ -// private static readonly Random r = new Random(); -// private readonly int num; + private static readonly Random r = new Random(); + private readonly int num; -// private void SleepRandom() -// { -// Thread.Sleep(TimeSpan.FromSeconds(5.0)); -// Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); -// } + private void SleepRandom() + { + Thread.Sleep(TimeSpan.FromSeconds(5.0)); + Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); + } -// private void Log(string scope, string msg) -// { -// ALog.Log($"{num} {scope} {msg}"); -// } + private void Log(string scope, string msg) + { + ALog.Log($"{num} {scope} {msg}"); + } -// private void Log(string scope, string msg, int num) -// { -// ALog.Log($"{this.num} {scope} {msg} {num}"); -// } + private void Log(string scope, string msg, int num) + { + ALog.Log($"{this.num} {scope} {msg} {num}"); + } -// private string GetCurrentTestName() -// { -// return $"[{TestContext.CurrentContext.Test.Name}]"; -// } -// } + private string GetCurrentTestName() + { + return $"[{TestContext.CurrentContext.Test.Name}]"; + } + } -// public class ALog -// { -// private static readonly object _lock = new object(); + public class ALog + { + private static readonly object _lock = new object(); -// public static void Log(string msg) -// { -// lock (_lock) -// { -// File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); -// } -// } -// } + public static void Log(string msg) + { + lock (_lock) + { + File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); + } + } + } + public interface ITestLifecycleComponent + { + } + public class Base + { + private readonly Dictionary> anyFields = new(); -// public class Base -// { -// private readonly Dictionary> anyFields = new(); + public void Setup() + { + var testId = 23; -// public void Setup() -// { -// var testId = 23; + var fields = new Dictionary(); + anyFields.Add(testId, fields); + YieldFields(field => + { + fields.Add(field.GetType(), field); + }); -// var fields = new Dictionary(); -// anyFields.Add(testId, fields); -// YieldFields(field => -// { -// fields.Add(field.GetType(), field); -// }); + } -// foreach (var field in fields.Values) -// { -// field.Start(); -// } -// } + public void TearDown() + { + var testId = 23; -// public void TearDown() -// { -// var testId = 23; + // foreach stop -// // foreach stop + anyFields.Remove(testId); + } -// anyFields.Remove(testId); -// } + public T Get() + { + int testId = 123; + var fields = anyFields[testId]; + var type = typeof(T); + var result = fields[type]; + return (T)result; + } -// public T Get() -// { -// int testId = 123; -// var fields = anyFields[testId]; -// var type = typeof(T); -// var result = fields[type]; -// return (T)result; -// } + public BaseFields GetBaseField() + { + return Get(); + } -// public BaseFields GetBaseField() -// { -// return Get(); -// } + protected virtual void YieldFields(Action giveField) + { + giveField(new BaseFields()); + } + } -// protected virtual void YieldFields(Action giveField) -// { -// giveField(new BaseFields()); -// } -// } + public class Mid : Base + { + protected override void YieldFields(Action giveField) + { + base.YieldFields(giveField); + giveField(new MidFields()); + } -// public class Mid : Base -// { -// protected override void YieldFields(Action giveField) -// { -// base.YieldFields(giveField); -// giveField(new MidFields()); -// } + public MidFields GetMid() + { + return Get(); + } + } -// public MidFields GetMid() -// { -// return Get(); -// } -// } + public class Top : Mid + { + protected override void YieldFields(Action giveField) + { + base.YieldFields(giveField); + giveField(new TopFields()); + } -// public class Top : Mid -// { -// protected override void YieldFields(Action giveField) -// { -// base.YieldFields(giveField); -// giveField(new TopFields()); -// } + public TopFields GetTop() + { + return Get(); + } + } -// public TopFields GetTop() -// { -// return Get(); -// } -// } + public class BaseFields : ITestLifecycleComponent + { + public string EntryPoint { get; set; } = string.Empty; + public string Log { get; set; } = string.Empty; + } -// public class BaseFields : ITestLifecycleComponent -// { -// public string EntryPoint { get; set; } = string.Empty; -// public string Log { get; set; } = string.Empty; -// } + public class MidFields : ITestLifecycleComponent + { + public string Nodes { get; set; } = string.Empty; + } -// public class MidFields : ITestLifecycleComponent -// { -// public string Nodes { get; set; } = string.Empty; -// } - -// public class TopFields : ITestLifecycleComponent -// { -// public string Geth { get; set; } = string.Empty; -// public string Contracts { get; set; } = string.Empty; -// } -//} + public class TopFields : ITestLifecycleComponent + { + public string Geth { get; set; } = string.Empty; + public string Contracts { get; set; } = string.Empty; + } +} From ad1b756db9c85f22e5a820bcb2fdc9e28661fb33 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 25 Apr 2025 11:08:38 +0200 Subject: [PATCH 45/51] Revert "while better the new plan is still a mess" This reverts commit 2dfdfac2bb52e4ff919ee5f1b3986a839d71b0a2. # Conflicts: # Tests/FrameworkTests/LifecycelyTest.cs --- .../OverwatchSupport/CodexTranscriptWriter.cs | 15 +- .../CodexTranscriptWriterConfig.cs | 4 +- .../MarketplaceAutoBootstrapDistTest.cs | 39 ++- Tests/DistTestCore/DistTest.cs | 124 +++++++--- .../DistTestLifecycleComponents.cs | 97 -------- Tests/DistTestCore/Logs/FixtureLog.cs | 13 +- Tests/DistTestCore/Logs/TestLog.cs | 2 + Tests/DistTestCore/TestLifecycle.cs | 30 +-- .../AutoBootstrapDistTest.cs | 62 ++--- Tests/ExperimentalTests/CodexDistTest.cs | 223 ++++++++++++------ .../CodexLogTrackerProvider.cs | 73 ------ Tests/FrameworkTests/Parallelism.cs | 6 - 12 files changed, 297 insertions(+), 391 deletions(-) delete mode 100644 Tests/DistTestCore/DistTestLifecycleComponents.cs delete mode 100644 Tests/ExperimentalTests/CodexLogTrackerProvider.cs delete mode 100644 Tests/FrameworkTests/Parallelism.cs diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs index 53ac1496..afd8d1a2 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs @@ -23,27 +23,16 @@ namespace CodexPlugin.OverwatchSupport converter = new CodexLogConverter(writer, config, identityMap); } - public void Finalize() + public void Finalize(string outputFilepath) { log.Log("Finalizing Codex transcript..."); writer.AddHeader(CodexHeaderKey, CreateCodexHeader()); - writer.Write(GetOutputFullPath()); + writer.Write(outputFilepath); log.Log("Done"); } - private string GetOutputFullPath() - { - var outputPath = Path.GetDirectoryName(log.GetFullName()); - if (outputPath == null) throw new Exception("Logfile path is null"); - var filename = Path.GetFileNameWithoutExtension(log.GetFullName()); - if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); - var outputFile = Path.Combine(outputPath, filename + "_" + config.OutputFilename); - if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; - return outputFile; - } - public ICodexNodeHooks CreateHooks(string nodeName) { nodeName = Str.Between(nodeName, "'", "'"); diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs index 8c5e7bf0..247494c8 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs @@ -2,13 +2,11 @@ { public class CodexTranscriptWriterConfig { - public CodexTranscriptWriterConfig(string outputFilename, bool includeBlockReceivedEvents) + public CodexTranscriptWriterConfig(bool includeBlockReceivedEvents) { - OutputFilename = outputFilename; IncludeBlockReceivedEvents = includeBlockReceivedEvents; } - public string OutputFilename { get; } public bool IncludeBlockReceivedEvents { get; } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index f6e8a9ca..a5ec0885 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -10,41 +10,18 @@ using Utils; namespace CodexReleaseTests.MarketTests { - public class MarketplaceTestComponent : ILifecycleComponent - { - public IGethNode Geth { get; } - public ICodexContracts Contracts { get; } - - public void Start(ILifecycleComponentAccess access) - { - throw new NotImplementedException(); - } - - public void Stop(ILifecycleComponentAccess access, DistTestResult result) - { - throw new NotImplementedException(); - } - } - public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest { + private readonly Dictionary handles = new Dictionary(); protected const int StartingBalanceTST = 1000; protected const int StartingBalanceEth = 10; - protected override void CreateComponents(ILifecycleComponentCollector collector) - { - base.CreateComponents(collector); - - - collector.AddComponent(new MarketplaceTestComponent()); - } - protected override void LifecycleStart(TestLifecycle lifecycle) { base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); - handles.Add(lifecycle, new MarketplaceTestComponent(geth, contracts)); + handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); } protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) @@ -346,5 +323,17 @@ namespace CodexReleaseTests.MarketTests public SlotFilledEventDTO SlotFilledEvent { get; } public ICodexNode Host { get; } } + + private class MarketplaceHandle + { + public MarketplaceHandle(IGethNode geth, ICodexContracts contracts) + { + Geth = geth; + Contracts = contracts; + } + + public IGethNode Geth { get; } + public ICodexContracts Contracts { get; } + } } } diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 9a969532..534eb74a 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -19,8 +19,9 @@ namespace DistTestCore private readonly Assembly[] testAssemblies; private readonly FixtureLog fixtureLog; private readonly StatusLog statusLog; + private readonly object lifecycleLock = new object(); private readonly EntryPoint globalEntryPoint; - private readonly DistTestLifecycleComponents lifecycleComponents = new DistTestLifecycleComponents(); + private readonly Dictionary lifecycles = new Dictionary(); private readonly string deployId; public DistTest() @@ -88,12 +89,7 @@ namespace DistTestCore { try { - var testName = GetCurrentTestName(); - fixtureLog.WriteLogTag(); - Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => - { - lifecycleComponents.Setup(testName, CreateComponents); - }); + CreateNewTestLifecycle(); } catch (Exception ex) { @@ -108,7 +104,7 @@ namespace DistTestCore { try { - Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", DisposeTestLifecycle); + DisposeTestLifecycle(); } catch (Exception ex) { @@ -175,49 +171,80 @@ namespace DistTestCore { } - protected virtual void CreateComponents(ILifecycleComponentCollector collector) - { - var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); - var lifecycle = new TestLifecycle( - fixtureLog.CreateTestLog(), - configuration, - GetWebCallTimeSet(), - GetK8sTimeSet(), - testNamespace, - GetCurrentTestName(), - deployId, - ShouldWaitForCleanup()); - - collector.AddComponent(lifecycle); - } - - protected virtual void DestroyComponents(TestLifecycle lifecycle, DistTestResult testResult) + protected virtual void LifecycleStart(TestLifecycle lifecycle) { } - public T Get() where T : ILifecycleComponent + protected virtual void LifecycleStop(TestLifecycle lifecycle, DistTestResult testResult) { - return lifecycleComponents.Get(GetCurrentTestName()); } - private TestLifecycle Get() + protected virtual void CollectStatusLogData(TestLifecycle lifecycle, Dictionary data) { - return Get(); + } + + protected TestLifecycle Get() + { + lock (lifecycleLock) + { + return lifecycles[GetCurrentTestName()]; + } + } + + private void CreateNewTestLifecycle() + { + var testName = GetCurrentTestName(); + fixtureLog.WriteLogTag(); + Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => + { + lock (lifecycleLock) + { + var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); + var lifecycle = new TestLifecycle( + fixtureLog.CreateTestLog(), + configuration, + GetWebCallTimeSet(), + GetK8sTimeSet(), + testNamespace, + deployId, + ShouldWaitForCleanup()); + lifecycles.Add(testName, lifecycle); + LifecycleStart(lifecycle); + } + }); } private void DisposeTestLifecycle() { - var testName = GetCurrentTestName(); - var results = GetTestResult(); var lifecycle = Get(); + var testResult = GetTestResult(); var testDuration = lifecycle.GetTestDuration(); var data = lifecycle.GetPluginMetadata(); - fixtureLog.Log($"{GetCurrentTestName()} = {results} ({testDuration})"); - statusLog.ConcludeTest(results, testDuration, data); + CollectStatusLogData(lifecycle, data); + fixtureLog.Log($"{GetCurrentTestName()} = {testResult} ({testDuration})"); + statusLog.ConcludeTest(testResult, testDuration, data); + Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", () => + { + WriteEndTestLog(lifecycle.Log); - lifecycleComponents.TearDown(testName, results); + IncludeLogsOnTestFailure(lifecycle); + LifecycleStop(lifecycle, testResult); + lifecycle.DeleteAllResources(); + lifecycles.Remove(GetCurrentTestName()); + }); } + private void WriteEndTestLog(TestLog log) + { + var result = TestContext.CurrentContext.Result; + + Log($"*** Finished: {GetCurrentTestName()} = {result.Outcome.Status}"); + if (!string.IsNullOrEmpty(result.Message)) + { + Log(result.Message); + Log($"{result.StackTrace}"); + } + } private IWebCallTimeSet GetWebCallTimeSet() { @@ -269,6 +296,28 @@ namespace DistTestCore .ToArray(); } + private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) + { + var testStatus = TestContext.CurrentContext.Result.Outcome.Status; + if (ShouldDownloadAllLogs(testStatus)) + { + lifecycle.Log.Log("Downloading all container logs..."); + lifecycle.DownloadAllLogs(); + } + } + + private bool ShouldDownloadAllLogs(TestStatus testStatus) + { + if (configuration.AlwaysDownloadContainerLogs) return true; + if (!IsDownloadingLogsEnabled()) return false; + if (testStatus == TestStatus.Failed) + { + return true; + } + + return false; + } + private string GetCurrentTestName() { return $"[{TestContext.CurrentContext.Test.Name}]"; @@ -279,8 +328,7 @@ namespace DistTestCore var success = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; var status = TestContext.CurrentContext.Result.Outcome.Status.ToString(); var result = TestContext.CurrentContext.Result.Message; - var trace = TestContext.CurrentContext.Result.StackTrace; - return new DistTestResult(success, status, result ?? string.Empty, trace ?? string.Empty); + return new DistTestResult(success, status, result ?? string.Empty); } private bool IsDownloadingLogsEnabled() @@ -291,18 +339,16 @@ namespace DistTestCore public class DistTestResult { - public DistTestResult(bool success, string status, string result, string trace) + public DistTestResult(bool success, string status, string result) { Success = success; Status = status; Result = result; - Trace = trace; } public bool Success { get; } public string Status { get; } public string Result { get; } - public string Trace { get; } public override string ToString() { diff --git a/Tests/DistTestCore/DistTestLifecycleComponents.cs b/Tests/DistTestCore/DistTestLifecycleComponents.cs deleted file mode 100644 index 203a891c..00000000 --- a/Tests/DistTestCore/DistTestLifecycleComponents.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace DistTestCore -{ - public interface ILifecycleComponent - { - void Start(ILifecycleComponentAccess access); - void Stop(ILifecycleComponentAccess access, DistTestResult result); - } - - public interface ILifecycleComponentCollector - { - void AddComponent(ILifecycleComponent component); - } - - public interface ILifecycleComponentAccess - { - T Get() where T : ILifecycleComponent; - } - - public class DistTestLifecycleComponents - { - private readonly object _lock = new object(); - private readonly Dictionary> components = new(); - - public void Setup(string testName, Action initializer) - { - var newComponents = new Dictionary(); - lock (_lock) - { - components.Add(testName, newComponents); - var collector = new Collector(newComponents); - initializer(collector); - } - - var access = new ScopedAccess(this, testName); - foreach (var component in newComponents.Values) - { - component.Start(access); - } - } - - public T Get(string testName) where T : ILifecycleComponent - { - var type = typeof(T); - lock (_lock) - { - return (T)components[testName][type]; - } - } - - public void TearDown(string testName, DistTestResult result) - { - var access = new ScopedAccess(this, testName); - var closingComponents = components[testName]; - foreach (var component in closingComponents.Values) - { - component.Stop(access, result); - } - - lock (_lock) - { - components.Remove(testName); - } - } - - private class Collector : ILifecycleComponentCollector - { - private readonly Dictionary components; - - public Collector(Dictionary components) - { - this.components = components; - } - - public void AddComponent(ILifecycleComponent component) - { - components.Add(component.GetType(), component); - } - } - - private class ScopedAccess : ILifecycleComponentAccess - { - private readonly DistTestLifecycleComponents parent; - private readonly string testName; - - public ScopedAccess(DistTestLifecycleComponents parent, string testName) - { - this.parent = parent; - this.testName = testName; - } - - public T Get() where T : ILifecycleComponent - { - return parent.Get(testName); - } - } - } -} diff --git a/Tests/DistTestCore/Logs/FixtureLog.cs b/Tests/DistTestCore/Logs/FixtureLog.cs index f90147d8..9d3c77d3 100644 --- a/Tests/DistTestCore/Logs/FixtureLog.cs +++ b/Tests/DistTestCore/Logs/FixtureLog.cs @@ -4,25 +4,26 @@ namespace DistTestCore.Logs { public class FixtureLog : BaseTestLog { + private readonly ILog backingLog; + private readonly string deployId; + public FixtureLog(ILog backingLog, string deployId) : base(backingLog, deployId) { + this.backingLog = backingLog; + this.deployId = deployId; } public TestLog CreateTestLog(string name = "") { - var result = TestLog.Create(this, name); - result.Log(NameUtils.GetRawFixtureName()); - return result; + return TestLog.Create(this, name); } public static FixtureLog Create(LogConfig config, DateTime start, string deployId, string name = "") { var fullName = NameUtils.GetFixtureFullName(config, start, name); var log = CreateMainLog(fullName, name); - var result = new FixtureLog(log, deployId); - result.Log(NameUtils.GetRawFixtureName()); - return result; + return new FixtureLog(log, deployId); } } } diff --git a/Tests/DistTestCore/Logs/TestLog.cs b/Tests/DistTestCore/Logs/TestLog.cs index 6a334138..0dca1464 100644 --- a/Tests/DistTestCore/Logs/TestLog.cs +++ b/Tests/DistTestCore/Logs/TestLog.cs @@ -1,4 +1,5 @@ using Logging; +using System.Xml.Linq; namespace DistTestCore.Logs { @@ -7,6 +8,7 @@ namespace DistTestCore.Logs public TestLog(ILog backingLog, string methodName, string deployId, string name = "") : base(backingLog, deployId) { + backingLog.Log($"*** Begin: {methodName}"); } public static TestLog Create(FixtureLog parentLog, string name = "") diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 550b6151..3d642d20 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -10,24 +10,22 @@ using WebUtils; namespace DistTestCore { - public class TestLifecycle : IK8sHooks, ILifecycleComponent + public class TestLifecycle : IK8sHooks { private const string TestsType = "dist-tests"; private readonly EntryPoint entryPoint; private readonly Dictionary metadata; private readonly List runningContainers = new(); - private readonly string testName; private readonly string deployId; private readonly List stoppedContainerLogs = new List(); - public TestLifecycle(TestLog log, Configuration configuration, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8sTimeSet, string testNamespace, string testName, string deployId, bool waitForCleanup) + public TestLifecycle(TestLog log, Configuration configuration, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8sTimeSet, string testNamespace, string deployId, bool waitForCleanup) { Log = log; Configuration = configuration; WebCallTimeSet = webCallTimeSet; K8STimeSet = k8sTimeSet; TestNamespace = testNamespace; - this.testName = testName; TestStart = DateTime.UtcNow; entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(k8sTimeSet, this, testNamespace), configuration.GetFileManagerFolder(), webCallTimeSet, k8sTimeSet); @@ -38,28 +36,6 @@ namespace DistTestCore log.WriteLogTag(); } - public void Start(ILifecycleComponentAccess access) - { - Log.Log($"*** Begin: {testName}"); - } - - public void Stop(ILifecycleComponentAccess access, DistTestResult result) - { - Log.Log($"*** Finished: {testName} = {result.Status}"); - if (!string.IsNullOrEmpty(result.Result)) - { - Log.Log(result.Result); - Log.Log($"{result.Trace}"); - } - - if (!result.Success) - { - DownloadAllLogs(); - } - - DeleteAllResources(); - } - public DateTime TestStart { get; } public TestLog Log { get; } public Configuration Configuration { get; } @@ -69,7 +45,7 @@ namespace DistTestCore public bool WaitForCleanup { get; } public CoreInterface CoreInterface { get; } - private void DeleteAllResources() + public void DeleteAllResources() { entryPoint.Decommission( deleteKubernetesResources: true, diff --git a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs index a2477597..d8ac6805 100644 --- a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs +++ b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs @@ -4,56 +4,46 @@ using DistTestCore; namespace CodexTests { - public class AutoBootstrapComponent : ILifecycleComponent - { - public ICodexNode? BootstrapNode { get; private set; } = null; - - public void Start(ILifecycleComponentAccess access) - { - if (BootstrapNode != null) return; - - var tl = access.Get(); - var ci = tl.CoreInterface; - var testNamespace = tl.TestNamespace; - - BootstrapNode = ci.StartCodexNode(s => s.WithName("BOOTSTRAP_" + testNamespace)); - } - - public void ApplyBootstrapNode(ICodexSetup setup) - { - if (BootstrapNode == null) return; - - setup.WithBootstrapNode(BootstrapNode); - } - - public void Stop(ILifecycleComponentAccess access, DistTestResult result) - { - if (BootstrapNode == null) return; - BootstrapNode.Stop(waitTillStopped: false); - } - } - public class AutoBootstrapDistTest : CodexDistTest { + private readonly Dictionary bootstrapNodes = new Dictionary(); + private bool isBooting = false; - protected override void CreateComponents(ILifecycleComponentCollector collector) + protected override void LifecycleStart(TestLifecycle tl) { - base.CreateComponents(collector); - collector.AddComponent(new AutoBootstrapComponent()); + base.LifecycleStart(tl); + if (!bootstrapNodes.ContainsKey(tl)) + { + isBooting = true; + bootstrapNodes.Add(tl, StartCodex(s => s.WithName("BOOTSTRAP_" + tl.TestNamespace))); + isBooting = false; + } + } + + protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + { + bootstrapNodes.Remove(lifecycle); + base.LifecycleStop(lifecycle, result); } protected override void OnCodexSetup(ICodexSetup setup) { - Get().ApplyBootstrapNode(setup); + if (isBooting) return; + + var node = BootstrapNode; + if (node != null) setup.WithBootstrapNode(node); } protected ICodexNode BootstrapNode { get { - var bn = Get().BootstrapNode; - if (bn == null) throw new InvalidOperationException("BootstrapNode accessed before initialized."); - return bn; + var tl = Get(); + if (bootstrapNodes.TryGetValue(tl, out var node)) + { + return node; + } + throw new InvalidOperationException("Bootstrap node not yet started."); } } } diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index 40e711fc..d27216d3 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -1,5 +1,6 @@ using BlockchainUtils; using CodexClient; +using CodexClient.Hooks; using CodexContractsPlugin; using CodexNetDeployer; using CodexPlugin; @@ -16,74 +17,86 @@ using Newtonsoft.Json; using NUnit.Framework; using NUnit.Framework.Constraints; using OverwatchTranscript; +using Utils; namespace CodexTests { - public class CodexDistTestComponents : ILifecycleComponent + public class CodexLogTrackerProvider : ICodexHooksProvider { - private readonly object nodesLock = new object(); + private readonly Action addNode; - public CodexDistTestComponents(CodexTranscriptWriter? writer) + public CodexLogTrackerProvider(Action addNode) { - Writer = writer; + this.addNode = addNode; } - public CodexTranscriptWriter? Writer { get; } - public BlockCache Cache { get; } = new(); - public List Nodes { get; } = new(); - - public void Start(ILifecycleComponentAccess access) + // See TestLifecycle.cs DownloadAllLogs() + public ICodexNodeHooks CreateHooks(string nodeName) { - var ci = access.Get().CoreInterface; - ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => + return new CodexLogTracker(addNode); + } + + public class CodexLogTracker : ICodexNodeHooks + { + private readonly Action addNode; + + public CodexLogTracker(Action addNode) { - lock (nodesLock) - { - Nodes.Add(n); - } - })); - } - - public void Stop(ILifecycleComponentAccess access, DistTestResult result) - { - var tl = access.Get(); - var log = tl.Log; - var logFiles = tl.DownloadAllLogs(); - - TeardownTranscript(log, logFiles, result); - - // todo: on not success: go to nodes and dl logs? - // or fix disttest failure log download so we can always have logs even for non-codexes? - } - - private void TeardownTranscript(TestLog log, IDownloadedLog[] logFiles, DistTestResult result) - { - if (Writer == null) return; - - Writer.AddResult(result.Success, result.Result); - - try - { - Stopwatch.Measure(log, "Transcript.ProcessLogs", () => - { - Writer.ProcessLogs(logFiles); - }); - - Stopwatch.Measure(log, $"Transcript.Finalize", () => - { - Writer.IncludeFile(log.GetFullName()); - Writer.Finalize(); - }); + this.addNode = addNode; } - catch (Exception ex) + + public void OnFileDownloaded(ByteSize size, ContentId cid) + { + } + + public void OnFileDownloading(ContentId cid) + { + } + + public void OnFileUploaded(string uid, ByteSize size, ContentId cid) + { + } + + public void OnFileUploading(string uid, ByteSize size) + { + } + + public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) + { + addNode(node); + } + + public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) + { + } + + public void OnNodeStopping() + { + } + + public void OnStorageAvailabilityCreated(StorageAvailability response) + { + } + + public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) + { + } + + public void OnStorageContractUpdated(StoragePurchase purchaseStatus) { - log.Error("Failure during transcript teardown: " + ex); } } } public class CodexDistTest : DistTest { + private static readonly object _lock = new object(); + private static readonly Dictionary writers = new Dictionary(); + private static readonly Dictionary blockCaches = new Dictionary(); + + // this entire structure is not good and needs to be destroyed at the earliest convenience: + private static readonly Dictionary> nodes = new Dictionary>(); + public CodexDistTest() { ProjectPlugin.Load(); @@ -99,12 +112,34 @@ namespace CodexTests localBuilder.Build(); } - protected override void CreateComponents(ILifecycleComponentCollector collector) + protected override void LifecycleStart(TestLifecycle lifecycle) { - base.CreateComponents(collector); - collector.AddComponent(new CodexDistTestComponents( - SetupTranscript() - )); + base.LifecycleStart(lifecycle); + SetupTranscript(lifecycle); + + Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => + { + lock (_lock) + { + if (!nodes.ContainsKey(lifecycle)) nodes.Add(lifecycle, new List()); + nodes[lifecycle].Add(n); + } + })); + } + + protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + { + base.LifecycleStop(lifecycle, result); + TeardownTranscript(lifecycle, result); + + if (!result.Success) + { + lock (_lock) + { + var codexNodes = nodes[lifecycle]; + foreach (var node in codexNodes) node.DownloadLog(); + } + } } public ICodexNode StartCodex() @@ -138,11 +173,6 @@ namespace CodexTests return Ci.StartGethNode(GetBlockCache(), setup); } - private BlockCache GetBlockCache() - { - return Get().Cache; - } - public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers() { return new PeerConnectionTestHelpers(GetTestLog()); @@ -150,7 +180,7 @@ namespace CodexTests public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers() { - return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); + return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); } public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "") @@ -228,20 +258,81 @@ namespace CodexTests return null; } - private CodexTranscriptWriter? SetupTranscript() + private void SetupTranscript(TestLifecycle lifecycle) { var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return null; + if (attr == null) return; var config = new CodexTranscriptWriterConfig( - attr.OutputFilename, attr.IncludeBlockReceivedEvents ); - var log = new LogPrefixer(GetTestLog(), "(Transcript) "); + var log = new LogPrefixer(lifecycle.Log, "(Transcript) "); var writer = new CodexTranscriptWriter(log, config, Transcript.NewWriter(log)); Ci.AddCodexHooksProvider(writer); - return writer; + lock (_lock) + { + writers.Add(lifecycle, writer); + } + } + + private void TeardownTranscript(TestLifecycle lifecycle, DistTestResult result) + { + var attr = GetTranscriptAttributeOfCurrentTest(); + if (attr == null) return; + + var outputFilepath = GetOutputFullPath(lifecycle, attr); + + CodexTranscriptWriter writer = null!; + lock (_lock) + { + writer = writers[lifecycle]; + writers.Remove(lifecycle); + } + + writer.AddResult(result.Success, result.Result); + + try + { + Stopwatch.Measure(lifecycle.Log, "Transcript.ProcessLogs", () => + { + writer.ProcessLogs(lifecycle.DownloadAllLogs()); + }); + + Stopwatch.Measure(lifecycle.Log, $"Transcript.Finalize: {outputFilepath}", () => + { + writer.IncludeFile(lifecycle.Log.GetFullName()); + writer.Finalize(outputFilepath); + }); + } + catch (Exception ex) + { + lifecycle.Log.Error("Failure during transcript teardown: " + ex); + } + } + + private string GetOutputFullPath(TestLifecycle lifecycle, CreateTranscriptAttribute attr) + { + var outputPath = Path.GetDirectoryName(lifecycle.Log.GetFullName()); + if (outputPath == null) throw new Exception("Logfile path is null"); + var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.GetFullName()); + if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); + var outputFile = Path.Combine(outputPath, filename + "_" + attr.OutputFilename); + if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; + return outputFile; + } + + private BlockCache GetBlockCache() + { + var lifecycle = Get(); + lock (_lock) + { + if (!blockCaches.ContainsKey(lifecycle)) + { + blockCaches[lifecycle] = new BlockCache(); + } + } + return blockCaches[lifecycle]; } } diff --git a/Tests/ExperimentalTests/CodexLogTrackerProvider.cs b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs deleted file mode 100644 index 1b2fdd66..00000000 --- a/Tests/ExperimentalTests/CodexLogTrackerProvider.cs +++ /dev/null @@ -1,73 +0,0 @@ -using CodexClient; -using CodexClient.Hooks; -using Utils; - -namespace CodexTests -{ - public class CodexLogTrackerProvider : ICodexHooksProvider - { - private readonly Action addNode; - - public CodexLogTrackerProvider(Action addNode) - { - this.addNode = addNode; - } - - // See TestLifecycle.cs DownloadAllLogs() - public ICodexNodeHooks CreateHooks(string nodeName) - { - return new CodexLogTracker(addNode); - } - - public class CodexLogTracker : ICodexNodeHooks - { - private readonly Action addNode; - - public CodexLogTracker(Action addNode) - { - this.addNode = addNode; - } - - public void OnFileDownloaded(ByteSize size, ContentId cid) - { - } - - public void OnFileDownloading(ContentId cid) - { - } - - public void OnFileUploaded(string uid, ByteSize size, ContentId cid) - { - } - - public void OnFileUploading(string uid, ByteSize size) - { - } - - public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) - { - addNode(node); - } - - public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) - { - } - - public void OnNodeStopping() - { - } - - public void OnStorageAvailabilityCreated(StorageAvailability response) - { - } - - public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) - { - } - - public void OnStorageContractUpdated(StoragePurchase purchaseStatus) - { - } - } - } -} diff --git a/Tests/FrameworkTests/Parallelism.cs b/Tests/FrameworkTests/Parallelism.cs deleted file mode 100644 index 8a877f41..00000000 --- a/Tests/FrameworkTests/Parallelism.cs +++ /dev/null @@ -1,6 +0,0 @@ -using NUnit.Framework; - -[assembly: LevelOfParallelism(100)] -namespace FrameworkTests -{ -} From dd888f30e363562a9db1d2d90e497f3343f73b34 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 25 Apr 2025 15:42:13 +0200 Subject: [PATCH 46/51] excellent cleanup --- .../OverwatchSupport/CodexTranscriptWriter.cs | 15 +- .../CodexTranscriptWriterConfig.cs | 4 +- .../MarketplaceAutoBootstrapDistTest.cs | 20 +- .../NodeTests/BasicInfoTests.cs | 8 +- Tests/CodexReleaseTests/Parallelism.cs | 2 +- Tests/DistTestCore/DistTest.cs | 154 +++++--------- Tests/DistTestCore/Global.cs | 60 ++++++ Tests/DistTestCore/Logs/FixtureLog.cs | 9 +- Tests/DistTestCore/Logs/TestLog.cs | 5 +- Tests/DistTestCore/NameUtils.cs | 14 +- .../AutoBootstrapDistTest.cs | 37 +--- Tests/ExperimentalTests/CodexDistTest.cs | 190 ++++-------------- .../CodexLogTrackerProvider.cs | 73 +++++++ 13 files changed, 258 insertions(+), 333 deletions(-) create mode 100644 Tests/DistTestCore/Global.cs create mode 100644 Tests/ExperimentalTests/CodexLogTrackerProvider.cs diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs index afd8d1a2..95b8dde6 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs @@ -23,16 +23,27 @@ namespace CodexPlugin.OverwatchSupport converter = new CodexLogConverter(writer, config, identityMap); } - public void Finalize(string outputFilepath) + public void FinalizeWriter() { log.Log("Finalizing Codex transcript..."); writer.AddHeader(CodexHeaderKey, CreateCodexHeader()); - writer.Write(outputFilepath); + writer.Write(GetOutputFullPath()); log.Log("Done"); } + private string GetOutputFullPath() + { + var outputPath = Path.GetDirectoryName(log.GetFullName()); + if (outputPath == null) throw new Exception("Logfile path is null"); + var filename = Path.GetFileNameWithoutExtension(log.GetFullName()); + if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); + var outputFile = Path.Combine(outputPath, filename + "_" + config.OutputPath); + if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; + return outputFile; + } + public ICodexNodeHooks CreateHooks(string nodeName) { nodeName = Str.Between(nodeName, "'", "'"); diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs index 247494c8..112f5b16 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs @@ -2,11 +2,13 @@ { public class CodexTranscriptWriterConfig { - public CodexTranscriptWriterConfig(bool includeBlockReceivedEvents) + public CodexTranscriptWriterConfig(string outputPath, bool includeBlockReceivedEvents) { + OutputPath = outputPath; IncludeBlockReceivedEvents = includeBlockReceivedEvents; } + public string OutputPath { get; } public bool IncludeBlockReceivedEvents { get; } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index a5ec0885..96f84813 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -3,41 +3,35 @@ using CodexContractsPlugin; using CodexContractsPlugin.Marketplace; using CodexPlugin; using CodexTests; -using DistTestCore; using GethPlugin; using Nethereum.Hex.HexConvertors.Extensions; +using NUnit.Framework; using Utils; namespace CodexReleaseTests.MarketTests { public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest { - private readonly Dictionary handles = new Dictionary(); + private MarketplaceHandle handle = null!; protected const int StartingBalanceTST = 1000; protected const int StartingBalanceEth = 10; - protected override void LifecycleStart(TestLifecycle lifecycle) + [SetUp] + public void SetupMarketplace() { - base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); - handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); - } - - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) - { - handles.Remove(lifecycle); - base.LifecycleStop(lifecycle, result); + handle = new MarketplaceHandle(geth, contracts); } protected IGethNode GetGeth() { - return handles[Get()].Geth; + return handle.Geth; } protected ICodexContracts GetContracts() { - return handles[Get()].Contracts; + return handle.Contracts; } protected TimeSpan GetPeriodDuration() diff --git a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs index 9b9f4bbf..f6cba228 100644 --- a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs +++ b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs @@ -1,11 +1,5 @@ -using CodexPlugin; -using CodexTests; +using CodexTests; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Utils; namespace CodexReleaseTests.NodeTests diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index f6e68cc0..d589af32 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(1)] +[assembly: LevelOfParallelism(10)] namespace CodexReleaseTests { } diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 534eb74a..93732405 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -12,70 +12,45 @@ using Assert = NUnit.Framework.Assert; namespace DistTestCore { [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public abstract class DistTest { - private const string TestNamespacePrefix = "cdx-"; - private readonly Configuration configuration = new Configuration(); - private readonly Assembly[] testAssemblies; + private static readonly Global global = new Global(); private readonly FixtureLog fixtureLog; private readonly StatusLog statusLog; - private readonly object lifecycleLock = new object(); - private readonly EntryPoint globalEntryPoint; - private readonly Dictionary lifecycles = new Dictionary(); - private readonly string deployId; - + private readonly TestLifecycle lifecycle; + private readonly string deployId = NameUtils.MakeDeployId(); + public DistTest() { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - testAssemblies = assemblies.Where(a => a.FullName!.ToLowerInvariant().Contains("test")).ToArray(); - - deployId = NameUtils.MakeDeployId(); - - var logConfig = configuration.GetLogConfig(); + var logConfig = global.Configuration.GetLogConfig(); var startTime = DateTime.UtcNow; fixtureLog = FixtureLog.Create(logConfig, startTime, deployId); statusLog = new StatusLog(logConfig, startTime, "dist-tests", deployId); - globalEntryPoint = new EntryPoint(fixtureLog, configuration.GetK8sConfiguration(new DefaultK8sTimeSet(), TestNamespacePrefix), configuration.GetFileManagerFolder()); + fixtureLog.Log("Test framework revision: " + GitInfo.GetStatus()); + + lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(startTime), global.Configuration, + GetWebCallTimeSet(), + GetK8sTimeSet(), + Global.TestNamespacePrefix + Guid.NewGuid().ToString(), + deployId, + ShouldWaitForCleanup() + ); Initialize(fixtureLog); } [OneTimeSetUp] - public void GlobalSetup() + public static void GlobalSetup() { - fixtureLog.Log($"Starting..."); - globalEntryPoint.Announce(); - - // Previous test run may have been interrupted. - // Begin by cleaning everything up. - try - { - Stopwatch.Measure(fixtureLog, "Global setup", () => - { - globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true); - }); - } - catch (Exception ex) - { - GlobalTestFailure.HasFailed = true; - fixtureLog.Error($"Global setup cleanup failed with: {ex}"); - throw; - } - - fixtureLog.Log("Test framework revision: " + GitInfo.GetStatus()); - fixtureLog.Log("Global setup cleanup successful"); + global.Setup(); } [OneTimeTearDown] - public void GlobalTearDown() + public static void GlobalTearDown() { - globalEntryPoint.Decommission( - // There shouldn't be any of either, but clean everything up regardless. - deleteKubernetesResources: true, - deleteTrackedFiles: true, - waitTillDone: true - ); + global.TearDown(); } [SetUp] @@ -85,18 +60,6 @@ namespace DistTestCore { Assert.Inconclusive("Skip test: Previous test failed during clean up."); } - else - { - try - { - CreateNewTestLifecycle(); - } - catch (Exception ex) - { - fixtureLog.Error("Setup failed: " + ex); - GlobalTestFailure.HasFailed = true; - } - } } [TearDown] @@ -117,18 +80,18 @@ namespace DistTestCore { get { - return Get().CoreInterface; + return lifecycle.CoreInterface; } } public TrackedFile GenerateTestFile(ByteSize size, string label = "") { - return Get().GenerateTestFile(size, label); + return lifecycle.GenerateTestFile(size, label); } public TrackedFile GenerateTestFile(Action options, string label = "") { - return Get().GenerateTestFile(options, label); + return lifecycle.GenerateTestFile(options, label); } /// @@ -137,12 +100,22 @@ namespace DistTestCore /// public void ScopedTestFiles(Action action) { - Get().GetFileManager().ScopedFiles(action); + lifecycle.GetFileManager().ScopedFiles(action); } public ILog GetTestLog() { - return Get().Log; + return lifecycle.Log; + } + + public IFileManager GetFileManager() + { + return lifecycle.GetFileManager(); + } + + public string GetTestNamespace() + { + return lifecycle.TestNamespace; } public void Log(string msg) @@ -159,64 +132,24 @@ namespace DistTestCore public void Measure(string name, Action action) { - Stopwatch.Measure(Get().Log, name, action); + Stopwatch.Measure(lifecycle.Log, name, action); } protected TimeRange GetTestRunTimeRange() { - return new TimeRange(Get().TestStart, DateTime.UtcNow); + return new TimeRange(lifecycle.TestStart, DateTime.UtcNow); } protected virtual void Initialize(FixtureLog fixtureLog) { } - protected virtual void LifecycleStart(TestLifecycle lifecycle) - { - } - - protected virtual void LifecycleStop(TestLifecycle lifecycle, DistTestResult testResult) - { - } - protected virtual void CollectStatusLogData(TestLifecycle lifecycle, Dictionary data) { } - protected TestLifecycle Get() - { - lock (lifecycleLock) - { - return lifecycles[GetCurrentTestName()]; - } - } - - private void CreateNewTestLifecycle() - { - var testName = GetCurrentTestName(); - fixtureLog.WriteLogTag(); - Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => - { - lock (lifecycleLock) - { - var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); - var lifecycle = new TestLifecycle( - fixtureLog.CreateTestLog(), - configuration, - GetWebCallTimeSet(), - GetK8sTimeSet(), - testNamespace, - deployId, - ShouldWaitForCleanup()); - lifecycles.Add(testName, lifecycle); - LifecycleStart(lifecycle); - } - }); - } - private void DisposeTestLifecycle() { - var lifecycle = Get(); var testResult = GetTestResult(); var testDuration = lifecycle.GetTestDuration(); var data = lifecycle.GetPluginMetadata(); @@ -228,9 +161,7 @@ namespace DistTestCore WriteEndTestLog(lifecycle.Log); IncludeLogsOnTestFailure(lifecycle); - LifecycleStop(lifecycle, testResult); lifecycle.DeleteAllResources(); - lifecycles.Remove(GetCurrentTestName()); }); } @@ -287,7 +218,7 @@ namespace DistTestCore var className = currentTest.ClassName; var methodName = currentTest.MethodName; - var testClasses = testAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray(); + var testClasses = global.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.Select(m => m.GetCustomAttribute()) @@ -296,19 +227,24 @@ namespace DistTestCore .ToArray(); } + protected IDownloadedLog[] DownloadAllLogs() + { + return lifecycle.DownloadAllLogs(); + } + private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) { var testStatus = TestContext.CurrentContext.Result.Outcome.Status; if (ShouldDownloadAllLogs(testStatus)) { lifecycle.Log.Log("Downloading all container logs..."); - lifecycle.DownloadAllLogs(); + DownloadAllLogs(); } } private bool ShouldDownloadAllLogs(TestStatus testStatus) { - if (configuration.AlwaysDownloadContainerLogs) return true; + if (global.Configuration.AlwaysDownloadContainerLogs) return true; if (!IsDownloadingLogsEnabled()) return false; if (testStatus == TestStatus.Failed) { @@ -323,7 +259,7 @@ namespace DistTestCore return $"[{TestContext.CurrentContext.Test.Name}]"; } - private DistTestResult GetTestResult() + public DistTestResult GetTestResult() { var success = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; var status = TestContext.CurrentContext.Result.Outcome.Status.ToString(); diff --git a/Tests/DistTestCore/Global.cs b/Tests/DistTestCore/Global.cs new file mode 100644 index 00000000..4ba1374b --- /dev/null +++ b/Tests/DistTestCore/Global.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using Core; +using Logging; + +namespace DistTestCore +{ + public class Global + { + public const string TestNamespacePrefix = "cdx-"; + public Configuration Configuration { get; } = new Configuration(); + + public Assembly[] TestAssemblies { get; } + private readonly EntryPoint globalEntryPoint; + private readonly ILog log; + + public Global() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + TestAssemblies = assemblies.Where(a => a.FullName!.ToLowerInvariant().Contains("test")).ToArray(); + + log = new ConsoleLog(); + globalEntryPoint = new EntryPoint( + log, + Configuration.GetK8sConfiguration( + new DefaultK8sTimeSet(), + TestNamespacePrefix + ), + Configuration.GetFileManagerFolder() + ); + } + + public void Setup() + { + try + { + Stopwatch.Measure(log, "Global setup", () => + { + globalEntryPoint.Announce(); + globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true); + }); + } + catch (Exception ex) + { + GlobalTestFailure.HasFailed = true; + log.Error($"Global setup cleanup failed with: {ex}"); + throw; + } + } + + public void TearDown() + { + globalEntryPoint.Decommission( + // There shouldn't be any of either, but clean everything up regardless. + deleteKubernetesResources: true, + deleteTrackedFiles: true, + waitTillDone: true + ); + } + } +} diff --git a/Tests/DistTestCore/Logs/FixtureLog.cs b/Tests/DistTestCore/Logs/FixtureLog.cs index 9d3c77d3..5978a602 100644 --- a/Tests/DistTestCore/Logs/FixtureLog.cs +++ b/Tests/DistTestCore/Logs/FixtureLog.cs @@ -4,19 +4,14 @@ namespace DistTestCore.Logs { public class FixtureLog : BaseTestLog { - private readonly ILog backingLog; - private readonly string deployId; - public FixtureLog(ILog backingLog, string deployId) : base(backingLog, deployId) { - this.backingLog = backingLog; - this.deployId = deployId; } - public TestLog CreateTestLog(string name = "") + public TestLog CreateTestLog(DateTime start, string name = "") { - return TestLog.Create(this, name); + return TestLog.Create(this, start, name); } public static FixtureLog Create(LogConfig config, DateTime start, string deployId, string name = "") diff --git a/Tests/DistTestCore/Logs/TestLog.cs b/Tests/DistTestCore/Logs/TestLog.cs index 0dca1464..5c38f951 100644 --- a/Tests/DistTestCore/Logs/TestLog.cs +++ b/Tests/DistTestCore/Logs/TestLog.cs @@ -1,5 +1,4 @@ using Logging; -using System.Xml.Linq; namespace DistTestCore.Logs { @@ -11,9 +10,9 @@ namespace DistTestCore.Logs backingLog.Log($"*** Begin: {methodName}"); } - public static TestLog Create(FixtureLog parentLog, string name = "") + public static TestLog Create(FixtureLog parentLog, DateTime start, string name = "") { - var methodName = NameUtils.GetTestMethodName(name); + var methodName = NameUtils.GetTestLogFileName(start, name); var fullName = Path.Combine(parentLog.GetFullName(), methodName); var backingLog = CreateMainLog(fullName, name); return new TestLog(backingLog, methodName, parentLog.DeployId); diff --git a/Tests/DistTestCore/NameUtils.cs b/Tests/DistTestCore/NameUtils.cs index 55489db9..44919d51 100644 --- a/Tests/DistTestCore/NameUtils.cs +++ b/Tests/DistTestCore/NameUtils.cs @@ -5,6 +5,11 @@ namespace DistTestCore { public static class NameUtils { + public static string GetTestLogFileName(DateTime start, string name = "") + { + return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{GetTestMethodName(name)}"; + } + public static string GetTestMethodName(string name = "") { if (!string.IsNullOrEmpty(name)) return name; @@ -16,7 +21,7 @@ namespace DistTestCore public static string GetFixtureFullName(LogConfig config, DateTime start, string name) { var folder = DetermineFolder(config, start); - var fixtureName = GetFixtureName(name, start); + var fixtureName = GetRawFixtureName(); return Path.Combine(folder, fixtureName); } @@ -85,13 +90,6 @@ namespace DistTestCore Pad(start.Day)); } - private static string GetFixtureName(string name, DateTime start) - { - var fixtureName = GetRawFixtureName(); - if (!string.IsNullOrEmpty(name)) fixtureName = name; - return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{fixtureName.Replace('.', '-')}"; - } - private static string Pad(int n) { return n.ToString().PadLeft(2, '0'); diff --git a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs index d8ac6805..93fcf6f7 100644 --- a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs +++ b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs @@ -1,29 +1,27 @@ using CodexClient; using CodexPlugin; -using DistTestCore; +using NUnit.Framework; namespace CodexTests { public class AutoBootstrapDistTest : CodexDistTest { - private readonly Dictionary bootstrapNodes = new Dictionary(); private bool isBooting = false; - protected override void LifecycleStart(TestLifecycle tl) + public ICodexNode BootstrapNode { get; private set; } = null!; + + [SetUp] + public void SetupBootstrapNode() { - base.LifecycleStart(tl); - if (!bootstrapNodes.ContainsKey(tl)) - { - isBooting = true; - bootstrapNodes.Add(tl, StartCodex(s => s.WithName("BOOTSTRAP_" + tl.TestNamespace))); - isBooting = false; - } + isBooting = true; + BootstrapNode = StartCodex(s => s.WithName("BOOTSTRAP_" + GetTestNamespace())); + isBooting = false; } - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + [TearDown] + public void TearDownBootstrapNode() { - bootstrapNodes.Remove(lifecycle); - base.LifecycleStop(lifecycle, result); + BootstrapNode.Stop(waitTillStopped: false); } protected override void OnCodexSetup(ICodexSetup setup) @@ -33,18 +31,5 @@ namespace CodexTests var node = BootstrapNode; if (node != null) setup.WithBootstrapNode(node); } - - protected ICodexNode BootstrapNode - { - get - { - var tl = Get(); - if (bootstrapNodes.TryGetValue(tl, out var node)) - { - return node; - } - throw new InvalidOperationException("Bootstrap node not yet started."); - } - } } } diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index d27216d3..5ecae330 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -1,6 +1,5 @@ using BlockchainUtils; using CodexClient; -using CodexClient.Hooks; using CodexContractsPlugin; using CodexNetDeployer; using CodexPlugin; @@ -17,85 +16,14 @@ using Newtonsoft.Json; using NUnit.Framework; using NUnit.Framework.Constraints; using OverwatchTranscript; -using Utils; namespace CodexTests { - public class CodexLogTrackerProvider : ICodexHooksProvider - { - private readonly Action addNode; - - public CodexLogTrackerProvider(Action addNode) - { - this.addNode = addNode; - } - - // See TestLifecycle.cs DownloadAllLogs() - public ICodexNodeHooks CreateHooks(string nodeName) - { - return new CodexLogTracker(addNode); - } - - public class CodexLogTracker : ICodexNodeHooks - { - private readonly Action addNode; - - public CodexLogTracker(Action addNode) - { - this.addNode = addNode; - } - - public void OnFileDownloaded(ByteSize size, ContentId cid) - { - } - - public void OnFileDownloading(ContentId cid) - { - } - - public void OnFileUploaded(string uid, ByteSize size, ContentId cid) - { - } - - public void OnFileUploading(string uid, ByteSize size) - { - } - - public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) - { - addNode(node); - } - - public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) - { - } - - public void OnNodeStopping() - { - } - - public void OnStorageAvailabilityCreated(StorageAvailability response) - { - } - - public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) - { - } - - public void OnStorageContractUpdated(StoragePurchase purchaseStatus) - { - } - } - } - public class CodexDistTest : DistTest { - private static readonly object _lock = new object(); - private static readonly Dictionary writers = new Dictionary(); - private static readonly Dictionary blockCaches = new Dictionary(); - - // this entire structure is not good and needs to be destroyed at the earliest convenience: - private static readonly Dictionary> nodes = new Dictionary>(); + private readonly BlockCache blockCache = new BlockCache(); + private readonly List nodes = new List(); + private CodexTranscriptWriter? writer; public CodexDistTest() { @@ -105,41 +33,26 @@ namespace CodexTests ProjectPlugin.Load(); } + [SetUp] + public void SetupCodexDistTest() + { + writer = SetupTranscript(); + + } + + [TearDown] + public void TearDownCodexDistTest() + { + TeardownTranscript(); + } + protected override void Initialize(FixtureLog fixtureLog) { var localBuilder = new LocalCodexBuilder(fixtureLog); localBuilder.Intialize(); localBuilder.Build(); - } - protected override void LifecycleStart(TestLifecycle lifecycle) - { - base.LifecycleStart(lifecycle); - SetupTranscript(lifecycle); - - Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => - { - lock (_lock) - { - if (!nodes.ContainsKey(lifecycle)) nodes.Add(lifecycle, new List()); - nodes[lifecycle].Add(n); - } - })); - } - - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) - { - base.LifecycleStop(lifecycle, result); - TeardownTranscript(lifecycle, result); - - if (!result.Success) - { - lock (_lock) - { - var codexNodes = nodes[lifecycle]; - foreach (var node in codexNodes) node.DownloadLog(); - } - } + Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(nodes.Add)); } public ICodexNode StartCodex() @@ -170,7 +83,7 @@ namespace CodexTests public IGethNode StartGethNode(Action setup) { - return Ci.StartGethNode(GetBlockCache(), setup); + return Ci.StartGethNode(blockCache, setup); } public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers() @@ -180,7 +93,7 @@ namespace CodexTests public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers() { - return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); + return new PeerDownloadTestHelpers(GetTestLog(), GetFileManager()); } public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "") @@ -258,82 +171,47 @@ namespace CodexTests return null; } - private void SetupTranscript(TestLifecycle lifecycle) + private CodexTranscriptWriter? SetupTranscript() { var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; + if (attr == null) return null; var config = new CodexTranscriptWriterConfig( + attr.OutputFilename, attr.IncludeBlockReceivedEvents ); - var log = new LogPrefixer(lifecycle.Log, "(Transcript) "); + var log = new LogPrefixer(GetTestLog(), "(Transcript) "); var writer = new CodexTranscriptWriter(log, config, Transcript.NewWriter(log)); Ci.AddCodexHooksProvider(writer); - lock (_lock) - { - writers.Add(lifecycle, writer); - } + return writer; } - private void TeardownTranscript(TestLifecycle lifecycle, DistTestResult result) + private void TeardownTranscript() { - var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; - - var outputFilepath = GetOutputFullPath(lifecycle, attr); - - CodexTranscriptWriter writer = null!; - lock (_lock) - { - writer = writers[lifecycle]; - writers.Remove(lifecycle); - } + if (writer == null) return; + var result = GetTestResult(); + var log = GetTestLog(); writer.AddResult(result.Success, result.Result); - try { - Stopwatch.Measure(lifecycle.Log, "Transcript.ProcessLogs", () => + Stopwatch.Measure(log, "Transcript.ProcessLogs", () => { - writer.ProcessLogs(lifecycle.DownloadAllLogs()); + writer.ProcessLogs(DownloadAllLogs()); }); - Stopwatch.Measure(lifecycle.Log, $"Transcript.Finalize: {outputFilepath}", () => + Stopwatch.Measure(log, $"Transcript.FinalizeWriter", () => { - writer.IncludeFile(lifecycle.Log.GetFullName()); - writer.Finalize(outputFilepath); + writer.IncludeFile(log.GetFullName()); + writer.FinalizeWriter(); }); } catch (Exception ex) { - lifecycle.Log.Error("Failure during transcript teardown: " + ex); + log.Error("Failure during transcript teardown: " + ex); } } - - private string GetOutputFullPath(TestLifecycle lifecycle, CreateTranscriptAttribute attr) - { - var outputPath = Path.GetDirectoryName(lifecycle.Log.GetFullName()); - if (outputPath == null) throw new Exception("Logfile path is null"); - var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.GetFullName()); - if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); - var outputFile = Path.Combine(outputPath, filename + "_" + attr.OutputFilename); - if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; - return outputFile; - } - - private BlockCache GetBlockCache() - { - var lifecycle = Get(); - lock (_lock) - { - if (!blockCaches.ContainsKey(lifecycle)) - { - blockCaches[lifecycle] = new BlockCache(); - } - } - return blockCaches[lifecycle]; - } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] diff --git a/Tests/ExperimentalTests/CodexLogTrackerProvider.cs b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs new file mode 100644 index 00000000..1b2fdd66 --- /dev/null +++ b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs @@ -0,0 +1,73 @@ +using CodexClient; +using CodexClient.Hooks; +using Utils; + +namespace CodexTests +{ + public class CodexLogTrackerProvider : ICodexHooksProvider + { + private readonly Action addNode; + + public CodexLogTrackerProvider(Action addNode) + { + this.addNode = addNode; + } + + // See TestLifecycle.cs DownloadAllLogs() + public ICodexNodeHooks CreateHooks(string nodeName) + { + return new CodexLogTracker(addNode); + } + + public class CodexLogTracker : ICodexNodeHooks + { + private readonly Action addNode; + + public CodexLogTracker(Action addNode) + { + this.addNode = addNode; + } + + public void OnFileDownloaded(ByteSize size, ContentId cid) + { + } + + public void OnFileDownloading(ContentId cid) + { + } + + public void OnFileUploaded(string uid, ByteSize size, ContentId cid) + { + } + + public void OnFileUploading(string uid, ByteSize size) + { + } + + public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) + { + addNode(node); + } + + public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) + { + } + + public void OnNodeStopping() + { + } + + public void OnStorageAvailabilityCreated(StorageAvailability response) + { + } + + public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) + { + } + + public void OnStorageContractUpdated(StoragePurchase purchaseStatus) + { + } + } + } +} From 24a25292b8f12ef2788167a628499c959a3906e5 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 25 Apr 2025 16:13:01 +0200 Subject: [PATCH 47/51] fixes transcript writing --- Framework/Utils/Str.cs | 5 ++++- Tests/ExperimentalTests/CodexDistTest.cs | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Framework/Utils/Str.cs b/Framework/Utils/Str.cs index aea19ec8..ab8fe46d 100644 --- a/Framework/Utils/Str.cs +++ b/Framework/Utils/Str.cs @@ -4,8 +4,11 @@ { public static string Between(string input, string open, string close) { - var openIndex = input.IndexOf(open) + open.Length; + var openI = input.IndexOf(open); + if (openI == -1) return input; + var openIndex = openI + open.Length; var closeIndex = input.LastIndexOf(close); + if (closeIndex == -1) return input; return input.Substring(openIndex, closeIndex - openIndex); } diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index 5ecae330..422b5bd7 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -37,7 +37,6 @@ namespace CodexTests public void SetupCodexDistTest() { writer = SetupTranscript(); - } [TearDown] @@ -203,7 +202,7 @@ namespace CodexTests Stopwatch.Measure(log, $"Transcript.FinalizeWriter", () => { - writer.IncludeFile(log.GetFullName()); + writer.IncludeFile(log.GetFullName() + ".log"); writer.FinalizeWriter(); }); } From acf5436d388448886d201fab4d296a1329fe72cd Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 29 Apr 2025 12:05:33 +0200 Subject: [PATCH 48/51] remove lifecycle test test --- Tests/FrameworkTests/LifecycelyTest.cs | 219 ------------------------- 1 file changed, 219 deletions(-) delete mode 100644 Tests/FrameworkTests/LifecycelyTest.cs diff --git a/Tests/FrameworkTests/LifecycelyTest.cs b/Tests/FrameworkTests/LifecycelyTest.cs deleted file mode 100644 index e420a911..00000000 --- a/Tests/FrameworkTests/LifecycelyTest.cs +++ /dev/null @@ -1,219 +0,0 @@ -using NUnit.Framework; - -namespace FrameworkTests -{ - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] - [TestFixture(10)] - [TestFixture(20)] - [TestFixture(30)] - public class LifecycelyTest - { - public LifecycelyTest(int num) - { - Log("ctor", GetCurrentTestName(), num); - this.num = num; - } - - [SetUp] - public void Setup() - { - Log(nameof(Setup), GetCurrentTestName()); - } - - [TearDown] - public void TearDown() - { - Log(nameof(TearDown), GetCurrentTestName()); - } - - //[Test] - //public void A() - //{ - // Log(nameof(A), "Run"); - // SleepRandom(); - // Log(nameof(A), "Finish"); - //} - - //[Test] - //public void B() - //{ - // Log(nameof(B), "Run"); - // SleepRandom(); - // Log(nameof(B), "Finish"); - //} - - //[Test] - //public void C() - //{ - // Log(nameof(C), "Run"); - // SleepRandom(); - // Log(nameof(C), "Finish"); - //} - - [Test] - [Combinatorial] - public void Multi( - [Values(1, 2, 3)] int num) - { - Log(nameof(Multi), "Run", num); - SleepRandom(); - Log(nameof(Multi), "Finish", num); - } - - - - - - - - - - - - - private static readonly Random r = new Random(); - private readonly int num; - - private void SleepRandom() - { - Thread.Sleep(TimeSpan.FromSeconds(5.0)); - Thread.Sleep(TimeSpan.FromMilliseconds(r.Next(100, 1000))); - } - - private void Log(string scope, string msg) - { - ALog.Log($"{num} {scope} {msg}"); - } - - private void Log(string scope, string msg, int num) - { - ALog.Log($"{this.num} {scope} {msg} {num}"); - } - - private string GetCurrentTestName() - { - return $"[{TestContext.CurrentContext.Test.Name}]"; - } - } - - - - - public class ALog - { - private static readonly object _lock = new object(); - - public static void Log(string msg) - { - lock (_lock) - { - File.AppendAllLines("C:\\Users\\vexor\\Desktop\\Alog.txt", [msg]); - } - } - } - - - - - - - public interface ITestLifecycleComponent - { - } - - - - - - public class Base - { - private readonly Dictionary> anyFields = new(); - - public void Setup() - { - var testId = 23; - - var fields = new Dictionary(); - anyFields.Add(testId, fields); - YieldFields(field => - { - fields.Add(field.GetType(), field); - }); - - } - - public void TearDown() - { - var testId = 23; - - // foreach stop - - anyFields.Remove(testId); - } - - public T Get() - { - int testId = 123; - var fields = anyFields[testId]; - var type = typeof(T); - var result = fields[type]; - return (T)result; - } - - public BaseFields GetBaseField() - { - return Get(); - } - - protected virtual void YieldFields(Action giveField) - { - giveField(new BaseFields()); - } - } - - public class Mid : Base - { - protected override void YieldFields(Action giveField) - { - base.YieldFields(giveField); - giveField(new MidFields()); - } - - public MidFields GetMid() - { - return Get(); - } - } - - public class Top : Mid - { - protected override void YieldFields(Action giveField) - { - base.YieldFields(giveField); - giveField(new TopFields()); - } - - public TopFields GetTop() - { - return Get(); - } - } - - public class BaseFields : ITestLifecycleComponent - { - public string EntryPoint { get; set; } = string.Empty; - public string Log { get; set; } = string.Empty; - } - - public class MidFields : ITestLifecycleComponent - { - public string Nodes { get; set; } = string.Empty; - } - - public class TopFields : ITestLifecycleComponent - { - public string Geth { get; set; } = string.Empty; - public string Contracts { get; set; } = string.Empty; - } -} From 17ffb41e150b5b133697cc99707b80626ea2a562 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 2 May 2025 08:01:44 +0200 Subject: [PATCH 49/51] Makes proof period report interval configurable default to 24 hours --- Tools/TestNetRewarder/Configuration.cs | 3 +++ Tools/TestNetRewarder/Processor.cs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index 5dbeac30..b3aa84cb 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -33,6 +33,9 @@ namespace TestNetRewarder [Uniform("proof-submitted-events", "pse", "PROOFSUBMITTEDEVENTS", false, "When greater than zero, chain event summary will include proof-submitted events.")] public int ShowProofSubmittedEvents { get; set; } = 0; // Defaulted to zero, aprox 7 to 10 such events every 2 minutes in testnet (from autoclient alone!) + [Uniform("proof-period-report-hours", "pprh", "PROOFPERIODREPORTHOURS", false, "Frequency in hours with which proof period reports are created.")] + public int ProofReportHours { get; set; } = 24; + public string LogPath { get diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index eed26962..649ebf47 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -22,6 +22,8 @@ namespace TestNetRewarder this.log = log; lastPeriodUpdateUtc = DateTime.UtcNow; + if (config.ProofReportHours < 1) throw new Exception("ProofReportHours must be one or greater"); + builder = new RequestBuilder(); eventsFormatter = new EventsFormatter(config); @@ -79,7 +81,7 @@ namespace TestNetRewarder private void ProcessPeriodUpdate() { if (config.ShowProofPeriodReports < 1) return; - if (DateTime.UtcNow < (lastPeriodUpdateUtc + TimeSpan.FromHours(1.0))) return; + if (DateTime.UtcNow < (lastPeriodUpdateUtc + TimeSpan.FromHours(config.ProofReportHours))) return; lastPeriodUpdateUtc = DateTime.UtcNow; eventsFormatter.ProcessPeriodReports(chainState.PeriodMonitor.GetAndClearReports()); From c68d4bb13f073c4c632f2f83ebf2a389934eb497 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 2 May 2025 08:21:56 +0200 Subject: [PATCH 50/51] Implement slow mode and recovery --- Tools/AutoClient/Configuration.cs | 3 + .../AutoClient/Modes/FolderStore/FileSaver.cs | 46 +++++++++------ .../Modes/FolderStore/FolderSaver.cs | 56 ++++++++----------- .../Modes/FolderStore/SlowModeHandler.cs | 54 ++++++++++++++++++ 4 files changed, 109 insertions(+), 50 deletions(-) create mode 100644 Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs diff --git a/Tools/AutoClient/Configuration.cs b/Tools/AutoClient/Configuration.cs index 13aea6fd..65996333 100644 --- a/Tools/AutoClient/Configuration.cs +++ b/Tools/AutoClient/Configuration.cs @@ -59,6 +59,9 @@ namespace AutoClient "/root/codex-testnet-starter/scripts/eth_7.address" + ";" + "/root/codex-testnet-starter/scripts/eth_8.address"; + [Uniform("slowModeDelayMinutes", "smdm", "SLOWMODEDELAYMINUTES", false, "When contract failure threshold is reached, slow down process for each file by this amount of minutes.")] + public int SlowModeDelayMinutes { get; set; } = 60 * 1; + public string LogPath { get diff --git a/Tools/AutoClient/Modes/FolderStore/FileSaver.cs b/Tools/AutoClient/Modes/FolderStore/FileSaver.cs index 027065e4..448755f0 100644 --- a/Tools/AutoClient/Modes/FolderStore/FileSaver.cs +++ b/Tools/AutoClient/Modes/FolderStore/FileSaver.cs @@ -7,6 +7,11 @@ namespace AutoClient.Modes.FolderStore public interface IFileSaverEventHandler { void SaveChanges(); + } + + public interface IFileSaverResultHandler + { + void OnSuccess(); void OnFailure(); } @@ -17,16 +22,18 @@ namespace AutoClient.Modes.FolderStore private readonly Stats stats; private readonly string folderFile; private readonly FileStatus entry; - private readonly IFileSaverEventHandler handler; + private readonly IFileSaverEventHandler saveHandler; + private readonly IFileSaverResultHandler resultHandler; - public FileSaver(ILog log, LoadBalancer loadBalancer, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler handler) + public FileSaver(ILog log, LoadBalancer loadBalancer, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler saveHandler, IFileSaverResultHandler resultHandler) { this.log = log; this.loadBalancer = loadBalancer; this.stats = stats; this.folderFile = folderFile; this.entry = entry; - this.handler = handler; + this.saveHandler = saveHandler; + this.resultHandler = resultHandler; } public void Process() @@ -46,9 +53,9 @@ namespace AutoClient.Modes.FolderStore loadBalancer.DispatchOnCodex(instance => { entry.CodexNodeId = instance.Node.GetName(); - handler.SaveChanges(); + saveHandler.SaveChanges(); - var run = new FileSaverRun(log, instance, stats, folderFile, entry, handler); + var run = new FileSaverRun(log, instance, stats, folderFile, entry, saveHandler, resultHandler); run.Process(); }); } @@ -57,7 +64,7 @@ namespace AutoClient.Modes.FolderStore { loadBalancer.DispatchOnSpecificCodex(instance => { - var run = new FileSaverRun(log, instance, stats, folderFile, entry, handler); + var run = new FileSaverRun(log, instance, stats, folderFile, entry, saveHandler, resultHandler); run.Process(); }, entry.CodexNodeId); } @@ -70,17 +77,19 @@ namespace AutoClient.Modes.FolderStore private readonly Stats stats; private readonly string folderFile; private readonly FileStatus entry; - private readonly IFileSaverEventHandler handler; + private readonly IFileSaverEventHandler saveHandler; + private readonly IFileSaverResultHandler resultHandler; private readonly QuotaCheck quotaCheck; - public FileSaverRun(ILog log, CodexWrapper instance, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler handler) + public FileSaverRun(ILog log, CodexWrapper instance, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler saveHandler, IFileSaverResultHandler resultHandler) { this.log = log; this.instance = instance; this.stats = stats; this.folderFile = folderFile; this.entry = entry; - this.handler = handler; + this.saveHandler = saveHandler; + this.resultHandler = resultHandler; quotaCheck = new QuotaCheck(log, folderFile, instance); } @@ -127,7 +136,7 @@ namespace AutoClient.Modes.FolderStore Thread.Sleep(TimeSpan.FromMinutes(1.0)); } Log("Could not upload: Insufficient local storage quota."); - handler.OnFailure(); + resultHandler.OnFailure(); return false; } @@ -206,9 +215,9 @@ namespace AutoClient.Modes.FolderStore entry.BasicCid = string.Empty; stats.FailedUploads++; log.Error("Failed to upload: " + exc); - handler.OnFailure(); + resultHandler.OnFailure(); } - handler.SaveChanges(); + saveHandler.SaveChanges(); } private void CreateNewPurchase() @@ -224,17 +233,18 @@ namespace AutoClient.Modes.FolderStore WaitForStarted(request); stats.StorageRequestStats.SuccessfullyStarted++; - handler.SaveChanges(); + saveHandler.SaveChanges(); Log($"Successfully started new purchase: '{entry.PurchaseId}' for {Time.FormatDuration(request.Purchase.Duration)}"); + resultHandler.OnSuccess(); } catch (Exception exc) { entry.EncodedCid = string.Empty; entry.PurchaseId = string.Empty; - handler.SaveChanges(); + saveHandler.SaveChanges(); log.Error("Failed to start new purchase: " + exc); - handler.OnFailure(); + resultHandler.OnFailure(); } } @@ -253,7 +263,7 @@ namespace AutoClient.Modes.FolderStore throw new Exception("CID received from storage request was not protected."); } - handler.SaveChanges(); + saveHandler.SaveChanges(); Log("Saved new purchaseId: " + entry.PurchaseId); return request; } @@ -289,7 +299,7 @@ namespace AutoClient.Modes.FolderStore Log("Request failed to start. State: " + update.State); entry.EncodedCid = string.Empty; entry.PurchaseId = string.Empty; - handler.SaveChanges(); + saveHandler.SaveChanges(); return; } } @@ -297,7 +307,7 @@ namespace AutoClient.Modes.FolderStore } catch (Exception exc) { - handler.OnFailure(); + resultHandler.OnFailure(); Log($"Exception in {nameof(WaitForSubmittedToStarted)}: {exc}"); throw; } diff --git a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs index 4d090e6d..d951aaee 100644 --- a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs +++ b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs @@ -11,14 +11,16 @@ namespace AutoClient.Modes.FolderStore private readonly JsonFile statusFile; private readonly FolderStatus status; private readonly BalanceChecker balanceChecker; + private readonly SlowModeHandler slowModeHandler; private int changeCounter = 0; - private int failureCount = 0; + private int saveFolderJsonCounter = 0; public FolderSaver(App app, LoadBalancer loadBalancer) { this.app = app; this.loadBalancer = loadBalancer; balanceChecker = new BalanceChecker(app); + slowModeHandler = new SlowModeHandler(app); statusFile = new JsonFile(app, Path.Combine(app.Config.FolderToStore, FolderSaverFilename)); status = statusFile.Load(); @@ -26,10 +28,11 @@ namespace AutoClient.Modes.FolderStore public void Run() { + saveFolderJsonCounter = 0; + var folderFiles = Directory.GetFiles(app.Config.FolderToStore); if (!folderFiles.Any()) throw new Exception("No files found in " + app.Config.FolderToStore); - var saveFolderJsonCounter = 0; balanceChecker.Check(); foreach (var folderFile in folderFiles) { @@ -41,35 +44,30 @@ namespace AutoClient.Modes.FolderStore SaveFile(folderFile); } - if (failureCount > 3) - { - app.Log.Error("Failure count reached threshold. Stopping..."); - app.Cts.Cancel(); - return; - } - - if (changeCounter > 1) - { - changeCounter = 0; - saveFolderJsonCounter++; - if (saveFolderJsonCounter > 5) - { - saveFolderJsonCounter = 0; - if (failureCount > 0) - { - app.Log.Log($"Failure count is reset. (Was: {failureCount})"); - failureCount = 0; - } - balanceChecker.Check(); - SaveFolderSaverJsonFile(); - } - } + slowModeHandler.Check(); + + CheckAndSaveChanges(); statusFile.Save(status); Thread.Sleep(100); } } + private void CheckAndSaveChanges() + { + if (changeCounter > 1) + { + changeCounter = 0; + saveFolderJsonCounter++; + if (saveFolderJsonCounter > 5) + { + saveFolderJsonCounter = 0; + balanceChecker.Check(); + SaveFolderSaverJsonFile(); + } + } + } + private void SaveFile(string folderFile) { var localFilename = Path.GetFileName(folderFile); @@ -114,7 +112,6 @@ namespace AutoClient.Modes.FolderStore } private const int MinCodexStorageFilesize = 262144; - private readonly Random random = new Random(); private readonly string paddingMessage = $"Codex currently requires a minimum filesize of {MinCodexStorageFilesize} bytes for datasets used in storage contracts. " + $"Anything smaller, and the erasure-coding algorithms used for data durability won't function. Therefore, we apply this padding field to make sure this " + $"file is larger than the minimal size. The following is pseudo-random: "; @@ -135,7 +132,7 @@ namespace AutoClient.Modes.FolderStore { var fixedLength = entry.Filename.PadRight(35); var prefix = $"[{fixedLength}] "; - return new FileSaver(new LogPrefixer(app.Log, prefix), loadBalancer, status.Stats, folderFile, entry, this); + return new FileSaver(new LogPrefixer(app.Log, prefix), loadBalancer, status.Stats, folderFile, entry, this, slowModeHandler); } public void SaveChanges() @@ -143,10 +140,5 @@ namespace AutoClient.Modes.FolderStore statusFile.Save(status); changeCounter++; } - - public void OnFailure() - { - failureCount++; - } } } diff --git a/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs b/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs new file mode 100644 index 00000000..37d3f7ee --- /dev/null +++ b/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs @@ -0,0 +1,54 @@ +namespace AutoClient.Modes.FolderStore +{ + public class SlowModeHandler : IFileSaverResultHandler + { + private readonly App app; + private int failureCount = 0; + private bool slowMode = false; + private int recoveryCount = 0; + + public SlowModeHandler(App app) + { + this.app = app; + } + + public void OnSuccess() + { + failureCount = 0; + if (slowMode) + { + recoveryCount++; + if (recoveryCount > 3) + { + Log("Recovery limit reached. Exiting slow mode."); + slowMode = false; + failureCount = 0; + } + } + } + + public void OnFailure() + { + failureCount++; + if (failureCount > 3 && !slowMode) + { + Log("Failure limit reached. Entering slow mode."); + slowMode = true; + recoveryCount = 0; + } + } + + public void Check() + { + if (slowMode) + { + Thread.Sleep(TimeSpan.FromMinutes(app.Config.SlowModeDelayMinutes)); + } + } + + private void Log(string msg) + { + app.Log.Log(msg); + } + } +} From 3fe2827080c805acacb8685bff6d14aa46425166 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 2 May 2025 08:49:43 +0200 Subject: [PATCH 51/51] Adds randomness to price per byte per second and contract duration --- Tools/AutoClient/CodexWrapper.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Tools/AutoClient/CodexWrapper.cs b/Tools/AutoClient/CodexWrapper.cs index e91c3cfa..02fcca3e 100644 --- a/Tools/AutoClient/CodexWrapper.cs +++ b/Tools/AutoClient/CodexWrapper.cs @@ -7,6 +7,7 @@ namespace AutoClient public class CodexWrapper { private readonly App app; + private static readonly Random r = new Random(); public CodexWrapper(App app, ICodexNode node) { @@ -26,11 +27,11 @@ namespace AutoClient var result = Node.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) { CollateralPerByte = app.Config.CollateralPerByte.TstWei(), - Duration = TimeSpan.FromMinutes(app.Config.ContractDurationMinutes), + Duration = GetDuration(), Expiry = TimeSpan.FromMinutes(app.Config.ContractExpiryMinutes), MinRequiredNumberOfNodes = Convert.ToUInt32(app.Config.NumHosts), NodeFailureTolerance = Convert.ToUInt32(app.Config.HostTolerance), - PricePerBytePerSecond = app.Config.PricePerBytePerSecond.TstWei(), + PricePerBytePerSecond = GetPricePerBytePerSecond(), ProofProbability = 15 }); return result; @@ -40,5 +41,25 @@ namespace AutoClient { return Node.GetPurchaseStatus(pid); } + + private TestToken GetPricePerBytePerSecond() + { + var i = app.Config.PricePerBytePerSecond; + i -= 100; + i += r.Next(0, 1000); + + return i.TstWei(); + } + + private TimeSpan GetDuration() + { + var i = app.Config.ContractDurationMinutes; + var day = 60 * 24; + i -= day; + i -= 10; // We don't want to accidentally hit exactly 7 days because that's the limit of the storage node availabilities. + i += r.Next(0, day * 2); + + return TimeSpan.FromMinutes(i); + } } }