Skip to content

Commit cd99010

Browse files
Auto load wallet on start up if available and deprecate password
Co-authored-by: Deverick <5827364+deverickapollo@users.noreply.github.com>
1 parent e17d930 commit cd99010

23 files changed

Lines changed: 297 additions & 94 deletions

.gitattributes

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66

77
# Denote all files that are truly binary and should not be modified.
88
*.png binary
9-
*.jpg binary
9+
*.jpg binary
10+
*.keys binary
11+
wallet binary

BTCPayServer.Plugins.IntegrationTests/BTCPayServer.Plugins.IntegrationTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
12+
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
1213
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
1314
<PackageReference Include="Microsoft.Playwright" Version="1.52.0" />
1415
<PackageReference Include="xunit" Version="2.9.3" />
@@ -32,6 +33,12 @@
3233
<ProjectReference Include="..\Plugins\Monero\BTCPayServer.Plugins.Monero.csproj" />
3334
</ItemGroup>
3435

36+
<ItemGroup>
37+
<Content Include="Resources\**\*">
38+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
39+
</Content>
40+
</ItemGroup>
41+
3542
<ItemDefinitionGroup>
3643
<ProjectReference>
3744
<Properties>StaticWebAssetsEnabled=false</Properties>

BTCPayServer.Plugins.IntegrationTests/Monero/IntegrationTestUtils.cs

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55

66
using Microsoft.Extensions.Logging;
77

8+
using Mono.Unix.Native;
9+
810
using Npgsql;
911

12+
using static Mono.Unix.Native.Syscall;
13+
1014
namespace BTCPayServer.Plugins.IntegrationTests.Monero;
1115

1216
public static class IntegrationTestUtils
@@ -16,17 +20,19 @@ public static class IntegrationTestUtils
1620
.Create(builder => builder.AddConsole())
1721
.CreateLogger("IntegrationTestUtils");
1822

23+
private static readonly string ContainerWalletDir = Environment.GetEnvironmentVariable("BTCPAY_XMR_WALLET_DAEMON_WALLETDIR") ?? "/wallet";
24+
1925
public static async Task CleanUpAsync(PlaywrightTester playwrightTester)
2026
{
21-
MoneroRPCProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService<MoneroRPCProvider>();
27+
MoneroRpcProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService<MoneroRpcProvider>();
2228
if (moneroRpcProvider.IsAvailable("XMR"))
2329
{
2430
await moneroRpcProvider.CloseWallet("XMR");
2531
}
2632

2733
if (playwrightTester.Server.PayTester.InContainer)
2834
{
29-
moneroRpcProvider.DeleteWallet();
35+
DeleteWalletInContainer();
3036
await DropDatabaseAsync(
3137
"btcpayserver",
3238
"Host=postgres;Port=5432;Username=postgres;Database=postgres");
@@ -62,6 +68,94 @@ FROM pg_stat_activity
6268
}
6369
}
6470

71+
public static async Task CopyWalletFilesToMoneroRpcDirAsync(PlaywrightTester playwrightTester)
72+
{
73+
Logger.LogInformation("Starting to copy wallet files");
74+
if (playwrightTester.Server.PayTester.InContainer)
75+
{
76+
CopyWalletFilesInContainer();
77+
}
78+
else
79+
{
80+
await CopyWalletFilesToLocalDocker();
81+
}
82+
}
83+
84+
private static void CopyWalletFilesInContainer()
85+
{
86+
try
87+
{
88+
CopyWalletFile("wallet");
89+
CopyWalletFile("wallet.keys");
90+
CopyWalletFile("password");
91+
}
92+
catch (Exception ex)
93+
{
94+
Logger.LogError(ex, "Failed to copy wallet files to the Monero directory.");
95+
}
96+
}
97+
98+
private static void CopyWalletFile(string name)
99+
{
100+
var resourceWalletDir = Path.Combine(AppContext.BaseDirectory, "Resources", "wallet");
101+
102+
var src = Path.Combine(resourceWalletDir, name);
103+
var dst = Path.Combine(ContainerWalletDir, name);
104+
105+
if (!File.Exists(src))
106+
{
107+
return;
108+
}
109+
110+
File.Copy(src, dst, overwrite: true);
111+
112+
// monero ownership
113+
if (chown(dst, 980, 980) == 0)
114+
{
115+
return;
116+
}
117+
118+
Logger.LogError("chown failed for {File}. errno={Errno}", dst, Stdlib.GetLastError());
119+
}
120+
121+
122+
private static async Task CopyWalletFilesToLocalDocker()
123+
{
124+
var walletDir = Path.Combine(AppContext.BaseDirectory, "Resources", "wallet");
125+
126+
await RunProcessAsync("docker",
127+
$"cp \"{Path.Combine(walletDir, "wallet")}\" xmr_wallet:/wallet/wallet");
128+
129+
await RunProcessAsync("docker",
130+
$"cp \"{Path.Combine(walletDir, "wallet.keys")}\" xmr_wallet:/wallet/wallet.keys");
131+
132+
await RunProcessAsync("docker",
133+
$"cp \"{Path.Combine(walletDir, "password")}\" xmr_wallet:/wallet/password");
134+
135+
await RunProcessAsync("docker",
136+
"exec xmr_wallet chown monero:monero /wallet/wallet /wallet/wallet.keys /wallet/password");
137+
}
138+
139+
static async Task RunProcessAsync(string fileName, string args)
140+
{
141+
var psi = new ProcessStartInfo
142+
{
143+
FileName = fileName,
144+
Arguments = args,
145+
RedirectStandardOutput = true,
146+
RedirectStandardError = true,
147+
UseShellExecute = false
148+
};
149+
150+
using var process = Process.Start(psi)!;
151+
await process.WaitForExitAsync();
152+
153+
if (process.ExitCode != 0)
154+
{
155+
throw new Exception(await process.StandardError.ReadToEndAsync());
156+
}
157+
}
158+
65159
private static async Task RemoveWalletFromLocalDocker()
66160
{
67161
try
@@ -101,4 +195,31 @@ private static async Task RemoveWalletFromLocalDocker()
101195
Logger.LogError(ex, "Wallet cleanup via Docker failed.");
102196
}
103197
}
198+
199+
private static void DeleteWalletInContainer()
200+
{
201+
try
202+
{
203+
var walletFile = Path.Combine(ContainerWalletDir, "wallet");
204+
var keysFile = walletFile + ".keys";
205+
var passwordFile = Path.Combine(ContainerWalletDir, "password");
206+
207+
if (File.Exists(walletFile))
208+
{
209+
File.Delete(walletFile);
210+
}
211+
if (File.Exists(keysFile))
212+
{
213+
File.Delete(keysFile);
214+
}
215+
if (File.Exists(passwordFile))
216+
{
217+
File.Delete(passwordFile);
218+
}
219+
}
220+
catch (Exception ex)
221+
{
222+
Logger.LogError(ex, "Failed to delete wallet files in directory {Dir}", ContainerWalletDir);
223+
}
224+
}
104225
}

BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ await s.Page.Locator("input#PrimaryAddress")
4444
await s.Page.Locator("input#PrivateViewKey")
4545
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
4646
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
47-
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
4847
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
4948
await s.Page.CheckAsync("#Enabled");
5049
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
@@ -119,7 +118,6 @@ await s.Page.Locator("input#PrimaryAddress")
119118
await s.Page.Locator("input#PrivateViewKey")
120119
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
121120
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
122-
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
123121
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
124122
var errorText = await s.Page
125123
.Locator("div.validation-summary-errors li")
@@ -136,12 +134,12 @@ public async Task ShouldFailWhenWalletFileAlreadyExists()
136134
await using var s = CreatePlaywrightTester();
137135
await s.StartAsync();
138136

139-
MoneroRPCProvider moneroRpcProvider = s.Server.PayTester.GetService<MoneroRPCProvider>();
137+
MoneroRpcProvider moneroRpcProvider = s.Server.PayTester.GetService<MoneroRpcProvider>();
140138
await moneroRpcProvider.WalletRpcClients["XMR"].SendCommandAsync<GenerateFromKeysRequest, GenerateFromKeysResponse>("generate_from_keys", new GenerateFromKeysRequest
141139
{
142140
PrimaryAddress = "43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L",
143141
PrivateViewKey = "1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e",
144-
WalletFileName = "view_wallet",
142+
WalletFileName = "wallet",
145143
Password = ""
146144
});
147145
await moneroRpcProvider.CloseWallet("XMR");
@@ -154,13 +152,30 @@ await s.Page.Locator("input#PrimaryAddress")
154152
await s.Page.Locator("input#PrivateViewKey")
155153
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
156154
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
157-
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
158155
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
159156
var errorText = await s.Page
160157
.Locator("div.validation-summary-errors li")
161158
.InnerTextAsync();
162159

163160
Assert.Equal("Could not generate view wallet from keys: Wallet already exists.", errorText);
161+
await IntegrationTestUtils.CleanUpAsync(s);
162+
}
163+
164+
[Fact]
165+
public async Task ShouldLoadViewWalletOnStartUpIfExists()
166+
{
167+
await using var s = CreatePlaywrightTester();
168+
await IntegrationTestUtils.CopyWalletFilesToMoneroRpcDirAsync(s);
169+
await s.StartAsync();
170+
await s.RegisterNewUser(true);
171+
await s.CreateNewStore();
172+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
173+
174+
var walletRpcIsAvailable = await s.Page
175+
.Locator("li.list-group-item:text('Wallet RPC available: True')")
176+
.InnerTextAsync();
177+
178+
Assert.Contains("Wallet RPC available: True", walletRpcIsAvailable);
164179

165180
await IntegrationTestUtils.CleanUpAsync(s);
166181
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pass123
401 KB
Binary file not shown.
Binary file not shown.

Plugins/Monero/BTCPayServer.Plugins.Monero.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PropertyGroup>
99
<Product>BTCPay Server: Monero support plugin</Product>
1010
<Description>This plugin extends BTCPay Server to enable users to receive payments via Monero.</Description>
11-
<Version>1.0.1</Version>
11+
<Version>1.1.0</Version>
1212
<EmbedUntrackedSources>true</EmbedUntrackedSources>
1313
</PropertyGroup>
1414

Plugins/Monero/BTCPayServer.Plugins.Monero.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"Identifier": "BTCPayServer.Plugins.Monero",
33
"Name": "BTCPay Server: Monero support plugin",
4-
"Version": "1.0.1.0",
4+
"Version": "1.1.0",
55
"Description": "This plugin extends BTCPay Server to enable users to receive payments via Monero.",
66
"SystemPlugin": false,
77
"Dependencies": [

Plugins/Monero/Controllers/MoneroLikeStoreController.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public class UIMoneroLikeStoreController : Controller
3333
{
3434
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
3535
private readonly StoreRepository _StoreRepository;
36-
private readonly MoneroRPCProvider _MoneroRpcProvider;
36+
private readonly MoneroRpcProvider _MoneroRpcProvider;
3737
private readonly PaymentMethodHandlerDictionary _handlers;
3838
private IStringLocalizer StringLocalizer { get; }
3939

4040
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
41-
StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
41+
StoreRepository storeRepository, MoneroRpcProvider moneroRpcProvider,
4242
PaymentMethodHandlerDictionary handlers,
4343
IStringLocalizer stringLocalizer)
4444
{
@@ -219,9 +219,8 @@ public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePayme
219219
{
220220
PrimaryAddress = viewModel.PrimaryAddress,
221221
PrivateViewKey = viewModel.PrivateViewKey,
222-
WalletFileName = "view_wallet",
223-
RestoreHeight = viewModel.RestoreHeight,
224-
Password = viewModel.WalletPassword
222+
WalletFileName = "wallet",
223+
RestoreHeight = viewModel.RestoreHeight
225224
});
226225
if (response?.Error != null)
227226
{
@@ -286,7 +285,7 @@ public class MoneroLikePaymentMethodListViewModel
286285

287286
public class MoneroLikePaymentMethodViewModel : IValidatableObject
288287
{
289-
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
288+
public MoneroRpcProvider.MoneroLikeSummary Summary { get; set; }
290289
public string CryptoCode { get; set; }
291290
public string NewAccountLabel { get; set; }
292291
public long AccountIndex { get; set; }
@@ -300,8 +299,6 @@ public class MoneroLikePaymentMethodViewModel : IValidatableObject
300299
public string PrivateViewKey { get; set; }
301300
[Display(Name = "Restore Height")]
302301
public int RestoreHeight { get; set; }
303-
[Display(Name = "Wallet Password")]
304-
public string WalletPassword { get; set; }
305302
[Display(Name = "Consider the invoice settled when the payment transaction …")]
306303
public MoneroLikeSettlementThresholdChoice SettlementConfirmationThresholdChoice { get; set; }
307304
[Display(Name = "Required Confirmations"), Range(0, 100)]

0 commit comments

Comments
 (0)