Skip to content

Commit 9b132b9

Browse files
committed
Simulate subaddress generate duplication
1 parent cdfdb2c commit 9b132b9

6 files changed

Lines changed: 217 additions & 8 deletions

File tree

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
run: dotnet clean
3232

3333
- name: Build projects
34-
run: dotnet build -c Release --no-restore
34+
run: dotnet build -maxcpucount:1 -c Release --no-restore
3535

3636
- name: Deterministic build check
3737
run: |

BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
using System.Diagnostics;
2+
3+
using BTCPayServer.Plugins.Monero.Services;
14
using BTCPayServer.Rating;
25
using BTCPayServer.Services.Rates;
6+
using BTCPayServer.Tests;
37
using BTCPayServer.Tests.Mocks;
48

59
using Xunit;
@@ -9,6 +13,7 @@ namespace BTCPayServer.Plugins.IntegrationTests.Monero;
913

1014
public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBitcoinIntegrationTestBase(helper)
1115
{
16+
1217
[Fact]
1318
public async Task EnableMoneroPluginSuccessfully()
1419
{
@@ -36,8 +41,101 @@ public async Task EnableMoneroPluginSuccessfully()
3641
await s.RegisterNewUser(true);
3742
await s.CreateNewStore(preferredExchange: "Kraken");
3843
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
39-
await s.Page.Locator("input#PrimaryAddress").FillAsync("43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
40-
await s.Page.Locator("input#PrivateViewKey").FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
44+
await s.Page.Locator("input#PrimaryAddress").FillAsync(
45+
"43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
46+
await s.Page.Locator("input#PrivateViewKey")
47+
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
48+
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
49+
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
50+
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
51+
await s.Page.CheckAsync("#Enabled");
52+
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
53+
await s.Page.ClickAsync("#SaveButton");
54+
var classList = await s.Page.Locator("svg.icon-checkmark").GetAttributeAsync("class");
55+
Assert.Contains("text-success", classList);
56+
57+
// Set rate provider
58+
await s.Page.Locator("#menu-item-General").ClickAsync();
59+
await s.Page.Locator("#menu-item-Rates").ClickAsync();
60+
await s.Page.FillAsync("#DefaultCurrencyPairs", "BTC_USD,XMR_USD,XMR_BTC");
61+
await s.Page.SelectOptionAsync("#PrimarySource_PreferredExchange", "kraken");
62+
await s.Page.Locator("#page-primary").ClickAsync();
63+
64+
// Generate a new invoice
65+
await s.Page.Locator("a.nav-link[href*='invoices']").ClickAsync();
66+
await s.Page.Locator("#page-primary").ClickAsync();
67+
await s.Page.FillAsync("#Amount", "4.20");
68+
await s.Page.FillAsync("#BuyerEmail", "monero@monero.com");
69+
await Task.Delay(TimeSpan.FromSeconds(25)); // wallet-rpc needs some time to sync. refactor this later
70+
await s.Page.Locator("#page-primary").ClickAsync();
71+
72+
// View the invoice
73+
var href = await s.Page.Locator("a[href^='/i/']").GetAttributeAsync("href");
74+
var invoiceId = href?.Split("/i/").Last();
75+
await s.Page.Locator($"a[href='/i/{invoiceId}']").ClickAsync();
76+
await s.Page.ClickAsync("#DetailsToggle");
77+
78+
// Verify the total fiat amount is $4.20
79+
var totalFiat = await s.Page.Locator("#PaymentDetails-TotalFiat dd.clipboard-button")
80+
.InnerTextAsync();
81+
Assert.Equal("$4.20", totalFiat);
82+
83+
await s.Page.GoBackAsync();
84+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
85+
86+
// Create a new account label
87+
await s.Page.FillAsync("#NewAccountLabel", "tst-account");
88+
await s.Page.ClickAsync("button[name='command'][value='add-account']");
89+
90+
// Select primary Account Index
91+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
92+
await s.Page.SelectOptionAsync("#AccountIndex", "1");
93+
await s.Page.ClickAsync("#SaveButton");
94+
95+
// Verify selected account index
96+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
97+
var selectedValue = await s.Page.Locator("#AccountIndex").InputValueAsync();
98+
Assert.Equal("1", selectedValue);
99+
100+
// Select confirmation time to 0
101+
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "3");
102+
await s.Page.ClickAsync("#SaveButton");
103+
104+
await CleanUp(s);
105+
}
106+
107+
[Fact]
108+
public async Task EnableMoneroPluginSuccessfullySecond()
109+
{
110+
await using var s = CreatePlaywrightTester();
111+
await s.StartAsync();
112+
113+
if (s.Server.PayTester.MockRates)
114+
{
115+
var rateProviderFactory = s.Server.PayTester.GetService<RateProviderFactory>();
116+
rateProviderFactory.Providers.Clear();
117+
118+
var coinAverageMock = new MockRateProvider();
119+
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_USD"), new BidAsk(5000m)));
120+
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_EUR"), new BidAsk(4000m)));
121+
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("XMR_BTC"), new BidAsk(4500m)));
122+
rateProviderFactory.Providers.Add("coingecko", coinAverageMock);
123+
124+
var kraken = new MockRateProvider();
125+
kraken.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_USD"), new BidAsk(0.1m)));
126+
kraken.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("XMR_USD"), new BidAsk(0.1m)));
127+
kraken.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("XMR_BTC"), new BidAsk(0.1m)));
128+
rateProviderFactory.Providers.Add("kraken", kraken);
129+
}
130+
131+
await s.RegisterNewUser(true);
132+
await s.CreateNewStore(preferredExchange: "Kraken");
133+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
134+
await s.Page.Locator("input#PrimaryAddress")
135+
.FillAsync(
136+
"43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
137+
await s.Page.Locator("input#PrivateViewKey")
138+
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
41139
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
42140
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
43141
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
@@ -48,8 +146,8 @@ public async Task EnableMoneroPluginSuccessfully()
48146
Assert.Contains("text-success", classList);
49147

50148
// Set rate provider
51-
await s.Page.Locator("#StoreNav-General").ClickAsync();
52-
await s.Page.Locator("#mainNav #StoreNav-Rates").ClickAsync();
149+
await s.Page.Locator("#menu-item-General").ClickAsync();
150+
await s.Page.Locator("#menu-item-Rates").ClickAsync();
53151
await s.Page.FillAsync("#DefaultCurrencyPairs", "BTC_USD,XMR_USD,XMR_BTC");
54152
await s.Page.SelectOptionAsync("#PrimarySource_PreferredExchange", "kraken");
55153
await s.Page.Locator("#page-primary").ClickAsync();
@@ -92,5 +190,65 @@ public async Task EnableMoneroPluginSuccessfully()
92190
// Select confirmation time to 0
93191
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "3");
94192
await s.Page.ClickAsync("#SaveButton");
193+
194+
await CleanUp(s);
195+
}
196+
197+
private static async Task CleanUp(PlaywrightTester playwrightTester)
198+
{
199+
MoneroRPCProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService<MoneroRPCProvider>();
200+
if (moneroRpcProvider.IsAvailable("XMR"))
201+
{
202+
await moneroRpcProvider.CloseWallet("XMR");
203+
await moneroRpcProvider.UpdateSummary("XMR");
204+
}
205+
206+
if (playwrightTester.Server.PayTester.InContainer)
207+
{
208+
moneroRpcProvider.DeleteWallet();
209+
}
210+
else
211+
{
212+
await RemoveWalletFromLocalDocker();
213+
}
214+
}
215+
216+
static async Task RemoveWalletFromLocalDocker()
217+
{
218+
try
219+
{
220+
var removeWalletFromDocker = new ProcessStartInfo
221+
{
222+
FileName = "docker",
223+
Arguments = "exec xmr_wallet sh -c \"rm -rf /wallet/*\"",
224+
RedirectStandardOutput = true,
225+
RedirectStandardError = true
226+
};
227+
228+
using var process = Process.Start(removeWalletFromDocker);
229+
if (process is null)
230+
{
231+
return;
232+
}
233+
234+
var stdout = await process.StandardOutput.ReadToEndAsync();
235+
var stderr = await process.StandardError.ReadToEndAsync();
236+
237+
await process.WaitForExitAsync();
238+
239+
if (!string.IsNullOrWhiteSpace(stdout))
240+
{
241+
Console.WriteLine(stdout);
242+
}
243+
244+
if (!string.IsNullOrWhiteSpace(stderr))
245+
{
246+
Console.WriteLine(stderr);
247+
}
248+
}
249+
catch (Exception ex)
250+
{
251+
Console.WriteLine($"Cleanup failed: {ex}");
252+
}
95253
}
96254
}

BTCPayServer.Plugins.IntegrationTests/docker-compose.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ services:
2222
- "tests:127.0.0.1"
2323
volumes:
2424
- ../coverage:/coverage
25+
- xmr_wallet:/wallet
2526

2627
# The dev container is not used, it is just handy to run `docker-compose up dev` to start all services
2728
dev:
@@ -89,7 +90,7 @@ services:
8990
- "bitcoin_datadir:/data"
9091

9192
monerod:
92-
image: btcpayserver/monero:0.18.4.2
93+
image: btcpayserver/monero:0.18.4.3
9394
restart: unless-stopped
9495
container_name: monerod
9596
command: monerod --fixed-difficulty 1 --log-level=2 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --regtest --no-igd --hide-my-port --offline --non-interactive
@@ -99,7 +100,7 @@ services:
99100
- "18081:18081"
100101

101102
xmr_wallet:
102-
image: btcpayserver/monero:0.18.4.2
103+
image: btcpayserver/monero:0.18.4.3
103104
restart: unless-stopped
104105
container_name: xmr_wallet
105106
command: monero-wallet-rpc --log-level 2 --allow-mismatched-daemon-version --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-dir=/wallet --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"

Plugins/Monero/Services/MoneroRPCProvider.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Immutable;
4+
using System.IO;
45
using System.Net.Http;
56
using System.Threading.Tasks;
67

@@ -52,6 +53,49 @@ private bool IsAvailable(MoneroLikeSummary summary)
5253
summary.WalletAvailable;
5354
}
5455

56+
public async Task CloseWallet(string cryptoCode)
57+
{
58+
if (!WalletRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var walletRpcClient))
59+
{
60+
throw new InvalidOperationException($"Wallet RPC client not found for {cryptoCode}");
61+
}
62+
63+
await walletRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, object>(
64+
"close_wallet", JsonRpcClient.NoRequestModel.Instance);
65+
}
66+
67+
public void DeleteWallet()
68+
{
69+
if (!_moneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue("XMR", out var configItem) ||
70+
string.IsNullOrEmpty(configItem.WalletDirectory))
71+
{
72+
return;
73+
}
74+
try
75+
{
76+
var walletFile = Path.Combine(configItem.WalletDirectory, "view_wallet");
77+
var keysFile = walletFile + ".keys";
78+
var passwordFile = Path.Combine(configItem.WalletDirectory, "password");
79+
80+
if (File.Exists(walletFile))
81+
{
82+
File.Delete(walletFile);
83+
}
84+
if (File.Exists(keysFile))
85+
{
86+
File.Delete(keysFile);
87+
}
88+
if (File.Exists(passwordFile))
89+
{
90+
File.Delete(passwordFile);
91+
}
92+
}
93+
catch
94+
{
95+
// ignored
96+
}
97+
}
98+
5599
public async Task<MoneroLikeSummary> UpdateSummary(string cryptoCode)
56100
{
57101
if (!DaemonRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var daemonRpcClient) ||

global.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"sdk": {
3+
"version": "8.0.416",
4+
"rollForward": "latestFeature"
5+
}
6+
}

submodules/btcpayserver

Submodule btcpayserver updated 545 files

0 commit comments

Comments
 (0)