Auto load wallet on start up if available and deprecate password
Co-authored-by: Deverick <5827364+deverickapollo@users.noreply.github.com>
This commit is contained in:
parent
50d3a23c7b
commit
f30d55072e
25 changed files with 320 additions and 95 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
|
@ -6,4 +6,6 @@
|
|||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpg binary
|
||||
*.keys binary
|
||||
wallet binary
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.52.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
|
|
@ -32,6 +33,12 @@
|
|||
<ProjectReference Include="..\Plugins\Monero\BTCPayServer.Plugins.Monero.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ProjectReference>
|
||||
<Properties>StaticWebAssetsEnabled=false</Properties>
|
||||
|
|
|
|||
|
|
@ -5,20 +5,26 @@ using BTCPayServer.Tests;
|
|||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Mono.Unix.Native;
|
||||
|
||||
using Npgsql;
|
||||
|
||||
using static Mono.Unix.Native.Syscall;
|
||||
|
||||
namespace BTCPayServer.Plugins.IntegrationTests.Monero;
|
||||
|
||||
public static class IntegrationTestUtils
|
||||
{
|
||||
|
||||
private static readonly ILogger Logger = LoggerFactory
|
||||
.Create(builder => builder.AddConsole())
|
||||
.CreateLogger("IntegrationTestUtils");
|
||||
|
||||
private static readonly string ContainerWalletDir =
|
||||
Environment.GetEnvironmentVariable("BTCPAY_XMR_WALLET_DAEMON_WALLETDIR") ?? "/wallet";
|
||||
|
||||
public static async Task CleanUpAsync(PlaywrightTester playwrightTester)
|
||||
{
|
||||
MoneroRPCProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService<MoneroRPCProvider>();
|
||||
MoneroRpcProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService<MoneroRpcProvider>();
|
||||
if (moneroRpcProvider.IsAvailable("XMR"))
|
||||
{
|
||||
await moneroRpcProvider.CloseWallet("XMR");
|
||||
|
|
@ -26,7 +32,7 @@ public static class IntegrationTestUtils
|
|||
|
||||
if (playwrightTester.Server.PayTester.InContainer)
|
||||
{
|
||||
moneroRpcProvider.DeleteWallet();
|
||||
DeleteWalletInContainer();
|
||||
await DropDatabaseAsync(
|
||||
"btcpayserver",
|
||||
"Host=postgres;Port=5432;Username=postgres;Database=postgres");
|
||||
|
|
@ -62,6 +68,101 @@ public static class IntegrationTestUtils
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task CopyWalletFilesToMoneroRpcDirAsync(PlaywrightTester playwrightTester, String walletDir)
|
||||
{
|
||||
Logger.LogInformation("Starting to copy wallet files");
|
||||
if (playwrightTester.Server.PayTester.InContainer)
|
||||
{
|
||||
CopyWalletFilesInContainer(walletDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CopyWalletFilesToLocalDocker(walletDir);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyWalletFilesInContainer(String walletDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
CopyWalletFile("wallet", walletDir);
|
||||
CopyWalletFile("wallet.keys", walletDir);
|
||||
CopyWalletFile("password", walletDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to copy wallet files to the Monero directory.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyWalletFile(string name, string walletDir)
|
||||
{
|
||||
var resourceWalletDir = Path.Combine(AppContext.BaseDirectory, "Resources", walletDir);
|
||||
|
||||
var src = Path.Combine(resourceWalletDir, name);
|
||||
var dst = Path.Combine(ContainerWalletDir, name);
|
||||
|
||||
if (!File.Exists(src))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
File.Copy(src, dst, overwrite: true);
|
||||
|
||||
// monero ownership
|
||||
if (chown(dst, 980, 980) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogError("chown failed for {File}. errno={Errno}", dst, Stdlib.GetLastError());
|
||||
}
|
||||
|
||||
|
||||
private static async Task CopyWalletFilesToLocalDocker(String walletDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullWalletDir = Path.Combine(AppContext.BaseDirectory, "Resources", walletDir);
|
||||
|
||||
await RunProcessAsync("docker",
|
||||
$"cp \"{Path.Combine(fullWalletDir, "wallet")}\" xmr_wallet:/wallet/wallet");
|
||||
|
||||
await RunProcessAsync("docker",
|
||||
$"cp \"{Path.Combine(fullWalletDir, "wallet.keys")}\" xmr_wallet:/wallet/wallet.keys");
|
||||
|
||||
await RunProcessAsync("docker",
|
||||
$"cp \"{Path.Combine(fullWalletDir, "password")}\" xmr_wallet:/wallet/password");
|
||||
|
||||
await RunProcessAsync("docker",
|
||||
"exec xmr_wallet chown monero:monero /wallet/wallet /wallet/wallet.keys /wallet/password");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to copy wallet files to the Monero directory.");
|
||||
}
|
||||
}
|
||||
|
||||
static async Task RunProcessAsync(string fileName, string args)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi)!;
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new Exception(await process.StandardError.ReadToEndAsync());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RemoveWalletFromLocalDocker()
|
||||
{
|
||||
try
|
||||
|
|
@ -101,4 +202,33 @@ public static class IntegrationTestUtils
|
|||
Logger.LogError(ex, "Wallet cleanup via Docker failed.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteWalletInContainer()
|
||||
{
|
||||
try
|
||||
{
|
||||
var walletFile = Path.Combine(ContainerWalletDir, "wallet");
|
||||
var keysFile = walletFile + ".keys";
|
||||
var passwordFile = Path.Combine(ContainerWalletDir, "password");
|
||||
|
||||
if (File.Exists(walletFile))
|
||||
{
|
||||
File.Delete(walletFile);
|
||||
}
|
||||
|
||||
if (File.Exists(keysFile))
|
||||
{
|
||||
File.Delete(keysFile);
|
||||
}
|
||||
|
||||
if (File.Exists(passwordFile))
|
||||
{
|
||||
File.Delete(passwordFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to delete wallet files in directory {Dir}", ContainerWalletDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,6 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
|
|||
await s.Page.Locator("input#PrivateViewKey")
|
||||
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
|
||||
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
|
||||
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
|
||||
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
|
||||
await s.Page.CheckAsync("#Enabled");
|
||||
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
|
||||
|
|
@ -119,7 +118,6 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
|
|||
await s.Page.Locator("input#PrivateViewKey")
|
||||
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
|
||||
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
|
||||
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
|
||||
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
|
||||
var errorText = await s.Page
|
||||
.Locator("div.validation-summary-errors li")
|
||||
|
|
@ -136,12 +134,12 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
|
|||
await using var s = CreatePlaywrightTester();
|
||||
await s.StartAsync();
|
||||
|
||||
MoneroRPCProvider moneroRpcProvider = s.Server.PayTester.GetService<MoneroRPCProvider>();
|
||||
MoneroRpcProvider moneroRpcProvider = s.Server.PayTester.GetService<MoneroRpcProvider>();
|
||||
await moneroRpcProvider.WalletRpcClients["XMR"].SendCommandAsync<GenerateFromKeysRequest, GenerateFromKeysResponse>("generate_from_keys", new GenerateFromKeysRequest
|
||||
{
|
||||
PrimaryAddress = "43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L",
|
||||
PrivateViewKey = "1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e",
|
||||
WalletFileName = "view_wallet",
|
||||
WalletFileName = "wallet",
|
||||
Password = ""
|
||||
});
|
||||
await moneroRpcProvider.CloseWallet("XMR");
|
||||
|
|
@ -154,13 +152,49 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
|
|||
await s.Page.Locator("input#PrivateViewKey")
|
||||
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
|
||||
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
|
||||
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
|
||||
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
|
||||
var errorText = await s.Page
|
||||
.Locator("div.validation-summary-errors li")
|
||||
.InnerTextAsync();
|
||||
|
||||
Assert.Equal("Could not generate view wallet from keys: Wallet already exists.", errorText);
|
||||
await IntegrationTestUtils.CleanUpAsync(s);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldLoadViewWalletOnStartUpIfExists()
|
||||
{
|
||||
await using var s = CreatePlaywrightTester();
|
||||
await IntegrationTestUtils.CopyWalletFilesToMoneroRpcDirAsync(s, "wallet");
|
||||
await s.StartAsync();
|
||||
await s.RegisterNewUser(true);
|
||||
await s.CreateNewStore();
|
||||
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
|
||||
|
||||
var walletRpcIsAvailable = await s.Page
|
||||
.Locator("li.list-group-item:text('Wallet RPC available: True')")
|
||||
.InnerTextAsync();
|
||||
|
||||
Assert.Contains("Wallet RPC available: True", walletRpcIsAvailable);
|
||||
|
||||
await IntegrationTestUtils.CleanUpAsync(s);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldLoadViewWalletWithPasswordOnStartUpIfExists()
|
||||
{
|
||||
await using var s = CreatePlaywrightTester();
|
||||
await IntegrationTestUtils.CopyWalletFilesToMoneroRpcDirAsync(s, "wallet_password");
|
||||
await s.StartAsync();
|
||||
await s.RegisterNewUser(true);
|
||||
await s.CreateNewStore();
|
||||
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
|
||||
|
||||
var walletRpcIsAvailable = await s.Page
|
||||
.Locator("li.list-group-item:text('Wallet RPC available: True')")
|
||||
.InnerTextAsync();
|
||||
|
||||
Assert.Contains("Wallet RPC available: True", walletRpcIsAvailable);
|
||||
|
||||
await IntegrationTestUtils.CleanUpAsync(s);
|
||||
}
|
||||
|
|
|
|||
BIN
BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet
Normal file
BIN
BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
pass123
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017-2025 btcpayserver
|
||||
Copyright (c) 2017-2026 btcpayserver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"Identifier": "BTCPayServer.Plugins.Monero",
|
||||
"Name": "BTCPay Server: Monero support plugin",
|
||||
"Version": "1.0.1.0",
|
||||
"Version": "1.1.0",
|
||||
"Description": "This plugin extends BTCPay Server to enable users to receive payments via Monero.",
|
||||
"SystemPlugin": false,
|
||||
"Dependencies": [
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
{
|
||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
private readonly MoneroRPCProvider _MoneroRpcProvider;
|
||||
private readonly MoneroRpcProvider _MoneroRpcProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private IStringLocalizer StringLocalizer { get; }
|
||||
|
||||
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
|
||||
StoreRepository storeRepository, MoneroRpcProvider moneroRpcProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IStringLocalizer stringLocalizer)
|
||||
{
|
||||
|
|
@ -219,9 +219,8 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
{
|
||||
PrimaryAddress = viewModel.PrimaryAddress,
|
||||
PrivateViewKey = viewModel.PrivateViewKey,
|
||||
WalletFileName = "view_wallet",
|
||||
RestoreHeight = viewModel.RestoreHeight,
|
||||
Password = viewModel.WalletPassword
|
||||
WalletFileName = "wallet",
|
||||
RestoreHeight = viewModel.RestoreHeight
|
||||
});
|
||||
if (response?.Error != null)
|
||||
{
|
||||
|
|
@ -286,7 +285,7 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
|
||||
public class MoneroLikePaymentMethodViewModel : IValidatableObject
|
||||
{
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public MoneroRpcProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string NewAccountLabel { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
|
|
@ -300,8 +299,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
public string PrivateViewKey { get; set; }
|
||||
[Display(Name = "Restore Height")]
|
||||
public int RestoreHeight { get; set; }
|
||||
[Display(Name = "Wallet Password")]
|
||||
public string WalletPassword { get; set; }
|
||||
[Display(Name = "Consider the invoice settled when the payment transaction …")]
|
||||
public MoneroLikeSettlementThresholdChoice SettlementConfirmationThresholdChoice { get; set; }
|
||||
[Display(Name = "Required Confirmations"), Range(0, 100)]
|
||||
|
|
|
|||
|
|
@ -75,9 +75,10 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
|
|||
PreAuthenticate = true
|
||||
};
|
||||
});
|
||||
services.AddSingleton<MoneroRPCProvider>();
|
||||
services.AddSingleton<MoneroRpcProvider>();
|
||||
services.AddHostedService<MoneroLikeSummaryUpdaterHostedService>();
|
||||
services.AddHostedService<MoneroListener>();
|
||||
services.AddHostedService<MoneroLoadUpService>();
|
||||
services.AddSingleton(provider =>
|
||||
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(MoneroLikePaymentMethodHandler), network));
|
||||
services.AddSingleton(provider =>
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ namespace BTCPayServer.Plugins.Monero.Payments
|
|||
private readonly MoneroLikeSpecificBtcPayNetwork _network;
|
||||
public MoneroLikeSpecificBtcPayNetwork Network => _network;
|
||||
public JsonSerializer Serializer { get; }
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
private readonly MoneroRpcProvider _moneroRpcProvider;
|
||||
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public MoneroLikePaymentMethodHandler(MoneroLikeSpecificBtcPayNetwork network, MoneroRPCProvider moneroRpcProvider)
|
||||
public MoneroLikePaymentMethodHandler(MoneroLikeSpecificBtcPayNetwork network, MoneroRpcProvider moneroRpcProvider)
|
||||
{
|
||||
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
_network = network;
|
||||
|
|
|
|||
10
Plugins/Monero/RPC/Models/OpenWalletRequest.cs
Normal file
10
Plugins/Monero/RPC/Models/OpenWalletRequest.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models
|
||||
{
|
||||
public class OpenWalletRequest
|
||||
{
|
||||
[JsonProperty("filename")] public string Filename { get; set; }
|
||||
[JsonProperty("password")] public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
12
Plugins/Monero/RPC/Models/OpenWalletResponse.cs
Normal file
12
Plugins/Monero/RPC/Models/OpenWalletResponse.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models
|
||||
{
|
||||
public class OpenWalletResponse
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("jsonrpc")] public string Jsonrpc { get; set; }
|
||||
[JsonProperty("result")] public object Result { get; set; }
|
||||
[JsonProperty("error")] public ErrorResponse Error { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,13 +12,13 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
{
|
||||
public class MoneroLikeSummaryUpdaterHostedService : IHostedService
|
||||
{
|
||||
private readonly MoneroRPCProvider _MoneroRpcProvider;
|
||||
private readonly MoneroRpcProvider _MoneroRpcProvider;
|
||||
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
|
||||
|
||||
public Logs Logs { get; }
|
||||
|
||||
private CancellationTokenSource _Cts;
|
||||
public MoneroLikeSummaryUpdaterHostedService(MoneroRPCProvider moneroRpcProvider, MoneroLikeConfiguration moneroLikeConfiguration, Logs logs)
|
||||
public MoneroLikeSummaryUpdaterHostedService(MoneroRpcProvider moneroRpcProvider, MoneroLikeConfiguration moneroLikeConfiguration, Logs logs)
|
||||
{
|
||||
_MoneroRpcProvider = moneroRpcProvider;
|
||||
_moneroLikeConfiguration = moneroLikeConfiguration;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
private readonly MoneroRpcProvider _moneroRpcProvider;
|
||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly ILogger<MoneroListener> _logger;
|
||||
|
|
@ -39,7 +39,7 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
|
||||
public MoneroListener(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
MoneroRPCProvider moneroRpcProvider,
|
||||
MoneroRpcProvider moneroRpcProvider,
|
||||
MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
ILogger<MoneroListener> logger,
|
||||
|
|
@ -62,12 +62,12 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
{
|
||||
base.SubscribeToEvents();
|
||||
Subscribe<MoneroEvent>();
|
||||
Subscribe<MoneroRPCProvider.MoneroDaemonStateChange>();
|
||||
Subscribe<MoneroRpcProvider.MoneroDaemonStateChange>();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is MoneroRPCProvider.MoneroDaemonStateChange stateChange)
|
||||
if (evt is MoneroRpcProvider.MoneroDaemonStateChange stateChange)
|
||||
{
|
||||
if (_moneroRpcProvider.IsAvailable(stateChange.CryptoCode))
|
||||
{
|
||||
|
|
|
|||
78
Plugins/Monero/Services/MoneroLoadUpService.cs
Normal file
78
Plugins/Monero/Services/MoneroLoadUpService.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.Services;
|
||||
|
||||
public class MoneroLoadUpService : IHostedService
|
||||
{
|
||||
private const string CryptoCode = "XMR";
|
||||
private readonly ILogger<MoneroLoadUpService> _logger;
|
||||
private readonly MoneroRpcProvider _moneroRpcProvider;
|
||||
|
||||
public MoneroLoadUpService(ILogger<MoneroLoadUpService> logger, MoneroRpcProvider moneroRpcProvider)
|
||||
{
|
||||
_moneroRpcProvider = moneroRpcProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Obsolete("Remove optional password parameter")]
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Attempt to load existing wallet");
|
||||
|
||||
string walletDir = _moneroRpcProvider.GetWalletDirectory(CryptoCode);
|
||||
if (!string.IsNullOrEmpty(walletDir))
|
||||
{
|
||||
string password = await TryToGetPassword(walletDir, cancellationToken);
|
||||
|
||||
await _moneroRpcProvider.WalletRpcClients[CryptoCode]
|
||||
.SendCommandAsync<OpenWalletRequest, OpenWalletResponse>("open_wallet",
|
||||
new OpenWalletRequest { Filename = "wallet", Password = password }, cancellationToken);
|
||||
|
||||
await _moneroRpcProvider.UpdateSummary(CryptoCode);
|
||||
_logger.LogInformation("Existing wallet successfully loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No wallet directory configured, skipping wallet migration");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load {CryptoCode} wallet. Error Message: {ErrorMessage}", CryptoCode,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Password is obsolete due to the inability to fully separate the password file from the wallet file.")]
|
||||
private async Task<string> TryToGetPassword(string walletDir, CancellationToken cancellationToken)
|
||||
{
|
||||
string password = "";
|
||||
string passwordFile = Path.Combine(walletDir, "password");
|
||||
if (File.Exists(passwordFile))
|
||||
{
|
||||
password = await File.ReadAllTextAsync(passwordFile, cancellationToken);
|
||||
password = password.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No password file found - ignoring");
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -9,32 +8,25 @@ using BTCPayServer.Plugins.Monero.Configuration;
|
|||
using BTCPayServer.Plugins.Monero.RPC;
|
||||
using BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.Services
|
||||
{
|
||||
public class MoneroRPCProvider
|
||||
public class MoneroRpcProvider
|
||||
{
|
||||
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
public ImmutableDictionary<string, JsonRpcClient> DaemonRpcClients;
|
||||
public ImmutableDictionary<string, JsonRpcClient> WalletRpcClients;
|
||||
private readonly ILogger<MoneroRPCProvider> _logger;
|
||||
|
||||
private readonly ConcurrentDictionary<string, MoneroLikeSummary> _summaries = new();
|
||||
public ConcurrentDictionary<string, MoneroLikeSummary> Summaries { get; } = new();
|
||||
|
||||
public ConcurrentDictionary<string, MoneroLikeSummary> Summaries => _summaries;
|
||||
|
||||
public MoneroRPCProvider(MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
public MoneroRpcProvider(MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
EventAggregator eventAggregator,
|
||||
ILogger<MoneroRPCProvider> logger,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_moneroLikeConfiguration = moneroLikeConfiguration;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
DaemonRpcClients =
|
||||
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.DaemonRpcUri, pair.Value.Username, pair.Value.Password,
|
||||
|
|
@ -49,7 +41,7 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
return _summaries.ContainsKey(cryptoCode) && IsAvailable(_summaries[cryptoCode]);
|
||||
return Summaries.ContainsKey(cryptoCode) && IsAvailable(Summaries[cryptoCode]);
|
||||
}
|
||||
|
||||
private bool IsAvailable(MoneroLikeSummary summary)
|
||||
|
|
@ -69,43 +61,12 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
"close_wallet", JsonRpcClient.NoRequestModel.Instance);
|
||||
}
|
||||
|
||||
public void DeleteWallet()
|
||||
public string GetWalletDirectory(string cryptoCode)
|
||||
{
|
||||
if (!_moneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue("XMR", out var configItem))
|
||||
{
|
||||
_logger.LogWarning("DeleteWallet: No XMR configuration found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(configItem.WalletDirectory))
|
||||
{
|
||||
_logger.LogWarning("DeleteWallet: WalletDirectory is null or empty for XMR configuration.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var walletFile = Path.Combine(configItem.WalletDirectory, "view_wallet");
|
||||
var keysFile = walletFile + ".keys";
|
||||
var passwordFile = Path.Combine(configItem.WalletDirectory, "password");
|
||||
|
||||
if (File.Exists(walletFile))
|
||||
{
|
||||
File.Delete(walletFile);
|
||||
}
|
||||
if (File.Exists(keysFile))
|
||||
{
|
||||
File.Delete(keysFile);
|
||||
}
|
||||
if (File.Exists(passwordFile))
|
||||
{
|
||||
File.Delete(passwordFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete wallet files in directory {Dir}",
|
||||
configItem.WalletDirectory);
|
||||
}
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
return !_moneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue(cryptoCode, out var configItem)
|
||||
? null
|
||||
: configItem.WalletDirectory;
|
||||
}
|
||||
|
||||
public async Task<MoneroLikeSummary> UpdateSummary(string cryptoCode)
|
||||
|
|
@ -146,9 +107,9 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
summary.WalletAvailable = false;
|
||||
}
|
||||
|
||||
var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
|
||||
var changed = !Summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
|
||||
|
||||
_summaries.AddOrReplace(cryptoCode, summary);
|
||||
Summaries.AddOrReplace(cryptoCode, summary);
|
||||
if (changed)
|
||||
{
|
||||
_eventAggregator.Publish(new MoneroDaemonStateChange() { Summary = summary, CryptoCode = cryptoCode });
|
||||
|
|
@ -9,9 +9,9 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
{
|
||||
public class MoneroSyncSummaryProvider : ISyncSummaryProvider
|
||||
{
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
private readonly MoneroRpcProvider _moneroRpcProvider;
|
||||
|
||||
public MoneroSyncSummaryProvider(MoneroRPCProvider moneroRpcProvider)
|
||||
public MoneroSyncSummaryProvider(MoneroRpcProvider moneroRpcProvider)
|
||||
{
|
||||
_moneroRpcProvider = moneroRpcProvider;
|
||||
}
|
||||
|
|
@ -42,6 +42,6 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
}
|
||||
}
|
||||
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public MoneroRpcProvider.MoneroLikeSummary Summary { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -52,11 +52,6 @@
|
|||
<input type="number" class="form-control" asp-for="RestoreHeight" required>
|
||||
<span asp-validation-for="RestoreHeight" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletPassword" class="form-label">Wallet Password</label>
|
||||
<input type="password" class="form-control" asp-for="WalletPassword" required>
|
||||
<span asp-validation-for="WalletPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<button name="command" value="set-wallet-details" class="btn btn-secondary" type="submit">Set Wallet Details</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
@using BTCPayServer
|
||||
@using BTCPayServer.Data
|
||||
@using BTCPayServer.Plugins.Monero.Services
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@inject MoneroRPCProvider MoneroRpcProvider
|
||||
@inject MoneroRpcProvider MoneroRpcProvider
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && MoneroRpcProvider.Summaries.Any())
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
We currently support the following versions of the Monero plugin for BTCPayServer:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | ✅ Yes |
|
||||
| 1.x | ❌ No |
|
||||
| Version | Supported |
|
||||
|---------|-------------|
|
||||
| 1.1.x | ✅ Yes |
|
||||
| 1.0.x | ❌ No |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 335c906b811b8f131a5973ef3e9cdcac99017654
|
||||
Subproject commit 3f52aa7fd9faf8bb99343572b3dae305037db2ba
|
||||
Loading…
Add table
Reference in a new issue