-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathComputeBoardClient.cs
More file actions
164 lines (151 loc) · 5.26 KB
/
Copy pathComputeBoardClient.cs
File metadata and controls
164 lines (151 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
namespace SpawnDev.ILGPU.P2P;
/// <summary>
/// Client for the compute request board on hub.spawndev.com.
/// Posts signed "need TFLOPS" requests and browses available swarms.
///
/// Authentication:
/// POST requires SwarmIdentity signature (OwnerFingerprint + PublicKey + Signature)
/// DELETE requires fingerprint query parameter matching the owner
/// </summary>
public class ComputeBoardClient
{
private readonly HttpClient _http;
/// <summary>
/// Base URL of the compute board server.
/// Default: hub.spawndev.com.
/// </summary>
public string BaseUrl { get; set; } = "https://hub.spawndev.com:44365";
public ComputeBoardClient(HttpClient? httpClient = null)
{
_http = httpClient ?? new HttpClient();
_http.Timeout = TimeSpan.FromSeconds(15);
}
/// <summary>
/// Post a signed compute request to the board.
/// </summary>
public async Task<ComputeBoardRequest?> PostRequestAsync(ComputeBoardRequest request)
{
try
{
var response = await _http.PostAsJsonAsync($"{BaseUrl}/compute/request", request);
if (response.IsSuccessStatusCode)
return await response.Content.ReadFromJsonAsync<ComputeBoardRequest>();
}
catch (Exception ex)
{
if (P2PCompute.VerboseLogging) Console.WriteLine($"[ComputeBoard] POST failed: {ex.Message}");
}
return null;
}
/// <summary>
/// Browse active compute requests.
/// </summary>
public async Task<List<ComputeBoardRequest>> GetRequestsAsync()
{
try
{
return await _http.GetFromJsonAsync<List<ComputeBoardRequest>>($"{BaseUrl}/compute/requests") ?? new();
}
catch (Exception ex)
{
if (P2PCompute.VerboseLogging) Console.WriteLine($"[ComputeBoard] GET requests failed: {ex.Message}");
return new();
}
}
/// <summary>
/// Get aggregate compute stats.
/// </summary>
public async Task<ComputeBoardStats?> GetStatsAsync()
{
try
{
return await _http.GetFromJsonAsync<ComputeBoardStats>($"{BaseUrl}/compute/stats");
}
catch (Exception ex)
{
if (P2PCompute.VerboseLogging) Console.WriteLine($"[ComputeBoard] GET stats failed: {ex.Message}");
return null;
}
}
/// <summary>
/// Remove a compute request. Requires the owner's fingerprint.
/// </summary>
public async Task<bool> RemoveRequestAsync(string id, string ownerFingerprint)
{
try
{
var response = await _http.DeleteAsync(
$"{BaseUrl}/compute/request/{id}?fingerprint={Uri.EscapeDataString(ownerFingerprint)}");
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
if (P2PCompute.VerboseLogging) Console.WriteLine($"[ComputeBoard] DELETE failed: {ex.Message}");
return false;
}
}
/// <summary>
/// Post a signed request for the given P2PCompute instance.
/// Signs the request payload with the swarm's identity.
/// </summary>
public async Task<ComputeBoardRequest?> PostFromComputeAsync(
P2PCompute compute, string purpose, double tflopsNeeded)
{
var request = new ComputeBoardRequest
{
SwarmName = "Compute Swarm",
Purpose = purpose,
OwnerFingerprint = compute.Identity.Fingerprint,
PublicKey = Convert.ToBase64String(compute.Identity.PublicKeySpki),
TflopsNeeded = tflopsNeeded,
TflopsAvailable = compute.TotalTflops,
PeerCount = compute.PeerCount,
MagnetLink = compute.MagnetLink,
JoinLink = compute.JoinLink,
};
// Sign the request payload
var payload = JsonSerializer.SerializeToUtf8Bytes(new
{
request.SwarmName,
request.Purpose,
request.OwnerFingerprint,
request.TflopsNeeded,
});
var signature = await compute.Identity.SignAsync(payload);
request.Signature = Convert.ToBase64String(signature);
return await PostRequestAsync(request);
}
}
/// <summary>
/// Client-side DTO for compute board requests.
/// Mirrors SpawnDev.WebTorrent.Server.ComputeRequest.
/// </summary>
public class ComputeBoardRequest
{
public string Id { get; set; } = "";
public string SwarmName { get; set; } = "";
public string Purpose { get; set; } = "";
public string? OwnerFingerprint { get; set; }
public string? PublicKey { get; set; }
public string? Signature { get; set; }
public double TflopsNeeded { get; set; }
public double TflopsAvailable { get; set; }
public int PeerCount { get; set; }
public string? MagnetLink { get; set; }
public string? JoinLink { get; set; }
public DateTimeOffset PostedAt { get; set; }
public DateTimeOffset ExpiresAt { get; set; }
}
/// <summary>
/// Client-side DTO for compute board stats.
/// </summary>
public class ComputeBoardStats
{
public int ActiveRequests { get; set; }
public double TotalTflopsNeeded { get; set; }
public double TotalTflopsAvailable { get; set; }
public int UniqueSwarms { get; set; }
}