Fix: Payments in mempool not detected

This commit is contained in:
nicolas.dorier
2025-01-10 19:27:29 +09:00
parent 73f9320112
commit 7e0183ad14
11 changed files with 90 additions and 53 deletions

2
.gitignore vendored
View File

@@ -3,5 +3,3 @@
.idea
Plugins/packed
.vs/
wallets/cashcow/
wallets/merchant/

View File

@@ -16,7 +16,6 @@ namespace BTCPayServer.Plugins.Monero.Configuration
public string WalletDirectory { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string CashCowWalletDirectory { get; set; }
public Uri CashCowWalletRpcUri { get; set; }
}
}

View File

@@ -101,7 +101,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
_MoneroRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary);
_MoneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue(cryptoCode,
out var configurationItem);
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
var accounts = accountsResponse?.SubaddressAccounts?.Select(account =>
new SelectListItem(
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
@@ -121,7 +120,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
return new MoneroLikePaymentMethodViewModel()
{
WalletFileFound = System.IO.File.Exists(fileAddress),
Enabled =
settings != null &&
!excludeFilters.Match(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)),
@@ -193,7 +191,11 @@ namespace BTCPayServer.Plugins.Monero.Controllers
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the view-only wallet keys file"]);
valid = false;
}
if (configurationItem.WalletDirectory == null)
{
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["This installation doesn't support wallet import (BTCPAY_XMR_WALLET_DAEMON_WALLETDIR is not set)"]);
valid = false;
}
if (valid)
{
if (_MoneroRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary))
@@ -288,6 +290,7 @@ namespace BTCPayServer.Plugins.Monero.Controllers
vm.AccountIndex = viewModel.AccountIndex;
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
vm.SupportWalletExport = configurationItem.WalletDirectory is not null;
return View("/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml", vm);
}
@@ -345,6 +348,7 @@ namespace BTCPayServer.Plugins.Monero.Controllers
public class MoneroLikePaymentMethodViewModel : IValidatableObject
{
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
public bool SupportWalletExport { get; set; }
public string CryptoCode { get; set; }
public string NewAccountLabel { get; set; }
public long AccountIndex { get; set; }

View File

@@ -128,17 +128,13 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
var walletDaemonWalletDirectory =
configuration.GetOrDefault<string>(
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_walletdir", null);
// Only for regtest
var walletCashCowDaemonWalletDirectory =
configuration.GetOrDefault<string>(
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_cashcow_wallet_daemon_walletdir", null);
var daemonUsername =
configuration.GetOrDefault<string>(
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_username", null);
var daemonPassword =
configuration.GetOrDefault<string>(
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_password", null);
if (daemonUri == null || walletDaemonUri == null || walletDaemonWalletDirectory == null)
if (daemonUri == null || walletDaemonUri == null)
{
var logger = serviceProvider.GetRequiredService<ILogger<MoneroPlugin>>();
var cryptoCode = moneroLikeSpecificBtcPayNetwork.CryptoCode.ToUpperInvariant();
@@ -150,10 +146,6 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
{
logger.LogWarning($"BTCPAY_{cryptoCode}_WALLET_DAEMON_URI is not configured");
}
if (walletDaemonWalletDirectory is null)
{
logger.LogWarning($"BTCPAY_{cryptoCode}_WALLET_DAEMON_WALLETDIR is not configured");
}
logger.LogWarning($"{cryptoCode} got disabled as it is not fully configured.");
}
else
@@ -165,7 +157,6 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
Password = daemonPassword,
InternalWalletRpcUri = walletDaemonUri,
WalletDirectory = walletDaemonWalletDirectory,
CashCowWalletDirectory = walletCashCowDaemonWalletDirectory,
CashCowWalletRpcUri = cashCowWalletDaemonUri,
});
}

View File

@@ -6,6 +6,6 @@ namespace BTCPayServer.Plugins.Monero.RPC.Models
{
[JsonProperty("txid")] public string TransactionId { get; set; }
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("account_index", DefaultValueHandling = DefaultValueHandling.Ignore)] public long? AccountIndex { get; set; }
}
}

View File

@@ -50,7 +50,7 @@ public class MoneroCheckoutCheatModeExtension : ICheckoutCheatModeExtension
public async Task<ICheckoutCheatModeExtension.MineBlockResult> MineBlock(ICheckoutCheatModeExtension.MineBlockContext mineBlockContext)
{
var cashcow = _rpcProvider.CashCowWalletRpcClients[_network.CryptoCode];
var deamon = _rpcProvider.WalletRpcClients[_network.CryptoCode];
var deamon = _rpcProvider.DaemonRpcClients[_network.CryptoCode];
var address = (await cashcow.SendCommandAsync<GetAddressRequest, GetAddressResponse>("get_address", new()
{
AccountIndex = 0

View File

@@ -89,6 +89,7 @@ namespace BTCPayServer.Plugins.Monero.Services
{
await OnNewBlock(moneroEvent.CryptoCode);
}
if (!string.IsNullOrEmpty(moneroEvent.TransactionHash))
{
await OnTransactionUpdated(moneroEvent.CryptoCode, moneroEvent.TransactionHash);
@@ -132,7 +133,8 @@ namespace BTCPayServer.Plugins.Monero.Services
var expandedInvoices = invoices.Select(entity => (Invoice: entity,
ExistingPayments: GetAllMoneroLikePayments(entity, cryptoCode),
Prompt: entity.GetPaymentPrompt(paymentId),
PaymentMethodDetails: handler.ParsePaymentPromptDetails(entity.GetPaymentPrompt(paymentId).Details)))
PaymentMethodDetails: handler.ParsePaymentPromptDetails(entity.GetPaymentPrompt(paymentId)
.Details)))
.Select(tuple => (
tuple.Invoice,
tuple.PaymentMethodDetails,
@@ -208,7 +210,8 @@ namespace BTCPayServer.Plugins.Monero.Services
return HandlePaymentData(cryptoCode, transfer.Address, transfer.Amount, transfer.SubaddrIndex.Major,
transfer.SubaddrIndex.Minor, transfer.Txid, transfer.Confirmations, transfer.Height, transfer.UnlockTime,invoice,
transfer.SubaddrIndex.Minor, transfer.Txid, transfer.Confirmations, transfer.Height,
transfer.UnlockTime, invoice,
updatedPaymentEntities);
}));
}
@@ -228,17 +231,16 @@ namespace BTCPayServer.Plugins.Monero.Services
private async Task OnNewBlock(string cryptoCode)
{
await UpdateAnyPendingMoneroLikePayment(cryptoCode);
_eventAggregator.Publish(new NewBlockEvent() { PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode) });
_eventAggregator.Publish(new NewBlockEvent()
{ PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode) });
}
private async Task OnTransactionUpdated(string cryptoCode, string transactionHash)
{
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
var transfer = await _moneroRpcProvider.WalletRpcClients[cryptoCode]
.SendCommandAsync<GetTransferByTransactionIdRequest, GetTransferByTransactionIdResponse>(
"get_transfer_by_txid",
new GetTransferByTransactionIdRequest() { TransactionId = transactionHash });
var transfer = await GetTransferByTxId(cryptoCode, transactionHash, this.CancellationToken);
if (transfer is null)
return;
var paymentsToUpdate = new List<(PaymentEntity Payment, InvoiceEntity invoice)>();
//group all destinations of the tx together and loop through the sets
@@ -259,7 +261,7 @@ namespace BTCPayServer.Plugins.Monero.Services
transfer.Transfer.Txid,
transfer.Transfer.Confirmations,
transfer.Transfer.Height
, transfer.Transfer.UnlockTime,invoice, paymentsToUpdate);
, transfer.Transfer.UnlockTime, invoice, paymentsToUpdate);
}
if (paymentsToUpdate.Any())
@@ -275,6 +277,49 @@ namespace BTCPayServer.Plugins.Monero.Services
}
}
private async Task<GetTransferByTransactionIdResponse> GetTransferByTxId(string cryptoCode,
string transactionHash, CancellationToken cancellationToken)
{
var accounts = await _moneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GetAccountsRequest, GetAccountsResponse>("get_accounts", new GetAccountsRequest(), cancellationToken);
var accountIndexes = accounts
.SubaddressAccounts
.Select(a => new long?(a.AccountIndex))
.ToList();
if (accountIndexes.Count is 0)
accountIndexes.Add(null);
var req = accountIndexes
.Select(i => GetTransferByTxId(cryptoCode, transactionHash, i))
.ToArray();
foreach (var task in req)
{
var result = await task;
if (result != null)
return result;
}
return null;
}
private async Task<GetTransferByTransactionIdResponse> GetTransferByTxId(string cryptoCode, string transactionHash, long? accountIndex)
{
try
{
var result = await _moneroRpcProvider.WalletRpcClients[cryptoCode]
.SendCommandAsync<GetTransferByTransactionIdRequest, GetTransferByTransactionIdResponse>(
"get_transfer_by_txid",
new GetTransferByTransactionIdRequest()
{
TransactionId = transactionHash,
AccountIndex = accountIndex
});
return result;
}
catch (JsonRpcClient.JsonRpcApiException e)
{
return null;
}
}
private async Task HandlePaymentData(string cryptoCode, string address, long totalAmount, long subaccountIndex,
long subaddressIndex,
string txId, long confirmations, long blockHeight, long locktime, InvoiceEntity invoice,
@@ -330,16 +375,17 @@ namespace BTCPayServer.Plugins.Monero.Services
=> ConfirmationsRequired(details, speedPolicy) <= details.ConfirmationCount;
public static long ConfirmationsRequired(MoneroLikePaymentData details, SpeedPolicy speedPolicy)
=> (details, speedPolicy) switch
{
(_, _) when details.ConfirmationCount < details.LockTime => details.LockTime - details.ConfirmationCount,
({ InvoiceSettledConfirmationThreshold: long v }, _) => v,
(_, SpeedPolicy.HighSpeed) => 0,
(_, SpeedPolicy.MediumSpeed) => 1,
(_, SpeedPolicy.LowMediumSpeed) => 2,
(_, SpeedPolicy.LowSpeed) => 6,
_ => 6,
};
=> (details, speedPolicy) switch
{
(_, _) when details.ConfirmationCount < details.LockTime =>
details.LockTime - details.ConfirmationCount,
({ InvoiceSettledConfirmationThreshold: long v }, _) => v,
(_, SpeedPolicy.HighSpeed) => 0,
(_, SpeedPolicy.MediumSpeed) => 1,
(_, SpeedPolicy.LowMediumSpeed) => 2,
(_, SpeedPolicy.LowSpeed) => 6,
_ => 6,
};
private async Task UpdateAnyPendingMoneroLikePayment(string cryptoCode)
@@ -358,4 +404,4 @@ namespace BTCPayServer.Plugins.Monero.Services
.Where(p => p.PaymentMethodId == PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode));
}
}
}
}

View File

@@ -20,14 +20,13 @@
<div class="card">
<ul class="list-group list-group-flush">
<li class="list-group-item">Node available: @Model.Summary.DaemonAvailable</li>
<li class="list-group-item">Wallet available: @Model.Summary.WalletAvailable (@(Model.WalletFileFound ? "Wallet file present" : "Wallet file not found"))</li>
<li class="list-group-item">Last updated: @Model.Summary.UpdatedAt</li>
<li class="list-group-item">Synced: @Model.Summary.Synced (@Model.Summary.CurrentHeight / @Model.Summary.TargetHeight)</li>
</ul>
</div>
}
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
@if (Model.SupportWalletExport && Model.Summary?.WalletHeight is null or 0)
{
<form method="post" asp-action="GetStoreMoneroLikePaymentMethod"
asp-route-storeId="@Context.GetRouteValue("storeId")"
@@ -63,7 +62,7 @@
class="mt-4" enctype="multipart/form-data">
<input type="hidden" asp-for="CryptoCode"/>
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
@if (Model.Summary?.WalletHeight is null or 0)
{
<input type="hidden" asp-for="AccountIndex"/>
}

View File

@@ -8,13 +8,13 @@ This plugin extends BTCPay Server to enable users to receive payments via Monero
Configure this plugin using the following environment variables:
| Environment variable | Description | Example |
| --- | --- | --- |
**BTCPAY_XMR_DAEMON_URI** | **Required**. The URI of the [monerod](https://github.com/monero-project/monero) RPC interface. | http://127.0.0.1:18081 |
**BTCPAY_XMR_DAEMON_USERNAME** | **Optional**. The username for authenticating with the daemon. | john |
**BTCPAY_XMR_DAEMON_PASSWORD** | **Optional**. The password for authenticating with the daemon. | secret |
**BTCPAY_XMR_WALLET_DAEMON_URI** | **Required**. The URI of the [monero-wallet-rpc](https://getmonero.dev/interacting/monero-wallet-rpc.html) RPC interface. | http://127.0.0.1:18082 |
**BTCPAY_XMR_WALLET_DAEMON_WALLETDIR** | **Required**. The directory where BTCPay Server saves wallet files uploaded via the UI ([See this blog post for more details](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server/#configure-the-bitcoin-wallet-of-choice)). | /home/cypherpunk/Monero/wallets/ |
| Environment variable | Description | Example |
| --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
**BTCPAY_XMR_DAEMON_URI** | **Required**. The URI of the [monerod](https://github.com/monero-project/monero) RPC interface. | http://127.0.0.1:18081 |
**BTCPAY_XMR_DAEMON_USERNAME** | **Optional**. The username for authenticating with the daemon. | john |
**BTCPAY_XMR_DAEMON_PASSWORD** | **Optional**. The password for authenticating with the daemon. | secret |
**BTCPAY_XMR_WALLET_DAEMON_URI** | **Required**. The URI of the [monero-wallet-rpc](https://getmonero.dev/interacting/monero-wallet-rpc.html) RPC interface. | http://127.0.0.1:18082 |
**BTCPAY_XMR_WALLET_DAEMON_WALLETDIR** | **Optional**. The directory where BTCPay Server saves wallet files uploaded via the UI ([See this blog post for more details](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server/#configure-the-bitcoin-wallet-of-choice)). | /home/cypherpunk/Monero/wallets/ |
BTCPay Server's Docker deployment simplifies the setup by automatically configuring these variables. For further details, refer to this [blog post](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server).
@@ -37,8 +37,6 @@ Then create the `appsettings.dev.json` file in `btcpayserver/BTCPayServer`, with
"XMR_DAEMON_URI": "http://127.0.0.1:18081",
"XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082",
"XMR_CASHCOW_WALLET_DAEMON_URI": "http://127.0.0.1:18092",
"XMR_WALLET_DAEMON_WALLETDIR": "C:\\Sources\\btcpayserver-monero-plugin\\wallets\\merchant",
"XMR_CASHCOW_WALLET_DAEMON_WALLETDIR": "C:\\Sources\\btcpayserver-monero-plugin\\wallets\\cashcow"
}
```

View File

@@ -86,7 +86,7 @@ services:
ports:
- "18082:18082"
volumes:
- "./wallets/merchant:/wallet"
- "tests_monero_merchant_wallet:/wallet"
depends_on:
- monerod
@@ -98,7 +98,7 @@ services:
ports:
- "18092:18092"
volumes:
- "./wallets/cashcow:/wallet"
- "tests_monero_cashcow_wallet:/wallet"
depends_on:
- monerod
@@ -114,6 +114,8 @@ services:
volumes:
bitcoin_datadir:
monero_data:
tests_monero_merchant_wallet:
tests_monero_cashcow_wallet:
networks:
default: