diff --git a/NBXplorer.Client/Models/CreatePSBTRequest.cs b/NBXplorer.Client/Models/CreatePSBTRequest.cs index 4ac58cc07..f6e380800 100644 --- a/NBXplorer.Client/Models/CreatePSBTRequest.cs +++ b/NBXplorer.Client/Models/CreatePSBTRequest.cs @@ -1,4 +1,4 @@ -using NBitcoin; +using NBitcoin; using System; using System.Collections.Generic; using System.Text; @@ -78,6 +78,11 @@ public class CreatePSBTRequest /// Disabling the randomization of unspecified parameters to match the network's fingerprint distribution /// public bool? DisableFingerprintRandomization { get; set; } + + /// + /// Attempt setting non_witness_utxo for all inputs even if they are segwit. + /// + public bool AlwaysIncludeNonWitnessUTXO { get; set; } } public class PSBTRebaseKeyRules { diff --git a/NBXplorer.Client/Models/RescanRequest.cs b/NBXplorer.Client/Models/RescanRequest.cs index 90fc61a6f..1f19d480a 100644 --- a/NBXplorer.Client/Models/RescanRequest.cs +++ b/NBXplorer.Client/Models/RescanRequest.cs @@ -1,4 +1,4 @@ -using NBitcoin; +using NBitcoin; using System; using System.Collections.Generic; using System.Text; diff --git a/NBXplorer.Client/Models/UpdatePSBTRequest.cs b/NBXplorer.Client/Models/UpdatePSBTRequest.cs index c4db8ee71..a43fd422f 100644 --- a/NBXplorer.Client/Models/UpdatePSBTRequest.cs +++ b/NBXplorer.Client/Models/UpdatePSBTRequest.cs @@ -20,6 +20,11 @@ public class UpdatePSBTRequest /// This transform (PubKey0, 0/0, accountFingerprint) by (PubKey0, m/49'/0'/0/0, masterFingerprint) /// public List RebaseKeyPaths { get; set; } + + /// + /// Attempt setting non_witness_utxo for all inputs even if they are segwit. + /// + public bool AlwaysIncludeNonWitnessUTXO { get; set; } } public class UpdatePSBTResponse { diff --git a/NBXplorer.Tests/UnitTest1.cs b/NBXplorer.Tests/UnitTest1.cs index 3cafbdd41..951ad6628 100644 --- a/NBXplorer.Tests/UnitTest1.cs +++ b/NBXplorer.Tests/UnitTest1.cs @@ -842,6 +842,36 @@ private static void CanCreatePSBTCore(ServerTester tester, bool segwit) }); Assert.True(psbt2.PSBT.TryGetEstimatedFeeRate(out var feeRate)); Assert.Equal(new FeeRate(1.0m), feeRate); + + if (segwit) + { + // some PSBT signers are incompliant with spec and require the non_witness_utxo even for segwit inputs + + Logs.Tester.LogInformation("Let's check that if we can create or update a psbt with non_witness_utxo filled even for segwit inputs"); + psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest() + { + Destinations = + { + new CreatePSBTDestination() + { + Destination = newAddress.Address, + Amount = Money.Coins(0.0001m) + } + }, + FeePreference = new FeePreference() + { + FallbackFeeRate = new FeeRate(1.0m) + }, + AlwaysIncludeNonWitnessUTXO = true + }); + + //in our case, we should have the tx to load this, but if someone restored the wallet and has a pruned node, this may not be set + foreach (var psbtInput in psbt2.PSBT.Inputs) + { + Assert.NotNull(psbtInput.NonWitnessUtxo); + } + } + } [Fact] diff --git a/NBXplorer/Controllers/MainController.PSBT.cs b/NBXplorer/Controllers/MainController.PSBT.cs index 74a2c2e98..45a0799cc 100644 --- a/NBXplorer/Controllers/MainController.PSBT.cs +++ b/NBXplorer/Controllers/MainController.PSBT.cs @@ -281,7 +281,8 @@ public async Task CreatePSBT( { DerivationScheme = strategy, PSBT = psbt, - RebaseKeyPaths = request.RebaseKeyPaths + RebaseKeyPaths = request.RebaseKeyPaths, + AlwaysIncludeNonWitnessUTXO = request.AlwaysIncludeNonWitnessUTXO }; await UpdatePSBTCore(update, network); var resp = new CreatePSBTResponse() @@ -323,8 +324,12 @@ private async Task UpdatePSBTCore(UpdatePSBTRequest update, NBXplorerNetwork net await UpdateHDKeyPathsWitnessAndRedeem(update, repo); } - foreach (var input in update.PSBT.Inputs) - input.TrySlimUTXO(); + if (!update.AlwaysIncludeNonWitnessUTXO) + { + foreach (var input in update.PSBT.Inputs) + input.TrySlimUTXO(); + } + HashSet rebased = new HashSet(); if (update.RebaseKeyPaths != null) @@ -433,7 +438,7 @@ private async Task UpdateUTXO(UpdatePSBTRequest update, Repository repo, Bitcoin { AnnotatedTransactionCollection txs = null; // First, we check for data in our history - foreach (var input in update.PSBT.Inputs.Where(NeedUTXO)) + foreach (var input in update.PSBT.Inputs.Where(psbtInput => update.AlwaysIncludeNonWitnessUTXO || NeedUTXO(psbtInput))) { txs = txs ?? await GetAnnotatedTransactions(repo, ChainProvider.GetChain(repo.Network), new DerivationSchemeTrackedSource(derivationScheme)); if (txs.GetByTxId(input.PrevOut.Hash) is AnnotatedTransaction tx) @@ -453,7 +458,7 @@ private async Task UpdateUTXO(UpdatePSBTRequest update, Repository repo, Bitcoin // then, we search data in the saved transactions await Task.WhenAll(update.PSBT.Inputs - .Where(NeedUTXO) + .Where(psbtInput => update.AlwaysIncludeNonWitnessUTXO || NeedUTXO(psbtInput)) .Select(async (input) => { // If this is not segwit, or we are unsure of it, let's try to grab from our saved transactions @@ -472,7 +477,7 @@ await Task.WhenAll(update.PSBT.Inputs { var batch = rpc.RPC.PrepareBatch(); var getTransactions = Task.WhenAll(update.PSBT.Inputs - .Where(NeedUTXO) + .Where(psbtInput => update.AlwaysIncludeNonWitnessUTXO || NeedUTXO(psbtInput)) .Where(input => input.NonWitnessUtxo == null) .Select(async input => { diff --git a/docs/API.md b/docs/API.md index 15cc57b1d..dd9d80ecf 100644 --- a/docs/API.md +++ b/docs/API.md @@ -953,7 +953,8 @@ Fields: "accountKeyPath": "ab5ed9ab/49'/0'/0'" } ], - "disableFingerprintRandomization": false + "disableFingerprintRandomization": false, + "alwaysIncludeNonWitnessUTXO": false } ``` @@ -982,6 +983,7 @@ Fields: * `rebaseKeyPaths[].accountKey`: The account key to rebase * `rebaseKeyPaths[].accountKeyPath`: The path from the root to the account key prefixed by the master public key fingerprint. * `disableFingerprintRandomization`: Disable the randomization of default parameter's value to match the network's fingerprint distribution. (randomized default values are `version`, `timeLock`, `rbf`, `discourageFeeSniping`) +* `alwaysIncludeNonWitnessUTXO`: Try to set the full transaction in `non_witness_utxo`, even for segwit inputs (default to `false`) Response: @@ -1026,6 +1028,7 @@ NBXplorer will take to complete as much information as it can about this PSBT. * `rebaseKeyPaths`: Optional. Rebase the hdkey paths (if no rebase, the key paths are relative to the xpub that NBXplorer knows about), a rebase can transform (PubKey0, 0/0, accountFingerprint) by (PubKey0, m/49'/0'/0/0, masterFingerprint) * `rebaseKeyPaths[].accountKey`: The account key to rebase * `rebaseKeyPaths[].accountKeyPath`: The path from the root to the account key prefixed by the master public key fingerprint. +* `alwaysIncludeNonWitnessUTXO`: Try to set the full transaction in `non_witness_utxo`, even for segwit inputs (default to `false`) Response: ```json