2024-06-27 13:38:13 +00:00
|
|
|
|
using CodexOpenApi;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
using CodexPlugin;
|
|
|
|
|
using Logging;
|
2024-06-27 13:38:13 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
using Utils;
|
|
|
|
|
|
|
|
|
|
namespace AutoClient
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
public class Purchaser
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
|
|
|
|
private readonly ILog log;
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private readonly HttpClient client;
|
|
|
|
|
private readonly Address address;
|
|
|
|
|
private readonly CodexApi codex;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
private readonly Configuration config;
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private readonly ImageGenerator generator;
|
2024-07-23 07:58:29 +00:00
|
|
|
|
private readonly CancellationToken ct;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
|
2024-07-23 07:58:29 +00:00
|
|
|
|
public Purchaser(ILog log, HttpClient client, Address address, CodexApi codex, Configuration config, ImageGenerator generator, CancellationToken ct)
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
|
|
|
|
this.log = log;
|
2024-06-27 13:38:13 +00:00
|
|
|
|
this.client = client;
|
|
|
|
|
this.address = address;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
this.codex = codex;
|
|
|
|
|
this.config = config;
|
2024-06-27 13:38:13 +00:00
|
|
|
|
this.generator = generator;
|
2024-07-23 07:58:29 +00:00
|
|
|
|
this.ct = ct;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 06:47:09 +00:00
|
|
|
|
public void Start()
|
|
|
|
|
{
|
|
|
|
|
Task.Run(Worker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task Worker()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
|
|
|
|
while (!ct.IsCancellationRequested)
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
var pid = await StartNewPurchase();
|
|
|
|
|
await WaitTillFinished(pid);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 06:47:09 +00:00
|
|
|
|
private async Task<string> StartNewPurchase()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-27 13:38:13 +00:00
|
|
|
|
var file = await CreateFile();
|
|
|
|
|
var cid = await UploadFile(file);
|
2024-06-28 06:47:09 +00:00
|
|
|
|
return await RequestStorage(cid);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private async Task<string> CreateFile()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-27 13:38:13 +00:00
|
|
|
|
return await generator.GenerateImage();
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private async Task<ContentId> UploadFile(string filename)
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
|
|
|
|
// Copied from CodexNode :/
|
2024-06-27 13:38:13 +00:00
|
|
|
|
using var fileStream = File.OpenRead(filename);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
|
2024-06-28 06:47:09 +00:00
|
|
|
|
log.Log($"Uploading file {filename}...");
|
2024-06-27 13:38:13 +00:00
|
|
|
|
var response = await codex.UploadAsync(fileStream, ct);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(response)) FrameworkAssert.Fail("Received empty response.");
|
|
|
|
|
if (response.StartsWith("Unable to store block")) FrameworkAssert.Fail("Node failed to store block.");
|
|
|
|
|
|
|
|
|
|
log.Log($"Uploaded file. Received contentId: '{response}'.");
|
|
|
|
|
return new ContentId(response);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private async Task<string> RequestStorage(ContentId cid)
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-27 13:38:13 +00:00
|
|
|
|
log.Log("Requesting storage for " + cid.Id);
|
|
|
|
|
var result = await codex.CreateStorageRequestAsync(cid.Id, new StorageRequestCreation()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-27 13:38:13 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2024-06-28 06:47:09 +00:00
|
|
|
|
log.Log("Purchase ID: " + result);
|
2024-06-27 13:38:13 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2024-06-28 06:47:09 +00:00
|
|
|
|
catch
|
2024-06-27 13:38:13 +00:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 06:47:09 +00:00
|
|
|
|
private async Task WaitTillFinished(string pid)
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
log.Log("Waiting...");
|
2024-04-01 18:40:03 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2024-04-01 19:09:24 +00:00
|
|
|
|
var emptyResponseTolerance = 10;
|
2024-04-01 18:40:03 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
var status = (await GetPurchaseState(pid))?.ToLowerInvariant();
|
2024-04-01 19:09:24 +00:00
|
|
|
|
if (string.IsNullOrEmpty(status))
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-04-01 19:09:24 +00:00
|
|
|
|
emptyResponseTolerance--;
|
|
|
|
|
if (emptyResponseTolerance == 0)
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
log.Log("Received 10 empty responses. Stop tracking this purchase.");
|
2024-06-27 13:38:13 +00:00
|
|
|
|
await ExpiryTimeDelay();
|
2024-04-01 19:09:24 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
if (status.Contains("cancel") ||
|
|
|
|
|
status.Contains("error") ||
|
|
|
|
|
status.Contains("finished"))
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-27 13:38:13 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2024-06-28 06:47:09 +00:00
|
|
|
|
if (status.Contains("started"))
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
await FixedDurationDelay();
|
2024-06-27 13:38:13 +00:00
|
|
|
|
}
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
2024-06-28 06:47:09 +00:00
|
|
|
|
|
|
|
|
|
await FixedShortDelay();
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
log.Log($"Wait failed with exception: {ex}. Assume contract will expire: Wait expiry time.");
|
2024-06-27 13:38:13 +00:00
|
|
|
|
await ExpiryTimeDelay();
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private async Task FixedDurationDelay()
|
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
await Task.Delay(config.ContractDurationMinutes * 60 * 1000, ct);
|
2024-06-27 13:38:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ExpiryTimeDelay()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
await Task.Delay(config.ContractExpiryMinutes * 60 * 1000, ct);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 13:38:13 +00:00
|
|
|
|
private async Task FixedShortDelay()
|
2024-04-01 18:40:03 +00:00
|
|
|
|
{
|
2024-06-28 06:47:09 +00:00
|
|
|
|
await Task.Delay(15 * 1000, ct);
|
2024-04-01 18:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|