Give more information to the user if payjoin fails

This commit is contained in:
nicolas.dorier
2020-04-06 13:19:51 +09:00
parent 9af7edf8b8
commit d939baac84
2 changed files with 53 additions and 46 deletions

View File

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using BTCPayServer.ModelBinders; using BTCPayServer.ModelBinders;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
@@ -144,24 +145,14 @@ namespace BTCPayServer.Controllers
} }
} }
private async Task<PSBT> TryGetPayjoinProposedTX(string bpu, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken) private async Task<PSBT> GetPayjoinProposedTX(string bpu, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken)
{ {
if (!string.IsNullOrEmpty(bpu) && Uri.TryCreate(bpu, UriKind.Absolute, out var endpoint)) if (string.IsNullOrEmpty(bpu) || !Uri.TryCreate(bpu, UriKind.Absolute, out var endpoint))
{ throw new InvalidOperationException("No payjoin url available");
var cloned = psbt.Clone(); var cloned = psbt.Clone();
cloned = cloned.Finalize(); cloned = cloned.Finalize();
await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1.0), cloned.ExtractTransaction(), btcPayNetwork); await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1.0), cloned.ExtractTransaction(), btcPayNetwork);
try return await _payjoinClient.RequestPayjoin(endpoint, derivationSchemeSettings, cloned, cancellationToken);
{
return await _payjoinClient.RequestPayjoin(endpoint, derivationSchemeSettings, cloned, cancellationToken);
}
catch (Exception)
{
return null;
}
}
return null;
} }
[HttpGet] [HttpGet]
@@ -321,22 +312,11 @@ namespace BTCPayServer.Controllers
switch (command) switch (command)
{ {
case "payjoin": case "payjoin":
var proposedPayjoin =await string error = null;
TryGetPayjoinProposedTX(vm.PayJoinEndpointUrl, psbt, derivationSchemeSettings, network, cancellationToken); try
if (proposedPayjoin == null)
{ {
//we possibly exposed the tx to the receiver, so we need to broadcast straight away var proposedPayjoin = await GetPayjoinProposedTX(vm.PayJoinEndpointUrl, psbt,
TempData.SetStatusMessageModel(new StatusMessageModel() derivationSchemeSettings, network, cancellationToken);
{
Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false,
Html = $"The payjoin transaction could not be created. The original transaction was broadcast instead."
});
return await WalletPSBTReady(walletId, vm, "broadcast");
}
else
{
try try
{ {
var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork); var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork);
@@ -345,20 +325,53 @@ namespace BTCPayServer.Controllers
RootedKeyPath.Parse(vm.SigningKeyPath)); RootedKeyPath.Parse(vm.SigningKeyPath));
vm.PSBT = proposedPayjoin.ToBase64(); vm.PSBT = proposedPayjoin.ToBase64();
vm.OriginalPSBT = psbt.ToBase64(); vm.OriginalPSBT = psbt.ToBase64();
proposedPayjoin.Finalize();
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
AllowDismiss = false,
Html = $"The payjoin transaction has been successfully broadcasted ({proposedPayjoin.ExtractTransaction().GetHash()})"
});
return await WalletPSBTReady(walletId, vm, "broadcast"); return await WalletPSBTReady(walletId, vm, "broadcast");
} }
catch (Exception) catch (Exception)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Severity = StatusMessageModel.StatusSeverity.Info, Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false, AllowDismiss = false,
Html = Html =
$"This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver. The amount being sent may appear higher but is in fact the same" $"This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver.<br/>" +
$"The amount being sent may appear higher but is in fact almost same.<br/><br/>" +
$"If you cancel refuse to sign this transaction, the payment will proceed without payjoin"
}); });
return ViewVault(walletId, proposedPayjoin, vm.PayJoinEndpointUrl, psbt); return ViewVault(walletId, proposedPayjoin, vm.PayJoinEndpointUrl, psbt);
} }
} }
catch (PayjoinReceiverException ex)
{
error = $"The payjoin receiver could not complete the payjoin: {ex.Message}";
}
catch (PayjoinSenderException ex)
{
error = $"We rejected the receiver's payjoin proposal: {ex.Message}";
}
catch (Exception ex)
{
error = $"Unexpected payjoin error: {ex.Message}";
}
//we possibly exposed the tx to the receiver, so we need to broadcast straight away
psbt.Finalize();
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false,
Html = $"The payjoin transaction could not be created.<br/>" +
$"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
$"{error}"
});
return await WalletPSBTReady(walletId, vm, "broadcast");
case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors): case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
vm.SetErrors(errors); vm.SetErrors(errors);
return View(nameof(WalletPSBTReady),vm); return View(nameof(WalletPSBTReady),vm);
@@ -376,7 +389,7 @@ namespace BTCPayServer.Controllers
{ {
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false, AllowDismiss = false,
Html = $"The payjoin transaction could not be broadcast.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast." Html = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast."
}); });
vm.PSBT = vm.OriginalPSBT; vm.PSBT = vm.OriginalPSBT;
vm.OriginalPSBT = null; vm.OriginalPSBT = null;
@@ -392,6 +405,11 @@ namespace BTCPayServer.Controllers
vm.GlobalError = "Error while broadcasting: " + ex.Message; vm.GlobalError = "Error while broadcasting: " + ex.Message;
return View(nameof(WalletPSBTReady),vm); return View(nameof(WalletPSBTReady),vm);
} }
if (!TempData.HasStatusMessage())
{
TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})";
}
return RedirectToWalletTransaction(walletId, transaction); return RedirectToWalletTransaction(walletId, transaction);
} }
case "analyze-psbt": case "analyze-psbt":

View File

@@ -869,17 +869,6 @@ namespace BTCPayServer.Controllers
var wallet = _walletProvider.GetWallet(network); var wallet = _walletProvider.GetWallet(network);
var derivationSettings = GetDerivationSchemeSettings(walletId); var derivationSettings = GetDerivationSchemeSettings(walletId);
wallet.InvalidateCache(derivationSettings.AccountDerivation); wallet.InvalidateCache(derivationSettings.AccountDerivation);
if (TempData.GetStatusMessageModel() == null)
{
TempData[WellKnownTempData.SuccessMessage] =
$"Transaction broadcasted successfully ({transaction.GetHash()})";
}
else
{
var statusMessageModel = TempData.GetStatusMessageModel();
statusMessageModel.Message += $" ({transaction.GetHash()})";
TempData.SetStatusMessageModel(statusMessageModel);
}
} }
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() }); return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
} }