Extracts codexClient assembly

This commit is contained in:
ThatBen 2025-01-16 11:31:50 +01:00
parent 48f4614e5d
commit 4a151880d4
No known key found for this signature in database
GPG Key ID: 62C543548433D43E
45 changed files with 383 additions and 320 deletions

View File

@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FileUtils\FileUtils.csproj" /> <ProjectReference Include="..\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" /> <ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
<ProjectReference Include="..\WebUtils\WebUtils.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,6 @@
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
using WebUtils;
namespace Core namespace Core
{ {
@ -8,16 +9,16 @@ namespace Core
private readonly IToolsFactory toolsFactory; private readonly IToolsFactory toolsFactory;
private readonly PluginManager manager = new PluginManager(); private readonly PluginManager manager = new PluginManager();
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet) public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
{ {
toolsFactory = new ToolsFactory(log, configuration, fileManagerRootFolder, timeSet); toolsFactory = new ToolsFactory(log, configuration, fileManagerRootFolder, webCallTimeSet, k8STimeSet);
Tools = toolsFactory.CreateTools(); Tools = toolsFactory.CreateTools();
manager.InstantiatePlugins(PluginFinder.GetPluginTypes(), toolsFactory); manager.InstantiatePlugins(PluginFinder.GetPluginTypes(), toolsFactory);
} }
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder) public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder)
: this(log, configuration, fileManagerRootFolder, new DefaultTimeSet()) : this(log, configuration, fileManagerRootFolder, new DefaultWebCallTimeSet(), new DefaultK8sTimeSet())
{ {
} }

View File

@ -1,12 +1,14 @@
using FileUtils; using FileUtils;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
using WebUtils;
namespace Core namespace Core
{ {
public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactory, IFileTool
{ {
ITimeSet TimeSet { get; } IWebCallTimeSet WebCallTimeSet { get; }
IK8sTimeSet K8STimeSet { get; }
/// <summary> /// <summary>
/// Deletes kubernetes and tracked file resources. /// Deletes kubernetes and tracked file resources.
@ -25,13 +27,6 @@ namespace Core
ILog GetLog(); ILog GetLog();
} }
public interface IHttpFactoryTool
{
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated);
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, ITimeSet timeSet);
IHttp CreateHttp(string id);
}
public interface IFileTool public interface IFileTool
{ {
IFileManager GetFileManager(); IFileManager GetFileManager();
@ -40,18 +35,22 @@ namespace Core
internal class PluginTools : IPluginTools internal class PluginTools : IPluginTools
{ {
private readonly WorkflowCreator workflowCreator; private readonly WorkflowCreator workflowCreator;
private readonly HttpFactory httpFactory;
private readonly IFileManager fileManager; private readonly IFileManager fileManager;
private readonly LogPrefixer log; private readonly LogPrefixer log;
internal PluginTools(ILog log, WorkflowCreator workflowCreator, string fileManagerRootFolder, ITimeSet timeSet) internal PluginTools(ILog log, WorkflowCreator workflowCreator, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
{ {
this.log = new LogPrefixer(log); this.log = new LogPrefixer(log);
this.workflowCreator = workflowCreator; this.workflowCreator = workflowCreator;
TimeSet = timeSet; httpFactory = new HttpFactory(log, webCallTimeSet);
WebCallTimeSet = webCallTimeSet;
K8STimeSet = k8STimeSet;
fileManager = new FileManager(log, fileManagerRootFolder); fileManager = new FileManager(log, fileManagerRootFolder);
} }
public ITimeSet TimeSet { get; } public IWebCallTimeSet WebCallTimeSet { get; }
public IK8sTimeSet K8STimeSet { get; }
public void ApplyLogPrefix(string prefix) public void ApplyLogPrefix(string prefix)
{ {
@ -60,17 +59,17 @@ namespace Core
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated) public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated)
{ {
return CreateHttp(id, onClientCreated, TimeSet); return httpFactory.CreateHttp(id, onClientCreated);
} }
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, ITimeSet ts) public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet timeSet)
{ {
return new Http(id, log, ts, onClientCreated); return httpFactory.CreateHttp(id, onClientCreated, timeSet);
} }
public IHttp CreateHttp(string id) public IHttp CreateHttp(string id)
{ {
return new Http(id, log, TimeSet); return httpFactory.CreateHttp(id);
} }
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null) public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)

View File

@ -1,5 +1,6 @@
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
using WebUtils;
namespace Core namespace Core
{ {
@ -13,19 +14,21 @@ namespace Core
private readonly ILog log; private readonly ILog log;
private readonly WorkflowCreator workflowCreator; private readonly WorkflowCreator workflowCreator;
private readonly string fileManagerRootFolder; private readonly string fileManagerRootFolder;
private readonly ITimeSet timeSet; private readonly IWebCallTimeSet webCallTimeSet;
private readonly IK8sTimeSet k8STimeSet;
public ToolsFactory(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet) public ToolsFactory(ILog log, Configuration configuration, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
{ {
this.log = log; this.log = log;
workflowCreator = new WorkflowCreator(log, configuration); workflowCreator = new WorkflowCreator(log, configuration);
this.fileManagerRootFolder = fileManagerRootFolder; this.fileManagerRootFolder = fileManagerRootFolder;
this.timeSet = timeSet; this.webCallTimeSet = webCallTimeSet;
this.k8STimeSet = k8STimeSet;
} }
public PluginTools CreateTools() public PluginTools CreateTools()
{ {
return new PluginTools(log, workflowCreator, fileManagerRootFolder, timeSet); return new PluginTools(log, workflowCreator, fileManagerRootFolder, webCallTimeSet, k8STimeSet);
} }
} }
} }

View File

@ -0,0 +1,42 @@
namespace Core
{
public interface IK8sTimeSet
{
/// <summary>
/// After a failed K8s operation, wait this long before trying again.
/// </summary>
TimeSpan K8sOperationRetryDelay();
/// <summary>
/// Maximum total time to attempt to perform a successful k8s operation.
/// If k8s operations fail during this timespan, retries will be made.
/// </summary>
TimeSpan K8sOperationTimeout();
}
public class DefaultK8sTimeSet : IK8sTimeSet
{
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(30);
}
}
public class LongK8sTimeSet : IK8sTimeSet
{
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(30);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromHours(1);
}
}
}

View File

@ -0,0 +1,20 @@
namespace Utils
{
[Serializable]
public class EthAccount
{
public EthAccount(EthAddress ethAddress, string privateKey)
{
EthAddress = ethAddress;
PrivateKey = privateKey;
}
public EthAddress EthAddress { get; }
public string PrivateKey { get; }
public override string ToString()
{
return EthAddress.ToString();
}
}
}

View File

@ -1,4 +1,4 @@
namespace GethPlugin namespace Utils
{ {
public interface IHasEthAddress public interface IHasEthAddress
{ {

View File

@ -1,4 +1,4 @@
namespace GethPlugin namespace Utils
{ {
public class Ether : IComparable<Ether> public class Ether : IComparable<Ether>
{ {

View File

@ -1,6 +1,6 @@
using System.Numerics; using System.Numerics;
namespace CodexContractsPlugin namespace Utils
{ {
public class TestToken : IComparable<TestToken> public class TestToken : IComparable<TestToken>
{ {

View File

@ -1,11 +1,10 @@
using Logging; using System.Net.Http.Headers;
using Newtonsoft.Json;
using Serialization = Newtonsoft.Json.Serialization;
using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using Logging;
using Newtonsoft.Json;
using Utils; using Utils;
namespace Core namespace WebUtils
{ {
public interface IEndpoint public interface IEndpoint
{ {
@ -119,7 +118,7 @@ namespace Core
var errors = new List<string>(); var errors = new List<string>();
var deserialized = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings() var deserialized = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings()
{ {
Error = delegate (object? sender, Serialization.ErrorEventArgs args) Error = delegate (object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{ {
if (args.CurrentObject == args.ErrorContext.OriginalObject) if (args.CurrentObject == args.ErrorContext.OriginalObject)
{ {

View File

@ -1,7 +1,7 @@
using Logging; using Logging;
using Utils; using Utils;
namespace Core namespace WebUtils
{ {
public interface IHttp public interface IHttp
{ {
@ -16,16 +16,16 @@ namespace Core
private static object lockLock = new object(); private static object lockLock = new object();
private static readonly Dictionary<string, object> httpLocks = new Dictionary<string, object>(); private static readonly Dictionary<string, object> httpLocks = new Dictionary<string, object>();
private readonly ILog log; private readonly ILog log;
private readonly ITimeSet timeSet; private readonly IWebCallTimeSet timeSet;
private readonly Action<HttpClient> onClientCreated; private readonly Action<HttpClient> onClientCreated;
private readonly string id; private readonly string id;
internal Http(string id, ILog log, ITimeSet timeSet) internal Http(string id, ILog log, IWebCallTimeSet timeSet)
: this(id, log, timeSet, DoNothing) : this(id, log, timeSet, DoNothing)
{ {
} }
internal Http(string id, ILog log, ITimeSet timeSet, Action<HttpClient> onClientCreated) internal Http(string id, ILog log, IWebCallTimeSet timeSet, Action<HttpClient> onClientCreated)
{ {
this.id = id; this.id = id;
this.log = log; this.log = log;

View File

@ -0,0 +1,43 @@
using Logging;
namespace WebUtils
{
public interface IHttpFactory
{
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated);
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet timeSet);
IHttp CreateHttp(string id);
}
public class HttpFactory : IHttpFactory
{
private readonly ILog log;
private readonly IWebCallTimeSet defaultTimeSet;
public HttpFactory(ILog log)
: this (log, new DefaultWebCallTimeSet())
{
}
public HttpFactory(ILog log, IWebCallTimeSet defaultTimeSet)
{
this.log = log;
this.defaultTimeSet = defaultTimeSet;
}
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated)
{
return CreateHttp(id, onClientCreated, defaultTimeSet);
}
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet ts)
{
return new Http(id, log, ts, onClientCreated);
}
public IHttp CreateHttp(string id)
{
return new Http(id, log, defaultTimeSet);
}
}
}

View File

@ -1,6 +1,6 @@
namespace Core namespace WebUtils
{ {
public interface ITimeSet public interface IWebCallTimeSet
{ {
/// <summary> /// <summary>
/// Timeout for a single HTTP call. /// Timeout for a single HTTP call.
@ -17,20 +17,9 @@
/// After a failed HTTP call, wait this long before trying again. /// After a failed HTTP call, wait this long before trying again.
/// </summary> /// </summary>
TimeSpan HttpCallRetryDelay(); TimeSpan HttpCallRetryDelay();
/// <summary>
/// After a failed K8s operation, wait this long before trying again.
/// </summary>
TimeSpan K8sOperationRetryDelay();
/// <summary>
/// Maximum total time to attempt to perform a successful k8s operation.
/// If k8s operations fail during this timespan, retries will be made.
/// </summary>
TimeSpan K8sOperationTimeout();
} }
public class DefaultTimeSet : ITimeSet public class DefaultWebCallTimeSet : IWebCallTimeSet
{ {
public TimeSpan HttpCallTimeout() public TimeSpan HttpCallTimeout()
{ {
@ -46,19 +35,9 @@
{ {
return TimeSpan.FromSeconds(1); return TimeSpan.FromSeconds(1);
} }
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(30);
}
} }
public class LongTimeSet : ITimeSet public class LongWebCallTimeSet : IWebCallTimeSet
{ {
public TimeSpan HttpCallTimeout() public TimeSpan HttpCallTimeout()
{ {
@ -74,15 +53,5 @@
{ {
return TimeSpan.FromSeconds(20); return TimeSpan.FromSeconds(20);
} }
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(30);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromHours(1);
}
} }
} }

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -1,26 +1,25 @@
using CodexOpenApi; using CodexOpenApi;
using Core;
using GethPlugin;
using Logging; using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Utils; using Utils;
using WebUtils;
namespace CodexPlugin namespace CodexClient
{ {
public class CodexAccess public class CodexAccess
{ {
private readonly ILog log; private readonly ILog log;
private readonly IPluginTools tools; private readonly IHttpFactory httpFactory;
private readonly IProcessControl processControl; private readonly IProcessControl processControl;
private ICodexInstance instance; private ICodexInstance instance;
private readonly Mapper mapper = new Mapper(); private readonly Mapper mapper = new Mapper();
public CodexAccess(IPluginTools tools, IProcessControl processControl, ICodexInstance instance, ICrashWatcher crashWatcher) public CodexAccess(ILog log, IHttpFactory httpFactory, IProcessControl processControl, ICodexInstance instance, ICrashWatcher crashWatcher)
{ {
this.tools = tools; this.log = log;
this.httpFactory = httpFactory;
this.processControl = processControl; this.processControl = processControl;
this.instance = instance; this.instance = instance;
log = tools.GetLog();
CrashWatcher = crashWatcher; CrashWatcher = crashWatcher;
CrashWatcher.Start(); CrashWatcher.Start();
@ -103,19 +102,14 @@ namespace CodexPlugin
}); });
} }
public string UploadFile(UploadInput uploadInput, Action<Failure> onFailure) public string UploadFile(UploadInput uploadInput)
{ {
return OnCodex( return OnCodex(api => api.UploadAsync(uploadInput.ContentType, uploadInput.ContentDisposition, uploadInput.FileStream));
api => api.UploadAsync(uploadInput.ContentType, uploadInput.ContentDisposition, uploadInput.FileStream),
CreateRetryConfig(nameof(UploadFile), onFailure));
} }
public Stream DownloadFile(string contentId, Action<Failure> onFailure) public Stream DownloadFile(string contentId)
{ {
var fileResponse = OnCodex( var fileResponse = OnCodex(api => api.DownloadNetworkStreamAsync(contentId));
api => api.DownloadNetworkStreamAsync(contentId),
CreateRetryConfig(nameof(DownloadFile), onFailure));
if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode); if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode);
return fileResponse.Stream; return fileResponse.Stream;
} }
@ -147,7 +141,6 @@ namespace CodexPlugin
return JsonConvert.DeserializeObject<LocalDatasetListJson>(str)!; return JsonConvert.DeserializeObject<LocalDatasetListJson>(str)!;
}, nameof(LocalFiles)); }, nameof(LocalFiles));
})); }));
} }
public StorageAvailability SalesAvailability(StorageAvailability request) public StorageAvailability SalesAvailability(StorageAvailability request)
@ -227,22 +220,22 @@ namespace CodexPlugin
processControl.DeleteDataDirFolder(instance); processControl.DeleteDataDirFolder(instance);
} }
private T OnCodex<T>(Func<CodexApi, Task<T>> action) private T OnCodex<T>(Func<openapiClient, Task<T>> action)
{ {
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action)); var result = httpFactory.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action));
return result; return result;
} }
private T OnCodex<T>(Func<CodexApi, Task<T>> action, Retry retry) private T OnCodex<T>(Func<openapiClient, Task<T>> action, Retry retry)
{ {
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry); var result = httpFactory.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry);
return result; return result;
} }
private T CallCodex<T>(HttpClient client, Func<CodexApi, Task<T>> action) private T CallCodex<T>(HttpClient client, Func<openapiClient, Task<T>> action)
{ {
var address = GetAddress(); var address = GetAddress();
var api = new CodexApi(client); var api = new openapiClient(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1"; api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
return CrashCheck(() => Time.Wait(action(api))); return CrashCheck(() => Time.Wait(action(api)));
} }
@ -261,7 +254,7 @@ namespace CodexPlugin
private IEndpoint GetEndpoint() private IEndpoint GetEndpoint()
{ {
return tools return httpFactory
.CreateHttp(GetHttpId(), CheckContainerCrashed) .CreateHttp(GetHttpId(), CheckContainerCrashed)
.CreateEndpoint(GetAddress(), "/api/codex/v1/", GetName()); .CreateEndpoint(GetAddress(), "/api/codex/v1/", GetName());
} }
@ -281,49 +274,6 @@ namespace CodexPlugin
if (CrashWatcher.HasCrashed()) throw new Exception($"Container {GetName()} has crashed."); if (CrashWatcher.HasCrashed()) throw new Exception($"Container {GetName()} has crashed.");
} }
private Retry CreateRetryConfig(string description, Action<Failure> onFailure)
{
var timeSet = tools.TimeSet;
return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure =>
{
onFailure(failure);
Investigate(failure, timeSet);
});
}
private void Investigate(Failure failure, ITimeSet timeSet)
{
Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " +
$"(HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " +
$"Checking if node responds to debug/info...");
try
{
var debugInfo = GetDebugInfo();
if (string.IsNullOrEmpty(debugInfo.Spr))
{
Log("Did not get value debug/info response.");
Throw(failure);
}
else
{
Log("Got valid response from debug/info.");
}
}
catch (Exception ex)
{
Log("Got exception from debug/info call: " + ex);
Throw(failure);
}
if (failure.Duration < timeSet.HttpCallTimeout())
{
Log("Retry failed within HTTP timeout duration.");
Throw(failure);
}
}
private void Throw(Failure failure) private void Throw(Failure failure)
{ {
throw failure.Exception; throw failure.Exception;

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="openapi.yaml" />
</ItemGroup>
<ItemGroup>
<OpenApiReference Include="openapi.yaml">
<CodeGenerator>NSwagCSharp</CodeGenerator>
<Namespace>CodexOpenApi</Namespace>
<ClassName></ClassName>
</OpenApiReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.18.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Framework\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
<ProjectReference Include="..\..\Framework\WebUtils\WebUtils.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
using Utils;
namespace CodexClient
{
public interface ICodexInstance
{
string Name { get; }
string ImageName { get; }
DateTime StartUtc { get; }
Address DiscoveryEndpoint { get; }
Address ApiEndpoint { get; }
Address ListenEndpoint { get; }
EthAccount? EthAccount { get; }
Address? MetricsEndpoint { get; }
}
public class CodexInstance : ICodexInstance
{
public CodexInstance(string name, string imageName, DateTime startUtc, Address discoveryEndpoint, Address apiEndpoint, Address listenEndpoint, EthAccount? ethAccount, Address? metricsEndpoint)
{
Name = name;
ImageName = imageName;
StartUtc = startUtc;
DiscoveryEndpoint = discoveryEndpoint;
ApiEndpoint = apiEndpoint;
ListenEndpoint = listenEndpoint;
EthAccount = ethAccount;
MetricsEndpoint = metricsEndpoint;
}
public string Name { get; }
public string ImageName { get; }
public DateTime StartUtc { get; }
public Address DiscoveryEndpoint { get; }
public Address ApiEndpoint { get; }
public Address ListenEndpoint { get; }
public EthAccount? EthAccount { get; }
public Address? MetricsEndpoint { get; }
}
}

View File

@ -1,4 +1,4 @@
namespace CodexPlugin namespace CodexClient
{ {
public enum CodexLogLevel public enum CodexLogLevel
{ {

View File

@ -1,6 +1,6 @@
using System.Globalization; using System.Globalization;
namespace CodexPlugin namespace CodexClient
{ {
public class CodexLogLine public class CodexLogLine
{ {

View File

@ -1,14 +1,11 @@
using CodexPlugin.Hooks; using CodexClient.Hooks;
using Core;
using FileUtils; using FileUtils;
using GethPlugin;
using Logging; using Logging;
using MetricsPlugin;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public interface ICodexNode : IHasMetricsScrapeTarget, IHasEthAddress public partial interface ICodexNode : IHasEthAddress
{ {
string GetName(); string GetName();
string GetImageName(); string GetImageName();
@ -17,10 +14,8 @@ namespace CodexPlugin
string GetSpr(); string GetSpr();
DebugPeer GetDebugPeer(string peerId); DebugPeer GetDebugPeer(string peerId);
ContentId UploadFile(TrackedFile file); ContentId UploadFile(TrackedFile file);
ContentId UploadFile(TrackedFile file, Action<Failure> onFailure); ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition);
ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure);
TrackedFile? DownloadContent(ContentId contentId, string fileLabel = ""); TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "");
LocalDataset DownloadStreamless(ContentId cid); LocalDataset DownloadStreamless(ContentId cid);
/// <summary> /// <summary>
/// TODO: This will monitor the quota-used of the node until 'size' bytes are added. That's a very bad way /// TODO: This will monitor the quota-used of the node until 'size' bytes are added. That's a very bad way
@ -54,23 +49,23 @@ namespace CodexPlugin
{ {
private const string UploadFailedMessage = "Unable to store block"; private const string UploadFailedMessage = "Unable to store block";
private readonly ILog log; private readonly ILog log;
private readonly IPluginTools tools;
private readonly ICodexNodeHooks hooks; private readonly ICodexNodeHooks hooks;
private readonly TransferSpeeds transferSpeeds; private readonly TransferSpeeds transferSpeeds;
private string peerId = string.Empty; private string peerId = string.Empty;
private string nodeId = string.Empty; private string nodeId = string.Empty;
private readonly CodexAccess codexAccess; private readonly CodexAccess codexAccess;
private readonly IFileManager fileManager;
public CodexNode(IPluginTools tools, CodexAccess codexAccess, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks) public CodexNode(ILog log, CodexAccess codexAccess, IFileManager fileManager, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks)
{ {
this.tools = tools;
this.codexAccess = codexAccess; this.codexAccess = codexAccess;
this.fileManager = fileManager;
Marketplace = marketplaceAccess; Marketplace = marketplaceAccess;
this.hooks = hooks; this.hooks = hooks;
Version = new DebugInfoVersion(); Version = new DebugInfoVersion();
transferSpeeds = new TransferSpeeds(); transferSpeeds = new TransferSpeeds();
log = new LogPrefixer(tools.GetLog(), $"{GetName()} "); this.log = new LogPrefixer(log, $"{GetName()} ");
} }
public void Awake() public void Awake()
@ -156,15 +151,10 @@ namespace CodexPlugin
public ContentId UploadFile(TrackedFile file) public ContentId UploadFile(TrackedFile file)
{ {
return UploadFile(file, DoNothing); return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"");
} }
public ContentId UploadFile(TrackedFile file, Action<Failure> onFailure) public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition)
{
return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"", onFailure);
}
public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure)
{ {
using var fileStream = File.OpenRead(file.Filename); using var fileStream = File.OpenRead(file.Filename);
var uniqueId = Guid.NewGuid().ToString(); var uniqueId = Guid.NewGuid().ToString();
@ -176,7 +166,7 @@ namespace CodexPlugin
var logMessage = $"Uploading file {file.Describe()} with contentType: '{input.ContentType}' and disposition: '{input.ContentDisposition}'..."; var logMessage = $"Uploading file {file.Describe()} with contentType: '{input.ContentType}' and disposition: '{input.ContentDisposition}'...";
var measurement = Stopwatch.Measure(log, logMessage, () => var measurement = Stopwatch.Measure(log, logMessage, () =>
{ {
return codexAccess.UploadFile(input, onFailure); return codexAccess.UploadFile(input);
}); });
var response = measurement.Value; var response = measurement.Value;
@ -194,17 +184,12 @@ namespace CodexPlugin
public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "") public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "")
{ {
return DownloadContent(contentId, DoNothing, fileLabel); var file = fileManager.CreateEmptyFile(fileLabel);
}
public TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "")
{
var file = tools.GetFileManager().CreateEmptyFile(fileLabel);
hooks.OnFileDownloading(contentId); hooks.OnFileDownloading(contentId);
Log($"Downloading '{contentId}'..."); Log($"Downloading '{contentId}'...");
var logMessage = $"Downloaded '{contentId}' to '{file.Filename}'"; var logMessage = $"Downloaded '{contentId}' to '{file.Filename}'";
var measurement = Stopwatch.Measure(log, logMessage, () => DownloadToFile(contentId.Id, file, onFailure)); var measurement = Stopwatch.Measure(log, logMessage, () => DownloadToFile(contentId.Id, file));
var size = file.GetFilesize(); var size = file.GetFilesize();
transferSpeeds.AddDownloadSample(size, measurement); transferSpeeds.AddDownloadSample(size, measurement);
@ -337,20 +322,20 @@ namespace CodexPlugin
.ToArray(); .ToArray();
} }
private void DownloadToFile(string contentId, TrackedFile file, Action<Failure> onFailure) private void DownloadToFile(string contentId, TrackedFile file)
{ {
using var fileStream = File.OpenWrite(file.Filename); using var fileStream = File.OpenWrite(file.Filename);
var timeout = tools.TimeSet.HttpCallTimeout(); var timeout = TimeSpan.FromMinutes(2.0); // todo: make this user-controllable.
try try
{ {
// Type of stream generated by openAPI client does not support timeouts. // Type of stream generated by openAPI client does not support timeouts.
var start = DateTime.UtcNow; var start = DateTime.UtcNow;
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
var downloadTask = Task.Run((Action)(() => var downloadTask = Task.Run(() =>
{ {
using var downloadStream = this.codexAccess.DownloadFile(contentId, onFailure); using var downloadStream = codexAccess.DownloadFile(contentId);
downloadStream.CopyTo((Stream)fileStream); downloadStream.CopyTo(fileStream);
}), cts.Token); }, cts.Token);
while (DateTime.UtcNow - start < timeout) while (DateTime.UtcNow - start < timeout)
{ {
@ -411,9 +396,5 @@ namespace CodexPlugin
{ {
log.Log(msg); log.Log(msg);
} }
private void DoNothing(Failure failure)
{
}
} }
} }

View File

@ -1,7 +1,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public class DebugInfo public class DebugInfo
{ {

View File

@ -1,4 +1,4 @@
namespace CodexPlugin namespace CodexClient
{ {
public static class CodexUtils public static class CodexUtils
{ {

View File

@ -1,7 +1,6 @@
using GethPlugin; using Utils;
using Utils;
namespace CodexPlugin.Hooks namespace CodexClient.Hooks
{ {
public interface ICodexHooksProvider public interface ICodexHooksProvider
{ {

View File

@ -1,7 +1,6 @@
using GethPlugin; using Utils;
using Utils;
namespace CodexPlugin.Hooks namespace CodexClient.Hooks
{ {
public interface ICodexNodeHooks public interface ICodexNodeHooks
{ {

View File

@ -1,10 +1,8 @@
using CodexContractsPlugin; using Newtonsoft.Json.Linq;
using CodexOpenApi;
using Newtonsoft.Json.Linq;
using System.Numerics; using System.Numerics;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public class Mapper public class Mapper
{ {
@ -80,12 +78,12 @@ namespace CodexPlugin
}; };
} }
public StorageAvailability[] Map(ICollection<SalesAvailabilityREAD> availabilities) public StorageAvailability[] Map(ICollection<CodexOpenApi.SalesAvailabilityREAD> availabilities)
{ {
return availabilities.Select(a => Map(a)).ToArray(); return availabilities.Select(a => Map(a)).ToArray();
} }
public StorageAvailability Map(SalesAvailabilityREAD availability) public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD availability)
{ {
return new StorageAvailability return new StorageAvailability
( (
@ -142,7 +140,7 @@ namespace CodexPlugin
// }; // };
//} //}
public CodexSpace Map(Space space) public CodexSpace Map(CodexOpenApi.Space space)
{ {
return new CodexSpace return new CodexSpace
{ {
@ -153,7 +151,7 @@ namespace CodexPlugin
}; };
} }
private DebugInfoVersion Map(CodexVersion obj) private DebugInfoVersion Map(CodexOpenApi.CodexVersion obj)
{ {
return new DebugInfoVersion return new DebugInfoVersion
{ {
@ -162,7 +160,7 @@ namespace CodexPlugin
}; };
} }
private DebugInfoTable Map(PeersTable obj) private DebugInfoTable Map(CodexOpenApi.PeersTable obj)
{ {
return new DebugInfoTable return new DebugInfoTable
{ {
@ -171,7 +169,7 @@ namespace CodexPlugin
}; };
} }
private DebugInfoTableNode Map(Node? token) private DebugInfoTableNode Map(CodexOpenApi.Node? token)
{ {
if (token == null) return new DebugInfoTableNode(); if (token == null) return new DebugInfoTableNode();
return new DebugInfoTableNode return new DebugInfoTableNode
@ -184,7 +182,7 @@ namespace CodexPlugin
}; };
} }
private DebugInfoTableNode[] Map(ICollection<Node> nodes) private DebugInfoTableNode[] Map(ICollection<CodexOpenApi.Node> nodes)
{ {
if (nodes == null || nodes.Count == 0) if (nodes == null || nodes.Count == 0)
{ {

View File

@ -1,8 +1,8 @@
using CodexPlugin.Hooks; using CodexClient.Hooks;
using Logging; using Logging;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public interface IMarketplaceAccess public interface IMarketplaceAccess
{ {

View File

@ -1,8 +1,7 @@
using CodexContractsPlugin; using Logging;
using Logging;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public class StoragePurchaseRequest public class StoragePurchaseRequest
{ {

View File

@ -0,0 +1,11 @@
using Logging;
namespace CodexClient
{
public interface IProcessControl
{
void Stop(ICodexInstance instance, bool waitTillStopped);
IDownloadedLog DownloadLog(ICodexInstance instance, LogFile file);
void DeleteDataDirFolder(ICodexInstance instance);
}
}

View File

@ -1,11 +1,9 @@
using CodexContractsPlugin; using CodexClient.Hooks;
using CodexPlugin.Hooks;
using GethPlugin;
using Logging; using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public interface IStoragePurchaseContract public interface IStoragePurchaseContract
{ {
@ -14,7 +12,7 @@ namespace CodexPlugin
ContentId ContentId { get; } ContentId ContentId { get; }
void WaitForStorageContractSubmitted(); void WaitForStorageContractSubmitted();
void WaitForStorageContractStarted(); void WaitForStorageContractStarted();
void WaitForStorageContractFinished(ICodexContracts contracts); void WaitForStorageContractFinished();
void WaitForContractFailed(); void WaitForContractFailed();
} }
@ -65,7 +63,7 @@ namespace CodexPlugin
AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
} }
public void WaitForStorageContractFinished(ICodexContracts contracts) public void WaitForStorageContractFinished()
{ {
if (!contractStartedUtc.HasValue) if (!contractStartedUtc.HasValue)
{ {
@ -77,13 +75,6 @@ namespace CodexPlugin
contractFinishedUtc = DateTime.UtcNow; contractFinishedUtc = DateTime.UtcNow;
LogFinishedDuration(); LogFinishedDuration();
AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished));
contracts.WaitUntilNextPeriod();
contracts.WaitUntilNextPeriod();
var blocks = 3;
Log($"Waiting {blocks} blocks for nodes to process payouts...");
Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
} }
public void WaitForContractFailed() public void WaitForContractFailed()

View File

@ -1,6 +1,6 @@
using Utils; using Utils;
namespace CodexPlugin namespace CodexClient
{ {
public interface ITransferSpeeds public interface ITransferSpeeds
{ {

View File

@ -1,47 +1,10 @@
using Core; using Core;
using GethPlugin;
using KubernetesWorkflow.Types; using KubernetesWorkflow.Types;
using Logging; using Logging;
using Utils; using Utils;
namespace CodexPlugin namespace CodexPlugin
{ {
public interface ICodexInstance
{
string Name { get; }
string ImageName { get; }
DateTime StartUtc { get; }
Address DiscoveryEndpoint { get; }
Address ApiEndpoint { get; }
Address ListenEndpoint { get; }
EthAccount? EthAccount { get; }
Address? MetricsEndpoint { get; }
}
public class CodexInstance : ICodexInstance
{
public CodexInstance(string name, string imageName, DateTime startUtc, Address discoveryEndpoint, Address apiEndpoint, Address listenEndpoint, EthAccount? ethAccount, Address? metricsEndpoint)
{
Name = name;
ImageName = imageName;
StartUtc = startUtc;
DiscoveryEndpoint = discoveryEndpoint;
ApiEndpoint = apiEndpoint;
ListenEndpoint = listenEndpoint;
EthAccount = ethAccount;
MetricsEndpoint = metricsEndpoint;
}
public string Name { get; }
public string ImageName { get; }
public DateTime StartUtc { get; }
public Address DiscoveryEndpoint { get; }
public Address ApiEndpoint { get; }
public Address ListenEndpoint { get; }
public EthAccount? EthAccount { get; }
public Address? MetricsEndpoint { get; }
}
public static class CodexInstanceContainerExtension public static class CodexInstanceContainerExtension
{ {
public static ICodexInstance CreateFromPod(RunningPod pod) public static ICodexInstance CreateFromPod(RunningPod pod)

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
@ -6,14 +6,6 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="openapi.yaml" />
</ItemGroup>
<ItemGroup>
<OpenApiReference Include="openapi.yaml" CodeGenerator="NSwagCSharp" Namespace="CodexOpenApi" ClassName="CodexApi" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2"> <PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -30,6 +22,7 @@
<ProjectReference Include="..\..\Framework\Core\Core.csproj" /> <ProjectReference Include="..\..\Framework\Core\Core.csproj" />
<ProjectReference Include="..\..\Framework\KubernetesWorkflow\KubernetesWorkflow.csproj" /> <ProjectReference Include="..\..\Framework\KubernetesWorkflow\KubernetesWorkflow.csproj" />
<ProjectReference Include="..\..\Framework\OverwatchTranscript\OverwatchTranscript.csproj" /> <ProjectReference Include="..\..\Framework\OverwatchTranscript\OverwatchTranscript.csproj" />
<ProjectReference Include="..\CodexClient\CodexClient.csproj" />
<ProjectReference Include="..\CodexContractsPlugin\CodexContractsPlugin.csproj" /> <ProjectReference Include="..\CodexContractsPlugin\CodexContractsPlugin.csproj" />
<ProjectReference Include="..\GethPlugin\GethPlugin.csproj" /> <ProjectReference Include="..\GethPlugin\GethPlugin.csproj" />
<ProjectReference Include="..\MetricsPlugin\MetricsPlugin.csproj" /> <ProjectReference Include="..\MetricsPlugin\MetricsPlugin.csproj" />

View File

@ -1,6 +1,5 @@
using CodexPlugin.Hooks; using CodexPlugin.Hooks;
using Core; using Core;
using GethPlugin;
using KubernetesWorkflow; using KubernetesWorkflow;
using KubernetesWorkflow.Types; using KubernetesWorkflow.Types;
using Logging; using Logging;

View File

@ -1,5 +1,4 @@
using CodexPlugin.Hooks; using CodexPlugin.Hooks;
using GethPlugin;
using OverwatchTranscript; using OverwatchTranscript;
using Utils; using Utils;

View File

@ -1,29 +0,0 @@
using Logging;
namespace CodexPlugin
{
public interface IProcessControl
{
void Stop(ICodexInstance instance, bool waitTillStopped);
IDownloadedLog DownloadLog(ICodexInstance instance, LogFile file);
void DeleteDataDirFolder(ICodexInstance instance);
}
//public void DeleteDataDirFolder()
//{
// try
// {
// var dataDirVar = container.Recipe.EnvVars.Single(e => e.Name == "CODEX_DATA_DIR");
// var dataDir = dataDirVar.Value;
// var workflow = tools.CreateWorkflow();
// workflow.ExecuteCommand(container, "rm", "-Rfv", $"/codex/{dataDir}/repo");
// log.Log("Deleted repo folder.");
// }
// catch (Exception e)
// {
// log.Log("Unable to delete repo folder: " + e);
// }
//}
}

View File

@ -12,11 +12,12 @@ public static class Program
{ {
Console.WriteLine("Injecting hash of 'openapi.yaml'..."); Console.WriteLine("Injecting hash of 'openapi.yaml'...");
var root = FindCodexPluginFolder(); var pluginRoot = FindCodexPluginFolder();
Console.WriteLine("Located CodexPlugin: " + root); var clientRoot = FindCodexClientFolder();
var openApiFile = Path.Combine(root, "openapi.yaml"); Console.WriteLine("Located CodexPlugin: " + pluginRoot);
var clientFile = Path.Combine(root, "obj", "openapiClient.cs"); var openApiFile = Path.Combine(pluginRoot, "openapi.yaml");
var targetFile = Path.Combine(root, "ApiChecker.cs"); var clientFile = Path.Combine(clientRoot, "obj", "openapiClient.cs");
var targetFile = Path.Combine(pluginRoot, "ApiChecker.cs");
// Force client rebuild by deleting previous artifact. // Force client rebuild by deleting previous artifact.
File.Delete(clientFile); File.Delete(clientFile);
@ -46,6 +47,13 @@ public static class Program
return folder; return folder;
} }
private static string FindCodexClientFolder()
{
var folder = Path.Combine(PluginPathUtils.ProjectPluginsDir, "CodexClient");
if (!Directory.Exists(folder)) throw new Exception("CodexClient folder not found. Expected: " + folder);
return folder;
}
private static string CreateHash(string openApiFile) private static string CreateHash(string openApiFile)
{ {
var file = File.ReadAllText(openApiFile); var file = File.ReadAllText(openApiFile);

View File

@ -1,20 +1,11 @@
using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Web3.Accounts; using Nethereum.Web3.Accounts;
using Utils;
namespace GethPlugin namespace GethPlugin
{ {
[Serializable] public static class EthAccountGenerator
public class EthAccount
{ {
public EthAccount(EthAddress ethAddress, string privateKey)
{
EthAddress = ethAddress;
PrivateKey = privateKey;
}
public EthAddress EthAddress { get; }
public string PrivateKey { get; }
public static EthAccount GenerateNew() public static EthAccount GenerateNew()
{ {
var ecKey = Nethereum.Signer.EthECKey.GenerateKey(); var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
@ -24,10 +15,5 @@ namespace GethPlugin
return new EthAccount(ethAddress, account.PrivateKey); return new EthAccount(ethAddress, account.PrivateKey);
} }
public override string ToString()
{
return EthAddress.ToString();
}
} }
} }

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin; using CodexClient;
using CodexContractsPlugin;
using CodexPlugin; using CodexPlugin;
using GethPlugin; using GethPlugin;
using NUnit.Framework; using NUnit.Framework;

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin; using CodexClient;
using CodexContractsPlugin;
using CodexPlugin; using CodexPlugin;
using GethPlugin; using GethPlugin;
using NUnit.Framework; using NUnit.Framework;
@ -37,6 +38,14 @@ namespace CodexReleaseTests.MarketTests
All(requests, r => r.WaitForStorageContractFinished(GetContracts())); All(requests, r => r.WaitForStorageContractFinished(GetContracts()));
// 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);
// todo: // todo:
//AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts); //AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts);
//AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts); //AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts);

View File

@ -1,6 +1,5 @@
using CodexPlugin; using CodexPlugin;
using DistTestCore; using DistTestCore;
using GethPlugin;
using MetricsPlugin; using MetricsPlugin;
using NUnit.Framework; using NUnit.Framework;
using Utils; using Utils;

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin; using CodexClient;
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace; using CodexContractsPlugin.Marketplace;
using CodexPlugin; using CodexPlugin;
using FileUtils; using FileUtils;
@ -109,6 +110,14 @@ namespace CodexTests.BasicTests
purchaseContract.WaitForStorageContractFinished(contracts); purchaseContract.WaitForStorageContractFinished(contracts);
// 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);
AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage."); AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage.");
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
} }

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin; using CodexClient;
using CodexContractsPlugin;
using CodexDiscordBotPlugin; using CodexDiscordBotPlugin;
using CodexPlugin; using CodexPlugin;
using Core; using Core;
@ -46,6 +47,15 @@ namespace CodexTests.UtilityTests
var purchaseContract = ClientPurchasesStorage(client); var purchaseContract = ClientPurchasesStorage(client);
purchaseContract.WaitForStorageContractStarted(); purchaseContract.WaitForStorageContractStarted();
purchaseContract.WaitForStorageContractFinished(contracts); purchaseContract.WaitForStorageContractFinished(contracts);
// 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); Thread.Sleep(rewarderInterval * 3);
apiCalls.Stop(); apiCalls.Stop();

View File

@ -1,5 +1,5 @@
using GethPlugin;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Utils;
namespace KeyMaker.Controllers namespace KeyMaker.Controllers
{ {

View File

@ -82,6 +82,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExperimentalTests", "Tests\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockchainUtils", "Framework\BlockchainUtils\BlockchainUtils.csproj", "{4648B5AA-A0A7-44BA-89BC-2FD57370943C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockchainUtils", "Framework\BlockchainUtils\BlockchainUtils.csproj", "{4648B5AA-A0A7-44BA-89BC-2FD57370943C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexClient", "ProjectPlugins\CodexClient\CodexClient.csproj", "{9AF12703-29AF-416D-9781-204223D6D0E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUtils", "Framework\WebUtils\WebUtils.csproj", "{372C9E5D-5453-4D45-9948-E9324E21AD65}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -220,6 +224,14 @@ Global
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Debug|Any CPU.Build.0 = Debug|Any CPU {4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.ActiveCfg = Release|Any CPU {4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.Build.0 = Release|Any CPU {4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.Build.0 = Release|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Release|Any CPU.Build.0 = Release|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -258,6 +270,8 @@ Global
{639A0603-4E80-465B-BB59-AB02F1DEEF5A} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {639A0603-4E80-465B-BB59-AB02F1DEEF5A} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
{BA7369CD-7C2F-4075-8E35-98BCC19EE203} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {BA7369CD-7C2F-4075-8E35-98BCC19EE203} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
{4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
{9AF12703-29AF-416D-9781-204223D6D0E5} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
{372C9E5D-5453-4D45-9948-E9324E21AD65} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}