Implements autoclient with image generator

This commit is contained in:
Ben 2024-06-27 15:38:13 +02:00
parent a820788c7d
commit 4c75cebcd6
No known key found for this signature in database
GPG Key ID: 541B9D8C9F1426A1
5 changed files with 121 additions and 182 deletions

View File

@ -1,118 +0,0 @@
using CodexOpenApi;
using CodexPlugin;
using Core;
using Utils;
using DebugInfo = CodexPlugin.DebugInfo;
namespace AutoClient
{
public class Codex
{
private readonly IPluginTools tools;
private readonly Address address;
private readonly Mapper mapper = new Mapper();
/// <summary>
/// This class was largely copied from CodexAccess in CodexPlugin.
/// Should really be generalized so CodexPlugin supports talking to custom Codex instances.
/// </summary>
public Codex(IPluginTools tools, Address address)
{
this.tools = tools;
this.address = address;
}
public DebugInfo GetDebugInfo()
{
return mapper.Map(OnCodex(api => api.GetDebugInfoAsync()));
}
public DebugPeer GetDebugPeer(string peerId)
{
// Cannot use openAPI: debug/peer endpoint is not specified there.
var endpoint = GetEndpoint();
var str = endpoint.HttpGetString($"debug/peer/{peerId}");
if (str.ToLowerInvariant() == "unable to find peer!")
{
return new DebugPeer
{
IsPeerFound = false
};
}
var result = endpoint.Deserialize<DebugPeer>(str);
result.IsPeerFound = true;
return result;
}
public void ConnectToPeer(string peerId, string[] peerMultiAddresses)
{
OnCodex(api =>
{
Time.Wait(api.ConnectPeerAsync(peerId, peerMultiAddresses));
return Task.FromResult(string.Empty);
});
}
public string UploadFile(FileStream fileStream)
{
return OnCodex(api => api.UploadAsync(fileStream));
}
public Stream DownloadFile(string contentId)
{
var fileResponse = OnCodex(api => api.DownloadNetworkAsync(contentId));
if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode);
return fileResponse.Stream;
}
public LocalDatasetList LocalFiles()
{
return mapper.Map(OnCodex(api => api.ListDataAsync()));
}
public StorageAvailability SalesAvailability(StorageAvailability request)
{
var body = mapper.Map(request);
var read = OnCodex<SalesAvailabilityREAD>(api => api.OfferStorageAsync(body));
return mapper.Map(read);
}
public string RequestStorage(StoragePurchaseRequest request)
{
var body = mapper.Map(request);
return OnCodex<string>(api => api.CreateStorageRequestAsync(request.ContentId.Id, body));
}
public StoragePurchase GetPurchaseStatus(string purchaseId)
{
return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId)));
}
public string GetPurchaseStatusRaw(string purchaseId)
{
var endpoint = GetEndpoint();
return endpoint.HttpGetString($"storage/purchases/{purchaseId}");
}
private T OnCodex<T>(Func<CodexApi, Task<T>> action)
{
var result = tools.CreateHttp()
.OnClient(client =>
{
var api = new CodexApi(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
return Time.Wait(action(api));
});
return result;
}
private IEndpoint GetEndpoint()
{
return tools
.CreateHttp()
.CreateEndpoint(address, "/api/codex/v1/");
}
}
}

View File

@ -19,9 +19,6 @@ namespace AutoClient
[Uniform("contract-expiry", "ce", "CONTRACTEXPIRY", false, "contract expiry in minutes. (default 15)")]
public int ContractExpiryMinutes { get; set; } = 15;
[Uniform("dataset-size", "ds", "DATASETSIZE", false, "Total dataset size in bytes. (default 10MB).")]
public int DatasetSizeBytes { get; set; } = 10 * 1024 * 1024;
[Uniform("num-hosts", "nh", "NUMHOSTS", false, "Number of hosts for contract. (default 5)")]
public int NumHosts { get; set; } = 5;

View File

@ -0,0 +1,17 @@
namespace AutoClient
{
public class ImageGenerator
{
public async Task<string> GenerateImage()
{
var httpClient = new HttpClient();
var thing = await httpClient.GetStreamAsync("https://picsum.photos/3840/2160");
var filename = $"{Guid.NewGuid().ToString().ToLowerInvariant()}.jpg";
using var file = File.OpenWrite(filename);
await thing.CopyToAsync(file);
return filename;
}
}
}

View File

@ -1,14 +1,14 @@
using ArgsUniform;
using AutoClient;
using CodexPlugin;
using CodexOpenApi;
using Core;
using Logging;
using static Org.BouncyCastle.Math.EC.ECCurve;
public static class Program
{
public static void Main(string[] args)
public static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
Console.CancelKeyPress += (sender, args) => cts.Cancel();
@ -28,24 +28,26 @@ public static class Program
log.Log($"Start. Address: {address}");
var tools = CreateTools(log, config);
var fileManager = tools.GetFileManager();
var codex = new Codex(tools, address);
var imgGenerator = new ImageGenerator();
CheckCodex(codex, log);
var client = new HttpClient();
var codex = new CodexApi(client);
codex.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
var runner = new Runner(log, codex, fileManager, cancellationToken, config);
runner.Run();
await CheckCodex(codex, log);
var runner = new Runner(log, client, address, codex, cancellationToken, config, imgGenerator);
await runner.Run();
log.Log("Done.");
}
private static void CheckCodex(Codex codex, ILog log)
private static async Task CheckCodex(CodexApi codex, ILog log)
{
log.Log("Checking Codex...");
try
{
var info = codex.GetDebugInfo();
var info = await codex.GetDebugInfoAsync();
if (string.IsNullOrEmpty(info.Id)) throw new Exception("Failed to fetch Codex node id");
}
catch (Exception ex)

View File

@ -1,7 +1,7 @@
using CodexContractsPlugin;
using CodexOpenApi;
using CodexPlugin;
using FileUtils;
using Logging;
using Newtonsoft.Json;
using Utils;
namespace AutoClient
@ -9,21 +9,25 @@ namespace AutoClient
public class Runner
{
private readonly ILog log;
private readonly Codex codex;
private readonly IFileManager fileManager;
private readonly HttpClient client;
private readonly Address address;
private readonly CodexApi codex;
private readonly CancellationToken ct;
private readonly Configuration config;
private readonly ImageGenerator generator;
public Runner(ILog log, Codex codex, IFileManager fileManager, CancellationToken ct, Configuration config)
public Runner(ILog log, HttpClient client, Address address, CodexApi codex, CancellationToken ct, Configuration config, ImageGenerator generator)
{
this.log = log;
this.client = client;
this.address = address;
this.codex = codex;
this.fileManager = fileManager;
this.ct = ct;
this.config = config;
this.generator = generator;
}
public void Run()
public async Task Run()
{
while (!ct.IsCancellationRequested)
{
@ -31,10 +35,7 @@ namespace AutoClient
try
{
fileManager.ScopedFiles(() =>
{
DoRun();
});
await DoRun();
log.Log("Run succcessful.");
}
@ -42,32 +43,32 @@ namespace AutoClient
{
log.Error("Exception during run: " + ex);
}
FixedShortDelay();
await FixedShortDelay();
}
}
private void DoRun()
private async Task DoRun()
{
var file = CreateFile();
var cid = UploadFile(file);
var pid = RequestStorage(cid);
WaitUntilStarted(pid);
var file = await CreateFile();
var cid = await UploadFile(file);
var pid = await RequestStorage(cid);
await WaitUntilStarted(pid);
}
private TrackedFile CreateFile()
private async Task<string> CreateFile()
{
return fileManager.GenerateFile(new ByteSize(Convert.ToInt64(config.DatasetSizeBytes)));
return await generator.GenerateImage();
}
private ContentId UploadFile(TrackedFile file)
private async Task<ContentId> UploadFile(string filename)
{
// Copied from CodexNode :/
using var fileStream = File.OpenRead(file.Filename);
using var fileStream = File.OpenRead(filename);
var logMessage = $"Uploading file {file.Describe()}...";
var logMessage = $"Uploading file {filename}...";
log.Log(logMessage);
var response = codex.UploadFile(fileStream);
var response = await codex.UploadAsync(fileStream, ct);
if (string.IsNullOrEmpty(response)) FrameworkAssert.Fail("Received empty response.");
if (response.StartsWith("Unable to store block")) FrameworkAssert.Fail("Node failed to store block.");
@ -76,22 +77,44 @@ namespace AutoClient
return new ContentId(response);
}
private string RequestStorage(ContentId cid)
private async Task<string> RequestStorage(ContentId cid)
{
var request = new StoragePurchaseRequest(cid)
log.Log("Requesting storage for " + cid.Id);
var result = await codex.CreateStorageRequestAsync(cid.Id, new StorageRequestCreation()
{
PricePerSlotPerSecond = config.Price.TestTokens(),
RequiredCollateral = config.RequiredCollateral.TestTokens(),
MinRequiredNumberOfNodes = Convert.ToUInt32(config.NumHosts),
NodeFailureTolerance = Convert.ToUInt32(config.HostTolerance),
Duration = TimeSpan.FromMinutes(config.ContractDurationMinutes),
Expiry = TimeSpan.FromMinutes(config.ContractExpiryMinutes)
};
request.Log(log);
return codex.RequestStorage(request);
Collateral = config.RequiredCollateral.ToString(),
Duration = (config.ContractDurationMinutes * 60).ToString(),
Expiry = (config.ContractExpiryMinutes * 60).ToString(),
Nodes = config.NumHosts,
Reward = config.Price.ToString(),
ProofProbability = "15",
Tolerance = config.HostTolerance
}, ct);
log.Log("Response: " + result);
return result;
}
private void WaitUntilStarted(string pid)
private async Task<string?> GetPurchaseState(string pid)
{
try
{
// openapi still don't match code.
var str = await client.GetStringAsync($"{address.Host}:{address.Port}/api/codex/v1/storage/purchases/{pid}");
if (string.IsNullOrEmpty(str)) return null;
var sp = JsonConvert.DeserializeObject<StoragePurchase>(str)!;
log.Log($"Purchase {pid} is {sp.State}");
if (!string.IsNullOrEmpty(sp.Error)) log.Log($"Purchase {pid} error is {sp.Error}");
return sp.State;
}
catch (Exception ex)
{
return null;
}
}
private async Task WaitUntilStarted(string pid)
{
log.Log("Waiting till contract is started, or expired...");
try
@ -99,30 +122,43 @@ namespace AutoClient
var emptyResponseTolerance = 10;
while (true)
{
FixedShortDelay();
var status = codex.GetPurchaseStatusRaw(pid).ToLowerInvariant();
log.Log($"Status response: '{status}'");
await FixedShortDelay();
var status = await GetPurchaseState(pid);
if (string.IsNullOrEmpty(status))
{
emptyResponseTolerance--;
if (emptyResponseTolerance == 0)
{
log.Log("Received 10 empty responses to '/storage/purchases/'. Applying expiry delay, then carrying on.");
ExpiryTimeDelay();
log.Log("Received 10 empty responses. Applying expiry delay, then carrying on.");
await ExpiryTimeDelay();
return;
}
FixedShortDelay();
await FixedShortDelay();
}
else
{
if (status.Contains("pending") || status.Contains("submitted"))
{
FixedShortDelay();
await FixedShortDelay();
}
else if (status.Contains("started"))
{
log.Log("Started.");
await FixedDurationDelay();
}
else if (status.Contains("finished"))
{
log.Log("Purchase finished.");
return;
}
else if (status.Contains("error"))
{
await FixedShortDelay();
return;
}
else
{
log.Log("Wait finished.");
return;
await FixedShortDelay();
}
}
}
@ -130,18 +166,23 @@ namespace AutoClient
catch (Exception ex)
{
log.Log($"Wait failed with exception: {ex}. Assume contract will expire: Wait expiry time.");
ExpiryTimeDelay();
await ExpiryTimeDelay();
}
}
private void ExpiryTimeDelay()
private async Task FixedDurationDelay()
{
Thread.Sleep(config.ContractExpiryMinutes * 60 * 1000);
await Task.Delay(config.ContractDurationMinutes * 60 * 1000);
}
private void FixedShortDelay()
private async Task ExpiryTimeDelay()
{
Thread.Sleep(15 * 1000);
await Task.Delay(config.ContractExpiryMinutes * 60 * 1000);
}
private async Task FixedShortDelay()
{
await Task.Delay(15 * 1000);
}
}
}