Remove httpCode from error message

This commit is contained in:
nicolas.dorier 2020-05-09 19:29:05 +09:00
parent 137c3ef2ce
commit da588380ed
No known key found for this signature in database
GPG Key ID: 6618763EF09186FE
2 changed files with 86 additions and 42 deletions

View File

@ -120,7 +120,7 @@ namespace BTCPayServer.Payments.PayJoin
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
if (network == null)
{
return BadRequest(CreatePayjoinError(400, "invalid-network", "Incorrect network"));
return BadRequest(CreatePayjoinError("invalid-network", "Incorrect network"));
}
var explorer = _explorerClientProvider.GetExplorerClient(network);
@ -128,12 +128,12 @@ namespace BTCPayServer.Payments.PayJoin
{
if (length > 1_000_000)
return this.StatusCode(413,
CreatePayjoinError(413, "payload-too-large", "The transaction is too big to be processed"));
CreatePayjoinError("payload-too-large", "The transaction is too big to be processed"));
}
else
{
return StatusCode(411,
CreatePayjoinError(411, "missing-content-length",
CreatePayjoinError("missing-content-length",
"The http header Content-Length should be filled"));
}
@ -150,7 +150,7 @@ namespace BTCPayServer.Payments.PayJoin
{
psbtFormat = false;
if (!Transaction.TryParse(rawBody, network.NBitcoinNetwork, out var tx))
return BadRequest(CreatePayjoinError(400, "invalid-format", "invalid transaction or psbt"));
return BadRequest(CreatePayjoinError("invalid-format", "invalid transaction or psbt"));
originalTx = tx;
psbt = PSBT.FromTransaction(tx, network.NBitcoinNetwork);
psbt = (await explorer.UpdatePSBTAsync(new UpdatePSBTRequest() {PSBT = psbt})).PSBT;
@ -163,7 +163,7 @@ namespace BTCPayServer.Payments.PayJoin
else
{
if (!psbt.IsAllFinalized())
return BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT should be finalized"));
return BadRequest(CreatePayjoinError("psbt-not-finalized", "The PSBT should be finalized"));
originalTx = psbt.ExtractTransaction();
}
@ -174,14 +174,14 @@ namespace BTCPayServer.Payments.PayJoin
var sendersInputType = psbt.GetInputsScriptPubKeyType();
if (sendersInputType is null)
return BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support segwit inputs (of the same type)"));
return BadRequest(CreatePayjoinError("unsupported-inputs", "Payjoin only support segwit inputs (of the same type)"));
if (psbt.CheckSanity() is var errors && errors.Count != 0)
{
return BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})"));
return BadRequest(CreatePayjoinError("insane-psbt", $"This PSBT is insane ({errors[0]})"));
}
if (!psbt.TryGetEstimatedFeeRate(out originalFeeRate))
{
return BadRequest(CreatePayjoinError(400, "need-utxo-information",
return BadRequest(CreatePayjoinError("need-utxo-information",
"You need to provide Witness UTXO information to the PSBT."));
}
@ -189,26 +189,26 @@ namespace BTCPayServer.Payments.PayJoin
// to leak global xpubs
if (psbt.GlobalXPubs.Any())
{
return BadRequest(CreatePayjoinError(400, "leaking-data",
return BadRequest(CreatePayjoinError("leaking-data",
"GlobalXPubs should not be included in the PSBT"));
}
if (psbt.Outputs.Any(o => o.HDKeyPaths.Count != 0) || psbt.Inputs.Any(o => o.HDKeyPaths.Count != 0))
{
return BadRequest(CreatePayjoinError(400, "leaking-data",
return BadRequest(CreatePayjoinError("leaking-data",
"Keypath information should not be included in the PSBT"));
}
if (psbt.Inputs.Any(o => !o.IsFinalized()))
{
return BadRequest(CreatePayjoinError(400, "psbt-not-finalized", "The PSBT Should be finalized"));
return BadRequest(CreatePayjoinError("psbt-not-finalized", "The PSBT Should be finalized"));
}
////////////
var mempool = await explorer.BroadcastAsync(originalTx, true);
if (!mempool.Success)
{
return BadRequest(CreatePayjoinError(400, "invalid-transaction",
return BadRequest(CreatePayjoinError("invalid-transaction",
$"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"));
}
@ -240,12 +240,12 @@ namespace BTCPayServer.Payments.PayJoin
if (!PayjoinClient.SupportedFormats.Contains(receiverInputsType))
{
//this should never happen, unless the store owner changed the wallet mid way through an invoice
return StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"));
return StatusCode(500, CreatePayjoinError("unavailable", $"This service is unavailable for now"));
}
if (sendersInputType != receiverInputsType)
{
return StatusCode(503,
CreatePayjoinError(503, "out-of-utxos",
CreatePayjoinError("out-of-utxos",
"We do not have any UTXO available for making a payjoin with the sender's inputs type"));
}
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
@ -255,7 +255,7 @@ namespace BTCPayServer.Payments.PayJoin
continue;
if (invoice.GetAllBitcoinPaymentData().Any())
{
return UnprocessableEntity(CreatePayjoinError(422, "already-paid",
return UnprocessableEntity(CreatePayjoinError("already-paid",
$"The invoice this PSBT is paying has already been partially or completely paid"));
}
@ -268,7 +268,7 @@ namespace BTCPayServer.Payments.PayJoin
if (!await _payJoinRepository.TryLockInputs(originalTx.Inputs.Select(i => i.PrevOut).ToArray()))
{
return BadRequest(CreatePayjoinError(400, "inputs-already-used",
return BadRequest(CreatePayjoinError("inputs-already-used",
"Some of those inputs have already been used to make payjoin transaction"));
}
@ -293,13 +293,13 @@ namespace BTCPayServer.Payments.PayJoin
if (!paidSomething)
{
return BadRequest(CreatePayjoinError(400, "invoice-not-found",
return BadRequest(CreatePayjoinError("invoice-not-found",
"This transaction does not pay any invoice with payjoin"));
}
if (due is null || due > Money.Zero)
{
return BadRequest(CreatePayjoinError(400, "invoice-not-fully-paid",
return BadRequest(CreatePayjoinError("invoice-not-fully-paid",
"The transaction must pay the whole invoice"));
}
@ -307,7 +307,7 @@ namespace BTCPayServer.Payments.PayJoin
{
await BroadcastNow();
return StatusCode(503,
CreatePayjoinError(503, "out-of-utxos",
CreatePayjoinError("out-of-utxos",
"We do not have any UTXO available for making a payjoin for now"));
}
@ -323,7 +323,7 @@ namespace BTCPayServer.Payments.PayJoin
// This should not happen, as we check the existance of private key before creating invoice with payjoin
await UnlockUTXOs();
await BroadcastNow();
return StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"));
return StatusCode(500, CreatePayjoinError("unavailable", $"This service is unavailable for now"));
}
Money contributedAmount = Money.Zero;
@ -438,7 +438,7 @@ namespace BTCPayServer.Payments.PayJoin
{
await UnlockUTXOs();
await BroadcastNow();
return UnprocessableEntity(CreatePayjoinError(422, "not-enough-money",
return UnprocessableEntity(CreatePayjoinError("not-enough-money",
"Not enough money is sent to pay for the additional payjoin inputs"));
}
}
@ -476,7 +476,7 @@ namespace BTCPayServer.Payments.PayJoin
{
await UnlockUTXOs();
await BroadcastNow();
return UnprocessableEntity(CreatePayjoinError(422, "already-paid",
return UnprocessableEntity(CreatePayjoinError("already-paid",
$"The original transaction has already been accounted"));
}
await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(originalTx);
@ -514,10 +514,9 @@ namespace BTCPayServer.Payments.PayJoin
return hash;
}
private JObject CreatePayjoinError(int httpCode, string errorCode, string friendlyMessage)
private JObject CreatePayjoinError(string errorCode, string friendlyMessage)
{
var o = new JObject();
o.Add(new JProperty("httpCode", httpCode));
o.Add(new JProperty("errorCode", errorCode));
o.Add(new JProperty("message", friendlyMessage));
return o;

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Payments.Changelly.Models;
using Google.Apis.Http;
using Microsoft.WindowsAzure.Storage.Queue.Protocol;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -34,7 +37,7 @@ namespace BTCPayServer.Services
if (i.WitnessUtxo.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
return ScriptPubKeyType.Segwit;
if (i.WitnessUtxo.ScriptPubKey.IsScriptType(ScriptType.P2SH) &&
PayToWitPubKeyHashTemplate.Instance.ExtractWitScriptParameters(i.FinalScriptWitness) is {})
PayToWitPubKeyHashTemplate.Instance.ExtractWitScriptParameters(i.FinalScriptWitness) is { })
return ScriptPubKeyType.SegwitP2SH;
return null;
}
@ -56,18 +59,22 @@ namespace BTCPayServer.Services
public PayjoinClient(ExplorerClientProvider explorerClientProvider, IHttpClientFactory httpClientFactory)
{
if (httpClientFactory == null) throw new ArgumentNullException(nameof(httpClientFactory));
if (httpClientFactory == null)
throw new ArgumentNullException(nameof(httpClientFactory));
_explorerClientProvider =
explorerClientProvider ?? throw new ArgumentNullException(nameof(explorerClientProvider));
_httpClientFactory = httpClientFactory;
_httpClientFactory = httpClientFactory;
}
public async Task<PSBT> RequestPayjoin(Uri endpoint, DerivationSchemeSettings derivationSchemeSettings,
PSBT originalTx, CancellationToken cancellationToken)
{
if (endpoint == null) throw new ArgumentNullException(nameof(endpoint));
if (derivationSchemeSettings == null) throw new ArgumentNullException(nameof(derivationSchemeSettings));
if (originalTx == null) throw new ArgumentNullException(nameof(originalTx));
if (endpoint == null)
throw new ArgumentNullException(nameof(endpoint));
if (derivationSchemeSettings == null)
throw new ArgumentNullException(nameof(derivationSchemeSettings));
if (originalTx == null)
throw new ArgumentNullException(nameof(originalTx));
if (originalTx.IsAllFinalized())
throw new InvalidOperationException("The original PSBT should not be finalized.");
@ -111,7 +118,7 @@ namespace BTCPayServer.Services
try
{
var error = JObject.Parse(errorStr);
throw new PayjoinReceiverException((int)bpuresponse.StatusCode, error["errorCode"].Value<string>(),
throw new PayjoinReceiverException(error["errorCode"].Value<string>(),
error["message"].Value<string>());
}
catch (JsonReaderException)
@ -151,7 +158,7 @@ namespace BTCPayServer.Services
foreach (var output in newPSBT.Outputs)
{
output.HDKeyPaths.Clear();
foreach (var originalOutput in originalTx.Outputs)
foreach (var originalOutput in originalTx.Outputs)
{
if (output.ScriptPubKey == originalOutput.ScriptPubKey)
output.UpdateFrom(originalOutput);
@ -212,10 +219,10 @@ namespace BTCPayServer.Services
if (sentAfter > sentBefore)
{
var overPaying = sentAfter - sentBefore;
if (!newPSBT.TryGetEstimatedFeeRate(out var newFeeRate) || !newPSBT.TryGetVirtualSize(out var newVirtualSize))
throw new PayjoinSenderException("The payjoin receiver did not included UTXO information to calculate fee correctly");
var additionalFee = newPSBT.GetFee() - originalFee;
if (overPaying > additionalFee)
throw new PayjoinSenderException("The payjoin receiver is sending more money to himself");
@ -250,23 +257,61 @@ namespace BTCPayServer.Services
}
}
public enum PayjoinReceiverWellknownErrors
{
LeakingData,
PSBTNotFinalized,
Unavailable,
OutOfUTXOS,
NotEnoughMoney,
InsanePSBT,
VersionUnsupported
}
public class PayjoinReceiverException : PayjoinException
{
public PayjoinReceiverException(int httpCode, string errorCode, string message) : base(FormatMessage(httpCode,
errorCode, message))
public PayjoinReceiverException(string errorCode, string receiverDebugMessage) : base(FormatMessage(errorCode, receiverDebugMessage))
{
HttpCode = httpCode;
ErrorCode = errorCode;
ErrorMessage = message;
ReceiverDebugMessage = receiverDebugMessage;
WellknownError = errorCode switch
{
"leaking-data" => PayjoinReceiverWellknownErrors.LeakingData,
"psbt-not-finalized" => PayjoinReceiverWellknownErrors.PSBTNotFinalized,
"unavailable" => PayjoinReceiverWellknownErrors.Unavailable,
"out-of-utxos" => PayjoinReceiverWellknownErrors.OutOfUTXOS,
"not-enough-money" => PayjoinReceiverWellknownErrors.NotEnoughMoney,
"insane-psbt" => PayjoinReceiverWellknownErrors.InsanePSBT,
"version-unsupported" => PayjoinReceiverWellknownErrors.VersionUnsupported,
_ => null
};
}
public int HttpCode { get; }
public string ErrorCode { get; }
public string ErrorMessage { get; }
public string ReceiverDebugMessage { get; }
private static string FormatMessage(in int httpCode, string errorCode, string message)
public PayjoinReceiverWellknownErrors? WellknownError
{
return $"{errorCode}: {message} (HTTP: {httpCode})";
get;
}
private static string FormatMessage(string errorCode, string receiverDebugMessage)
{
return $"{errorCode}: {GetMessage(errorCode)}";
}
private static string GetMessage(string errorCode)
{
return errorCode switch
{
"leaking-data" => "Key path information or GlobalXPubs should not be included in the original PSBT.",
"psbt-not-finalized" => "The original PSBT must be finalized.",
"unavailable" => "The payjoin endpoint is not available for now.",
"out-of-utxos" => "The receiver does not have any UTXO to contribute in a payjoin proposal.",
"not-enough-money" => "The receiver added some inputs but could not bump the fee of the payjoin proposal.",
"insane-psbt" => "Some consistency check on the PSBT failed.",
"version-unsupported" => "This version of payjoin is not supported.",
_ => "Unknown error"
};
}
}