Skip to content

Commit 5e9e7fd

Browse files
Add administration methods
1 parent 257031f commit 5e9e7fd

11 files changed

+484
-3
lines changed

Monero.Lws.IntegrationTests/MoneroLwsServiceIntegrationTest.cs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Monero.Lws.IntegrationTests;
66
public class MoneroLwsServiceIntegrationTest
77
{
88
private static readonly string Address = TestUtils.Address;
9-
private static readonly string ViewKey = TestUtils.ViewKey;
9+
private static readonly string ViewKey = TestUtils.PrivateViewKey;
1010
private static readonly MoneroLwsService Lws = TestUtils.GetLwsService();
1111

1212
[Fact]
@@ -26,6 +26,18 @@ public async Task TestGetVersion()
2626
Assert.False(response.Testnet);
2727
}
2828

29+
[Fact]
30+
public async Task GetDaemonStatus()
31+
{
32+
var response = await Lws.GetDaemonStatus();
33+
Assert.True(response.OutgoingConnectionsCount >= 0);
34+
Assert.True(response.IncomingConnectionsCount >= 0);
35+
Assert.True(response.Height >= 0);
36+
Assert.True(response.TargetHeight >= 0);
37+
Assert.False(string.IsNullOrEmpty(response.Network));
38+
Assert.False(string.IsNullOrEmpty(response.State));
39+
}
40+
2941
[Fact]
3042
public async Task TestLogin()
3143
{
@@ -179,6 +191,74 @@ public async Task TestGetSubaddrs()
179191
TestSubaddrsEntry(entry);
180192
}
181193
}
194+
195+
[Fact]
196+
public async Task TestRescan()
197+
{
198+
var response = await Lws.Rescan(0, [Address]);
199+
Assert.NotEmpty(response.UpdatedAddresses);
200+
Assert.Single(response.UpdatedAddresses);
201+
Assert.Equal(Address, response.UpdatedAddresses.First());
202+
}
203+
204+
[Fact]
205+
public async Task TestValidate()
206+
{
207+
var response = await Lws.Validate(TestUtils.PublicViewKey, TestUtils.PublicSpendKey, TestUtils.PrivateViewKey);
208+
209+
if (response.Error != null)
210+
{
211+
Assert.Fail($"{response.Error.Field}: {response.Error.Details}");
212+
}
213+
214+
Assert.False(string.IsNullOrEmpty(response.Address));
215+
Assert.Equal(TestUtils.Address, response.Address);
216+
}
217+
218+
[Fact]
219+
public async Task TestAddAccount()
220+
{
221+
var address = "43a1cERdj8rT1513tmMUY5MBcWbt1hgSo2fgLhoRrkYTPXejjRjU9y2WCjYfdZMLfZ6LKVc7YRGJMdtxD3x9Dtjc6fFjH9q";
222+
var viewKey = "4f8d491198f5219b80a03ae2be337b37ace5a4626c67e80a68beb7d0e3eaaa08";
223+
var response = await Lws.AddAccount(address, viewKey);
224+
Assert.NotEmpty(response.UpdatedAddresses);
225+
Assert.Single(response.UpdatedAddresses);
226+
Assert.Equal(address, response.UpdatedAddresses.First());
227+
}
228+
229+
[Fact]
230+
public async Task TestModifyAccountStatus()
231+
{
232+
// deactivate account
233+
var response = await Lws.ModifyAccountStatus("inactive", [TestUtils.Address]);
234+
Assert.NotEmpty(response.UpdatedAddresses);
235+
Assert.Single(response.UpdatedAddresses);
236+
Assert.Equal(TestUtils.Address, response.UpdatedAddresses.First());
237+
// wait for lws to catch up
238+
Thread.Sleep(5000);
239+
// reactivate account
240+
response = await Lws.ModifyAccountStatus("active", [TestUtils.Address]);
241+
Assert.NotEmpty(response.UpdatedAddresses);
242+
Assert.Single(response.UpdatedAddresses);
243+
Assert.Equal(TestUtils.Address, response.UpdatedAddresses.First());
244+
}
245+
246+
[Fact]
247+
public async Task TestListAccounts()
248+
{
249+
var response = await Lws.ListAccounts();
250+
TestAccounts(response.Active);
251+
TestAccounts(response.Inactive);
252+
TestAccounts(response.Hidden);
253+
}
254+
255+
[Fact]
256+
public async Task TestListRequests()
257+
{
258+
var response = await Lws.ListRequests();
259+
TestAccounts(response.Create, true);
260+
TestAccounts(response.Import, true);
261+
}
182262

183263
private static void TestTransaction(MoneroLwsTransaction? tx)
184264
{
@@ -355,4 +435,31 @@ private static void TestAddressMeta(MoneroLwsAddressMeta? addressMeta)
355435
Assert.True(addressMeta.MinIndex >= 0);
356436
}
357437

438+
private static void TestAccount(MoneroLwsAccount? account, bool request)
439+
{
440+
Assert.NotNull(account);
441+
Assert.False(string.IsNullOrEmpty(account.Address));
442+
if (request)
443+
{
444+
Assert.True(account.StartHeight >= 0);
445+
return;
446+
}
447+
448+
Assert.True(account.ScanHeight >= 0);
449+
Assert.True(account.AccessTime >= 0);
450+
}
451+
452+
private static void TestAccounts(List<MoneroLwsAccount>? accounts, bool request)
453+
{
454+
Assert.NotNull(accounts);
455+
foreach (var account in accounts)
456+
{
457+
TestAccount(account, request);
458+
}
459+
}
460+
461+
private static void TestAccounts(List<MoneroLwsAccount>? accounts)
462+
{
463+
TestAccounts(accounts, false);
464+
}
358465
}

Monero.Lws.IntegrationTests/Utils/TestUtils.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ internal static class TestUtils
77
public const string Username = "";
88
public const string Password = "";
99
public const string Address = "42EhKmBx6pAPYhX4QCHKBPRw8dgc3VVVdA7g2dxr5wz21crqvPUkwPTde64Xac5uawQeFbh6K7PD4YLqiX1VTP5jUH7gZez";
10-
public const string ViewKey = "41f55a92b942681e35bf7bb64f71142729039bd8e606a4f4218c543065c15c05";
10+
public const string PublicViewKey = "b244f89be70e16db0d8905628480708d590ffd4e820303bb61c96cf2395bfaf1";
11+
public const string PublicSpendKey = "1030f7c992ec9b86cc0055d2c44632951104e67b05d28c367ecd8382c0931303";
12+
public const string PrivateViewKey = "41f55a92b942681e35bf7bb64f71142729039bd8e606a4f4218c543065c15c05";
1113

1214
private static MoneroLwsService? _lwsService = null;
1315

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Monero.Lws.Common;
4+
5+
public class MoneroLwsAccount
6+
{
7+
/// <summary>
8+
/// Account primary address.
9+
/// </summary>
10+
[JsonPropertyName("address")] public string Address { get; set; } = "";
11+
12+
/// <summary>
13+
/// Account scan height.
14+
/// </summary>
15+
[JsonPropertyName("scan_height")] public long ScanHeight { get; set; } = 0;
16+
17+
/// <summary>
18+
/// Account start height.
19+
/// </summary>
20+
[JsonPropertyName("start_height")] public long StartHeight { get; set; } = 0;
21+
22+
/// <summary>
23+
/// Account last access time.
24+
/// </summary>
25+
[JsonPropertyName("access_time")] public long AccessTime { get; set; } = 0;
26+
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Monero.Lws.Common;
4+
5+
public class MoneroLwsDaemonStatus
6+
{
7+
/// <summary>
8+
/// Outgoing connections from daemon.
9+
/// </summary>
10+
[JsonPropertyName("outgoing_connections_count")] public long OutgoingConnectionsCount { get; set; } = 0;
11+
12+
/// <summary>
13+
/// Incoming connections to daemon.
14+
/// </summary>
15+
[JsonPropertyName("incoming_connections_count")] public long IncomingConnectionsCount { get; set; } = 0;
16+
17+
/// <summary>
18+
/// Daemon height.
19+
/// </summary>
20+
[JsonPropertyName("height")] public long Height { get; set; } = 0;
21+
22+
/// <summary>
23+
/// Target height.
24+
/// </summary>
25+
[JsonPropertyName("target_height")] public long TargetHeight { get; set; } = 0;
26+
27+
/// <summary>
28+
/// Network type.
29+
/// </summary>
30+
[JsonPropertyName("network")] public string Network { get; set; } = "";
31+
32+
/// <summary>
33+
/// Daemon status.
34+
/// </summary>
35+
[JsonPropertyName("state")] public string State { get; set; } = "";
36+
}

Monero.Lws/MoneroLwsService.cs

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,16 @@ public void SetCredentials(string username, string password)
106106
_username = username;
107107
_password = password;
108108
}
109-
109+
110+
/// <summary>
111+
/// Get LWS server version.
112+
/// </summary>
113+
/// <returns></returns>
114+
public async Task<MoneroLwsDaemonStatus> GetDaemonStatus()
115+
{
116+
return await SendCommandAsync<MoneroLwsDaemonStatus>("daemon_status");
117+
}
118+
110119
/// <summary>
111120
/// Get LWS server version.
112121
/// </summary>
@@ -322,4 +331,153 @@ public async Task<MoneroLwsSubaddrs> ProvisionSubaddrs(string address, string vi
322331

323332
return await SendCommandAsync<MoneroLwsProvisionSubaddrsRequest, MoneroLwsSubaddrs>("provision_subaddrs", req);
324333
}
334+
335+
#region Administration
336+
337+
/// <summary>
338+
/// Accepts create or import requests in the incoming queue.
339+
/// </summary>
340+
/// <param name="type"><c>create</c> or <c>import</c>.</param>
341+
/// <param name="addresses">Account addresses to accept.</param>
342+
/// <returns></returns>
343+
public async Task<MoneroLwsUpdateResponse> AcceptRequests(string type, List<string> addresses)
344+
{
345+
var req = new MoneroLwsAdminRequest
346+
{
347+
Params = new MoneroLwsAdminParams
348+
{
349+
RequestType = type,
350+
Addresses = addresses
351+
}
352+
};
353+
354+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsUpdateResponse>("accept_requests", req);
355+
}
356+
357+
/// <summary>
358+
/// Reject account creation or import from the incoming queue.
359+
/// </summary>
360+
/// <param name="type"><c>create</c> or <c>import</c>.</param>
361+
/// <param name="addresses">Account addresses to reject.</param>
362+
/// <returns></returns>
363+
public async Task<MoneroLwsUpdateResponse> RejectRequests(string type, List<string> addresses)
364+
{
365+
var req = new MoneroLwsAdminRequest
366+
{
367+
Params = new MoneroLwsAdminParams
368+
{
369+
RequestType = type,
370+
Addresses = addresses
371+
}
372+
};
373+
374+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsUpdateResponse>("reject_requests", req);
375+
}
376+
377+
/// <summary>
378+
/// Add account for view-key scanning.
379+
/// </summary>
380+
/// <param name="address">Account primary address.</param>
381+
/// <param name="viewKey">Account private view key.</param>
382+
/// <returns></returns>
383+
public async Task<MoneroLwsUpdateResponse> AddAccount(string address, string viewKey)
384+
{
385+
var req = new MoneroLwsAdminRequest
386+
{
387+
Params = new MoneroLwsAdminParams
388+
{
389+
Address = address,
390+
Key = viewKey
391+
}
392+
};
393+
394+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsUpdateResponse>("add_account", req);
395+
}
396+
397+
/// <summary>
398+
/// Change an account status to <c>active</c>, <c>inactive</c> or <c>hidden</c>.
399+
/// The <c>active</c> state is the normal state: the account is being scanned and returned by the API.
400+
/// The <c>inactive</c> state is still returned by the API, but is no longer being scanned.
401+
/// The <c>hidden</c> is the current way to <i>delete</i> an account: it is not scanned nor returned by the API.
402+
/// Accounts cannot currently be deleted due to internal DB requirements.
403+
/// </summary>
404+
/// <param name="status">Account status to apply (<c>active</c>, <c>inactive</c> or <c>hidden</c>).</param>
405+
/// <param name="addresses">Account addresses.</param>
406+
/// <returns></returns>
407+
public async Task<MoneroLwsUpdateResponse> ModifyAccountStatus(string status, List<string> addresses)
408+
{
409+
var req = new MoneroLwsAdminRequest
410+
{
411+
Params = new MoneroLwsAdminParams
412+
{
413+
Addresses = addresses,
414+
Status = status
415+
}
416+
};
417+
418+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsUpdateResponse>("modify_account_status", req);
419+
}
420+
421+
/// <summary>
422+
/// Request a listing of all active accounts in the database.
423+
/// </summary>
424+
/// <returns></returns>
425+
public async Task<MoneroLwsListAccountsResponse> ListAccounts()
426+
{
427+
return await SendCommandAsync<MoneroLwsListAccountsResponse>("list_accounts");
428+
}
429+
430+
/// <summary>
431+
/// Return the listing of all pending new account requests and all requests to import from genesis block requests.
432+
/// </summary>
433+
/// <returns></returns>
434+
public async Task<MoneroLwsListRequestsResponse> ListRequests()
435+
{
436+
return await SendCommandAsync<MoneroLwsListRequestsResponse>("list_requests");
437+
}
438+
439+
/// <summary>
440+
/// Rescan specific account(s) from the specified height.
441+
/// </summary>
442+
/// <param name="height">Blockchain height.</param>
443+
/// <param name="addresses">Addresses to rescan.</param>
444+
/// <returns></returns>
445+
public async Task<MoneroLwsUpdateResponse> Rescan(long height, List<string> addresses)
446+
{
447+
var req = new MoneroLwsAdminRequest
448+
{
449+
Params = new MoneroLwsAdminParams
450+
{
451+
Addresses = addresses,
452+
Height = height
453+
}
454+
};
455+
456+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsUpdateResponse>("rescan", req);
457+
}
458+
459+
/// <summary>
460+
/// Validate account keys.
461+
/// </summary>
462+
/// <param name="viewPublicHex">Account view public key as hex.</param>
463+
/// <param name="spendPublicHex">Account spend public key as hex.</param>
464+
/// <param name="viewKeyHex">Account private view key as hex.</param>
465+
/// <returns></returns>
466+
public async Task<MoneroLwsValidateResponse> Validate(string viewPublicHex, string spendPublicHex,
467+
string viewKeyHex)
468+
{
469+
var req = new MoneroLwsAdminRequest
470+
{
471+
Params = new MoneroLwsAdminParams
472+
{
473+
ViewPublicHex = viewPublicHex,
474+
SpendPublicHex = spendPublicHex,
475+
ViewKeyHex = viewKeyHex
476+
}
477+
};
478+
479+
return await SendCommandAsync<MoneroLwsAdminRequest, MoneroLwsValidateResponse>("validate", req);
480+
}
481+
482+
#endregion
325483
}

0 commit comments

Comments
 (0)